#ifdef __GNUC__
#ident "$Id: saslauthd.c,v 1.1 2002/02/28 00:18:10 snsimon Exp $"
#endif
#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 "mechanisms.h"
#include "globals.h"
authmech_t *authmech;
authmech_t *proxymech;
int debug;
int flag_use_tod;
char *r_host;
char *r_service;
#if defined(AUTH_SIA)
int g_argc;
char **g_argv;
#endif
int retry_read(int fd, void *buf, unsigned nbyte);
int retry_writev(int fd, struct iovec *iov, int iovcnt);
char *path_mux;
int master_pid;
extern char *optarg;
void do_request(int, int);
RETSIGTYPE server_exit(int);
RETSIGTYPE sigchld_ignore(int);
void show_version(void);
#define LOCK_FILE_MODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH)
#define LOCK_SUFFIX ".pid"
#define ACCEPT_LOCK_SUFFIX ".accept"
#define MAX_REQ_LEN 256
#ifdef _AIX
# define SALEN_TYPE size_t
#else
# define SALEN_TYPE int
#endif
int
main(
int argc,
char *argv[]
) {
int c;
int count;
int s;
int conn;
int rc;
int lfd,alfd=0;
char *lockfile;
char *acceptlockfile;
struct flock lockinfo;
struct flock alockinfo;
char *cwd;
pid_t pid;
struct sockaddr_un server, client;
SALEN_TYPE len;
int i;
int num_threads = 5;
#ifdef SO_REUSEADDR
int one = 1;
#endif
#if defined(AUTH_SIA)
set_auth_parameters(argc, argv);
#endif
authmech = NULL;
proxymech = NULL;
debug = 0;
flag_use_tod = 0;
path_mux = PATH_SASLAUTHD_RUNDIR "/mux";
r_host = NULL;
openlog("saslauthd", LOG_PID|LOG_NDELAY, LOG_AUTH);
syslog(LOG_INFO, "START: saslauthd %s", VERSION);
while ((c = getopt(argc, argv, "a:dF:H:m:n:P:Tv")) != -1)
switch (c) {
case 'a':
for (authmech = mechanisms; authmech->name != NULL; authmech++) {
if (!strcasecmp(authmech->name, optarg))
break;
}
if (authmech->name == NULL) {
syslog(LOG_ERR,
"FATAL: unknown authentication mechanism: %s",
optarg);
fprintf(stderr,
"saslauthd: unknown authentication mechanism: %s\n",
optarg);
exit(1);
}
break;
case 'd':
debug++;
break;
case 'H':
r_host = strdup(optarg);
break;
case 'm':
if (*optarg != '/') {
syslog(LOG_ERR, "FATAL: -m requires an absolute pathname");
fprintf(stderr, "saslauthd: -m requires an absolute pathname");
exit(1);
}
path_mux = optarg;
break;
case 'n':
num_threads = atoi(optarg);
if(num_threads < 0) {
fprintf(stderr, "invalid number of threads");
exit(1);
}
break;
case 'P':
for (proxymech = mechanisms; proxymech->name != NULL; proxymech++)
{
if (!strcasecmp(proxymech->name, optarg))
break;
}
if (proxymech->name == NULL) {
syslog(LOG_ERR,
"FATAL: unknown authentication mechanism: %s",
optarg);
fprintf(stderr,
"saslauthd: unknown authentication mechanism %s\n",
optarg);
exit(1);
}
break;
case 'T':
flag_use_tod++;
break;
case 'v':
show_version();
exit(0);
break;
default:
break;
}
#if defined(AUTH_SIA)
g_argc = argc;
g_argv = argv;
#endif
umask(077);
signal(SIGPIPE, SIG_IGN);
cwd = strdup(path_mux);
if (cwd == NULL) {
syslog(LOG_ERR, "FATAL: strdup(path_mux) failure");
fprintf(stderr, "saslauthd: strdup(path_mux) failure\n");
exit(1);
}
if (strrchr(cwd, '/') != NULL)
*(strrchr(cwd, '/')) = '\0';
if (chdir(cwd) == -1) {
rc = errno;
syslog(LOG_ERR, "FATAL: chdir(%s): %m", cwd);
fprintf(stderr, "saslauthd: ");
errno = rc;
perror(cwd);
exit(1);
}
free(cwd);
if (authmech == NULL) {
syslog(LOG_ERR, "FATAL: no authentication mechanism specified");
fprintf(stderr, "saslauthd: no authentication mechanism specified\n");
exit(1);
}
if (proxymech != NULL) {
if (proxymech == authmech) {
syslog(LOG_ERR, "FATAL: -a and -P specify identical mechanisms");
fprintf(stderr,
"saslauthd: -a and -P specify identical mechanisms\n");
exit(1);
}
if (strcasecmp("sasldb", authmech->name)) {
syslog(LOG_ERR, "FATAL: %s does not support proxy creation",
authmech->name);
fprintf(stderr, "saslauthd: %s does not support proxy creation",
authmech->name);
exit(1);
}
}
if (!debug) {
count = 5;
while (count--) {
pid = fork();
if (pid > 0)
_exit(0);
if ((pid == -1) && (errno == EAGAIN)) {
syslog(LOG_WARNING, "master fork failed (sleeping): %m");
sleep(5);
continue;
}
}
if (pid == -1) {
rc = errno;
syslog(LOG_ERR, "FATAL: master fork failed: %m");
fprintf(stderr, "saslauthd: ");
errno = rc;
perror("fork");
exit(1);
}
if (setsid() == -1) {
rc = errno;
syslog(LOG_ERR, "FATAL: setsid: %m");
fprintf(stderr, "saslauthd: ");
errno = rc;
perror("setsid");
exit(1);
}
s = open("/dev/null", O_RDWR, 0);
if (s == -1) {
rc = errno;
syslog(LOG_ERR, "FATAL: /dev/null: %m");
fprintf(stderr, "saslauthd: ");
errno = rc;
perror("/dev/null");
exit(1);
}
dup2(s, fileno(stdin));
dup2(s, fileno(stdout));
dup2(s, fileno(stderr));
if (s > 2) {
close(s);
}
}
master_pid = getpid();
syslog(LOG_INFO, "master PID is: %d", master_pid);
lockfile = malloc(strlen(path_mux) + sizeof(LOCK_SUFFIX));
if (lockfile == NULL) {
syslog(LOG_ERR, "malloc(lockfile) failed");
exit(1);
}
strcpy(lockfile, path_mux);
strcat(lockfile, LOCK_SUFFIX);
acceptlockfile = malloc(strlen(path_mux) + sizeof(ACCEPT_LOCK_SUFFIX));
if (lockfile == NULL) {
syslog(LOG_ERR, "malloc(acceptlockfile) failed");
exit(1);
}
strcpy(acceptlockfile, path_mux);
strcat(acceptlockfile, ACCEPT_LOCK_SUFFIX);
lfd = open(lockfile, O_WRONLY|O_CREAT, LOCK_FILE_MODE);
if (lfd < 0) {
syslog(LOG_ERR, "FATAL: %s: %m", lockfile);
exit(1);
}
lockinfo.l_type = F_WRLCK;
lockinfo.l_start = 0;
lockinfo.l_len = 0;
lockinfo.l_whence = SEEK_SET;
rc = fcntl(lfd, F_SETLK, &lockinfo);
if (rc == -1) {
syslog(LOG_ERR, "FATAL: setting master lock on %s: %m", lockfile);
exit(1);
}
{
char pid_buf[100];
int l;
sprintf(pid_buf, "%lu\n", (unsigned long)getpid());
l = strlen(pid_buf);
rc = write(lfd, pid_buf, l);
if (rc < 0) {
syslog(LOG_ERR, "FATAL: %s: %m", lockfile);
exit(1);
} else if (rc != l) {
syslog(LOG_ERR, "FATAL: %s: short write (%d != %d)",
lockfile, rc, l);
exit(1);
}
}
signal(SIGHUP, server_exit);
signal(SIGINT, server_exit);
signal(SIGTERM, server_exit);
(void)unlink(path_mux);
s = socket(AF_UNIX, SOCK_STREAM, 0);
if (s == -1) {
syslog(LOG_ERR, "FATAL: socket :%m");
exit(1);
}
memset(&server, 0, sizeof(server));
server.sun_family = AF_UNIX;
strcpy(server.sun_path, path_mux);
#ifdef SO_REUSEADDR
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof(one));
#endif
umask(0);
rc = bind(s, (struct sockaddr *)&server, sizeof(server));
if (rc == -1) {
syslog(LOG_ERR, "FATAL: %s: %m", path_mux);
closelog();
exit(1);
}
if (chmod(path_mux, S_IRWXU|S_IRWXG|S_IRWXO) == -1) {
syslog(LOG_ERR, "FATAL: chmod(%s): %m", path_mux);
closelog();
exit(1);
}
fchmod(s, S_IRWXU|S_IRWXG|S_IRWXO);
umask(077);
if (authmech->initialize != NULL) {
if (authmech->initialize() != 0) {
syslog(LOG_ERR,
"FATAL: %s initialization failed",
authmech->name);
closelog();
exit(1);
}
}
if ((proxymech != NULL) && (proxymech->initialize != NULL)) {
if (proxymech->initialize() != 0) {
syslog(LOG_ERR,
"FATAL: %s initialization failed",
proxymech->name);
closelog();
exit(1);
}
}
if (listen(s, 5) == -1) {
syslog(LOG_ERR, "FATAL: listen: %m");
closelog();
exit(1);
};
syslog(LOG_INFO, "daemon started, listening on %s", path_mux);
len = sizeof(client);
signal(SIGCHLD, sigchld_ignore);
if(!debug) {
for(i=1; i<num_threads; i++) {
int pid = fork();
if(pid < 0) {
syslog(LOG_ERR, "FATAL: fork(): %m");
fprintf(stderr, "saslauthd FATAL: could not fork()\n");
exit(1);
} else if (pid == 0) {
break;
}
}
}
if(!debug && num_threads > 0) {
alfd = open(acceptlockfile, O_WRONLY|O_CREAT, LOCK_FILE_MODE);
if(alfd < 0) {
syslog(LOG_ERR, "FATAL: open(acceptlockfile): %m");
fprintf(stderr, "could not open acceptlockfile\n");
exit(1);
}
alockinfo.l_start = 0;
alockinfo.l_len = 0;
alockinfo.l_whence = SEEK_SET;
}
while (1) {
if(!debug && num_threads > 0) {
alockinfo.l_type = F_WRLCK;
while ( (rc = fcntl(alfd, F_SETLKW, &alockinfo)) < 0
&& errno == EINTR)
;
if (rc < 0) {
syslog(LOG_ERR,
"fcntl: F_SETLKW: error getting accept lock: %m");
exit(1);
}
}
conn = accept(s, (struct sockaddr *)&client, &len);
if(!debug && num_threads > 0) {
alockinfo.l_type = F_UNLCK;
while ( (rc = fcntl(alfd, F_SETLKW, &alockinfo)) < 0
&& errno == EINTR)
;
if (rc < 0) {
syslog(LOG_ERR,
"fcntl: F_SETLKW: error releasing accept lock: %m");
exit(1);
}
}
if (conn == -1) {
if (errno != EINTR) {
syslog(LOG_ERR, "accept: %m");
}
continue;
}
if(!debug && num_threads == 0) {
pid = fork();
if (pid == 0) {
close(s);
do_request(conn, conn);
close(conn);
closelog();
exit(0);
} else if (pid > 0) {
close(conn);
} else if (pid == -1) {
syslog(LOG_ERR, "accept fork: %m");
close(conn);
}
} else {
do_request(conn, conn);
close(conn);
}
}
close(alfd);
exit(0);
}
void
do_request
(
int in,
int out
)
{
char *reply;
struct iovec iov[2];
unsigned short count;
int rc;
char login[MAX_REQ_LEN + 1];
char password[MAX_REQ_LEN + 1];
char service[MAX_REQ_LEN + 1];
char realm[MAX_REQ_LEN + 1];
int error_condition;
error_condition = 0;
reply = NULL;
rc = (retry_read(in, &count, sizeof(count)) < (int)
sizeof(count));
if (!rc) {
count = ntohs(count);
if (count > MAX_REQ_LEN)
rc = error_condition = 1;
else {
rc = (retry_read(in, login, count) < (int) count);
login[count] = '\0';
}
}
if (!rc)
rc = (retry_read(in, &count, sizeof(count)) < (int) sizeof(count));
if (!rc) {
count = ntohs(count);
if (count > MAX_REQ_LEN)
rc = error_condition = 1;
else {
rc = (retry_read(in, password, count) < (int) count);
password[count] = '\0';
}
}
if (!rc)
rc = (retry_read(in, &count, sizeof(count)) < (int) sizeof(count));
if (!rc) {
count = ntohs(count);
if (count > MAX_REQ_LEN)
rc = error_condition = 1;
else {
rc = (retry_read(in, service, count) < (int) count);
service[count] = '\0';
}
}
if (!rc)
rc = (retry_read(in, &count, sizeof(count)) < (int) sizeof(count));
if (!rc) {
count = ntohs(count);
if (count > MAX_REQ_LEN)
rc = error_condition = 1;
else {
rc = (retry_read(in, realm, count) < (int) count);
realm[count] = '\0';
}
}
if (error_condition) {
syslog(LOG_ERR,
"ALERT: input data exceeds %d bytes! Possible intrusion attempt?",
MAX_REQ_LEN);
reply = strdup("NO");
}
else if (rc) {
syslog(LOG_ERR, "do_request read failed: %m");
return;
}
if ((*login == '\0') || (*password == '\0')) {
error_condition = 1;
syslog(LOG_NOTICE, "null login/password received");
reply = strdup("NO Null login/password (saslauthd)");
} else {
if (debug) {
syslog(LOG_DEBUG, "authenticating %s", login);
}
}
if (!error_condition) {
reply = authmech->authenticate(login, password, service, realm);
memset(password, 0, strlen(password));
if (reply == NULL) {
error_condition = 1;
syslog(LOG_ERR,
"AUTHFAIL: mechanism %s doesn't grok this environment",
authmech->name);
reply = strdup("NO authentication mechanism failed to cope! (saslauthd)");
}
}
if (!strncmp(reply, "NO", sizeof("NO")-1)) {
if (strlen(reply) < sizeof("NO "))
syslog(LOG_WARNING, "AUTHFAIL: user=%s service=%s realm=%s",
login, service, realm);
else
syslog(LOG_WARNING, "AUTHFAIL: user=%s service=%s realm=%s [%s]",
login, service, realm, reply + 3);
} else {
if (debug) {
syslog(LOG_INFO, "OK: user=%s service=%s realm=%s",
login, service, realm);
}
}
count = htons(strlen(reply));
iov[0].iov_base = (void *) &count;
iov[0].iov_len = sizeof(count);
iov[1].iov_base = (void *) reply;
iov[1].iov_len = strlen(reply);
rc = retry_writev(out, iov, 2);
if (debug)
printf("Just Wrote: %d:%s\n",ntohs(count),reply);
if (rc == -1)
syslog(LOG_ERR, "do_request write failed: %m");
free(reply);
return;
}
int retry_read(int fd, void *buf, unsigned nbyte)
{
int n;
int nread = 0;
if (nbyte == 0) return 0;
for (;;) {
n = read(fd, buf, nbyte);
if (n == -1) {
if (errno == EINTR) continue;
return -1;
}
nread += n;
if (n >= (int) nbyte) return nread;
buf += n;
nbyte -= n;
}
}
int
retry_writev (
int fd,
struct iovec *iov,
int iovcnt
)
{
int n;
int i;
int written;
static int iov_max;
#ifdef MAXIOV
iov_max = MAXIOV;
#else
# ifdef IOV_MAX
iov_max = IOV_MAX;
# else
iov_max = 8192;
# endif
#endif
written = 0;
for (;;) {
while (iovcnt && iov[0].iov_len == 0) {
iov++;
iovcnt--;
}
if (!iovcnt) {
return written;
}
n = writev(fd, iov, iovcnt > iov_max ? iov_max : iovcnt);
if (n == -1) {
if (errno == EINVAL && iov_max > 10) {
iov_max /= 2;
continue;
}
if (errno == EINTR) {
continue;
}
return -1;
} else {
written += n;
}
for (i = 0; i < iovcnt; i++) {
if (iov[i].iov_len > (unsigned) n) {
iov[i].iov_base = (char *)iov[i].iov_base + n;
iov[i].iov_len -= n;
break;
}
n -= iov[i].iov_len;
iov[i].iov_len = 0;
}
if (i == iovcnt) {
return written;
}
}
}
void
show_version(
void
)
{
authmech_t *authmech;
fprintf(stderr, "saslauthd %s\nauthentication mechanisms:",
VERSION);
for (authmech = mechanisms; authmech->name != NULL; authmech++) {
fprintf(stderr, " %s", authmech->name);
}
fputs("\n", stderr);
exit(0);
}
RETSIGTYPE
server_exit(
int sig
)
{
if(getpid() == master_pid) {
syslog(LOG_NOTICE,
"Caught signal %d. Cleaning up and terminating.", sig);
kill(-master_pid, sig);
}
exit(0);
}
RETSIGTYPE
sigchld_ignore (
int sig __attribute__((unused))
)
{
pid_t pid;
while ((pid = waitpid(-1, 0, WNOHANG)) > 0) {
}
signal(SIGCHLD, sigchld_ignore);
#if RETSIGTYPE == void
return;
#else
return 0;
#endif
}