Initial import from FreeBSD RELENG_4:
[dragonfly.git] / crypto / kerberosIV / kadmin / admin_server.c
1 /*
2   Copyright (C) 1989 by the Massachusetts Institute of Technology
3
4    Export of this software from the United States of America is assumed
5    to require a specific license from the United States Government.
6    It is the responsibility of any person or organization contemplating
7    export to obtain such a license before exporting.
8
9 WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
10 distribute this software and its documentation for any purpose and
11 without fee is hereby granted, provided that the above copyright
12 notice appear in all copies and that both that copyright notice and
13 this permission notice appear in supporting documentation, and that
14 the name of M.I.T. not be used in advertising or publicity pertaining
15 to distribution of the software without specific, written prior
16 permission.  M.I.T. makes no representations about the suitability of
17 this software for any purpose.  It is provided "as is" without express
18 or implied warranty.
19
20   */
21
22 /*
23  * Top-level loop of the kerberos Administration server
24  */
25
26 /*
27   admin_server.c
28   this holds the main loop and initialization and cleanup code for the server
29 */
30
31 #include "kadm_locl.h"
32
33 RCSID("$Id: admin_server.c,v 1.49.2.2 2000/10/18 20:24:57 assar Exp $");
34
35 /* Almost all procs and such need this, so it is global */
36 admin_params prm;               /* The command line parameters struct */
37
38 /* GLOBAL */
39 char *acldir = DEFAULT_ACL_DIR;
40 static char krbrlm[REALM_SZ];
41
42 #define MAXCHILDREN 100
43
44 struct child {
45     pid_t pid;
46     int pipe_fd;
47     int authenticated;
48 };
49
50 static unsigned nchildren = 0;
51 static struct child children[MAXCHILDREN];
52
53 static int exit_now = 0;
54
55 static
56 RETSIGTYPE
57 doexit(int sig)
58 {
59     exit_now = 1;
60     SIGRETURN(0);
61 }
62    
63 static sig_atomic_t do_wait;
64
65 static
66 RETSIGTYPE
67 do_child(int sig)
68 {
69     do_wait = 1;
70     SIGRETURN(0);
71 }
72
73
74 static void
75 kill_children(void)
76 {
77     int i;
78
79     for (i = 0; i < nchildren; i++) {
80         kill(children[i].pid, SIGINT);
81         close (children[i].pipe_fd);
82         krb_log("killing child %d", children[i].pid);
83     }
84 }
85
86 /* close the system log file */
87 static void
88 close_syslog(void)
89 {
90    krb_log("Shutting down admin server");
91 }
92
93 static void
94 byebye(void)                    /* say goodnight gracie */
95 {
96    printf("Admin Server (kadm server) has completed operation.\n");
97 }
98
99 static void
100 clear_secrets(void)
101 {
102     memset(server_parm.master_key, 0, sizeof(server_parm.master_key));
103     memset(server_parm.master_key_schedule, 0,
104           sizeof(server_parm.master_key_schedule));
105     server_parm.master_key_version = 0L;
106 }
107
108 static void
109 cleanexit(int val)
110 {
111     kerb_fini();
112     clear_secrets();
113     exit(val);
114 }
115
116 static RETSIGTYPE
117 sigalrm(int sig)
118 {
119     cleanexit(1);
120 }
121
122 /*
123  * handle the client on the socket `fd' from `who'
124  * `signal_fd' is a pipe on which to signal when the user has been
125  * authenticated
126  */
127
128 static void
129 process_client(int fd, struct sockaddr_in *who, int signal_fd)
130 {
131     u_char *dat;
132     int dat_len;
133     u_short dlen;
134     int retval;
135     Principal service;
136     des_cblock skey;
137     int more;
138     int status;
139     int authenticated = 0;
140
141     /* make this connection time-out after 1 second if the user has
142        not managed one transaction succesfully in kadm_ser_in */
143
144     signal(SIGALRM, sigalrm);
145     alarm(2);
146
147 #if defined(SO_KEEPALIVE) && defined(HAVE_SETSOCKOPT)
148     {
149         int on = 1;
150             
151         if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
152                        (void *)&on, sizeof(on)) < 0)
153             krb_log("setsockopt keepalive: %d",errno);
154     }
155 #endif
156
157     server_parm.recv_addr = *who;
158
159     if (kerb_init()) {                  /* Open as client */
160         krb_log("can't open krb db");
161         cleanexit(1);
162     }
163     /* need to set service key to changepw.KRB_MASTER */
164
165     status = kerb_get_principal(server_parm.sname, server_parm.sinst, &service,
166                                 1, &more);
167     if (status == -1) {
168       /* db locked */
169       char *pdat;
170       
171       dat_len = KADM_VERSIZE + 4;
172       dat = (u_char *) malloc(dat_len);
173       if (dat == NULL) {
174           krb_log("malloc failed");
175           cleanexit(4);
176       }
177       pdat = (char *) dat;
178       memcpy(pdat, KADM_ULOSE, KADM_VERSIZE);
179       krb_put_int (KADM_DB_INUSE, pdat + KADM_VERSIZE, 4, 4);
180       goto out;
181     } else if (!status) {
182       krb_log("no service %s.%s",server_parm.sname, server_parm.sinst);
183       cleanexit(2);
184     }
185
186     copy_to_key(&service.key_low, &service.key_high, skey);
187     memset(&service, 0, sizeof(service));
188     kdb_encrypt_key (&skey, &skey, &server_parm.master_key,
189                      server_parm.master_key_schedule, DES_DECRYPT);
190     krb_set_key(skey, 0); /* if error, will show up when
191                                             rd_req fails */
192     memset(skey, 0, sizeof(skey));
193
194     while (1) {
195         void *errpkt;
196
197         errpkt = malloc(KADM_VERSIZE + 4);
198         if (errpkt == NULL) {
199             krb_log("malloc: no memory");
200             close(fd);
201             cleanexit(4);
202         }
203
204         if ((retval = krb_net_read(fd, &dlen, sizeof(u_short))) !=
205             sizeof(u_short)) {
206             if (retval < 0)
207                 krb_log("dlen read: %s",error_message(errno));
208             else if (retval)
209                 krb_log("short dlen read: %d",retval);
210             close(fd);
211             cleanexit(retval ? 3 : 0);
212         }
213         if (exit_now) {
214             cleanexit(0);
215         }
216         dat_len = ntohs(dlen);
217         dat = (u_char *) malloc(dat_len);
218         if (dat == NULL) {
219             krb_log("malloc: No memory");
220             close(fd);
221             cleanexit(4);
222         }
223         if ((retval = krb_net_read(fd, dat, dat_len)) != dat_len) {
224             if (retval < 0)
225                 krb_log("data read: %s",error_message(errno));
226             else
227                 krb_log("short read: %d vs. %d", dat_len, retval);
228             close(fd);
229             cleanexit(5);
230         }
231         if (exit_now) {
232             cleanexit(0);
233         }
234         retval = kadm_ser_in(&dat, &dat_len, errpkt);
235
236         if (retval == KADM_SUCCESS) {
237             if (!authenticated) {
238                 unsigned char one = 1;
239
240                 authenticated = 1;
241                 alarm (0);
242                 write (signal_fd, &one, 1);
243             }
244         } else {
245             krb_log("processing request: %s", error_message(retval));
246         }
247     
248         /* kadm_ser_in did the processing and returned stuff in
249            dat & dat_len , return the appropriate data */
250     
251     out:
252         dlen = htons(dat_len);
253     
254         if (krb_net_write(fd, &dlen, sizeof(u_short)) < 0) {
255             krb_log("writing dlen to client: %s",error_message(errno));
256             close(fd);
257             cleanexit(6);
258         }
259     
260         if (krb_net_write(fd, dat, dat_len) < 0) {
261             krb_log("writing to client: %s", error_message(errno));
262             close(fd);
263             cleanexit(7);
264         }
265         free(dat);
266     }
267     /*NOTREACHED*/
268 }
269
270 static void
271 accept_client (int admin_fd)
272 {
273     int pipe_fd[2];
274     int addrlen;
275     struct sockaddr_in peer;
276     pid_t pid;
277     int peer_fd;
278
279     /* using up the maximum number of children, try to get rid
280        of one unauthenticated one */
281
282     if (nchildren >= MAXCHILDREN) {
283         int i, nunauth = 0;
284         int victim;
285
286         for (;;) {
287             for (i = 0; i < nchildren; ++i)
288                 if (children[i].authenticated == 0)
289                     ++nunauth;
290             if (nunauth == 0)
291                 return;
292
293             victim = rand() % nchildren;
294             if (children[victim].authenticated == 0) {
295                 kill(children[victim].pid, SIGINT);
296                 close(children[victim].pipe_fd);
297                 for (i = victim; i < nchildren; ++i)
298                     children[i] = children[i + 1];
299                 --nchildren;
300                 break;
301             }
302         }
303     }
304
305     /* accept the conn */
306     addrlen = sizeof(peer);
307     peer_fd = accept(admin_fd, (struct sockaddr *)&peer, &addrlen);
308     if (peer_fd < 0) {
309         krb_log("accept: %s",error_message(errno));
310         return;
311     }
312     if (pipe (pipe_fd) < 0) {
313         krb_log ("pipe: %s", error_message(errno));
314         return;
315     }
316
317     if (pipe_fd[0] >= FD_SETSIZE
318         || pipe_fd[1] >= FD_SETSIZE) {
319         krb_log ("pipe fds too large");
320         close (pipe_fd[0]);
321         close (pipe_fd[1]);
322         return;
323     }
324
325     pid = fork ();
326
327     if (pid < 0) {
328         krb_log ("fork: %s", error_message(errno));
329         close (pipe_fd[0]);
330         close (pipe_fd[1]);
331         return;
332     }
333
334     if (pid != 0) {
335         /* parent */
336         /* fork succeded: keep tabs on child */
337         close(peer_fd);
338         children[nchildren].pid     = pid;
339         children[nchildren].pipe_fd = pipe_fd[0];
340         children[nchildren].authenticated = 0;
341         ++nchildren;
342         close (pipe_fd[1]);
343
344     } else {
345         int i;
346
347         /* child */
348         close(admin_fd);
349         close(pipe_fd[0]);
350
351         for (i = 0; i < nchildren; ++i)
352             close (children[i].pipe_fd);
353
354         /*
355          * If we are multihomed we need to figure out which
356          * local address that is used this time since it is
357          * used in "direction" comparison.
358          */
359         getsockname(peer_fd,
360                     (struct sockaddr *)&server_parm.admin_addr,
361                     &addrlen);
362         /* do stuff */
363         process_client (peer_fd, &peer, pipe_fd[1]);
364     }
365 }
366
367 /*
368  * handle data signaled from child `child' kadmind
369  */
370
371 static void
372 handle_child_signal (int child)
373 {
374     int ret;
375     unsigned char data[1];
376
377     ret = read (children[child].pipe_fd, data, 1);
378     if (ret < 0) {
379         if (errno != EINTR)
380             krb_log ("read from child %d: %s", child,
381                      error_message(errno));
382         return;
383     }
384     if (ret == 0) {
385         close (children[child].pipe_fd);
386         children[child].pipe_fd = -1;
387         return;
388     }
389     if (data)
390         children[child].authenticated = 1;
391 }
392
393 /*
394  * handle dead children
395  */
396
397 static void
398 handle_sigchld (void)
399 {
400     pid_t pid;
401     int status;
402     int i, j;
403
404     for (;;) {
405         int found = 0;
406
407         pid = waitpid(-1, &status, WNOHANG|WUNTRACED);
408         if (pid == 0 || (pid < 0 && errno == ECHILD))
409             break;
410         if (pid < 0) {
411             krb_log("waitpid: %s", error_message(errno));
412             break;
413         }
414         for (i = 0; i < nchildren; i++)
415             if (children[i].pid == pid) {
416                 /* found it */
417                 close(children[i].pipe_fd);
418                 for (j = i; j < nchildren; j++)
419                     /* copy others down */
420                     children[j] = children[j+1];
421                 --nchildren;
422 #if 0
423                 if ((WIFEXITED(status) && WEXITSTATUS(status) != 0)
424                     || WIFSIGNALED(status))
425                     krb_log("child %d: termsig %d, retcode %d", pid,
426                             WTERMSIG(status), WEXITSTATUS(status));
427 #endif
428                 found = 1;
429             }
430 #if 0
431         if (!found)
432             krb_log("child %d not in list: termsig %d, retcode %d", pid,
433                     WTERMSIG(status), WEXITSTATUS(status));
434 #endif
435     }
436     do_wait = 0;
437 }
438
439 /*
440 kadm_listen
441 listen on the admin servers port for a request
442 */
443 static int
444 kadm_listen(void)
445 {
446     int found;
447     int admin_fd;
448     fd_set readfds;
449
450     signal(SIGINT, doexit);
451     signal(SIGTERM, doexit);
452     signal(SIGHUP, doexit);
453     signal(SIGQUIT, doexit);
454     signal(SIGPIPE, SIG_IGN); /* get errors on write() */
455     signal(SIGALRM, doexit);
456     signal(SIGCHLD, do_child);
457     if (setsid() < 0)
458         krb_log("setsid() failed");
459
460     if ((admin_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
461         return KADM_NO_SOCK;
462
463     if (admin_fd >= FD_SETSIZE) {
464         krb_log("admin_fd too big");
465         return KADM_NO_BIND;
466     }
467         
468 #if defined(SO_REUSEADDR) && defined(HAVE_SETSOCKOPT)
469     {
470       int one = 1;
471       setsockopt(admin_fd, SOL_SOCKET, SO_REUSEADDR, (void *)&one,
472                  sizeof(one));
473     }
474 #endif
475     if (bind(admin_fd, (struct sockaddr *)&server_parm.admin_addr,
476              sizeof(struct sockaddr_in)) < 0)
477         return KADM_NO_BIND;
478     if (listen(admin_fd, SOMAXCONN) < 0)
479         return KADM_NO_BIND;
480
481     for (;;) {                          /* loop nearly forever */
482         int i;
483         int maxfd = -1;
484
485         if (exit_now) {
486             clear_secrets();
487             kill_children();
488             return(0);
489         }
490         if (do_wait)
491             handle_sigchld ();
492
493         FD_ZERO(&readfds);
494         FD_SET(admin_fd, &readfds);
495         maxfd = max(maxfd, admin_fd);
496         for (i = 0; i < nchildren; ++i)
497             if (children[i].pipe_fd >= 0) {
498                 FD_SET(children[i].pipe_fd, &readfds);
499                 maxfd = max(maxfd, children[i].pipe_fd);
500             }
501
502         found = select(maxfd + 1, &readfds, NULL, NULL, NULL);
503         if (found < 0) {
504             if (errno != EINTR)
505                 krb_log("select: %s",error_message(errno));
506             continue;
507         }
508         if (FD_ISSET(admin_fd, &readfds)) 
509             accept_client (admin_fd);
510         for (i = 0; i < nchildren; ++i)
511             if (children[i].pipe_fd >= 0
512                 && FD_ISSET(children[i].pipe_fd, &readfds)) {
513                 handle_child_signal (i);
514             }
515     }
516     /*NOTREACHED*/
517 }
518
519 /*
520 ** Main does the logical thing, it sets up the database and RPC interface,
521 **  as well as handling the creation and maintenance of the syslog file...
522 */
523 int
524 main(int argc, char **argv)             /* admin_server main routine */
525 {
526     int errval;
527     int c;
528     struct in_addr i_addr;
529
530     set_progname (argv[0]);
531
532     umask(077);         /* Create protected files */
533
534     i_addr.s_addr = INADDR_ANY;
535     /* initialize the admin_params structure */
536     prm.sysfile = KADM_SYSLOG;          /* default file name */
537     prm.inter = 0;
538
539     memset(krbrlm, 0, sizeof(krbrlm));
540
541     while ((c = getopt(argc, argv, "f:hmnd:a:r:i:")) != -1)
542         switch(c) {
543         case 'f':                       /* Syslog file name change */
544             prm.sysfile = optarg;
545             break;
546         case 'n':
547             prm.inter = 0;
548             break;
549         case 'm':
550             prm.inter = 1;
551             break;
552         case 'a':                       /* new acl directory */
553             acldir = optarg;
554             break;
555         case 'd':
556             /* put code to deal with alt database place */
557             if ((errval = kerb_db_set_name(optarg)))
558                 errx (1, "opening database %s: %s",
559                       optarg, error_message(errval));
560             break;
561         case 'r':
562             strlcpy (krbrlm, optarg, sizeof(krbrlm));
563             break;
564         case 'i':
565             /* Only listen on this address */
566             if(inet_aton (optarg, &i_addr) == 0) {
567                 fprintf (stderr, "Bad address: %s\n", optarg);
568                 exit (1);
569             }
570             break;
571         case 'h':                       /* get help on using admin_server */
572         default:
573             errx(1, "Usage: kadmind [-h] [-n] [-m] [-r realm] [-d dbname] [-f filename] [-a acldir] [-i address_to_listen_on]");
574         }
575
576     if (krbrlm[0] == 0)
577         if (krb_get_lrealm(krbrlm, 1) != KSUCCESS)
578             errx (1, "Unable to get local realm.  Fix krb.conf or use -r.");
579
580     printf("KADM Server %s initializing\n",KADM_VERSTR);
581     printf("Please do not use 'kill -9' to kill this job, use a\n");
582     printf("regular kill instead\n\n");
583
584     kset_logfile(prm.sysfile);
585     krb_log("Admin server starting");
586
587     kerb_db_set_lockmode(KERB_DBL_NONBLOCKING);
588     errval = kerb_init();               /* Open the Kerberos database */
589     if (errval) {
590         warnx ("error: kerb_init() failed");
591         close_syslog();
592         byebye();
593     }
594     /* set up the server_parm struct */
595     if ((errval = kadm_ser_init(prm.inter, krbrlm, i_addr))==KADM_SUCCESS) {
596         kerb_fini();                    /* Close the Kerberos database--
597                                            will re-open later */
598         errval = kadm_listen();         /* listen for calls to server from
599                                            clients */
600     }
601     if (errval != KADM_SUCCESS) {
602         warnx("error:  %s",error_message(errval));
603         kerb_fini();                    /* Close if error */
604     }
605     close_syslog();                     /* Close syslog file, print
606                                            closing note */
607     byebye();                           /* Say bye bye on the terminal
608                                            in use */
609     exit(1);
610 }                                       /* procedure main */