/* nslcd.c - ldap local connection daemon Copyright (C) 2006 West Consulting Copyright (C) 2006-2014 Arthur de Jong This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "config.h" #include #include #include #ifdef HAVE_STDINT_H #include #endif /* HAVE_STDINT_H */ #include #include #include #ifdef HAVE_GETOPT_H #include #endif /* HAVE_GETOPT_H */ #include #include #include #include #include #include #include #include #include #ifdef HAVE_NSS_H #include #endif /* HAVE_NSS_H */ #include #ifdef HAVE_PTHREAD_NP_H #include #endif /* HAVE_PTHREAD_NP_H */ #ifndef HAVE_GETOPT_LONG #include "compat/getopt_long.h" #endif /* not HAVE_GETOPT_LONG */ #include #include #include #include #include "nslcd.h" #include "log.h" #include "cfg.h" #include "common.h" #include "compat/attrs.h" #include "compat/getpeercred.h" #include "compat/socket.h" /* read timeout is half a second because clients should send their request quickly, write timeout is 60 seconds because clients could be taking some time to process the results */ #define READ_TIMEOUT 500 #define WRITE_TIMEOUT 60 * 1000 /* buffer sizes for I/O */ #define READBUFFER_MINSIZE 32 #define READBUFFER_MAXSIZE 64 #define WRITEBUFFER_MINSIZE 1024 #define WRITEBUFFER_MAXSIZE 1 * 1024 * 1024 /* flag to indicate if we are in debugging mode */ static int nslcd_debugging = 0; /* the flag to indicate that a signal was received */ static volatile int nslcd_receivedsignal = 0; /* the server socket used for communication */ static int nslcd_serversocket = -1; /* thread ids of all running threads */ static pthread_t *nslcd_threads; /* display version information */ static void display_version(FILE *fp) { fprintf(fp, "%s\n", PACKAGE_STRING); fprintf(fp, "Written by Luke Howard and Arthur de Jong.\n\n"); fprintf(fp, "Copyright (C) 1997-2014 Luke Howard, Arthur de Jong and West Consulting\n" "This is free software; see the source for copying conditions. There is NO\n" "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"); } /* display usage information */ static void display_usage(FILE *fp, const char *program_name) { fprintf(fp, "Usage: %s [OPTION]...\n", program_name); fprintf(fp, "Name Service LDAP connection daemon.\n"); fprintf(fp, " -d, --debug print debugging to stderr\n"); fprintf(fp, " --help display this help and exit\n"); fprintf(fp, " --version output version information and exit\n"); fprintf(fp, "\n" "Report bugs to <%s>.\n", PACKAGE_BUGREPORT); } /* the definition of options for getopt(). see getopt(2) */ static struct option const nslcd_options[] = { {"debug", no_argument, NULL, 'd'}, {"help", no_argument, NULL, 'h'}, {"version", no_argument, NULL, 'V'}, {NULL, 0, NULL, 0} }; #define NSLCD_OPTIONSTRING "cndhV" /* parse command line options and save settings in struct */ static void parse_cmdline(int argc, char *argv[]) { int optc; while ((optc = getopt_long(argc, argv, NSLCD_OPTIONSTRING, nslcd_options, NULL)) != -1) { switch (optc) { case 'd': /* -d, --debug print debugging to stderr */ nslcd_debugging++; log_setdefaultloglevel(LOG_DEBUG); break; case 'h': /* --help display this help and exit */ display_usage(stdout, argv[0]); exit(EXIT_SUCCESS); case 'V': /* --version output version information and exit */ display_version(stdout); exit(EXIT_SUCCESS); case ':': /* missing required parameter */ case '?': /* unknown option character or extraneous parameter */ default: fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]); exit(EXIT_FAILURE); } } /* check for remaining arguments */ if (optind < argc) { fprintf(stderr, "%s: unrecognized option '%s'\n", argv[0], argv[optind]); fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]); exit(EXIT_FAILURE); } } /* signal handler for storing information on received signals */ static void sig_handler(int signum) { /* just save the signal to indicate that we're stopping */ nslcd_receivedsignal = signum; } /* do some cleaning up before terminating */ static void exithandler(void) { /* close socket if it's still in use */ if (nslcd_serversocket >= 0) { if (close(nslcd_serversocket)) log_log(LOG_WARNING, "problem closing server socket (ignored): %s", strerror(errno)); } /* log exit */ log_log(LOG_INFO, "version %s bailing out", VERSION); } static int get_socket() { int r, fd; r = sd_listen_fds(1); if (r != 1) { if (r < 0) log_log(LOG_ERR, "failed to aquire sockets from systemd: %s", strerror(-r)); else log_log(LOG_ERR, "wrong number of sockets from systemd: " "expected %d but got %d", 1, r); exit(EXIT_FAILURE); } fd = SD_LISTEN_FDS_START; r = sd_is_socket(fd, AF_UNIX, SOCK_STREAM, 1); if (r < 1) { if (r < 0) log_log(LOG_ERR, "unable to verify socket type:%d: %s", fd, strerror(-r)); else log_log(LOG_ERR, "socket not of the right type:%d", fd); exit(EXIT_FAILURE); } return fd; } /* read the version information and action from the stream this function returns the read action in location pointer to by action */ static int read_header(TFILE *fp, int32_t *action) { int32_t tmpint32; int32_t protocol; /* read the protocol version */ READ_INT32(fp, protocol); if (protocol != (int32_t)NSLCD_VERSION) { log_log(LOG_DEBUG, "invalid nslcd version id: 0x%08x", (unsigned int)protocol); return -1; } /* read the request type */ READ_INT32(fp, *action); return 0; } /* read a request message, returns <0 in case of errors, this function closes the socket */ static void handleconnection(int sock, MYLDAP_SESSION *session) { TFILE *fp; int32_t action; uid_t uid = (uid_t)-1; gid_t gid = (gid_t)-1; pid_t pid = (pid_t)-1; /* log connection */ if (getpeercred(sock, &uid, &gid, &pid)) log_log(LOG_DEBUG, "connection from unknown client: %s", strerror(errno)); else log_log(LOG_DEBUG, "connection from pid=%d uid=%d gid=%d", (int)pid, (int)uid, (int)gid); /* create a stream object */ if ((fp = tio_fdopen(sock, READ_TIMEOUT, WRITE_TIMEOUT, READBUFFER_MINSIZE, READBUFFER_MAXSIZE, WRITEBUFFER_MINSIZE, WRITEBUFFER_MAXSIZE)) == NULL) { log_log(LOG_WARNING, "cannot create stream for writing: %s", strerror(errno)); (void)close(sock); return; } /* read request */ if (read_header(fp, &action)) { (void)tio_close(fp); return; } /* handle request */ switch (action) { case NSLCD_ACTION_CONFIG_GET: (void)nslcd_config_get(fp, session); break; case NSLCD_ACTION_ALIAS_BYNAME: (void)nslcd_alias_byname(fp, session); break; case NSLCD_ACTION_ALIAS_ALL: (void)nslcd_alias_all(fp, session); break; case NSLCD_ACTION_ETHER_BYNAME: (void)nslcd_ether_byname(fp, session); break; case NSLCD_ACTION_ETHER_BYETHER: (void)nslcd_ether_byether(fp, session); break; case NSLCD_ACTION_ETHER_ALL: (void)nslcd_ether_all(fp, session); break; case NSLCD_ACTION_GROUP_BYNAME: (void)nslcd_group_byname(fp, session); break; case NSLCD_ACTION_GROUP_BYGID: (void)nslcd_group_bygid(fp, session); break; case NSLCD_ACTION_GROUP_BYMEMBER: (void)nslcd_group_bymember(fp, session); break; case NSLCD_ACTION_GROUP_ALL: (void)nslcd_group_all(fp, session); break; case NSLCD_ACTION_HOST_BYNAME: (void)nslcd_host_byname(fp, session); break; case NSLCD_ACTION_HOST_BYADDR: (void)nslcd_host_byaddr(fp, session); break; case NSLCD_ACTION_HOST_ALL: (void)nslcd_host_all(fp, session); break; case NSLCD_ACTION_NETGROUP_BYNAME: (void)nslcd_netgroup_byname(fp, session); break; case NSLCD_ACTION_NETGROUP_ALL: (void)nslcd_netgroup_all(fp, session); break; case NSLCD_ACTION_NETWORK_BYNAME: (void)nslcd_network_byname(fp, session); break; case NSLCD_ACTION_NETWORK_BYADDR: (void)nslcd_network_byaddr(fp, session); break; case NSLCD_ACTION_NETWORK_ALL: (void)nslcd_network_all(fp, session); break; case NSLCD_ACTION_PASSWD_BYNAME: (void)nslcd_passwd_byname(fp, session, uid); break; case NSLCD_ACTION_PASSWD_BYUID: (void)nslcd_passwd_byuid(fp, session, uid); break; case NSLCD_ACTION_PASSWD_ALL: (void)nslcd_passwd_all(fp, session, uid); break; case NSLCD_ACTION_PROTOCOL_BYNAME: (void)nslcd_protocol_byname(fp, session); break; case NSLCD_ACTION_PROTOCOL_BYNUMBER:(void)nslcd_protocol_bynumber(fp, session); break; case NSLCD_ACTION_PROTOCOL_ALL: (void)nslcd_protocol_all(fp, session); break; case NSLCD_ACTION_RPC_BYNAME: (void)nslcd_rpc_byname(fp, session); break; case NSLCD_ACTION_RPC_BYNUMBER: (void)nslcd_rpc_bynumber(fp, session); break; case NSLCD_ACTION_RPC_ALL: (void)nslcd_rpc_all(fp, session); break; case NSLCD_ACTION_SERVICE_BYNAME: (void)nslcd_service_byname(fp, session); break; case NSLCD_ACTION_SERVICE_BYNUMBER: (void)nslcd_service_bynumber(fp, session); break; case NSLCD_ACTION_SERVICE_ALL: (void)nslcd_service_all(fp, session); break; case NSLCD_ACTION_SHADOW_BYNAME: (void)nslcd_shadow_byname(fp, session, uid); break; case NSLCD_ACTION_SHADOW_ALL: (void)nslcd_shadow_all(fp, session, uid); break; case NSLCD_ACTION_PAM_AUTHC: (void)nslcd_pam_authc(fp, session, uid); break; case NSLCD_ACTION_PAM_AUTHZ: (void)nslcd_pam_authz(fp, session); break; case NSLCD_ACTION_PAM_SESS_O: (void)nslcd_pam_sess_o(fp, session); break; case NSLCD_ACTION_PAM_SESS_C: (void)nslcd_pam_sess_c(fp, session); break; case NSLCD_ACTION_PAM_PWMOD: (void)nslcd_pam_pwmod(fp, session, uid); break; case NSLCD_ACTION_USERMOD: (void)nslcd_usermod(fp, session, uid); break; default: log_log(LOG_WARNING, "invalid request id: 0x%08x", (unsigned int)action); break; } /* we're done with the request */ myldap_session_cleanup(session); (void)tio_close(fp); return; } /* try to install signal handler and check result */ static void install_sighandler(int signum, void (*handler) (int)) { struct sigaction act; memset(&act, 0, sizeof(struct sigaction)); act.sa_handler = handler; sigemptyset(&act.sa_mask); act.sa_flags = SA_RESTART | SA_NOCLDSTOP; if (sigaction(signum, &act, NULL) != 0) { log_log(LOG_ERR, "error installing signal handler for '%s': %s", signame(signum), strerror(errno)); exit(EXIT_FAILURE); } } static void worker_cleanup(void *arg) { MYLDAP_SESSION *session = (MYLDAP_SESSION *)arg; myldap_session_close(session); } static void *worker(void UNUSED(*arg)) { MYLDAP_SESSION *session; int csock; int j; struct sockaddr_storage addr; socklen_t alen; fd_set fds; struct timeval tv; /* create a new LDAP session */ session = myldap_create_session(); /* clean up the session if we're done */ pthread_cleanup_push(worker_cleanup, session); /* start waiting for incoming connections */ while (1) { /* time out connection to LDAP server if needed */ myldap_session_check(session); /* set up the set of fds to wait on */ FD_ZERO(&fds); FD_SET(nslcd_serversocket, &fds); /* set up our timeout value */ tv.tv_sec = nslcd_cfg->idle_timelimit; tv.tv_usec = 0; /* wait for a new connection */ j = select(nslcd_serversocket + 1, &fds, NULL, NULL, nslcd_cfg->idle_timelimit > 0 ? &tv : NULL); /* check result of select() */ if (j < 0) { if (errno == EINTR) log_log(LOG_DEBUG, "select() failed (ignored): %s", strerror(errno)); else log_log(LOG_ERR, "select() failed: %s", strerror(errno)); continue; } /* see if our file descriptor is actually ready */ if (!FD_ISSET(nslcd_serversocket, &fds)) continue; /* wait for a new connection */ alen = (socklen_t)sizeof(struct sockaddr_storage); csock = accept(nslcd_serversocket, (struct sockaddr *)&addr, &alen); if (csock < 0) { if ((errno == EINTR) || (errno == EAGAIN) || (errno == EWOULDBLOCK)) log_log(LOG_DEBUG, "accept() failed (ignored): %s", strerror(errno)); else log_log(LOG_ERR, "accept() failed: %s", strerror(errno)); continue; } /* make sure O_NONBLOCK is not inherited */ if ((j = fcntl(csock, F_GETFL, 0)) < 0) { log_log(LOG_ERR, "fctnl(F_GETFL) failed: %s", strerror(errno)); if (close(csock)) log_log(LOG_WARNING, "problem closing socket: %s", strerror(errno)); continue; } if (fcntl(csock, F_SETFL, j & ~O_NONBLOCK) < 0) { log_log(LOG_ERR, "fctnl(F_SETFL,~O_NONBLOCK) failed: %s", strerror(errno)); if (close(csock)) log_log(LOG_WARNING, "problem closing socket: %s", strerror(errno)); continue; } /* indicate new connection to logging module (generates unique id) */ log_newsession(); /* handle the connection */ handleconnection(csock, session); /* indicate end of session in log messages */ log_clearsession(); } pthread_cleanup_pop(1); return NULL; } /* function to disable lookups through the nss_ldap module to avoid lookup loops */ static void disable_nss_ldap(void) { void *handle; char *error; char **version_info; int *enable_flag; /* try to load the NSS module */ #ifdef RTLD_NODELETE handle = dlopen(NSS_LDAP_SONAME, RTLD_LAZY | RTLD_NODELETE); #else /* not RTLD_NODELETE */ handle = dlopen(NSS_LDAP_SONAME, RTLD_LAZY); #endif /* RTLD_NODELETE */ if (handle == NULL) { log_log(LOG_WARNING, "Warning: NSS_LDAP module not loaded: %s", dlerror()); return; } /* clear any existing errors */ dlerror(); /* lookup the NSS version if possible */ version_info = (char **)dlsym(handle, "_nss_ldap_version"); error = dlerror(); if ((version_info != NULL) && (error == NULL)) log_log(LOG_DEBUG, "NSS_LDAP %s %s", version_info[0], version_info[1]); else log_log(LOG_WARNING, "Warning: NSS_LDAP version missing: %s", error); /* clear any existing errors */ dlerror(); /* try to look up the flag */ enable_flag = (int *)dlsym(handle, "_nss_ldap_enablelookups"); error = dlerror(); if ((enable_flag == NULL) || (error != NULL)) { log_log(LOG_WARNING, "Warning: %s (probably older NSS module loaded)", error); /* fall back to changing the way host lookup is done */ #ifdef HAVE___NSS_CONFIGURE_LOOKUP if (__nss_configure_lookup("hosts", "files dns")) log_log(LOG_ERR, "unable to override hosts lookup method: %s", strerror(errno)); #endif /* HAVE___NSS_CONFIGURE_LOOKUP */ dlclose(handle); return; } /* disable nss_ldap */ *enable_flag = 0; #ifdef RTLD_NODELETE /* only close the handle if RTLD_NODELETE was used */ dlclose(handle); #endif /* RTLD_NODELETE */ } /* the main program... */ int main(int argc, char *argv[]) { int i; sigset_t signalmask, oldmask; #ifdef HAVE_PTHREAD_TIMEDJOIN_NP struct timespec ts; #endif /* HAVE_PTHREAD_TIMEDJOIN_NP */ /* parse the command line */ parse_cmdline(argc, argv); /* disable the nss_ldap module for this process */ disable_nss_ldap(); /* set LDAP log level */ if (myldap_set_debuglevel(nslcd_debugging) != LDAP_SUCCESS) exit(EXIT_FAILURE); /* read configuration file */ cfg_init(NSLCD_CONF_PATH); /* intilialize logging */ if (!nslcd_debugging) { log_startlogging(); } /* log start */ log_log(LOG_INFO, "version %s starting", VERSION); /* install handler to close stuff off on exit and log notice */ if (atexit(exithandler)) { log_log(LOG_ERR, "atexit() failed: %s", strerror(errno)); exit(EXIT_FAILURE); } /* get socket */ nslcd_serversocket = get_socket(); /* start subprocess to do invalidating if reconnect_invalidate is set */ for (i = 0; i < LM_NONE; i++) if (nslcd_cfg->reconnect_invalidate[i]) break; if (i < LM_NONE) invalidator_start(); /* change nslcd group and supplemental groups */ if ((nslcd_cfg->gid != NOGID) && (nslcd_cfg->uidname != NULL)) { #ifdef HAVE_INITGROUPS /* load supplementary groups */ if (initgroups(nslcd_cfg->uidname, nslcd_cfg->gid) < 0) log_log(LOG_WARNING, "cannot initgroups(\"%s\",%d) (ignored): %s", nslcd_cfg->uidname, (int)nslcd_cfg->gid, strerror(errno)); else log_log(LOG_DEBUG, "initgroups(\"%s\",%d) done", nslcd_cfg->uidname, (int)nslcd_cfg->gid); #else /* not HAVE_INITGROUPS */ #ifdef HAVE_SETGROUPS /* just drop all supplemental groups */ if (setgroups(0, NULL) < 0) log_log(LOG_WARNING, "cannot setgroups(0,NULL) (ignored): %s", strerror(errno)); else log_log(LOG_DEBUG, "setgroups(0,NULL) done"); #else /* not HAVE_SETGROUPS */ log_log(LOG_DEBUG, "neither initgroups() or setgroups() available"); #endif /* not HAVE_SETGROUPS */ #endif /* not HAVE_INITGROUPS */ } /* change to nslcd gid */ if (nslcd_cfg->gid != NOGID) { if (setgid(nslcd_cfg->gid) != 0) { log_log(LOG_ERR, "cannot setgid(%d): %s", (int)nslcd_cfg->gid, strerror(errno)); exit(EXIT_FAILURE); } log_log(LOG_DEBUG, "setgid(%d) done", (int)nslcd_cfg->gid); } /* change to nslcd uid */ if (nslcd_cfg->uid != NOUID) { if (setuid(nslcd_cfg->uid) != 0) { log_log(LOG_ERR, "cannot setuid(%d): %s", (int)nslcd_cfg->uid, strerror(errno)); exit(EXIT_FAILURE); } log_log(LOG_DEBUG, "setuid(%d) done", (int)nslcd_cfg->uid); } /* block all these signals so our worker threads won't handle them */ sigemptyset(&signalmask); sigaddset(&signalmask, SIGHUP); sigaddset(&signalmask, SIGINT); sigaddset(&signalmask, SIGQUIT); sigaddset(&signalmask, SIGABRT); sigaddset(&signalmask, SIGPIPE); sigaddset(&signalmask, SIGTERM); sigaddset(&signalmask, SIGUSR1); sigaddset(&signalmask, SIGUSR2); pthread_sigmask(SIG_BLOCK, &signalmask, &oldmask); /* start worker threads */ log_log(LOG_INFO, "accepting connections"); nslcd_threads = (pthread_t *)malloc(nslcd_cfg->threads * sizeof(pthread_t)); if (nslcd_threads == NULL) { log_log(LOG_CRIT, "main(): malloc() failed to allocate memory"); exit(EXIT_FAILURE); } for (i = 0; i < nslcd_cfg->threads; i++) { if (pthread_create(&nslcd_threads[i], NULL, worker, NULL)) { log_log(LOG_ERR, "unable to start worker thread %d: %s", i, strerror(errno)); exit(EXIT_FAILURE); } } pthread_sigmask(SIG_SETMASK, &oldmask, NULL); /* install signalhandlers for some signals */ install_sighandler(SIGHUP, sig_handler); install_sighandler(SIGINT, sig_handler); install_sighandler(SIGQUIT, sig_handler); install_sighandler(SIGABRT, sig_handler); install_sighandler(SIGPIPE, SIG_IGN); install_sighandler(SIGTERM, sig_handler); install_sighandler(SIGUSR1, sig_handler); install_sighandler(SIGUSR2, SIG_IGN); /* wait until we received a signal */ while ((nslcd_receivedsignal == 0) || (nslcd_receivedsignal == SIGUSR1)) { sleep(INT_MAX); /* sleep as long as we can or until we receive a signal */ if (nslcd_receivedsignal == SIGUSR1) { log_log(LOG_INFO, "caught signal %s (%d), refresh retries", signame(nslcd_receivedsignal), nslcd_receivedsignal); myldap_immediate_reconnect(); nslcd_receivedsignal = 0; } } /* print something about received signal */ log_log(LOG_INFO, "caught signal %s (%d), shutting down", signame(nslcd_receivedsignal), nslcd_receivedsignal); /* cancel all running threads */ for (i = 0; i < nslcd_cfg->threads; i++) if (pthread_cancel(nslcd_threads[i])) log_log(LOG_WARNING, "failed to stop thread %d (ignored): %s", i, strerror(errno)); /* close server socket to trigger failures in threads waiting on accept() */ close(nslcd_serversocket); nslcd_serversocket = -1; /* if we can, wait a few seconds for the threads to finish */ #ifdef HAVE_PTHREAD_TIMEDJOIN_NP ts.tv_sec = time(NULL) + 3; ts.tv_nsec = 0; #endif /* HAVE_PTHREAD_TIMEDJOIN_NP */ for (i = 0; i < nslcd_cfg->threads; i++) { #ifdef HAVE_PTHREAD_TIMEDJOIN_NP pthread_timedjoin_np(nslcd_threads[i], NULL, &ts); #endif /* HAVE_PTHREAD_TIMEDJOIN_NP */ if (pthread_kill(nslcd_threads[i], 0) == 0) log_log(LOG_ERR, "thread %d is still running, shutting down anyway", i); } /* we're done */ return EXIT_FAILURE; }