/******************************************************************************* * * ipc_unix.c * * Description: Implements the AF_UNIX IPC method. * * Copyright (c) 1997-2000 Messaging Direct Ltd. * All rights reserved. * * Portions Copyright (c) 2003 Jeremy Rumpf * jrumpf@heavyload.net * * 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. * * THIS SOFTWARE IS PROVIDED BY MESSAGING DIRECT LTD. ``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 MESSAGING DIRECT LTD. OR * ITS EMPLOYEES OR AGENTS 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. * * * HISTORY * * * This source file created using 8 space tabs. * ********************************************************************************/ /**************************************** * enable/disable ifdef *****************************************/ #include "saslauthd-main.h" #ifdef USE_UNIX_IPC /****************************************/ /**************************************** * includes *****************************************/ #include #include #include #include #include #include #include #include #include #include #include "globals.h" #include "utils.h" /**************************************** * declarations/protos *****************************************/ static void do_request(int); static void send_no(int, char *); static int rel_accept_lock(); static int get_accept_lock(); /**************************************** * module globals *****************************************/ static int sock_fd; /* descriptor for the socket */ static int accept_fd; /* descriptor for the accept lock */ static struct sockaddr_un server; /* domain socket control, server side */ static struct sockaddr_un client; /* domain socket control, client side */ static SALEN_TYPE len; /* length for the client sockaddr_un */ static char *sock_file; /* path to the AF_UNIX socket */ static char *accept_file;/* path to the accept() lock file */ /**************************************** * flags global from saslauthd-main.c * run_path global from saslauthd-main.c * num_procs global from saslauthd-main.c * detach_tty() function from saslauthd-main.c * rx_rec() function from utils.c * tx_rec() function from utils.c * logger() function from utils.c *****************************************/ /************************************************************* * IPC init. Initialize the environment specific to the * AF_UNIX IPC method. * * __Required Function__ **************************************************************/ void ipc_init() { int rc; size_t sock_file_len; /********************************************************* * When we're not preforking, using an accept lock is a * waste of resources. Otherwise, setup the accept lock * file. **********************************************************/ if (num_procs == 0) flags &= ~USE_ACCEPT_LOCK; if (flags & USE_ACCEPT_LOCK) { size_t accept_file_len; accept_file_len = strlen(run_path) + sizeof(ACCEPT_LOCK_FILE) + 1; if ((accept_file = malloc(accept_file_len)) == NULL) { logger(L_ERR, L_FUNC, "could not allocate memory"); exit(1); } strlcpy(accept_file, run_path, accept_file_len); strlcat(accept_file, ACCEPT_LOCK_FILE, accept_file_len); if ((accept_fd = open(accept_file, O_RDWR|O_CREAT|O_TRUNC, S_IWUSR|S_IRUSR)) == -1) { rc = errno; logger(L_ERR, L_FUNC, "could not open accept lock file: %s", accept_file); logger(L_ERR, L_FUNC, "open: %s", strerror(rc)); exit(1); } if (flags & VERBOSE) logger(L_DEBUG, L_FUNC, "using accept lock file: %s", accept_file); } /************************************************************** * We're at the point where we can't really do anything else * until we attempt to detach or daemonize. **************************************************************/ detach_tty(); /************************************************************** * Setup the UNIX domain socket **************************************************************/ sock_file_len = strlen(run_path) + sizeof(SOCKET_FILE) + 1; if ((sock_file = malloc(sock_file_len)) == NULL) { logger(L_ERR, L_FUNC, "could not allocate memory"); exit(1); } strlcpy(sock_file, run_path, sock_file_len); strlcat(sock_file, SOCKET_FILE, sock_file_len); unlink(sock_file); memset(&server, 0, sizeof(server)); strlcpy(server.sun_path, sock_file, sizeof(server.sun_path)); server.sun_family = AF_UNIX; if ((sock_fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { rc = errno; logger(L_ERR, L_FUNC, "could not create socket"); logger(L_ERR, L_FUNC, "socket: %s", strerror(rc)); exit(1); } umask(0); if (bind(sock_fd, (struct sockaddr *)&server, sizeof(server)) == -1) { rc = errno; logger(L_ERR, L_FUNC, "could not bind to socket: %s", sock_file); logger(L_ERR, L_FUNC, "bind: %s", strerror(rc)); exit(1); } if (chmod(sock_file, S_IRWXU|S_IRWXG|S_IRWXO) == -1) { rc = errno; logger(L_ERR, L_FUNC, "could not chmod socket: %s", sock_file); logger(L_ERR, L_FUNC, "chmod: %s", strerror(rc)); exit(1); } fchmod(sock_fd, S_IRWXU|S_IRWXG|S_IRWXO); umask(077); if (listen(sock_fd, SOCKET_BACKLOG) == -1) { rc = errno; logger(L_ERR, L_FUNC, "could not listen on socket: %s", sock_file); logger(L_ERR, L_FUNC, "listen: %s", strerror(rc)); exit(1); } logger(L_INFO, L_FUNC, "listening on socket: %s", sock_file); /************************************************************** * Ok boys... Let's procreate... If necessary of course... * Num_procs == 0 means we're running one shot per process. In * that case, we'll handle forking on a per connection basis. **************************************************************/ if (num_procs != 0) flags |= USE_PROCESS_MODEL; return; } /************************************************************* * Main IPC loop. Handle all the socket accept stuff, fork if * needed, then pass things off to do_request(). * * __Required Function__ **************************************************************/ void ipc_loop() { int rc; int conn_fd; while(1) { len = sizeof(client); /************************************************************** * First, if needed, get the accept lock. If it fails, take a * nap and go to the top of the loop. (or should we just die?) *************************************************************/ if (get_accept_lock() != 0) { sleep(5); continue; } conn_fd = accept(sock_fd, (struct sockaddr *)&client, &len); rc = errno; rel_accept_lock(); if (conn_fd == -1) { if (rc != EINTR) { logger(L_ERR, L_FUNC, "socket accept failure"); logger(L_ERR, L_FUNC, "accept: %s", strerror(rc)); sleep(5); } continue; } /************************************************************** * If we're running one shot, drop off a kid to handle the * connection. *************************************************************/ if (num_procs == 0) { if(flags & DETACH_TTY) { if (have_baby() > 0) { /* parent */ close(conn_fd); continue; } close(sock_fd); /* child */ } do_request(conn_fd); close(conn_fd); if(flags & DETACH_TTY) { exit(0); } else { continue; } } /************************************************************** * Normal prefork mode. *************************************************************/ do_request(conn_fd); close(conn_fd); } return; } /************************************************************* * General cleanup. Unlock, close, and unlink our files. * * __Required Function__ **************************************************************/ void ipc_cleanup() { struct flock lock_st; if (flags & USE_ACCEPT_LOCK) { lock_st.l_type = F_UNLCK; lock_st.l_start = 0; lock_st.l_whence = SEEK_SET; lock_st.l_len = 1; fcntl(accept_fd, F_SETLK, &lock_st); close(accept_fd); unlink(accept_file); if (flags & VERBOSE) logger(L_DEBUG, L_FUNC, "accept lock file removed: %s", accept_file); } close(sock_fd); unlink(sock_file); if (flags & VERBOSE) logger(L_DEBUG, L_FUNC, "socket removed: %s", sock_file); } /************************************************************* * Handle the comms on the socket, pass the request off to * do_auth() back in saslauthd-main.c, then transmit the * result back out on the socket. **************************************************************/ void do_request(int conn_fd) { unsigned short count; /* input/output data byte count */ unsigned short ncount; /* input/output data byte count, network */ char *response; /* response to send to the client */ char login[MAX_REQ_LEN + 1]; /* account name to authenticate */ char password[MAX_REQ_LEN + 1]; /* password for authentication */ char service[MAX_REQ_LEN + 1]; /* service name for authentication */ char realm[MAX_REQ_LEN + 1]; /* user realm for authentication */ /************************************************************** * The input data stream consists of the login id, password, * service name and user realm as counted length strings. * We read in each string, then dispatch the data. **************************************************************/ /* login id */ if (rx_rec(conn_fd, (void *)&count, (size_t)sizeof(count)) != (ssize_t)sizeof(count)) return; count = ntohs(count); if (count > MAX_REQ_LEN) { logger(L_ERR, L_FUNC, "login exceeded MAX_REQ_LEN: %d", MAX_REQ_LEN); send_no(conn_fd, ""); return; } if (rx_rec(conn_fd, (void *)login, (size_t)count) != (ssize_t)count) return; login[count] = '\0'; /* password */ if (rx_rec(conn_fd, (void *)&count, (size_t)sizeof(count)) != (ssize_t)sizeof(count)) return; count = ntohs(count); if (count > MAX_REQ_LEN) { logger(L_ERR, L_FUNC, "password exceeded MAX_REQ_LEN: %d", MAX_REQ_LEN); send_no(conn_fd, ""); return; } if (rx_rec(conn_fd, (void *)password, (size_t)count) != (ssize_t)count) return; password[count] = '\0'; /* service */ if (rx_rec(conn_fd, (void *)&count, (size_t)sizeof(count)) != (ssize_t)sizeof(count)) return; count = ntohs(count); if (count > MAX_REQ_LEN) { logger(L_ERR, L_FUNC, "service exceeded MAX_REQ_LEN: %d", MAX_REQ_LEN); send_no(conn_fd, ""); return; } if (rx_rec(conn_fd, (void *)service, (size_t)count) != (ssize_t)count) return; service[count] = '\0'; /* realm */ if (rx_rec(conn_fd, (void *)&count, (size_t)sizeof(count)) != (ssize_t)sizeof(count)) return; count = ntohs(count); if (count > MAX_REQ_LEN) { logger(L_ERR, L_FUNC, "realm exceeded MAX_REQ_LEN: %d", MAX_REQ_LEN); send_no(conn_fd, ""); return; } if (rx_rec(conn_fd, (void *)realm, (size_t)count) != (ssize_t)count) return; realm[count] = '\0'; /************************************************************** * We don't allow NULL passwords or login names **************************************************************/ if (*login == '\0') { logger(L_ERR, L_FUNC, "NULL login received"); send_no(conn_fd, "NULL login received"); return; } if (*password == '\0') { logger(L_ERR, L_FUNC, "NULL password received"); send_no(conn_fd, "NULL password received"); return; } /************************************************************** * Get the mechanism response from do_auth() and send it back. **************************************************************/ response = do_auth(login, password, service, realm); memset(password, 0, strlen(password)); if (response == NULL) { send_no(conn_fd, "NULL response from mechanism"); return; } count = strlen(response); ncount = htons(count); if (tx_rec(conn_fd, (void *)&ncount, (size_t)sizeof(ncount)) != (ssize_t)sizeof(ncount)) { free(response); return; } if (tx_rec(conn_fd, (void *)response, (size_t)count) != (ssize_t)sizeof(count)) { free(response); return; } if (flags & VERBOSE) logger(L_DEBUG, L_FUNC, "response: %s", response); free(response); return; } /************************************************************* * In case something went out to lunch while reading in the * request data, we may want to attempt to send out a default * "NO" response on the socket. The mesg is optional. **************************************************************/ void send_no(int conn_fd, char *mesg) { char buff[1024]; unsigned short count; unsigned short ncount; buff[0] = 'N'; buff[1] = 'O'; buff[2] = ' '; /* buff, except for the trailing NUL and 'NO ' */ strncpy(buff + 3, mesg, sizeof(buff) - 1 - 3); buff[1023] = '\0'; count = strlen(buff); ncount = htons(count); if (tx_rec(conn_fd, (void *)&ncount, (size_t)sizeof(ncount)) != (ssize_t)sizeof(ncount)) return; if (tx_rec(conn_fd, (void *)buff, (size_t)count) != (ssize_t)sizeof(count)) return; if (flags & VERBOSE) logger(L_DEBUG, L_FUNC, "response: %s", buff); return; } /************************************************************* * Attempt to get a write lock on the accept lock file. * Return 0 if everything went ok, return -1 if something bad * happened. This function is expected to block. **************************************************************/ int get_accept_lock() { struct flock lock_st; int rc; if (!(flags & USE_ACCEPT_LOCK)) return 0; lock_st.l_type = F_WRLCK; lock_st.l_start = 0; lock_st.l_whence = SEEK_SET; lock_st.l_len = 1; errno = 0; do { rc = fcntl(accept_fd, F_SETLKW, &lock_st); } while (rc != 0 && errno == EINTR); if (rc != 0) { rc = errno; logger(L_ERR, L_FUNC, "could not acquire accept lock"); logger(L_ERR, L_FUNC, "fcntl: %s", strerror(rc)); return -1; } if (flags & VERBOSE) logger(L_DEBUG, L_FUNC, "acquired accept lock"); return 0; } /************************************************************* * Attempt to release the write lock on the accept lock file. * Return 0 if everything went ok, return -1 if something bad * happened. **************************************************************/ int rel_accept_lock() { struct flock lock_st; int rc; if (!(flags & USE_ACCEPT_LOCK)) return 0; lock_st.l_type = F_UNLCK; lock_st.l_start = 0; lock_st.l_whence = SEEK_SET; lock_st.l_len = 1; errno = 0; do { rc = fcntl(accept_fd, F_SETLKW, &lock_st); } while (rc != 0 && errno == EINTR); if (rc != 0) { rc = errno; logger(L_ERR, L_FUNC, "could not release accept lock"); logger(L_ERR, L_FUNC, "fcntl: %s", strerror(rc)); return -1; } if (flags & VERBOSE) logger(L_DEBUG, L_FUNC, "released accept lock"); return 0; } #endif /* USE_UNIX_IPC */