From b48bed5f42c02fe8562c7ce623172cf3e29c9c90 Mon Sep 17 00:00:00 2001 From: Nicolas Williams Date: Tue, 17 Mar 2015 16:03:15 -0500 Subject: [PATCH] Daemons detach atomically to avoid having to wait Tests that start daemons have to "wait" for them to start. This commit makes Heimdal daemons prep to detach (when requested) by forking early, then having the child signal readiness to the parent when the child really is ready. The parent exits only which the child is ready. This means that tests will no longer need to wait for daemons. However, tests will still need a pidfile or such so they can stop the daemons. Note that the --detach options should not be used on OS X from launchd, only from tests. --- cf/roken-frag.m4 | 4 - kadmin/kadm_conn.c | 4 + kadmin/kadmind.c | 22 +++- kcm/config.c | 25 ++--- kcm/kcm_locl.h | 3 +- kcm/main.c | 9 +- kdc/config.c | 34 +++--- kdc/connect.c | 5 +- kdc/kdc-tester.c | 4 +- kdc/kdc_locl.h | 6 +- kdc/main.c | 8 +- kpasswd/kpasswdd.c | 156 ++++++++++++++------------ lib/kadm5/ipropd_master.c | 44 ++++---- lib/kadm5/ipropd_slave.c | 34 +++--- lib/krb5/principal.c | 4 +- lib/roken/Makefile.am | 7 +- lib/roken/NTMakefile | 5 + lib/roken/daemon.c | 2 +- lib/roken/detach.c | 211 +++++++++++++++++++++++++++++++++++ lib/roken/getarg.c | 2 +- lib/roken/roken-common.h | 6 +- lib/roken/roken.h.in | 3 + lib/roken/test-detach.c | 82 ++++++++++++++ lib/roken/version-script.map | 2 + lib/roken/write_pid.c | 37 ++++-- 25 files changed, 522 insertions(+), 197 deletions(-) create mode 100644 lib/roken/detach.c create mode 100644 lib/roken/test-detach.c diff --git a/cf/roken-frag.m4 b/cf/roken-frag.m4 index e4db259ce..faf8daf68 100644 --- a/cf/roken-frag.m4 +++ b/cf/roken-frag.m4 @@ -307,14 +307,10 @@ AC_FIND_IF_NOT_BROKEN(gai_strerror,, #include #endif],[0]) -dnl Darwin is weird, and in some senses not unix, launchd doesn't want -dnl servers to use daemon(), so its deprecated. case "$host_os" in darwin*) ;; *) - AC_DEFINE([SUPPORT_DETACH], 1, - [Define if os support want to detach is daemonens.]) AC_BROKEN([daemon]) ;; esac diff --git a/kadmin/kadm_conn.c b/kadmin/kadm_conn.c index 58aceb8e1..0baf165af 100644 --- a/kadmin/kadm_conn.c +++ b/kadmin/kadm_conn.c @@ -36,6 +36,8 @@ #include #endif +extern int daemon_child; + struct kadm_port { char *port; unsigned short def_port; @@ -292,5 +294,7 @@ start_server(krb5_context contextp, const char *port_str) if(num_socks == 0) krb5_errx(contextp, 1, "no sockets to listen to - exiting"); + roken_detach_finish(NULL, daemon_child); + wait_for_connection(contextp, socks, num_socks); } diff --git a/kadmin/kadmind.c b/kadmin/kadmind.c index 916374a0b..12abaa598 100644 --- a/kadmin/kadmind.c +++ b/kadmin/kadmind.c @@ -45,6 +45,9 @@ static int debug_flag; static char *port_str; char *realm; +static int detach_from_console = -1; +int daemon_child = -1; + static struct getargs args[] = { { "config-file", 'c', arg_string, &config_file, @@ -68,6 +71,14 @@ static struct getargs args[] = { { "debug", 'd', arg_flag, &debug_flag, "enable debugging", NULL }, + { + "detach", 0 , arg_flag, &detach_from_console, + "detach from console", NULL + }, + { + "daemon-child", 0 , arg_integer, &daemon_child, + "private argument, do not use", NULL + }, { "ports", 'p', arg_string, &port_str, "ports to listen to", "port" }, { "help", 'h', arg_flag, &help_flag, NULL, NULL }, @@ -98,10 +109,6 @@ main(int argc, char **argv) setprogname(argv[0]); - ret = krb5_init_context(&context); - if (ret) - errx (1, "krb5_init_context failed: %d", ret); - if (getarg(args, num_args, argc, argv, &optidx)) { warnx("error at argument `%s'", argv[optidx]); usage(1); @@ -115,6 +122,13 @@ main(int argc, char **argv) exit(0); } + if (detach_from_console > 0 && daemon_child == -1) + roken_detach_prep(argc, argv, "--daemon-child"); + + ret = krb5_init_context(&context); + if (ret) + errx (1, "krb5_init_context failed: %d", ret); + argc -= optidx; argv += optidx; diff --git a/kcm/config.c b/kcm/config.c index bb4530a21..a3a7d3465 100644 --- a/kcm/config.c +++ b/kcm/config.c @@ -44,10 +44,8 @@ char *door_path = NULL; static char *max_request_str; /* `max_request' as a string */ -#ifdef SUPPORT_DETACH int detach_from_console = -1; -#define DETACH_IS_DEFAULT FALSE -#endif +int daemon_child = -1; static const char *system_cache_name = NULL; static const char *system_keytab = NULL; @@ -88,19 +86,14 @@ static struct getargs args[] = { "launchd", 0, arg_flag, &launchd_flag, "when in use by launchd", NULL }, -#ifdef SUPPORT_DETACH -#if DETACH_IS_DEFAULT - { - "detach", 'D', arg_negative_flag, &detach_from_console, - "don't detach from console", NULL - }, -#else { "detach", 0 , arg_flag, &detach_from_console, "detach from console", NULL }, -#endif -#endif + { + "daemon-child", 0 , arg_integer, &daemon_child, + "private argument, do not use", NULL + }, { "help", 'h', arg_flag, &help_flag, NULL, NULL }, { "system-principal", 'k', arg_string, &system_principal, @@ -331,10 +324,10 @@ kcm_configure(int argc, char **argv) int optidx = 0; const char *p; - while(getarg(args, num_args, argc, argv, &optidx)) + while (getarg(args, num_args, argc, argv, &optidx)) warnx("error at argument `%s'", argv[optidx]); - if(help_flag) + if (help_flag) usage (0); if (version_flag) { @@ -387,13 +380,11 @@ kcm_configure(int argc, char **argv) krb5_err(kcm_context, 1, ret, "initializing system ccache"); } -#ifdef SUPPORT_DETACH if(detach_from_console == -1) detach_from_console = krb5_config_get_bool_default(kcm_context, NULL, - DETACH_IS_DEFAULT, + FALSE, "kcm", "detach", NULL); -#endif kcm_openlog(); if(max_request == 0) max_request = 64 * 1024; diff --git a/kcm/kcm_locl.h b/kcm/kcm_locl.h index 56bb7045b..a70e8b839 100644 --- a/kcm/kcm_locl.h +++ b/kcm/kcm_locl.h @@ -169,9 +169,8 @@ extern char *door_path; extern size_t max_request; extern sig_atomic_t exit_flag; extern int name_constraints; -#ifdef SUPPORT_DETACH extern int detach_from_console; -#endif +extern int daemon_child; extern int launchd_flag; extern int disallow_getting_krbtgt; diff --git a/kcm/main.c b/kcm/main.c index eb836ed28..38c577fb8 100644 --- a/kcm/main.c +++ b/kcm/main.c @@ -86,11 +86,8 @@ main(int argc, char **argv) signal(SIGUSR2, sigusr2); signal(SIGPIPE, SIG_IGN); #endif -#ifdef SUPPORT_DETACH - if (detach_from_console) - if (daemon(0, 0) == -1) - err(1, "daemon"); -#endif + if (detach_from_console && !launchd_flag && daemon_child == -1) + roken_detach_prep(argc, argv, "--daemon-child"); pidfile(NULL); if (launchd_flag) { @@ -101,6 +98,8 @@ main(int argc, char **argv) heim_sipc_service_unix(service_name, kcm_service, NULL, &un); } + roken_detach_finish(NULL, daemon_child); + heim_ipc_main(); krb5_free_context(kcm_context); diff --git a/kdc/config.c b/kdc/config.c index bc5820c75..16a72c59b 100644 --- a/kdc/config.c +++ b/kdc/config.c @@ -94,19 +94,14 @@ static struct getargs args[] = { { "ports", 'P', arg_string, rk_UNCONST(&port_str), "ports to listen to", "portspec" }, -#ifdef SUPPORT_DETACH -#if DETACH_IS_DEFAULT - { - "detach", 'D', arg_negative_flag, &detach_from_console, - "don't detach from console", NULL - }, -#else { "detach", 0 , arg_flag, &detach_from_console, "detach from console", NULL }, -#endif -#endif + { + "daemon-child", 0 , arg_flag, &daemon_child, + "private argument, do not use", NULL + }, { "addresses", 0, arg_strings, &addresses_str, "addresses to listen on", "list of addresses" }, { "disable-des", 0, arg_flag, &disable_des, @@ -158,10 +153,10 @@ configure(krb5_context context, int argc, char **argv, int *optidx) *optidx = 0; - while(getarg(args, num_args, argc, argv, optidx)) + while (getarg(args, num_args, argc, argv, optidx)) warnx("error at argument `%s'", argv[*optidx]); - if(help_flag) + if (help_flag) usage (0); if (version_flag) { @@ -179,6 +174,15 @@ configure(krb5_context context, int argc, char **argv, int *optidx) exit(0); } + if(detach_from_console == -1) + detach_from_console = krb5_config_get_bool_default(context, NULL, + FALSE, + "kdc", + "detach", NULL); + + if (detach_from_console && daemon_child == -1) + roken_detach_prep(argc, argv, "--daemon-child"); + { char **files; int aret; @@ -265,14 +269,6 @@ configure(krb5_context context, int argc, char **argv, int *optidx) krb5_errx(context, 1, "enforce-transited-policy deprecated, " "use [kdc]transited-policy instead"); -#ifdef SUPPORT_DETACH - if(detach_from_console == -1) - detach_from_console = krb5_config_get_bool_default(context, NULL, - DETACH_IS_DEFAULT, - "kdc", - "detach", NULL); -#endif /* SUPPORT_DETACH */ - if(max_request_tcp == 0) max_request_tcp = 64 * 1024; if(max_request_udp == 0) diff --git a/kdc/connect.c b/kdc/connect.c index 52c8f5ee0..81ef8cba0 100644 --- a/kdc/connect.c +++ b/kdc/connect.c @@ -867,16 +867,17 @@ next_min_free(krb5_context context, struct descr **d, unsigned int *ndescr) } void -loop(krb5_context context, - krb5_kdc_configuration *config) +loop(krb5_context context, krb5_kdc_configuration *config) { struct descr *d; unsigned int ndescr; + int ret; ndescr = init_sockets(context, config, &d); if(ndescr <= 0) krb5_errx(context, 1, "No sockets!"); kdc_log(context, config, 0, "KDC started"); + roken_detach_finish(NULL, daemon_child); while(exit_flag == 0){ struct timeval tmout; fd_set fds; diff --git a/kdc/kdc-tester.c b/kdc/kdc-tester.c index 4f2597102..bb4d1ea3e 100644 --- a/kdc/kdc-tester.c +++ b/kdc/kdc-tester.c @@ -44,10 +44,8 @@ struct perf { struct perf *next; } *ptop; -#ifdef SUPPORT_DETACH int detach_from_console = -1; -#define DETACH_IS_DEFAULT FALSE -#endif +int daemon_child = -1; static krb5_kdc_configuration *kdc_config; static krb5_context kdc_context; diff --git a/kdc/kdc_locl.h b/kdc/kdc_locl.h index 804eebeaf..1a30cca28 100644 --- a/kdc/kdc_locl.h +++ b/kdc/kdc_locl.h @@ -99,12 +99,8 @@ extern krb5_addresses explicit_addresses; extern int enable_http; -#ifdef SUPPORT_DETACH - -#define DETACH_IS_DEFAULT FALSE - extern int detach_from_console; -#endif +extern int daemon_child; extern const struct units _kdc_digestunits[]; diff --git a/kdc/main.c b/kdc/main.c index 258b31b2c..50ac8e41d 100644 --- a/kdc/main.c +++ b/kdc/main.c @@ -44,9 +44,8 @@ sig_atomic_t exit_flag = 0; -#ifdef SUPPORT_DETACH int detach_from_console = -1; -#endif +int daemon_child = -1; static RETSIGTYPE sigterm(int sig) @@ -105,7 +104,6 @@ switch_environment(void) #endif } - int main(int argc, char **argv) { @@ -157,10 +155,6 @@ main(int argc, char **argv) signal(SIGPIPE, SIG_IGN); #endif #endif -#ifdef SUPPORT_DETACH - if (detach_from_console) - daemon(0, 0); -#endif #ifdef __APPLE__ bonjour_announce(context, config); #endif diff --git a/kpasswd/kpasswdd.c b/kpasswd/kpasswdd.c index a7533109a..6459f5e44 100644 --- a/kpasswd/kpasswdd.c +++ b/kpasswd/kpasswdd.c @@ -644,8 +644,46 @@ out: krb5_auth_con_free(context, auth_context); } +static const char *check_library = NULL; +static const char *check_function = NULL; +static getarg_strings policy_libraries = { 0, NULL }; +static char sHDB[] = "HDBGET:"; +static char *keytab_str = sHDB; +static char *realm_str; +static int version_flag; +static int help_flag; +static int detach_from_console; +static int daemon_child = -1; +static char *port_str; +static char *config_file; + +struct getargs args[] = { +#ifdef HAVE_DLOPEN + { "check-library", 0, arg_string, &check_library, + "library to load password check function from", "library" }, + { "check-function", 0, arg_string, &check_function, + "password check function to load", "function" }, + { "policy-libraries", 0, arg_strings, &policy_libraries, + "password check function to load", "function" }, +#endif + { "addresses", 0, arg_strings, &addresses_str, + "addresses to listen on", "list of addresses" }, + { "detach", 0, arg_flag, &detach_from_console, + "detach from console", NULL }, + { "daemon-child", 0 , arg_integer, &daemon_child, + "private argument, do not use", NULL }, + { "keytab", 'k', arg_string, &keytab_str, + "keytab to get authentication key from", "kspec" }, + { "config-file", 'c', arg_string, &config_file, NULL, NULL }, + { "realm", 'r', arg_string, &realm_str, "default realm", "realm" }, + { "port", 'p', arg_string, &port_str, "port", NULL }, + { "version", 0, arg_flag, &version_flag, NULL, NULL }, + { "help", 0, arg_flag, &help_flag, NULL, NULL } +}; +int num_args = sizeof(args) / sizeof(args[0]); + static int -doit (krb5_keytab keytab, int port) +doit(krb5_keytab keytab, int port) { krb5_error_code ret; int *sockets; @@ -659,54 +697,56 @@ doit (krb5_keytab keytab, int port) if (explicit_addresses.len) { addrs = explicit_addresses; } else { - ret = krb5_get_all_server_addrs (context, &addrs); + ret = krb5_get_all_server_addrs(context, &addrs); if (ret) - krb5_err (context, 1, ret, "krb5_get_all_server_addrs"); + krb5_err(context, 1, ret, "krb5_get_all_server_addrs"); } n = addrs.len; - sockets = malloc (n * sizeof(*sockets)); + sockets = malloc(n * sizeof(*sockets)); if (sockets == NULL) - krb5_errx (context, 1, "out of memory"); + krb5_errx(context, 1, "out of memory"); maxfd = -1; FD_ZERO(&real_fdset); for (i = 0; i < n; ++i) { krb5_socklen_t sa_size = sizeof(__ss); - krb5_addr2sockaddr (context, &addrs.val[i], sa, &sa_size, port); + krb5_addr2sockaddr(context, &addrs.val[i], sa, &sa_size, port); - sockets[i] = socket (__ss.ss_family, SOCK_DGRAM, 0); + sockets[i] = socket(__ss.ss_family, SOCK_DGRAM, 0); if (sockets[i] < 0) - krb5_err (context, 1, errno, "socket"); - if (bind (sockets[i], sa, sa_size) < 0) { + krb5_err(context, 1, errno, "socket"); + if (bind(sockets[i], sa, sa_size) < 0) { char str[128]; size_t len; int save_errno = errno; - ret = krb5_print_address (&addrs.val[i], str, sizeof(str), &len); + ret = krb5_print_address(&addrs.val[i], str, sizeof(str), &len); if (ret) strlcpy(str, "unknown address", sizeof(str)); - krb5_warn (context, save_errno, "bind(%s)", str); + krb5_warn(context, save_errno, "bind(%s)", str); continue; } - maxfd = max (maxfd, sockets[i]); + maxfd = max(maxfd, sockets[i]); if (maxfd >= FD_SETSIZE) - krb5_errx (context, 1, "fd too large"); + krb5_errx(context, 1, "fd too large"); FD_SET(sockets[i], &real_fdset); } if (maxfd == -1) - krb5_errx (context, 1, "No sockets!"); + krb5_errx(context, 1, "No sockets!"); - while(exit_flag == 0) { + roken_detach_finish(NULL, daemon_child); + + while (exit_flag == 0) { krb5_ssize_t retx; fd_set fdset = real_fdset; - retx = select (maxfd + 1, &fdset, NULL, NULL, NULL); + retx = select(maxfd + 1, &fdset, NULL, NULL, NULL); if (retx < 0) { if (errno == EINTR) continue; else - krb5_err (context, 1, errno, "select"); + krb5_err(context, 1, errno, "select"); } for (i = 0; i < n; ++i) if (FD_ISSET(sockets[i], &fdset)) { @@ -716,13 +756,13 @@ doit (krb5_keytab keytab, int port) retx = recvfrom(sockets[i], buf, sizeof(buf), 0, sa, &addrlen); if (retx < 0) { - if(errno == EINTR) + if (errno == EINTR) break; else - krb5_err (context, 1, errno, "recvfrom"); + krb5_err(context, 1, errno, "recvfrom"); } - process (keytab, sockets[i], + process(keytab, sockets[i], &addrs.val[i], sa, addrlen, buf, retx); @@ -733,8 +773,8 @@ doit (krb5_keytab keytab, int port) close(sockets[i]); free(sockets); - krb5_free_addresses (context, &addrs); - krb5_free_context (context); + krb5_free_addresses(context, &addrs); + krb5_free_context(context); return 0; } @@ -744,40 +784,8 @@ sigterm(int sig) exit_flag = 1; } -static const char *check_library = NULL; -static const char *check_function = NULL; -static getarg_strings policy_libraries = { 0, NULL }; -static char sHDB[] = "HDBGET:"; -static char *keytab_str = sHDB; -static char *realm_str; -static int version_flag; -static int help_flag; -static char *port_str; -static char *config_file; - -struct getargs args[] = { -#ifdef HAVE_DLOPEN - { "check-library", 0, arg_string, &check_library, - "library to load password check function from", "library" }, - { "check-function", 0, arg_string, &check_function, - "password check function to load", "function" }, - { "policy-libraries", 0, arg_strings, &policy_libraries, - "password check function to load", "function" }, -#endif - { "addresses", 0, arg_strings, &addresses_str, - "addresses to listen on", "list of addresses" }, - { "keytab", 'k', arg_string, &keytab_str, - "keytab to get authentication key from", "kspec" }, - { "config-file", 'c', arg_string, &config_file, NULL, NULL }, - { "realm", 'r', arg_string, &realm_str, "default realm", "realm" }, - { "port", 'p', arg_string, &port_str, "port", NULL }, - { "version", 0, arg_flag, &version_flag, NULL, NULL }, - { "help", 0, arg_flag, &help_flag, NULL, NULL } -}; -int num_args = sizeof(args) / sizeof(args[0]); - int -main (int argc, char **argv) +main(int argc, char **argv) { krb5_keytab keytab; krb5_error_code ret; @@ -787,13 +795,17 @@ main (int argc, char **argv) krb5_program_setup(&context, argc, argv, args, num_args, NULL); - if(help_flag) + if (help_flag) krb5_std_usage(0, args, num_args); - if(version_flag) { + + if (version_flag) { print_version(NULL); exit(0); } + if (detach_from_console > 0 && daemon_child == -1) + roken_detach_prep(argc, argv, "--daemon-child"); + if (config_file == NULL) { aret = asprintf(&config_file, "%s/kdc.conf", hdb_db_dir(context)); if (aret == -1) @@ -809,37 +821,37 @@ main (int argc, char **argv) if (ret) krb5_err(context, 1, ret, "reading configuration files"); - if(realm_str) + if (realm_str) krb5_set_default_realm(context, realm_str); - krb5_openlog (context, "kpasswdd", &log_facility); + krb5_openlog(context, "kpasswdd", &log_facility); krb5_set_warn_dest(context, log_facility); if (port_str != NULL) { - struct servent *s = roken_getservbyname (port_str, "udp"); + struct servent *s = roken_getservbyname(port_str, "udp"); if (s != NULL) port = s->s_port; else { char *ptr; - port = strtol (port_str, &ptr, 10); + port = strtol(port_str, &ptr, 10); if (port == 0 && ptr == port_str) - krb5_errx (context, 1, "bad port `%s'", port_str); + krb5_errx(context, 1, "bad port `%s'", port_str); port = htons(port); } } else - port = krb5_getportbyname (context, "kpasswd", "udp", KPASSWD_PORT); + port = krb5_getportbyname(context, "kpasswd", "udp", KPASSWD_PORT); ret = krb5_kt_register(context, &hdb_get_kt_ops); - if(ret) + if (ret) krb5_err(context, 1, ret, "krb5_kt_register"); ret = krb5_kt_resolve(context, keytab_str, &keytab); - if(ret) + if (ret) krb5_err(context, 1, ret, "%s", keytab_str); - kadm5_setup_passwd_quality_check (context, check_library, check_function); + kadm5_setup_passwd_quality_check(context, check_library, check_function); for (i = 0; i < policy_libraries.num_strings; i++) { ret = kadm5_add_passwd_quality_verifier(context, @@ -858,16 +870,16 @@ main (int argc, char **argv) int j; for (j = 0; j < addresses_str.num_strings; ++j) - add_one_address (addresses_str.strings[j], j == 0); - free_getarg_strings (&addresses_str); + add_one_address(addresses_str.strings[j], j == 0); + free_getarg_strings(&addresses_str); } else { - char **foo = krb5_config_get_strings (context, NULL, + char **foo = krb5_config_get_strings(context, NULL, "kdc", "addresses", NULL); if (foo != NULL) { - add_one_address (*foo++, TRUE); + add_one_address(*foo++, TRUE); while (*foo) - add_one_address (*foo++, FALSE); + add_one_address(*foo++, FALSE); } } @@ -889,5 +901,5 @@ main (int argc, char **argv) pidfile(NULL); - return doit (keytab, port); + return doit(keytab, port); } diff --git a/lib/kadm5/ipropd_master.c b/lib/kadm5/ipropd_master.c index 6f27ca552..3f9635c79 100644 --- a/lib/kadm5/ipropd_master.c +++ b/lib/kadm5/ipropd_master.c @@ -929,9 +929,8 @@ static char *keytab_str = sHDB; static char *database; static char *config_file; static char *port_str; -#ifdef SUPPORT_DETACH -static int detach_from_console = 0; -#endif +static int detach_from_console; +static int daemon_child = -1; static struct getargs args[] = { { "config-file", 'c', arg_string, &config_file, NULL, NULL }, @@ -947,10 +946,10 @@ static struct getargs args[] = { "time of inactivity after which a slave is considered gone", "time"}, { "port", 0, arg_string, &port_str, "port ipropd will listen to", "port"}, -#ifdef SUPPORT_DETACH { "detach", 0, arg_flag, &detach_from_console, "detach from console", NULL }, -#endif + { "daemon-child", 0 , arg_integer, &daemon_child, + "private argument, do not use", NULL }, { "hostname", 0, arg_string, rk_UNCONST(&master_hostname), "hostname of master (if not same as hostname)", "hostname" }, { "version", 0, arg_flag, &version_flag, NULL, NULL }, @@ -973,16 +972,29 @@ main(int argc, char **argv) krb5_keytab keytab; char **files; int aret; + int optidx = 0; - (void) krb5_program_setup(&context, argc, argv, args, num_args, NULL); + setprogname(argv[0]); - if(help_flag) + if (getarg(args, num_args, argc, argv, &optidx)) + krb5_std_usage(1, args, num_args); + + if (help_flag) krb5_std_usage(0, args, num_args); - if(version_flag) { + + if (version_flag) { print_version(NULL); exit(0); } + if (detach_from_console && daemon_child == -1) + roken_detach_prep(argc, argv, "--daemon-child"); + pidfile(NULL); + + ret = krb5_init_context(&context); + if (ret) + errx(1, "krb5_init_context failed: %d", ret); + setup_signal(); if (config_file == NULL) { @@ -1007,17 +1019,7 @@ main(int argc, char **argv) if (time_before_missing < 0) krb5_errx (context, 1, "couldn't parse time: %s", slave_time_missing); -#ifdef SUPPORT_DETACH - if (detach_from_console) { - aret = daemon(0, 0); - if (aret == -1) { - /* not much to do if detaching fails... */ - krb5_err(context, 1, aret, "failed to daemon(3)ise"); - } - } -#endif - pidfile (NULL); - krb5_openlog (context, "ipropd-master", &log_facility); + krb5_openlog(context, "ipropd-master", &log_facility); krb5_set_warn_dest(context, log_facility); ret = krb5_kt_register(context, &hdb_get_kt_ops); @@ -1059,7 +1061,9 @@ main(int argc, char **argv) krb5_warnx(context, "ipropd-master started at version: %lu", (unsigned long)current_version); - while(exit_flag == 0){ + roken_detach_finish(NULL, daemon_child); + + while (exit_flag == 0){ slave *p; fd_set readset; int max_fd = 0; diff --git a/lib/kadm5/ipropd_slave.c b/lib/kadm5/ipropd_slave.c index 5caab9bcd..0e87a381e 100644 --- a/lib/kadm5/ipropd_slave.c +++ b/lib/kadm5/ipropd_slave.c @@ -514,9 +514,8 @@ static int version_flag; static int help_flag; static char *keytab_str; static char *port_str; -#ifdef SUPPORT_DETACH -static int detach_from_console = 0; -#endif +static int detach_from_console; +static int daemon_child = -1; static struct getargs args[] = { { "config-file", 'c', arg_string, &config_file, NULL, NULL }, @@ -529,10 +528,10 @@ static struct getargs args[] = { "file to write out status into", "file" }, { "port", 0, arg_string, &port_str, "port ipropd-slave will connect to", "port"}, -#ifdef SUPPORT_DETACH { "detach", 0, arg_flag, &detach_from_console, "detach from console", NULL }, -#endif + { "daemon-child", 0 , arg_integer, &daemon_child, + "private argument, do not use", NULL }, { "hostname", 0, arg_string, rk_UNCONST(&slave_str), "hostname of slave (if not same as hostname)", "hostname" }, { "version", 0, arg_flag, &version_flag, NULL, NULL }, @@ -572,16 +571,21 @@ main(int argc, char **argv) setprogname(argv[0]); - if(getarg(args, num_args, argc, argv, &optidx)) + if (getarg(args, num_args, argc, argv, &optidx)) usage(1); - if(help_flag) + if (help_flag) usage(0); - if(version_flag) { + + if (version_flag) { print_version(NULL); exit(0); } + if (detach_from_console && daemon_child == -1) + roken_detach_prep(argc, argv, "--daemon-child"); + pidfile(NULL); + ret = krb5_init_context(&context); if (ret) errx (1, "krb5_init_context failed: %d", ret); @@ -616,17 +620,7 @@ main(int argc, char **argv) krb5_errx(context, 1, "can't allocate status file buffer"); } -#ifdef SUPPORT_DETACH - if (detach_from_console){ - int aret = daemon(0, 0); - if (aret == -1) { - /* not much to do if detaching fails... */ - krb5_err(context, 1, aret, "failed to daemon(3)ise"); - } - } -#endif - pidfile (NULL); - krb5_openlog (context, "ipropd-slave", &log_facility); + krb5_openlog(context, "ipropd-slave", &log_facility); krb5_set_warn_dest(context, log_facility); slave_status(context, status_file, "bootstrapping"); @@ -683,6 +677,8 @@ main(int argc, char **argv) slave_status(context, status_file, "ipropd-slave started"); + roken_detach_finish(NULL, daemon_child); + while (!exit_flag) { time_t now, elapsed; int connected = FALSE; diff --git a/lib/krb5/principal.c b/lib/krb5/principal.c index 5496dd2cf..69fa73219 100644 --- a/lib/krb5/principal.c +++ b/lib/krb5/principal.c @@ -1609,7 +1609,7 @@ make_rules_safe(krb5_context context, krb5_name_canon_rule rules) * conversion. Better let the user get failures and make them think about * their naming rules. */ - if (rules != NULL) + if (rules == NULL) return; for (; rules[0].type != KRB5_NCRT_BOGUS; rules++) { if (rules->type == KRB5_NCRT_NSS) @@ -1843,7 +1843,7 @@ apply_name_canon_rule(krb5_context context, krb5_name_canon_rule rules, } /* If we stripped off a :port, add it back in */ - if (port != NULL) { + if (port != NULL && new_hostname != NULL) { if (asprintf(&hostname_with_port, "%s%s", new_hostname, port) == -1 || hostname_with_port == NULL) { ret = krb5_enomem(context); diff --git a/lib/roken/Makefile.am b/lib/roken/Makefile.am index 72d02d30f..974b82c75 100644 --- a/lib/roken/Makefile.am +++ b/lib/roken/Makefile.am @@ -20,7 +20,9 @@ if HAVE_DBHEADER AM_CPPFLAGS += -I$(DBHEADER) endif -noinst_PROGRAMS = snprintf-test resolve-test rkpty +noinst_PROGRAMS = snprintf-test resolve-test rkpty test-detach + +CHECK_LOCAL = snprintf-test resolve-test rkpty make-roken check_PROGRAMS = \ base64-test \ @@ -50,6 +52,8 @@ parse_reply_test_CFLAGS = -DTEST_RESOLVE test_readenv_SOURCES = test-readenv.c test-mem.c +test_detach_SOURCES = test-detach.c + rkpty_LDADD = $(LIB_openpty) $(LDADD) parse_time_test_SOURCES = parse_time-test.c test-mem.c @@ -75,6 +79,7 @@ libroken_la_SOURCES = \ concat.c \ cloexec.c \ ct.c \ + detach.c \ doxygen.c \ dumpdata.c \ environment.c \ diff --git a/lib/roken/NTMakefile b/lib/roken/NTMakefile index 1ae8084ec..57ef7506f 100644 --- a/lib/roken/NTMakefile +++ b/lib/roken/NTMakefile @@ -39,6 +39,7 @@ libroken_la_OBJS = \ $(OBJ)\concat.obj \ $(OBJ)\cloexec.obj \ $(OBJ)\ct.obj \ + $(OBJ)\detach.obj \ $(OBJ)\dirent.obj \ $(OBJ)\dlfcn_w32.obj \ $(OBJ)\dumpdata.obj \ @@ -189,6 +190,7 @@ TEST_PROGS = \ $(OBJ)\getaddrinfo-test.exe \ $(OBJ)\getifaddrs-test.exe \ $(OBJ)\hex-test.exe \ + $(OBJ)\test-detach.exe \ $(OBJ)\test-readenv.exe \ $(OBJ)\parse_bytes-test.exe \ $(OBJ)\parse_reply-test.exe \ @@ -252,6 +254,9 @@ $(OBJ)\hex-test.exe: $(OBJ)\hex-test.obj $(LIBROKEN) $(OBJ)\parse_bytes-test.exe: $(OBJ)\parse_bytes-test.obj $(LIBROKEN) $(EXECONLINK) +$(OBJ)\test-detach.exe: $(OBJ)\test-detach.obj $(OBJ)\detach.obj $(LIBROKEN) + $(EXECONLINK) + $(OBJ)\dirent-test.exe: $(OBJ)\dirent-test.obj $(LIBROKEN) $(EXECONLINK) diff --git a/lib/roken/daemon.c b/lib/roken/daemon.c index 591a9a953..6128bebe3 100644 --- a/lib/roken/daemon.c +++ b/lib/roken/daemon.c @@ -72,7 +72,7 @@ daemon(int nochdir, int noclose) dup2(fd, STDOUT_FILENO); dup2(fd, STDERR_FILENO); if (fd > 2) - close (fd); + (void) close(fd); } return (0); } diff --git a/lib/roken/detach.c b/lib/roken/detach.c new file mode 100644 index 000000000..e9fbafc39 --- /dev/null +++ b/lib/roken/detach.c @@ -0,0 +1,211 @@ +/*- + * Copyright (c) 2015 + * Cryptonector LLC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Cryptonector LLC may not be used to endorse or promote products + * derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#ifdef WIN32 +#include +#include +#else +#include +#endif +#include "roken.h" + +#ifdef WIN32 +#define dup2 _dup2 +#endif + +static int pipefds[2] = {-1, -1}; + +ROKEN_LIB_FUNCTION void ROKEN_LIB_CALL +roken_detach_prep(int argc, char **argv, char *special_arg) +{ + pid_t child; + char buf[1]; + ssize_t bytes; + int status; + + pipefds[0] = -1; + pipefds[1] = -1; + +#ifdef WIN32 + if (_pipe(pipefds, 4, O_BINARY) == -1) + err(1, "failed to setup to detach daemon (_pipe failed)"); +#else + if (pipe(pipefds) == -1) + err(1, "failed to setup to detach daemon (pipe failed)"); +#endif + +#ifndef WIN32 + fflush(stdout); + child = fork(); +#else + { + intptr_t child_handle; + int write_side; + size_t i; + char *fildes; + char **new_argv; + + new_argv = calloc(argc + 2, sizeof(*new_argv)); + if (new_argv == NULL) + err(1, "Out of memory"); + + write_side = _dup(pipefds[1]); /* The new fd will be inherited */ + if (write_side == -1) + err(1, "Out of memory"); + + if (asprintf(&fildes, "%d", write_side) == -1 || + fildes == NULL) + err(1, "failed to setup to detach daemon (_dup failed)"); + + new_argv[0] = argv[0]; + new_argv[1] = special_arg; + new_argv[2] = fildes; + for (i = 1; argv[i] != NULL; i++) + new_argv[i + 1] = argv[i]; + new_argv[argc + 2] = NULL; + + _flushall(); + child_handle = spawnvp(_P_NOWAIT, argv[0], new_argv); + if (child_handle == -1) + child = (pid_t)-1; + else + child = GetProcessId((HANDLE)child_handle); + } +#endif + if (child == (pid_t)-1) + err(1, "failed to setup to fork daemon (fork failed)"); + +#ifndef WIN32 + if (child == 0) { + int fd; + + (void) close(pipefds[0]); + pipefds[0] = -1; + /* + * Keep stdout/stderr for now so output and errors prior to + * detach_finish() can be seen by the user. + */ + fd = open(_PATH_DEVNULL, O_RDWR, 0); + if (fd == -1) + err(1, "failed to open /dev/null"); + (void) dup2(fd, STDIN_FILENO); + if (fd > STDERR_FILENO) + (void) close(fd); + return; + } +#endif + + (void) close(pipefds[1]); + pipefds[1] = -1; + do { + bytes = read(pipefds[0], buf, sizeof(buf)); + } while (bytes == -1 && errno == EINTR); + if (bytes == -1) { + /* + * No need to wait for the process. We've killed it. If it + * doesn't want to exit, we'd have to wait potentially forever, + * but we want to indicate failure to the user as soon as + * possible. A wait with timeout would end the same way + * (attempting to kill the process). + */ + err(1, "failed to setup daemon child (read from child pipe)"); + } + if (bytes == 0) { + warnx("daemon child preparation failed, waiting for child"); + status = wait_for_process(child); + if (SE_IS_ERROR(status) || SE_PROCSTATUS(status) != 0) + errx(SE_PROCSTATUS(status), + "daemon child preparation failed (child exited)"); + } + _exit(0); +} + +#ifdef WIN32 +#ifdef dup2 +#undef dup2 +#endif +#define dup2 _dup2 +#endif + +ROKEN_LIB_FUNCTION void ROKEN_LIB_CALL +roken_detach_finish(const char *dir, int daemon_child_fd) +{ + char buf[1]; + ssize_t bytes; + int fd; + + if (pipefds[1] == -1 && daemon_child_fd != -1) + pipefds[1] = daemon_child_fd; + if (pipefds[0] != -1) + (void) close(pipefds[0]); + if (pipefds[1] == -1) + return; + +#ifdef HAVE_SETSID + if (setsid() == -1) + err(1, "failed to detach from tty"); +#endif + +#ifndef WIN32 + /* + * Hopefully we've written any pidfiles by now, if they had to be in + * the current directory... + * + * The daemons do re-open logs and so on, therefore this chdir() + * call needs to be optional for testing. + */ + if (dir != NULL && chdir(dir) == -1) + err(1, "failed to chdir to /"); +#endif + + buf[1] = 0; + do { + bytes = write(pipefds[1], buf, sizeof(buf)); + } while (bytes == -1 && errno == EINTR); + if (bytes == -1) + err(1, "failed to signal parent while detaching"); + (void) close(pipefds[1]); + if (bytes != sizeof(buf)) + errx(1, "failed to signal parent while detaching"); + + fd = open(_PATH_DEVNULL, O_RDWR, 0); + if (fd == -1) + err(1, "failed to open /dev/null"); + /* + * Maybe we should check that our output got written, if redirected + * to a file. File utils normally do this. + */ + (void) dup2(fd, STDOUT_FILENO); + (void) dup2(fd, STDERR_FILENO); + if (fd > 2) + (void) close(fd); +} diff --git a/lib/roken/getarg.c b/lib/roken/getarg.c index d6a504868..76f3c808a 100644 --- a/lib/roken/getarg.c +++ b/lib/roken/getarg.c @@ -595,7 +595,7 @@ struct getargs args[] = { int main(int argc, char **argv) { int goptind = 0; - while(getarg(args, 5, argc, argv, &goptind)) + while (getarg(args, 5, argc, argv, &goptind)) printf("Bad arg: %s\n", argv[goptind]); printf("flag1 = %d\n", flag1); printf("flag2 = %d\n", flag2); diff --git a/lib/roken/roken-common.h b/lib/roken/roken-common.h index c6e734c80..8221c2a76 100644 --- a/lib/roken/roken-common.h +++ b/lib/roken/roken-common.h @@ -126,7 +126,11 @@ #define O_NOFOLLOW 0 #endif -#ifndef _WIN32 +#ifdef _WIN32 + +#define _PATH_DEVNULL "\\\\.\\NUL" + +#else #ifndef _PATH_DEV #define _PATH_DEV "/dev/" diff --git a/lib/roken/roken.h.in b/lib/roken/roken.h.in index 46449b248..14034dc12 100644 --- a/lib/roken/roken.h.in +++ b/lib/roken/roken.h.in @@ -810,6 +810,9 @@ ROKEN_LIB_FUNCTION int ROKEN_LIB_CALL roken_vconcat (char *, size_t, va_list); ROKEN_LIB_FUNCTION size_t ROKEN_LIB_CALL roken_vmconcat (char **, size_t, va_list); +ROKEN_LIB_FUNCTION void ROKEN_LIB_CALL roken_detach_prep(int, char **, char *); +ROKEN_LIB_FUNCTION void ROKEN_LIB_CALL roken_detach_finish(const char *, int); + ROKEN_LIB_FUNCTION ssize_t ROKEN_LIB_CALL net_write (rk_socket_t, const void *, size_t); diff --git a/lib/roken/test-detach.c b/lib/roken/test-detach.c new file mode 100644 index 000000000..09434d8ab --- /dev/null +++ b/lib/roken/test-detach.c @@ -0,0 +1,82 @@ +/*********************************************************************** + * Copyright (c) 2015, Cryptonector LLC + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + **********************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#ifdef WIN32 +#include +#ifdef getpid +#undef getpid +#endif +#define getpid _getpid +#else +#include +#endif +#include "roken.h" + +int main(int argc, char **argv) +{ + char *ends; + long n; + int fd = -1; + + if (argc > 1) { + if (argc != 3) + errx(1, "Usage: test-detach [--daemon-child fd]"); + fprintf(stderr, "Child started (argv[1] = %s, argv[2] = %s)!\n", argv[1], argv[2]); + errno = 0; + n = strtol(argv[2], &ends, 10); + fd = n; + if (errno != 0) + err(1, "Usage: test-detach [--daemon-child fd]"); + if (n < 0 || ends == NULL || *ends != '\0' || n != fd) + errx(1, "Usage: test-detach [--daemon-child fd]"); + } else { + fprintf(stderr, "Parent started as %ld\n", (long)getpid()); + roken_detach_prep(argc, argv, "--daemon-child"); + } + fprintf(stderr, "Now should be the child: %ld\n", (long)getpid()); + roken_detach_finish(NULL, fd); + /* + * These printfs will not appear: stderr will have been replaced + * with /dev/null. + */ + fprintf(stderr, "Now should be the child: %ld, wrote to parent\n", (long)getpid()); + sleep(5); + fprintf(stderr, "Daemon child done\n"); + return 0; +} diff --git a/lib/roken/version-script.map b/lib/roken/version-script.map index d396b3041..31c942cdc 100644 --- a/lib/roken/version-script.map +++ b/lib/roken/version-script.map @@ -162,6 +162,8 @@ HEIMDAL_ROKEN_1.0 { rk_warnerr; rk_xfree; roken_concat; + roken_detach_prep; + roken_detach_finish; roken_getaddrinfo_hostspec2; roken_getaddrinfo_hostspec; roken_gethostby_setup; diff --git a/lib/roken/write_pid.c b/lib/roken/write_pid.c index 6d83d87af..4b549f1b3 100644 --- a/lib/roken/write_pid.c +++ b/lib/roken/write_pid.c @@ -36,29 +36,42 @@ #include "roken.h" ROKEN_LIB_FUNCTION char * ROKEN_LIB_CALL -pid_file_write (const char *progname) +pid_file_write(const char *progname) { + const char *pidfile_dir = NULL; char *ret = NULL; FILE *fp; - if (asprintf (&ret, "%s%s.pid", _PATH_VARRUN, progname) < 0 || ret == NULL) + /* + * Maybe we could have a version of this function (and pidfile()) + * where we get a directory from the caller. That would allow us to + * have command-line options for the daemons for this. + * + * For now we use an environment variable. + */ + if (!issuid()) + pidfile_dir = getenv("HEIM_PIDFILE_DIR"); + if (pidfile_dir == NULL) + pidfile_dir = _PATH_VARRUN; + + if (asprintf(&ret, "%s%s.pid", pidfile_dir, progname) < 0 || ret == NULL) return NULL; - fp = fopen (ret, "w"); + fp = fopen(ret, "w"); if (fp == NULL) { - free (ret); + free(ret); return NULL; } - fprintf (fp, "%u", (unsigned)getpid()); - fclose (fp); + fprintf(fp, "%u", (unsigned)getpid()); + fclose(fp); return ret; } ROKEN_LIB_FUNCTION void ROKEN_LIB_CALL -pid_file_delete (char **filename) +pid_file_delete(char **filename) { if (*filename != NULL) { - unlink (*filename); - free (*filename); + unlink(*filename); + free(*filename); *filename = NULL; } } @@ -69,16 +82,16 @@ static char *pidfile_path; static void pidfile_cleanup(void) { - if(pidfile_path != NULL) + if (pidfile_path != NULL) pid_file_delete(&pidfile_path); } ROKEN_LIB_FUNCTION void ROKEN_LIB_CALL pidfile(const char *bname) { - if(pidfile_path != NULL) + if (pidfile_path != NULL) return; - if(bname == NULL) + if (bname == NULL) bname = getprogname(); pidfile_path = pid_file_write(bname); #if defined(HAVE_ATEXIT)