#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#ifdef _AIX
# include <strings.h>
#endif
#include <syslog.h>
#include <signal.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/uio.h>
#include "globals.h"
#include "saslauthd-main.h"
#include "cache.h"
#include "utils.h"
static void show_version();
static void show_usage();
int flags = 0;
int g_argc;
char **g_argv;
char *run_path = NULL;
authmech_t *auth_mech = NULL;
char *mech_option = NULL;
int num_procs = 5;
extern char *optarg;
static int master_pid;
static int pid_fd;
static int pid_file_lock_fd;
static char *pid_file;
static char *pid_file_lock;
static int startup_pipe[2] = { -1, -1 };
int main(int argc, char **argv) {
int option;
int rc;
int x;
struct flock lockinfo;
char *auth_mech_name = NULL;
size_t pid_file_size;
SET_AUTH_PARAMETERS(argc, argv);
g_argc = argc;
g_argv = argv;
flags |= USE_ACCEPT_LOCK;
flags |= DETACH_TTY;
flags |= LOG_USE_SYSLOG;
flags |= LOG_USE_STDERR;
flags |= AM_MASTER;
while ((option = getopt(argc, argv, "a:cdhO:lm:n:s:t:vV")) != -1) {
switch(option) {
case 'a':
if(auth_mech_name) {
show_usage();
break;
}
auth_mech_name = strdup(optarg);
if (!auth_mech_name) {
logger(L_ERR, L_FUNC,
"could not allocate memory");
exit(1);
}
break;
case 'c':
flags |= CACHE_ENABLED;
break;
case 'd':
flags |= VERBOSE;
flags &= ~DETACH_TTY;
break;
case 'h':
show_usage();
break;
case 'O':
set_mech_option(optarg);
break;
case 'l':
flags &= ~USE_ACCEPT_LOCK;
break;
case 'm':
set_run_path(optarg);
break;
case 'n':
set_max_procs(optarg);
break;
case 's':
cache_set_table_size(optarg);
break;
case 't':
cache_set_timeout(optarg);
break;
case 'V':
flags |= VERBOSE;
break;
case 'v':
show_version();
break;
default:
show_usage();
break;
}
}
if (run_path == NULL)
run_path = PATH_SASLAUTHD_RUNDIR;
if (auth_mech_name == NULL) {
logger(L_ERR, L_FUNC, "no authentication mechanism specified");
show_usage();
exit(1);
}
set_auth_mech(auth_mech_name);
if (flags & VERBOSE) {
logger(L_DEBUG, L_FUNC, "num_procs : %d", num_procs);
if (mech_option == NULL)
logger(L_DEBUG, L_FUNC, "mech_option: NULL");
else
logger(L_DEBUG, L_FUNC, "mech_option: %s", mech_option);
logger(L_DEBUG, L_FUNC, "run_path : %s", run_path);
logger(L_DEBUG, L_FUNC, "auth_mech : %s", auth_mech->name);
}
if (chdir(run_path) == -1) {
rc = errno;
logger(L_ERR, L_FUNC, "could not chdir to: %s", run_path);
logger(L_ERR, L_FUNC, "chdir: %s", strerror(rc));
logger(L_ERR, L_FUNC, "Check to make sure the directory exists and is");
logger(L_ERR, L_FUNC, "writeable by the user this process runs as.");
exit(1);
}
umask(077);
pid_file_size = strlen(run_path) + sizeof(PID_FILE_LOCK) + 1;
if ((pid_file_lock = malloc(pid_file_size)) == NULL) {
logger(L_ERR, L_FUNC, "could not allocate memory");
exit(1);
}
strlcpy(pid_file_lock, run_path, pid_file_size);
strlcat(pid_file_lock, PID_FILE_LOCK, pid_file_size);
if ((pid_file_lock_fd = open(pid_file_lock, O_CREAT|O_TRUNC|O_RDWR, 644)) < 0) {
rc = errno;
logger(L_ERR, L_FUNC, "could not open pid lock file: %s", pid_file_lock);
logger(L_ERR, L_FUNC, "open: %s", strerror(rc));
logger(L_ERR, L_FUNC,
"Check to make sure the directory exists and is");
logger(L_ERR, L_FUNC, "writeable by the user this process runs as.");
exit(1);
}
lockinfo.l_type = F_WRLCK;
lockinfo.l_start = 0;
lockinfo.l_len = 0;
lockinfo.l_whence = SEEK_SET;
if (fcntl(pid_file_lock_fd, F_SETLK, &lockinfo) == -1) {
rc = errno;
logger(L_ERR, L_FUNC, "could not lock pid lock file: %s", pid_file_lock);
logger(L_ERR, L_FUNC, "fcntl: %s", strerror(rc));
exit(1);
}
if(pipe(startup_pipe) == -1) {
logger(L_ERR, L_FUNC, "can't create startup pipe");
exit(1);
}
signal_setup();
if (cache_init() != 0)
exit(1);
ipc_init();
atexit(server_exit);
if (flags & USE_PROCESS_MODEL) {
if (flags & VERBOSE)
logger(L_DEBUG, L_FUNC, "using process model");
for (x = 1; x < num_procs; x++) {
if (have_baby() != 0)
continue;
break;
}
}
ipc_loop();
exit(0);
}
char *do_auth(const char *login, const char *password, const char *service, const char *realm) {
struct cache_result lkup_result;
char *response;
int cached = 0;
if (cache_lookup(login, realm, service, password, &lkup_result) == CACHE_OK) {
response = strdup("OK");
cached = 1;
} else {
response = auth_mech->authenticate(login, password, service, realm);
if (response == NULL) {
logger(L_ERR, L_FUNC, "internal mechanism failure: %s", auth_mech->name);
response = strdup("NO internal mechanism failure");
}
}
if (strncmp(response, "OK", 2) == 0) {
cache_commit(&lkup_result);
if (flags & VERBOSE) {
if (cached)
logger(L_DEBUG, L_FUNC, "auth success (cached): [user=%s] [service=%s] [realm=%s]", \
login, service, realm);
else
logger(L_DEBUG, L_FUNC, "auth success: [user=%s] [service=%s] [realm=%s] [mech=%s]", \
login, service, realm, auth_mech->name);
}
return response;
}
if (strncmp(response, "NO", 2) == 0) {
logger(L_INFO, L_FUNC, "auth failure: [user=%s] [service=%s] [realm=%s] [mech=%s] [reason=%s]", \
login, service, realm, auth_mech->name,
strlen(response) >= 4 ? response+3 : "Unknown");
return response;
}
logger(L_ERR, L_FUNC, "mechanism returned unknown response: %s", auth_mech->name);
response = strdup("NO internal mechanism failure");
return response;
}
void set_auth_mech(const char *mech) {
for (auth_mech = mechanisms; auth_mech->name != NULL; auth_mech++) {
if (strcasecmp(auth_mech->name, mech) == 0)
break;
}
if (auth_mech->name == NULL) {
logger(L_ERR, L_FUNC, "unknown authentication mechanism: %s", mech);
exit(1);
}
if (auth_mech->initialize) {
if(auth_mech->initialize() != 0) {
logger(L_ERR, L_FUNC, "failed to initilize mechanism %s",
auth_mech->name);
exit(1);
}
}
}
void set_max_procs(const char *procs) {
num_procs = atoi(procs);
if(num_procs < 0) {
logger(L_ERR, L_FUNC, "invalid number of worker processes defined");
exit(1);
}
return;
}
void set_mech_option(const char *option) {
free(mech_option);
mech_option = NULL;
if ((mech_option = strdup(option)) == NULL) {
logger(L_ERR, L_FUNC, "could not allocate memory");
exit(1);
}
return;
}
void set_run_path(const char *path) {
if (*path != '/') {
logger(L_ERR, L_FUNC, "-m requires an absolute pathname");
exit(1);
}
free(run_path);
run_path = NULL;
if ((run_path = strdup(path)) == NULL) {
logger(L_ERR, L_FUNC, "could not allocate memory");
exit(1);
}
return;
}
void signal_setup() {
static struct sigaction act_sigchld;
static struct sigaction act_sigalrm;
static struct sigaction act_sigterm;
static struct sigaction act_sigpipe;
static struct sigaction act_sighup;
static struct sigaction act_sigint;
int rc;
act_sigchld.sa_handler = handle_sigchld;
sigemptyset(&act_sigchld.sa_mask);
if (sigaction(SIGCHLD, &act_sigchld, NULL) != 0) {
rc = errno;
logger(L_ERR, L_FUNC, "failed to set sigaction for SIGCHLD");
logger(L_ERR, L_FUNC, "sigaction: %s", strerror(rc));
exit(1);
}
act_sigalrm.sa_handler = SIG_IGN;
sigemptyset(&act_sigalrm.sa_mask);
if (sigaction(SIGALRM, &act_sigalrm, NULL) != 0) {
rc = errno;
logger(L_ERR, L_FUNC, "failed to set sigaction for SIGALRM");
logger(L_ERR, L_FUNC, "sigaction: %s", strerror(rc));
exit(1);
}
act_sigpipe.sa_handler = SIG_IGN;
sigemptyset(&act_sigpipe.sa_mask);
if (sigaction(SIGPIPE, &act_sigpipe, NULL) != 0) {
rc = errno;
logger(L_ERR, L_FUNC, "failed to set sigaction for SIGPIPE");
logger(L_ERR, L_FUNC, "sigaction: %s", strerror(rc));
exit(1);
}
act_sighup.sa_handler = SIG_IGN;
sigemptyset(&act_sighup.sa_mask);
if (sigaction(SIGHUP, &act_sighup, NULL) != 0) {
rc = errno;
logger(L_ERR, L_FUNC, "failed to set sigaction for SIGHUP");
logger(L_ERR, L_FUNC, "sigaction: %s", strerror(rc));
exit(1);
}
act_sigterm.sa_handler = server_exit;
sigemptyset(&act_sigterm.sa_mask);
if (sigaction(SIGTERM, &act_sigterm, NULL) != 0) {
rc = errno;
logger(L_ERR, L_FUNC, "failed to set sigaction for SIGTERM");
logger(L_ERR, L_FUNC, "sigaction: %s", strerror(rc));
exit(1);
}
act_sigint.sa_handler = server_exit;
sigemptyset(&act_sigint.sa_mask);
if (sigaction(SIGINT, &act_sigint, NULL) != 0) {
rc = errno;
logger(L_ERR, L_FUNC, "failed to set sigaction for SIGINT");
logger(L_ERR, L_FUNC, "sigaction: %s", strerror(rc));
exit(1);
}
return;
}
void detach_tty() {
int x;
int rc;
int null_fd;
int exit_result;
pid_t pid;
char pid_buf[100];
struct flock lockinfo;
if (flags & DETACH_TTY) {
for(x=5; x; x--) {
pid = fork();
if ((pid == -1) && (errno == EAGAIN)) {
logger(L_ERR, L_FUNC,
"fork failed, retrying");
sleep(5);
continue;
}
break;
}
if (pid == -1) {
rc = errno;
logger(L_ERR, L_FUNC, "Cannot start saslauthd");
logger(L_ERR, L_FUNC, "saslauthd master fork failed: %s",
strerror(rc));
exit(1);
} else if (pid != 0) {
int exit_code;
if(read(startup_pipe[0], &exit_code, sizeof(exit_code)) == -1) {
logger(L_ERR, L_FUNC,
"Cannot start saslauthd");
logger(L_ERR, L_FUNC,
"could not read from startup_pipe");
unlink(pid_file_lock);
exit(1);
} else {
if (exit_code != 0) {
logger(L_ERR, L_FUNC, "Cannot start saslauthd");
if (exit_code == 2) {
logger(L_ERR, L_FUNC,
"Another instance of saslauthd is currently running");
} else {
logger(L_ERR, L_FUNC, "Check syslog for errors");
}
}
unlink(pid_file_lock);
exit(exit_code);
}
}
close(startup_pipe[0]);
free(pid_file_lock);
if (setsid() == -1) {
exit_result = 1;
rc = errno;
logger(L_ERR, L_FUNC, "failed to set session id: %s",
strerror(rc));
write(startup_pipe[1], &exit_result, sizeof(exit_result));
exit(1);
}
if ((null_fd = open("/dev/null", O_RDWR, 0)) == -1) {
exit_result = 1;
rc = errno;
logger(L_ERR, L_FUNC, "failed to open /dev/null: %s",
strerror(rc));
write(startup_pipe[1], &exit_result, sizeof(exit_result));
exit(1);
}
flags &= ~LOG_USE_STDERR;
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
dup2(null_fd, STDIN_FILENO);
dup2(null_fd, STDOUT_FILENO);
dup2(null_fd, STDERR_FILENO);
if (null_fd > 2)
close(null_fd);
if (!(pid_file = malloc(strlen(run_path) + sizeof(PID_FILE) + 1))) {
exit_result = 1;
logger(L_ERR, L_FUNC, "could not allocate memory");
write(startup_pipe[1], &exit_result, sizeof(exit_result));
exit(1);
}
strcpy(pid_file, run_path);
strcat(pid_file, PID_FILE);
pid_fd = open(pid_file, O_CREAT|O_RDWR, 0644);
if(pid_fd == -1) {
rc = errno;
exit_result = 1;
logger(L_ERR, L_FUNC, "could not open pid file %s: %s",
pid_file, strerror(rc));
write(startup_pipe[1], &exit_result, sizeof(exit_result));
exit(1);
} else {
char buf[100];
lockinfo.l_type = F_WRLCK;
lockinfo.l_start = 0;
lockinfo.l_len = 0;
lockinfo.l_whence = SEEK_SET;
if (fcntl(pid_fd, F_SETLK, &lockinfo) == -1) {
exit_result = 2;
rc = errno;
logger(L_ERR, L_FUNC, "could not lock pid file %s: %s",
pid_file, strerror(rc));
write(startup_pipe[1], &exit_result, sizeof(exit_result));
exit(2);
} else {
int pid_fd_flags = fcntl(pid_fd, F_GETFD, 0);
if (pid_fd_flags != -1) {
pid_fd_flags =
fcntl(pid_fd, F_SETFD, pid_fd_flags | FD_CLOEXEC);
}
if (pid_fd_flags == -1) {
int exit_result = 1;
logger(L_ERR, L_FUNC, "unable to set close-on-exec for pidfile");
write(startup_pipe[1], &exit_result, sizeof(exit_result));
exit(1);
}
master_pid = getpid();
snprintf(buf, sizeof(buf), "%lu\n", (unsigned long)master_pid);
if (lseek(pid_fd, 0, SEEK_SET) == -1 ||
ftruncate(pid_fd, 0) == -1 ||
write(pid_fd, buf, strlen(buf)) == -1) {
int exit_result = 1;
rc = errno;
logger(L_ERR, L_FUNC, "could not write to pid file %s: %s", pid_file, strerror(rc));
write(startup_pipe[1], &exit_result, sizeof(exit_result));
exit(1);
}
fsync(pid_fd);
}
}
{
int exit_result = 0;
if(write(startup_pipe[1], &exit_result, sizeof(exit_result)) == -1) {
logger(L_ERR, L_FUNC,
"could not write success result to startup pipe");
exit(1);
}
}
close(startup_pipe[1]);
if(pid_file_lock_fd != -1) close(pid_file_lock_fd);
}
logger(L_INFO, L_FUNC, "master pid is: %lu", (unsigned long)master_pid);
return;
}
pid_t have_baby() {
pid_t pid;
int rc;
pid = fork();
if (pid < 0) {
rc = errno;
logger(L_ERR, L_FUNC, "could not fork child process");
logger(L_ERR, L_FUNC, "fork: %s", strerror(rc));
exit(1);
}
if (pid == 0) {
flags &= ~AM_MASTER;
return pid;
}
if (flags & VERBOSE) {
logger(L_DEBUG, L_FUNC, "forked child: %lu",
(unsigned long)pid);
}
return pid;
}
void handle_sigchld() {
pid_t pid;
while ((pid = waitpid(-1, 0, WNOHANG)) > 0) {
if (flags & VERBOSE)
logger(L_DEBUG, L_FUNC, "child exited: %lu", (unsigned long)pid);
}
return;
}
void server_exit() {
struct flock lock_st;
if (!(flags & AM_MASTER)) {
if (flags & VERBOSE)
logger(L_DEBUG, L_FUNC, "child exited: %d", getpid());
_exit(0);
}
kill(-master_pid, SIGTERM);
if(flags & DETACH_TTY) {
if(getpid() == master_pid) unlink(pid_file);
close(pid_fd);
if (flags & VERBOSE)
logger(L_DEBUG, L_FUNC, "pid file removed: %s", pid_file);
free(pid_file);
} else {
unlink(pid_file_lock);
close(pid_file_lock_fd);
if (flags & VERBOSE)
logger(L_DEBUG, L_FUNC, "pid file lock removed: %s",
pid_file_lock);
free(pid_file_lock);
}
if (flags & CACHE_ENABLED) {
cache_cleanup_lock();
cache_cleanup_mm();
}
ipc_cleanup();
logger(L_INFO, L_FUNC, "master exited: %d", master_pid);
_exit(0);
}
void show_version() {
authmech_t *authmech;
fprintf(stderr, "saslauthd %s\nauthentication mechanisms:", VERSION);
for (authmech = mechanisms; authmech->name != NULL; authmech++) {
fprintf(stderr, " %s", authmech->name);
}
fprintf(stderr, "\n\n");
exit(0);
}
void show_usage() {
fprintf(stderr, "usage: saslauthd [options]\n\n");
fprintf(stderr, "option information:\n");
fprintf(stderr, " -a <authmech> Selects the authentication mechanism to use.\n");
fprintf(stderr, " -c Enable credential caching.\n");
fprintf(stderr, " -d Debugging (don't detach from tty, implies -V)\n");
fprintf(stderr, " -O <option> Optional argument to pass to the authentication\n");
fprintf(stderr, " mechanism.\n");
fprintf(stderr, " -l Disable accept() locking. Increases performance, but\n");
fprintf(stderr, " may not be compatible with some operating systems.\n");
fprintf(stderr, " -m <path> Alternate path for the saslauthd working directory,\n");
fprintf(stderr, " must be absolute.\n");
fprintf(stderr, " -n <procs> Number of worker processes to create.\n");
fprintf(stderr, " -s <kilobytes> Size of the credential cache (in kilobytes)\n");
fprintf(stderr, " -t <seconds> Timeout for items in the credential cache (in seconds)\n");
fprintf(stderr, " -v Display version information and available mechs\n");
fprintf(stderr, " -V Enable verbose logging\n");
fprintf(stderr, " authentication mechanisms and exit.\n");
fprintf(stderr, " -h Display this message.\n\n");
show_version();
exit(0);
}