NetBSD/crypto/dist/krb4/kadmin/admin_server.c

648 lines
14 KiB
C

/*
Copyright (C) 1989 by the Massachusetts Institute of Technology
Export of this software from the United States of America is assumed
to require a specific license from the United States Government.
It is the responsibility of any person or organization contemplating
export to obtain such a license before exporting.
WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
distribute this software and its documentation for any purpose and
without fee is hereby granted, provided that the above copyright
notice appear in all copies and that both that copyright notice and
this permission notice appear in supporting documentation, and that
the name of M.I.T. not be used in advertising or publicity pertaining
to distribution of the software without specific, written prior
permission. M.I.T. makes no representations about the suitability of
this software for any purpose. It is provided "as is" without express
or implied warranty.
*/
/*
* Top-level loop of the kerberos Administration server
*/
/*
admin_server.c
this holds the main loop and initialization and cleanup code for the server
*/
#include "kadm_locl.h"
__RCSID("$KTH-KRB: admin_server.c,v 1.55 2001/12/05 14:49:07 assar Exp $"
"$NetBSD: admin_server.c,v 1.1.1.4 2002/09/12 12:22:08 joda Exp $");
/* Almost all procs and such need this, so it is global */
admin_params prm; /* The command line parameters struct */
/* GLOBAL */
char *acldir = DEFAULT_ACL_DIR;
static char krbrlm[REALM_SZ];
#define MAXCHILDREN 100
struct child {
pid_t pid;
int pipe_fd;
int authenticated;
};
static unsigned nchildren = 0;
static struct child children[MAXCHILDREN];
static int exit_now = 0;
static
RETSIGTYPE
doexit(int sig)
{
exit_now = 1;
SIGRETURN(0);
}
static sig_atomic_t do_wait;
static
RETSIGTYPE
do_child(int sig)
{
do_wait = 1;
SIGRETURN(0);
}
static void
kill_children(void)
{
int i;
for (i = 0; i < nchildren; i++) {
kill(children[i].pid, SIGINT);
close (children[i].pipe_fd);
krb_log("killing child %d", children[i].pid);
}
}
/* close the system log file */
static void
close_syslog(void)
{
krb_log("Shutting down admin server");
}
static void
byebye(void) /* say goodnight gracie */
{
printf("Admin Server (kadm server) has completed operation.\n");
}
static void
clear_secrets(void)
{
memset(server_parm.master_key, 0, sizeof(server_parm.master_key));
memset(server_parm.master_key_schedule, 0,
sizeof(server_parm.master_key_schedule));
server_parm.master_key_version = 0L;
}
static void
cleanexit(int val)
{
kerb_fini();
clear_secrets();
exit(val);
}
static RETSIGTYPE
sigalrm(int sig)
{
cleanexit(1);
}
/*
* handle the client on the socket `fd' from `who'
* `signal_fd' is a pipe on which to signal when the user has been
* authenticated
*/
static void
process_client(int fd, struct sockaddr_in *who, int signal_fd)
{
u_char *dat;
int dat_len;
u_short dlen;
int retval;
Principal service;
des_cblock skey;
int more;
int status;
int authenticated = 0;
/* make this connection time-out after 1 second if the user has
not managed one transaction succesfully in kadm_ser_in */
signal(SIGALRM, sigalrm);
alarm(2);
#if defined(SO_KEEPALIVE) && defined(HAVE_SETSOCKOPT)
{
int on = 1;
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
(void *)&on, sizeof(on)) < 0)
krb_log("setsockopt keepalive: %d",errno);
}
#endif
server_parm.recv_addr = *who;
if (kerb_init()) { /* Open as client */
krb_log("can't open krb db");
cleanexit(1);
}
/* need to set service key to changepw.KRB_MASTER */
status = kerb_get_principal(server_parm.sname, server_parm.sinst, &service,
1, &more);
if (status == -1) {
/* db locked */
char *pdat;
dat_len = KADM_VERSIZE + 4;
dat = (u_char *) malloc(dat_len);
if (dat == NULL) {
krb_log("malloc failed");
cleanexit(4);
}
pdat = (char *) dat;
memcpy(pdat, KADM_ULOSE, KADM_VERSIZE);
krb_put_int (KADM_DB_INUSE, pdat + KADM_VERSIZE, 4, 4);
goto out;
} else if (!status) {
krb_log("no service %s.%s",server_parm.sname, server_parm.sinst);
cleanexit(2);
}
copy_to_key(&service.key_low, &service.key_high, skey);
memset(&service, 0, sizeof(service));
kdb_encrypt_key (&skey, &skey, &server_parm.master_key,
server_parm.master_key_schedule, DES_DECRYPT);
krb_set_key(skey, 0); /* if error, will show up when
rd_req fails */
memset(skey, 0, sizeof(skey));
while (1) {
void *errpkt;
errpkt = malloc(KADM_VERSIZE + 4);
if (errpkt == NULL) {
krb_log("malloc: no memory");
close(fd);
cleanexit(4);
}
if ((retval = krb_net_read(fd, &dlen, sizeof(u_short))) !=
sizeof(u_short)) {
if (retval < 0)
krb_log("dlen read: %s",error_message(errno));
else if (retval)
krb_log("short dlen read: %d",retval);
close(fd);
cleanexit(retval ? 3 : 0);
}
if (exit_now) {
cleanexit(0);
}
dat_len = ntohs(dlen);
dat = (u_char *) malloc(dat_len);
if (dat == NULL) {
krb_log("malloc: No memory");
close(fd);
cleanexit(4);
}
if ((retval = krb_net_read(fd, dat, dat_len)) != dat_len) {
if (retval < 0)
krb_log("data read: %s",error_message(errno));
else
krb_log("short read: %d vs. %d", dat_len, retval);
close(fd);
cleanexit(5);
}
if (exit_now) {
cleanexit(0);
}
retval = kadm_ser_in(&dat, &dat_len, errpkt);
if (retval == KADM_SUCCESS) {
if (!authenticated) {
unsigned char one = 1;
authenticated = 1;
alarm (0);
write (signal_fd, &one, 1);
}
} else {
krb_log("processing request: %s", error_message(retval));
}
/* kadm_ser_in did the processing and returned stuff in
dat & dat_len , return the appropriate data */
out:
dlen = htons(dat_len);
if (krb_net_write(fd, &dlen, sizeof(u_short)) < 0) {
krb_log("writing dlen to client: %s",error_message(errno));
close(fd);
cleanexit(6);
}
if (krb_net_write(fd, dat, dat_len) < 0) {
krb_log("writing to client: %s", error_message(errno));
close(fd);
cleanexit(7);
}
free(dat);
}
/*NOTREACHED*/
}
static void
accept_client (int admin_fd)
{
int pipe_fd[2];
socklen_t addrlen;
struct sockaddr_in peer;
pid_t pid;
int peer_fd;
/* using up the maximum number of children, try to get rid
of one unauthenticated one */
if (nchildren >= MAXCHILDREN) {
int i, nunauth = 0;
int victim;
for (;;) {
for (i = 0; i < nchildren; ++i)
if (children[i].authenticated == 0)
++nunauth;
if (nunauth == 0)
return;
#ifdef HAVE_RANDOM
victim = random() % nchildren;
#else
victim = rand() % nchildren;
#endif
if (children[victim].authenticated == 0) {
kill(children[victim].pid, SIGINT);
close(children[victim].pipe_fd);
for (i = victim; i < nchildren; ++i)
children[i] = children[i + 1];
--nchildren;
break;
}
}
}
/* accept the conn */
addrlen = sizeof(peer);
peer_fd = accept(admin_fd, (struct sockaddr *)&peer, &addrlen);
if (peer_fd < 0) {
krb_log("accept: %s",error_message(errno));
return;
}
if (pipe (pipe_fd) < 0) {
krb_log ("pipe: %s", error_message(errno));
return;
}
if (pipe_fd[0] >= FD_SETSIZE
|| pipe_fd[1] >= FD_SETSIZE) {
krb_log ("pipe fds too large");
close (pipe_fd[0]);
close (pipe_fd[1]);
return;
}
pid = fork ();
if (pid < 0) {
krb_log ("fork: %s", error_message(errno));
close (pipe_fd[0]);
close (pipe_fd[1]);
return;
}
if (pid != 0) {
/* parent */
/* fork succeded: keep tabs on child */
close(peer_fd);
children[nchildren].pid = pid;
children[nchildren].pipe_fd = pipe_fd[0];
children[nchildren].authenticated = 0;
++nchildren;
close (pipe_fd[1]);
} else {
int i;
/* child */
close(admin_fd);
close(pipe_fd[0]);
for (i = 0; i < nchildren; ++i)
close (children[i].pipe_fd);
/*
* If we are multihomed we need to figure out which
* local address that is used this time since it is
* used in "direction" comparison.
*/
getsockname(peer_fd,
(struct sockaddr *)&server_parm.admin_addr,
&addrlen);
/* do stuff */
process_client (peer_fd, &peer, pipe_fd[1]);
}
}
/*
* handle data signaled from child `child' kadmind
*/
static void
handle_child_signal (int child)
{
int ret;
unsigned char data[1];
ret = read (children[child].pipe_fd, data, 1);
if (ret < 0) {
if (errno != EINTR)
krb_log ("read from child %d: %s", child,
error_message(errno));
return;
}
if (ret == 0) {
close (children[child].pipe_fd);
children[child].pipe_fd = -1;
return;
}
if (data)
children[child].authenticated = 1;
}
/*
* handle dead children
*/
static void
handle_sigchld (void)
{
pid_t pid;
int status;
int i, j;
for (;;) {
int found = 0;
pid = waitpid(-1, &status, WNOHANG|WUNTRACED);
if (pid == 0 || (pid < 0 && errno == ECHILD))
break;
if (pid < 0) {
krb_log("waitpid: %s", error_message(errno));
break;
}
for (i = 0; i < nchildren; i++)
if (children[i].pid == pid) {
/* found it */
close(children[i].pipe_fd);
for (j = i; j < nchildren; j++)
/* copy others down */
children[j] = children[j+1];
--nchildren;
#if 0
if ((WIFEXITED(status) && WEXITSTATUS(status) != 0)
|| WIFSIGNALED(status))
krb_log("child %d: termsig %d, retcode %d", pid,
WTERMSIG(status), WEXITSTATUS(status));
#endif
found = 1;
}
#if 0
if (!found)
krb_log("child %d not in list: termsig %d, retcode %d", pid,
WTERMSIG(status), WEXITSTATUS(status));
#endif
}
do_wait = 0;
}
/*
kadm_listen
listen on the admin servers port for a request
*/
static int
kadm_listen(void)
{
int found;
int admin_fd;
fd_set readfds;
signal(SIGINT, doexit);
signal(SIGTERM, doexit);
signal(SIGHUP, doexit);
signal(SIGQUIT, doexit);
signal(SIGPIPE, SIG_IGN); /* get errors on write() */
signal(SIGALRM, doexit);
signal(SIGCHLD, do_child);
if (setsid() < 0)
krb_log("setsid() failed");
if ((admin_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
return KADM_NO_SOCK;
if (admin_fd >= FD_SETSIZE) {
krb_log("admin_fd too big");
return KADM_NO_BIND;
}
#if defined(SO_REUSEADDR) && defined(HAVE_SETSOCKOPT)
{
int one = 1;
setsockopt(admin_fd, SOL_SOCKET, SO_REUSEADDR, (void *)&one,
sizeof(one));
}
#endif
if (bind(admin_fd, (struct sockaddr *)&server_parm.admin_addr,
sizeof(struct sockaddr_in)) < 0)
return KADM_NO_BIND;
if (listen(admin_fd, SOMAXCONN) < 0)
return KADM_NO_BIND;
for (;;) { /* loop nearly forever */
int i;
int maxfd = -1;
if (exit_now) {
clear_secrets();
kill_children();
return(0);
}
if (do_wait)
handle_sigchld ();
FD_ZERO(&readfds);
FD_SET(admin_fd, &readfds);
maxfd = max(maxfd, admin_fd);
for (i = 0; i < nchildren; ++i)
if (children[i].pipe_fd >= 0) {
FD_SET(children[i].pipe_fd, &readfds);
maxfd = max(maxfd, children[i].pipe_fd);
}
found = select(maxfd + 1, &readfds, NULL, NULL, NULL);
if (found < 0) {
if (errno != EINTR)
krb_log("select: %s",error_message(errno));
continue;
}
if (FD_ISSET(admin_fd, &readfds))
accept_client (admin_fd);
for (i = 0; i < nchildren; ++i)
if (children[i].pipe_fd >= 0
&& FD_ISSET(children[i].pipe_fd, &readfds)) {
handle_child_signal (i);
}
}
/*NOTREACHED*/
}
/*
** Main does the logical thing, it sets up the database and RPC interface,
** as well as handling the creation and maintenance of the syslog file...
*/
int
main(int argc, char **argv) /* admin_server main routine */
{
int errval;
int c;
struct in_addr i_addr;
int port = 0;
setprogname (argv[0]);
umask(077); /* Create protected files */
i_addr.s_addr = INADDR_ANY;
/* initialize the admin_params structure */
prm.sysfile = KADM_SYSLOG; /* default file name */
prm.inter = 0;
memset(krbrlm, 0, sizeof(krbrlm));
#if defined(HAVE_SRANDOMDEV)
srandomdev();
#elif defined(HAVE_RANDOM)
srandom(time(NULL));
#else
srand (time(NULL));
#endif
while ((c = getopt(argc, argv, "f:hmnd:a:r:i:p:")) != -1)
switch(c) {
case 'f': /* Syslog file name change */
prm.sysfile = optarg;
break;
case 'n':
prm.inter = 0;
break;
case 'm':
prm.inter = 1;
break;
case 'a': /* new acl directory */
acldir = optarg;
break;
case 'd':
/* put code to deal with alt database place */
if ((errval = kerb_db_set_name(optarg)))
errx (1, "opening database %s: %s",
optarg, error_message(errval));
break;
case 'r':
strlcpy (krbrlm, optarg, sizeof(krbrlm));
break;
case 'i':
/* Only listen on this address */
if(inet_aton (optarg, &i_addr) == 0) {
fprintf (stderr, "Bad address: %s\n", optarg);
exit (1);
}
break;
case 'p' : {
struct servent *sp;
sp = getservbyname(optarg, "tcp");
if (sp != NULL) {
port = sp->s_port;
} else {
char *end;
port = htons(strtol(optarg, &end, 0));
if (port == 0 && end == optarg) {
fprintf(stderr, "Bad port: %s\n", optarg);
exit (1);
}
}
break;
}
case 'h': /* get help on using admin_server */
default:
errx(1, "Usage: kadmind [-h] [-n] [-m] [-r realm] [-d dbname] [-f filename] [-a acldir] [-i address_to_listen_on] [-p port]");
}
if (krbrlm[0] == 0)
if (krb_get_lrealm(krbrlm, 1) != KSUCCESS)
errx (1, "Unable to get local realm. Fix krb.conf or use -r.");
printf("KADM Server %s initializing\n",KADM_VERSTR);
printf("Please do not use 'kill -9' to kill this job, use a\n");
printf("regular kill instead\n\n");
kset_logfile(prm.sysfile);
krb_log("Admin server starting");
kerb_db_set_lockmode(KERB_DBL_NONBLOCKING);
errval = kerb_init(); /* Open the Kerberos database */
if (errval) {
warnx ("error: kerb_init() failed");
close_syslog();
byebye();
}
if (port == 0)
port = k_getportbyname (KADM_SNAME,
"tcp",
htons(751));
/* set up the server_parm struct */
if ((errval = kadm_ser_init(prm.inter, krbrlm, i_addr,
port))==KADM_SUCCESS) {
kerb_fini(); /* Close the Kerberos database--
will re-open later */
errval = kadm_listen(); /* listen for calls to server from
clients */
}
if (errval != KADM_SUCCESS) {
warnx("error: %s",error_message(errval));
kerb_fini(); /* Close if error */
}
close_syslog(); /* Close syslog file, print
closing note */
byebye(); /* Say bye bye on the terminal
in use */
exit(1);
} /* procedure main */