]> code.citadel.org Git - citadel.git/blob - citadel/sysdep.c
put some obsurd limit on client_gets() input. So somebody can't blow you
[citadel.git] / citadel / sysdep.c
1 /*
2  * $Id$
3  *
4  * Citadel/UX "system dependent" stuff.
5  * See copyright.txt for copyright information.
6  *
7  * Here's where we (hopefully) have most parts of the Citadel server that
8  * would need to be altered to run the server in a non-POSIX environment.
9  * 
10  * If we ever port to a different platform and either have multiple
11  * variants of this file or simply load it up with #ifdefs.
12  *
13  */
14
15
16 #include "sysdep.h"
17 #include <stdlib.h>
18 #include <unistd.h>
19 #include <stdio.h>
20 #include <fcntl.h>
21 #include <ctype.h>
22 #include <signal.h>
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <sys/wait.h>
26 #include <sys/socket.h>
27 #include <sys/time.h>
28 #include <limits.h>
29 #include <netinet/in.h>
30 #include <netdb.h>
31 #include <sys/un.h>
32 #include <string.h>
33 #include <pwd.h>
34 #include <errno.h>
35 #include <stdarg.h>
36 #include <syslog.h>
37 #include <grp.h>
38 #ifdef __GNUC__
39 #include <malloc.h>
40 #endif
41 #ifdef HAVE_PTHREAD_H
42 #include <pthread.h>
43 #endif
44 #include "citadel.h"
45 #include "server.h"
46 #include "sysdep_decls.h"
47 #include "citserver.h"
48 #include "support.h"
49 #include "config.h"
50 #include "database.h"
51 #include "housekeeping.h"
52 #include "dynloader.h"
53 #include "tools.h"
54
55 #ifdef HAVE_SYS_SELECT_H
56 #include <sys/select.h>
57 #endif
58
59 #ifndef HAVE_SNPRINTF
60 #include "snprintf.h"
61 #endif
62
63 #ifdef DEBUG_MEMORY_LEAKS
64 struct TheHeap *heap = NULL;
65 #endif
66
67 pthread_mutex_t Critters[MAX_SEMAPHORES];       /* Things needing locking */
68 pthread_key_t MyConKey;                         /* TSD key for MyContext() */
69
70 int verbosity = DEFAULT_VERBOSITY;              /* Logging level */
71
72 struct CitContext masterCC;
73 int rescan[2];                                  /* The Rescan Pipe */
74 time_t last_purge = 0;                          /* Last dead session purge */
75 static int num_threads = 0;                     /* Current number of threads */
76 int num_sessions = 0;                           /* Current number of sessions */
77
78 fd_set masterfds;                               /* Master sockets etc. */
79 int masterhighest;
80
81 time_t last_timer = 0L;                         /* Last timer hook processing */
82
83 static pthread_t initial_thread;                /* tid for main() thread */
84
85
86 /*
87  * lprintf()  ...   Write logging information
88  * 
89  * Note: the variable "buf" below needs to be large enough to handle any
90  * log data sent through this function.  BE CAREFUL!
91  */
92  
93 static int /* I stole this from snprintf.c because it might not get configed in */
94 neededalso (const char *fmt, va_list argp)
95 {
96   static FILE *sink = NULL;
97
98   /* ok, there's a small race here that could result in the sink being
99    * opened more than once if we're threaded, but I'd rather ignore it than
100    * spend cycles synchronizing :-) */
101
102   if (sink == NULL)
103     {
104       if ((sink = fopen("/dev/null", "w")) == NULL)
105         {
106           perror("/dev/null");
107           exit(1);
108         }
109     }
110
111   return vfprintf(sink, fmt, argp);
112 }
113
114 void lprintf(int loglevel, const char *format, ...) {   
115         va_list arg_ptr;
116         char *buf;
117   
118     /*  stu We'll worry about speed later if it's a problem. */
119         va_start(arg_ptr, format);   
120         buf = mallok(neededalso(format, arg_ptr)+1);
121         va_end(arg_ptr);   
122         
123         va_start(arg_ptr, format);   
124         vsprintf(buf, format, arg_ptr);   
125         va_end(arg_ptr);   
126
127         if (loglevel <= verbosity) { 
128                 struct timeval tv;
129                 struct tm *tim;
130
131                 gettimeofday(&tv, NULL);
132                 tim = localtime(&(tv.tv_sec));
133                 /*
134                  * Log provides millisecond accuracy.  If you need
135                  * microsecond accuracy and your OS supports it, change
136                  * %03ld to %06ld and remove " / 1000" after tv.tv_usec.
137                  */
138                 fprintf(stderr, "%04d/%02d/%02d %2d:%02d:%02d.%03ld %s",
139                         tim->tm_year + 1900, tim->tm_mon + 1, tim->tm_mday,
140                         tim->tm_hour, tim->tm_min, tim->tm_sec,
141                         tv.tv_usec / 1000, buf);
142                 fflush(stderr);
143         }
144
145         PerformLogHooks(loglevel, buf);
146         phree(buf);
147 }   
148
149
150
151 #ifdef DEBUG_MEMORY_LEAKS
152
153 /* These functions presume a long is 32 bits. */
154
155 void *getmem (long m)
156   {
157     /* must zero out data */
158     char *z;
159     char *zf;
160
161     z = malloc(m+8);  /* make room for size and "CITX" */
162     zf = z + m+6;
163
164     /* store check info */
165     memcpy (z, &m, 4); /* copy the long in */
166     memcpy (z+m+4, "CITX", 4); /* my over run check bytes */
167
168     return z + 4;
169   }
170
171 void freemem (void *m)
172   {
173     /* check to see if we overran */
174     long sz;
175
176     memcpy (&sz, m-4, 4); /* get long back */
177
178     if (memcmp (m + sz, "CITX", 4) != 0)
179       {
180                 lprintf(3, "DANGER! Memory overrun!\n", "", "", "");
181       }
182     free(m - 4); // nobody tells me these things
183   }
184
185 void *reallocmem(void *m, ULONG newsize)
186   {
187     /* check to see if we overran */
188     ULONG sz;
189     char *ret;
190
191     memcpy (&sz, m-4, 4); /* get long back */
192
193     if (memcmp (m + sz, "CITX", 4) != 0)
194                 lprintf(3, "DANGER! Memory overrun!\n", "", "", "");
195
196     /* just like malloc */
197     ret = (char *)realloc (m-4, newsize+8);
198     memcpy (ret, &newsize, 4); /* copy the long in */
199     memcpy (ret + newsize + 4, "CITX", 4); /* my over run check bytes */
200
201     return (ret + 4);
202   }
203
204 void *tracked_malloc(size_t tsize, char *tfile, int tline) {
205         void *ptr;
206         struct TheHeap *hptr;
207
208         ptr = getmem(tsize); /* stu, thought you might like this for debugging */
209         if (ptr == NULL) {
210                 lprintf(3, "DANGER!  mallok(%d) at %s:%d failed!\n",
211                         tsize, tfile, tline);
212                 return(NULL);
213         }
214
215         hptr = (struct TheHeap *) getmem(sizeof(struct TheHeap));
216         strcpy(hptr->h_file, tfile);
217         hptr->h_line = tline;
218         hptr->next = heap;
219         hptr->h_ptr = ptr;
220         heap = hptr;
221         return ptr;
222 }
223
224 char *tracked_strdup(const char *orig, char *tfile, int tline) {
225         char *s;
226
227         s = tracked_malloc( (strlen(orig)+1), tfile, tline);
228         if (s == NULL) return NULL;
229
230         strcpy(s, orig);
231         return s;
232 }
233
234 void tracked_free(void *ptr) {
235         struct TheHeap *hptr, *freeme;
236
237         if (heap->h_ptr == ptr) {
238                 hptr = heap->next;
239                 freemem(heap);
240                 heap = hptr;
241         }
242         else {
243                 for (hptr=heap; hptr->next!=NULL; hptr=hptr->next) {
244                         if (hptr->next->h_ptr == ptr) {
245                                 freeme = hptr->next;
246                                 hptr->next = hptr->next->next;
247                                 freemem(freeme);
248                         }
249                 }
250         }
251
252         freemem(ptr);
253 }
254
255 void *tracked_realloc(void *ptr, size_t size) {
256         void *newptr;
257         struct TheHeap *hptr;
258         
259         newptr = reallocmem(ptr, size);
260
261         for (hptr=heap; hptr!=NULL; hptr=hptr->next) {
262                 if (hptr->h_ptr == ptr) hptr->h_ptr = newptr;
263         }
264
265         return newptr;
266 }
267
268
269 void dump_tracked() {
270         struct TheHeap *hptr;
271
272         cprintf("%d Here's what's allocated...\n", LISTING_FOLLOWS);    
273         for (hptr=heap; hptr!=NULL; hptr=hptr->next) {
274                 cprintf("%20s %5d\n",
275                         hptr->h_file, hptr->h_line);
276         }
277 #ifdef __GNUC__
278         malloc_stats();
279 #endif
280
281         cprintf("000\n");
282 }
283 #endif
284
285
286 /*
287  * We used to use master_cleanup() as a signal handler to shut down the server.
288  * however, master_cleanup() and the functions it calls do some things that
289  * aren't such a good idea to do from a signal handler: acquiring mutexes,
290  * playing with signal masks on BSDI systems, etc. so instead we install the
291  * following signal handler to set a global variable to inform the main loop
292  * that it's time to call master_cleanup() and exit.
293  */
294
295 volatile int time_to_die = 0;
296
297 static RETSIGTYPE signal_cleanup(int signum) {
298         time_to_die = 1;
299 }
300
301
302 /*
303  * Some initialization stuff...
304  */
305 void init_sysdep(void) {
306         int a;
307
308         /* Set up a bunch of semaphores to be used for critical sections */
309         for (a=0; a<MAX_SEMAPHORES; ++a) {
310                 pthread_mutex_init(&Critters[a], NULL);
311         }
312
313         /*
314          * Set up a place to put thread-specific data.
315          * We only need a single pointer per thread - it points to the
316          * CitContext structure (in the ContextList linked list) of the
317          * session to which the calling thread is currently bound.
318          */
319         if (pthread_key_create(&MyConKey, NULL) != 0) {
320                 lprintf(1, "Can't create TSD key!!  %s\n", strerror(errno));
321         }
322
323         /*
324          * The action for unexpected signals and exceptions should be to
325          * call signal_cleanup() to gracefully shut down the server.
326          */
327         signal(SIGINT, signal_cleanup);
328         signal(SIGQUIT, signal_cleanup);
329         signal(SIGHUP, signal_cleanup);
330         signal(SIGTERM, signal_cleanup);
331
332         /*
333          * Do not shut down the server on broken pipe signals, otherwise the
334          * whole Citadel service would come down whenever a single client
335          * socket breaks.
336          */
337         signal(SIGPIPE, SIG_IGN);
338 }
339
340
341 /*
342  * Obtain a semaphore lock to begin a critical section.
343  */
344 void begin_critical_section(int which_one)
345 {
346         /* lprintf(9, "begin_critical_section(%d)\n", which_one); */
347         pthread_mutex_lock(&Critters[which_one]);
348 }
349
350 /*
351  * Release a semaphore lock to end a critical section.
352  */
353 void end_critical_section(int which_one)
354 {
355         /* lprintf(9, "end_critical_section(%d)\n", which_one); */
356         pthread_mutex_unlock(&Critters[which_one]);
357 }
358
359
360
361 /*
362  * This is a generic function to set up a master socket for listening on
363  * a TCP port.  The server shuts down if the bind fails.
364  *
365  */
366 int ig_tcp_server(int port_number, int queue_len)
367 {
368         struct sockaddr_in sin;
369         int s, i;
370         int actual_queue_len;
371
372         actual_queue_len = queue_len;
373         if (actual_queue_len < 5) actual_queue_len = 5;
374
375         memset(&sin, 0, sizeof(sin));
376         sin.sin_family = AF_INET;
377         sin.sin_addr.s_addr = INADDR_ANY;
378         sin.sin_port = htons((u_short)port_number);
379
380         s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
381
382         if (s < 0) {
383                 lprintf(1, "citserver: Can't create a socket: %s\n",
384                         strerror(errno));
385                 return(-1);
386         }
387
388         i = 1;
389         setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
390
391         if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
392                 lprintf(1, "citserver: Can't bind: %s\n",
393                         strerror(errno));
394                 close(s);
395                 return(-1);
396         }
397
398         if (listen(s, actual_queue_len) < 0) {
399                 lprintf(1, "citserver: Can't listen: %s\n", strerror(errno));
400                 close(s);
401                 return(-1);
402         }
403
404         return(s);
405 }
406
407
408
409 /*
410  * Create a Unix domain socket and listen on it
411  */
412 int ig_uds_server(char *sockpath, int queue_len)
413 {
414         struct sockaddr_un addr;
415         int s;
416         int i;
417         int actual_queue_len;
418
419         actual_queue_len = queue_len;
420         if (actual_queue_len < 5) actual_queue_len = 5;
421
422         i = unlink(sockpath);
423         if (i != 0) if (errno != ENOENT) {
424                 lprintf(1, "citserver: can't unlink %s: %s\n",
425                         sockpath, strerror(errno));
426                 return(-1);
427         }
428
429         memset(&addr, 0, sizeof(addr));
430         addr.sun_family = AF_UNIX;
431         safestrncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
432
433         s = socket(AF_UNIX, SOCK_STREAM, 0);
434         if (s < 0) {
435                 lprintf(1, "citserver: Can't create a socket: %s\n",
436                         strerror(errno));
437                 return(-1);
438         }
439
440         if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
441                 lprintf(1, "citserver: Can't bind: %s\n",
442                         strerror(errno));
443                 return(-1);
444         }
445
446         if (listen(s, actual_queue_len) < 0) {
447                 lprintf(1, "citserver: Can't listen: %s\n", strerror(errno));
448                 return(-1);
449         }
450
451         chmod(sockpath, 0777);
452         return(s);
453 }
454
455
456
457 /*
458  * Return a pointer to the CitContext structure bound to the thread which
459  * called this function.  If there's no such binding (for example, if it's
460  * called by the housekeeper thread) then a generic 'master' CC is returned.
461  */
462 struct CitContext *MyContext(void) {
463         struct CitContext *retCC;
464         retCC = (struct CitContext *) pthread_getspecific(MyConKey);
465         if (retCC == NULL) retCC = &masterCC;
466         return(retCC);
467 }
468
469
470 /*
471  * Initialize a new context and place it in the list.  The session number
472  * used to be the PID (which is why it's called cs_pid), but that was when we
473  * had one process per session.  Now we just assign them sequentially, starting
474  * at 1 (don't change it to 0 because masterCC uses 0) and re-using them when
475  * sessions terminate.
476  */
477 struct CitContext *CreateNewContext(void) {
478         struct CitContext *me, *ptr;
479         int num = 1;
480         int startover = 0;
481
482         me = (struct CitContext *) mallok(sizeof(struct CitContext));
483         if (me == NULL) {
484                 lprintf(1, "citserver: can't allocate memory!!\n");
485                 return NULL;
486         }
487         memset(me, 0, sizeof(struct CitContext));
488
489         /* The new context will be created already in the CON_EXECUTING state
490          * in order to prevent another thread from grabbing it while it's
491          * being set up.
492          */
493         me->state = CON_EXECUTING;
494
495         begin_critical_section(S_SESSION_TABLE);
496
497         /* obtain a unique session number */
498         do {
499                 startover = 0;
500                 for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
501                         if (ptr->cs_pid == num) {
502                                 ++num;
503                                 startover = 1;
504                         }
505                 }
506         } while (startover == 1);
507
508         me->cs_pid = num;
509         me->next = ContextList;
510         ContextList = me;
511         ++num_sessions;
512
513         end_critical_section(S_SESSION_TABLE);
514         return(me);
515 }
516
517
518
519 /*
520  * client_write()   ...    Send binary data to the client.
521  */
522 void client_write(char *buf, int nbytes)
523 {
524         int bytes_written = 0;
525         int retval;
526         int sock;
527
528         if (CC->redirect_fp != NULL) {
529                 fwrite(buf, nbytes, 1, CC->redirect_fp);
530                 return;
531         }
532
533         if (CC->redirect_sock > 0) {
534                 sock = CC->redirect_sock;       /* and continue below... */
535         }
536         else {
537                 sock = CC->client_socket;
538         }
539
540         while (bytes_written < nbytes) {
541                 retval = write(sock, &buf[bytes_written],
542                         nbytes - bytes_written);
543                 if (retval < 1) {
544                         lprintf(2, "client_write() failed: %s\n",
545                                 strerror(errno));
546                         if (sock == CC->client_socket) CC->kill_me = 1;
547                         return;
548                 }
549                 bytes_written = bytes_written + retval;
550         }
551 }
552
553
554 /*
555  * cprintf()  ...   Send formatted printable data to the client.   It is
556  *                  implemented in terms of client_write() but remains in
557  *                  sysdep.c in case we port to somewhere without va_args...
558  */
559 void cprintf(const char *format, ...) {   
560         va_list arg_ptr;   
561         char buf[SIZ];   
562    
563         va_start(arg_ptr, format);   
564         if (vsnprintf(buf, sizeof buf, format, arg_ptr) == -1)
565                 buf[sizeof buf - 2] = '\n';
566         client_write(buf, strlen(buf)); 
567         va_end(arg_ptr);
568 }   
569
570
571 /*
572  * Read data from the client socket.
573  * Return values are:
574  *      1       Requested number of bytes has been read.
575  *      0       Request timed out.
576  *      -1      The socket is broken.
577  * If the socket breaks, the session will be terminated.
578  */
579 int client_read_to(char *buf, int bytes, int timeout)
580 {
581         int len,rlen;
582         fd_set rfds;
583         struct timeval tv;
584         int retval;
585
586         len = 0;
587         while(len<bytes) {
588                 FD_ZERO(&rfds);
589                 FD_SET(CC->client_socket, &rfds);
590                 tv.tv_sec = timeout;
591                 tv.tv_usec = 0;
592
593                 retval = select( (CC->client_socket)+1, 
594                                         &rfds, NULL, NULL, &tv);
595
596                 if (FD_ISSET(CC->client_socket, &rfds) == 0) {
597                         return(0);
598                 }
599
600                 rlen = read(CC->client_socket, &buf[len], bytes-len);
601                 if (rlen<1) {
602                         lprintf(2, "client_read() failed: %s\n",
603                                 strerror(errno));
604                         CC->kill_me = 1;
605                         return(-1);
606                 }
607                 len = len + rlen;
608         }
609         return(1);
610 }
611
612 /*
613  * Read data from the client socket with default timeout.
614  * (This is implemented in terms of client_read_to() and could be
615  * justifiably moved out of sysdep.c)
616  */
617 int client_read(char *buf, int bytes)
618 {
619         return(client_read_to(buf, bytes, config.c_sleeping));
620 }
621
622
623 /*
624  * client_gets()   ...   Get a LF-terminated line of text from the client.
625  * (This is implemented in terms of client_read() and could be
626  * justifiably moved out of sysdep.c)
627  */
628  
629 /* stu 2/7/2001. Rigging this to do dynamic allocating to recieve 
630    random length commands. The memory is held by the session. The
631    pointer returned to the caller is for reading only for they do
632    not know how big it is. The context owns the buffer. Thus there
633    is one per session, gets cleaned up in remove_session or something
634    like that. Not going for killer speed here since this isn't really
635    a bottleneck function. */
636
637 int client_gets(char **retbuf)
638 {
639         int i, retval;
640
641         /* Read one character at a time. */
642         char *b = CC->readbuf;
643         int sz = CC->readbuf_alloc;
644         if (b == NULL) /* first time in? */
645           {
646             b = mallok(SIZ+1); /* start with something */
647             sz = SIZ;
648           }
649         else
650           {
651             /* take this out if you prefer not wasting the time. */
652             if (sz > (SIZ*2)) /* if it went up, don't put at min */
653               {
654                 b = reallok(b, SIZ*2+1); /* resize down */
655                 sz = SIZ*2;
656               }
657           }
658         *b = '\0'; /* in case we bail early */
659         i = 0;
660         while (1)
661       {
662         retval = client_read(b+i, 1);
663         if (retval != 1 || b[i] == '\n')
664           break;
665         if (i == 1024*1024) // set some obscene upper limit
666           break;
667         i++;
668         if (i >= sz)
669           {
670             sz *= 2; /* resize up */
671             b = reallok(b, sz+1);
672           }
673       }
674
675         /* Strip the trailing newline and any trailing nonprintables (cr's) */
676         *(b+i) = '\0';
677         while ((strlen(b)>0)&&(!isprint(*(b+strlen(b)-1))))
678                 *(b+strlen(b)-1) = '\0';
679         if (retval < 0) 
680           strcpy(b, "000");
681
682     CC->readbuf = b; /* faster if we do it once at the end */
683     CC->readbuf_alloc = sz;
684
685
686     *retbuf = b;
687         return(retval);
688 }
689
690 int oldclient_gets(char *buf)
691 {
692         int i, retval;
693
694         /* Read one character at a time.
695          */
696         for (i = 0;;i++) {
697                 retval = client_read(&buf[i], 1);
698                 if (retval != 1 || buf[i] == '\n' || i == (SIZ-1))
699                         break;
700         }
701
702         /* If we got a long line, discard characters until the newline.
703          */
704         if (i == (SIZ-1))
705                 while (buf[i] != '\n' && retval == 1)
706                         retval = client_read(&buf[i], 1);
707
708         /* Strip the trailing newline and any trailing nonprintables (cr's)
709          */
710         buf[i] = 0;
711         while ((strlen(buf)>0)&&(!isprint(buf[strlen(buf)-1])))
712                 buf[strlen(buf)-1] = 0;
713         if (retval < 0) strcpy(buf, "000");
714         return(retval);
715 }
716
717
718
719 /*
720  * The system-dependent part of master_cleanup() - close the master socket.
721  */
722 void sysdep_master_cleanup(void) {
723         struct ServiceFunctionHook *serviceptr;
724
725         /*
726          * close all protocol master sockets
727          */
728         for (serviceptr = ServiceHookTable; serviceptr != NULL;
729             serviceptr = serviceptr->next ) {
730
731                 if (serviceptr->tcp_port > 0)
732                         lprintf(3, "Closing listener on port %d\n",
733                                 serviceptr->tcp_port);
734
735                 if (serviceptr->sockpath != NULL)
736                         lprintf(3, "Closing listener on '%s'\n",
737                                 serviceptr->sockpath);
738
739                 close(serviceptr->msock);
740
741                 /* If it's a Unix domain socket, remove the file. */
742                 if (serviceptr->sockpath != NULL) {
743                         unlink(serviceptr->sockpath);
744                 }
745         }
746 }
747
748
749 /*
750  * Terminate another session.
751  * (This could justifiably be moved out of sysdep.c because it
752  * no longer does anything that is system-dependent.)
753  */
754 void kill_session(int session_to_kill) {
755         struct CitContext *ptr;
756
757         begin_critical_section(S_SESSION_TABLE);
758         for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
759                 if (ptr->cs_pid == session_to_kill) {
760                         ptr->kill_me = 1;
761                 }
762         }
763         end_critical_section(S_SESSION_TABLE);
764 }
765
766
767
768
769 /*
770  * Start running as a daemon.  Only close stdio if do_close_stdio is set.
771  */
772 void start_daemon(int do_close_stdio) {
773         if (do_close_stdio) {
774                 /* close(0); */
775                 close(1);
776                 close(2);
777         }
778         signal(SIGHUP,SIG_IGN);
779         signal(SIGINT,SIG_IGN);
780         signal(SIGQUIT,SIG_IGN);
781         if (fork()!=0) exit(0);
782 }
783
784
785
786 /*
787  * Tie in to the 'netsetup' program.
788  *
789  * (We're going to hope that netsetup never feeds more than 4096 bytes back.)
790  */
791 void cmd_nset(char *cmdbuf)
792 {
793         int retcode;
794         char fbuf[4096];
795         FILE *netsetup;
796         int ch;
797         int a, b;
798         char netsetup_args[3][SIZ];
799
800         if (CC->usersupp.axlevel < 6) {
801                 cprintf("%d Higher access required.\n", 
802                         ERROR + HIGHER_ACCESS_REQUIRED);
803                 return;
804         }
805
806         for (a=1; a<=3; ++a) {
807                 if (num_parms(cmdbuf) >= a) {
808                         extract(netsetup_args[a-1], cmdbuf, a-1);
809                         for (b=0; b<strlen(netsetup_args[a-1]); ++b) {
810                                 if (netsetup_args[a-1][b] == 34) {
811                                         netsetup_args[a-1][b] = '_';
812                                 }
813                         }
814                 }
815                 else {
816                         netsetup_args[a-1][0] = 0;
817                 }
818         }
819
820         sprintf(fbuf, "./netsetup \"%s\" \"%s\" \"%s\" </dev/null 2>&1",
821                 netsetup_args[0], netsetup_args[1], netsetup_args[2]);
822         netsetup = popen(fbuf, "r");
823         if (netsetup == NULL) {
824                 cprintf("%d %s\n", ERROR, strerror(errno));
825                 return;
826         }
827
828         fbuf[0] = 0;
829         while (ch = getc(netsetup), (ch > 0)) {
830                 fbuf[strlen(fbuf)+1] = 0;
831                 fbuf[strlen(fbuf)] = ch;
832         }
833
834         retcode = pclose(netsetup);
835
836         if (retcode != 0) {
837                 for (a=0; a<strlen(fbuf); ++a) {
838                         if (fbuf[a] < 32) fbuf[a] = 32;
839                 }
840                 fbuf[245] = 0;
841                 cprintf("%d %s\n", ERROR, fbuf);
842                 return;
843         }
844
845         cprintf("%d Command succeeded.  Output follows:\n", LISTING_FOLLOWS);
846         cprintf("%s", fbuf);
847         if (fbuf[strlen(fbuf)-1] != 10) cprintf("\n");
848         cprintf("000\n");
849 }
850
851
852
853 /*
854  * Generic routine to convert a login name to a full name (gecos)
855  * Returns nonzero if a conversion took place
856  */
857 int convert_login(char NameToConvert[]) {
858         struct passwd *pw;
859         int a;
860
861         pw = getpwnam(NameToConvert);
862         if (pw == NULL) {
863                 return(0);
864         }
865         else {
866                 strcpy(NameToConvert, pw->pw_gecos);
867                 for (a=0; a<strlen(NameToConvert); ++a) {
868                         if (NameToConvert[a] == ',') NameToConvert[a] = 0;
869                 }
870                 return(1);
871         }
872 }
873
874 static struct worker_node {
875         pthread_t tid;
876         struct worker_node *next;
877 } *worker_list = NULL;
878
879
880 /*
881  * create a worker thread. this function must always be called from within
882  * an S_WORKER_LIST critical section!
883  */
884 static void create_worker(void) {
885         int ret;
886         struct worker_node *n = mallok(sizeof *n);
887
888         if (n == NULL) {
889                 lprintf(1, "can't allocate worker_node, exiting\n");
890                 time_to_die = -1;
891                 return;
892         }
893
894         if ((ret = pthread_create(&n->tid, NULL, worker_thread, NULL) != 0))
895         {
896
897                 lprintf(1, "Can't create worker thread: %s\n",
898                         strerror(ret));
899         }
900
901         n->next = worker_list;
902         worker_list = n;
903 }
904
905
906
907 /*
908  * Purge all sessions which have the 'kill_me' flag set.
909  * This function has code to prevent it from running more than once every
910  * few seconds, because running it after every single unbind would waste a lot
911  * of CPU time and keep the context list locked too much.
912  *
913  * After that's done, we raise or lower the size of the worker thread pool
914  * if such an action is appropriate.
915  */
916 void dead_session_purge(void) {
917         struct CitContext *ptr, *rem;
918         struct worker_node **node, *tmp;
919         pthread_t self;
920
921         if ( (time(NULL) - last_purge) < 5 ) return;    /* Too soon, go away */
922         time(&last_purge);
923
924         do {
925                 rem = NULL;
926                 begin_critical_section(S_SESSION_TABLE);
927                 for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
928                         if ( (ptr->state == CON_IDLE) && (ptr->kill_me) ) {
929                                 rem = ptr;
930                         }
931                 }
932                 end_critical_section(S_SESSION_TABLE);
933
934                 /* RemoveContext() enters its own S_SESSION_TABLE critical
935                  * section, so we have to do it like this.
936                  */     
937                 if (rem != NULL) {
938                         lprintf(9, "Purging session %d\n", rem->cs_pid);
939                         RemoveContext(rem);
940                 }
941
942         } while (rem != NULL);
943
944
945         /* Raise or lower the size of the worker thread pool if such
946          * an action is appropriate.
947          */
948
949         self = pthread_self();
950
951         if ( (num_sessions > num_threads)
952            && (num_threads < config.c_max_workers) ) {
953                 begin_critical_section(S_WORKER_LIST);
954                 create_worker();
955                 end_critical_section(S_WORKER_LIST);
956         }
957         
958         /* don't let the initial thread die since it's responsible for
959            waiting for all the other threads to terminate. */
960         else if ( (num_sessions < num_threads)
961            && (num_threads > config.c_min_workers)
962            && (self != initial_thread) ) {
963                 cdb_free_tsd();
964                 begin_critical_section(S_WORKER_LIST);
965                 --num_threads;
966
967                 /* we're exiting before server shutdown... unlink ourself from
968                    the worker list and detach our thread to avoid memory leaks
969                  */
970
971                 for (node = &worker_list; *node != NULL; node = &(*node)->next)
972                         if ((*node)->tid == self) {
973                                 tmp = *node;
974                                 *node = (*node)->next;
975                                 phree(tmp);
976                                 break;
977                         }
978
979                 pthread_detach(self);
980                 end_critical_section(S_WORKER_LIST);
981                 pthread_exit(NULL);
982         }
983
984 }
985
986
987
988
989
990 /*
991  * Redirect a session's output to a file or socket.
992  * This function may be called with a file handle *or* a socket (but not
993  * both).  Call with neither to return output to its normal client socket.
994  */
995 void CtdlRedirectOutput(FILE *fp, int sock) {
996
997         if (fp != NULL) CC->redirect_fp = fp;
998         else CC->redirect_fp = NULL;
999
1000         if (sock > 0) CC->redirect_sock = sock;
1001         else CC->redirect_sock = (-1);
1002
1003 }
1004
1005
1006 /*
1007  * masterCC is the context we use when not attached to a session.  This
1008  * function initializes it.
1009  */
1010 void InitializeMasterCC(void) {
1011         memset(&masterCC, 0, sizeof(struct CitContext));
1012         masterCC.internal_pgm = 1;
1013         masterCC.cs_pid = 0;
1014 }
1015
1016
1017
1018 /*
1019  * Set up a fd_set containing all the master sockets to which we
1020  * always listen.  It's computationally less expensive to just copy
1021  * this to a local fd_set when starting a new select() and then add
1022  * the client sockets than it is to initialize a new one and then
1023  * figure out what to put there.
1024  */
1025 void init_master_fdset(void) {
1026         struct ServiceFunctionHook *serviceptr;
1027         int m;
1028
1029         lprintf(9, "Initializing master fdset\n");
1030
1031         FD_ZERO(&masterfds);
1032         masterhighest = 0;
1033
1034         lprintf(9, "Will listen on rescan pipe %d\n", rescan[0]);
1035         FD_SET(rescan[0], &masterfds);
1036         if (rescan[0] > masterhighest) masterhighest = rescan[0];
1037
1038         for (serviceptr = ServiceHookTable; serviceptr != NULL;
1039             serviceptr = serviceptr->next ) {
1040                 m = serviceptr->msock;
1041                 lprintf(9, "Will listen on master socket %d\n", m);
1042                 FD_SET(m, &masterfds);
1043                 if (m > masterhighest) {
1044                         masterhighest = m;
1045                 }
1046         }
1047         lprintf(9, "masterhighest = %d\n", masterhighest);
1048 }
1049
1050
1051
1052 /*
1053  * Here's where it all begins.
1054  */
1055 int main(int argc, char **argv)
1056 {
1057         char tracefile[128];            /* Name of file to log traces to */
1058         int a, i;                       /* General-purpose variables */
1059         struct passwd *pw;
1060         int drop_root_perms = 1;
1061         char *moddir;
1062         struct worker_node *wnp;
1063         
1064         /* specify default port name and trace file */
1065         strcpy(tracefile, "");
1066
1067         /* initialize the master context */
1068         InitializeMasterCC();
1069
1070         /* parse command-line arguments */
1071         for (a=1; a<argc; ++a) {
1072
1073                 /* -t specifies where to log trace messages to */
1074                 if (!strncmp(argv[a], "-t", 2)) {
1075                         strcpy(tracefile, argv[a]);
1076                         strcpy(tracefile, &tracefile[2]);
1077                         freopen(tracefile, "r", stdin);
1078                         freopen(tracefile, "w", stdout);
1079                         freopen(tracefile, "w", stderr);
1080                 }
1081
1082                 /* run in the background if -d was specified */
1083                 else if (!strcmp(argv[a], "-d")) {
1084                         start_daemon( (strlen(tracefile) > 0) ? 0 : 1 ) ;
1085                 }
1086
1087                 /* -x specifies the desired logging level */
1088                 else if (!strncmp(argv[a], "-x", 2)) {
1089                         verbosity = atoi(&argv[a][2]);
1090                 }
1091
1092                 else if (!strncmp(argv[a], "-h", 2)) {
1093                         safestrncpy(bbs_home_directory, &argv[a][2],
1094                                     sizeof bbs_home_directory);
1095                         home_specified = 1;
1096                 }
1097
1098                 else if (!strncmp(argv[a], "-f", 2)) {
1099                         do_defrag = 1;
1100                 }
1101
1102                 /* -r tells the server not to drop root permissions. don't use
1103                  * this unless you know what you're doing. this should be
1104                  * removed in the next release if it proves unnecessary. */
1105                 else if (!strcmp(argv[a], "-r"))
1106                         drop_root_perms = 0;
1107
1108                 /* any other parameter makes it crash and burn */
1109                 else {
1110                         lprintf(1,      "citserver: usage: "
1111                                         "citserver [-tTraceFile] [-d] [-f]"
1112                                         " [-xLogLevel] [-hHomeDir]\n");
1113                         exit(1);
1114                 }
1115
1116         }
1117
1118         /* Tell 'em who's in da house */
1119         lprintf(1,
1120 "\nMultithreaded message server for Citadel/UX\n"
1121 "Copyright (C) 1987-2000 by the Citadel/UX development team.\n"
1122 "Citadel/UX is free software, covered by the GNU General Public License, and\n"
1123 "you are welcome to change it and/or distribute copies of it under certain\n"
1124 "conditions.  There is absolutely no warranty for this software.  Please\n"
1125 "read the 'COPYING.txt' file for details.\n\n");
1126
1127         /* Initialize... */
1128         init_sysdep();
1129         openlog("citserver", LOG_PID, LOG_USER);
1130
1131         /* Load site-specific parameters */
1132         lprintf(7, "Loading citadel.config\n");
1133         get_config();
1134
1135
1136         /*
1137          * Do non system dependent startup functions.
1138          */
1139         master_startup();
1140
1141         /*
1142          * Bind the server to a Unix-domain socket.
1143          */
1144         CtdlRegisterServiceHook(0,
1145                                 "citadel.socket",
1146                                 citproto_begin_session,
1147                                 do_command_loop);
1148
1149         /*
1150          * Bind the server to our favorite TCP port (usually 504).
1151          */
1152         CtdlRegisterServiceHook(config.c_port_number,
1153                                 NULL,
1154                                 citproto_begin_session,
1155                                 do_command_loop);
1156
1157         /*
1158          * Load any server-side modules (plugins) available here.
1159          */
1160         lprintf(7, "Initializing loadable modules\n");
1161         if ((moddir = malloc(strlen(bbs_home_directory) + 9)) != NULL) {
1162                 sprintf(moddir, "%s/modules", bbs_home_directory);
1163                 DLoader_Init(moddir);
1164                 free(moddir);
1165         }
1166
1167         /*
1168          * The rescan pipe exists so that worker threads can be woken up and
1169          * told to re-scan the context list for fd's to listen on.  This is
1170          * necessary, for example, when a context is about to go idle and needs
1171          * to get back on that list.
1172          */
1173         if (pipe(rescan)) {
1174                 lprintf(1, "Can't create rescan pipe!\n");
1175                 exit(errno);
1176         }
1177
1178         init_master_fdset();
1179
1180         /*
1181          * Now that we've bound the sockets, change to the BBS user id and its
1182          * corresponding group ids
1183          */
1184         if (drop_root_perms) {
1185                 if ((pw = getpwuid(BBSUID)) == NULL)
1186                         lprintf(1, "WARNING: getpwuid(%d): %s\n"
1187                                    "Group IDs will be incorrect.\n", BBSUID,
1188                                 strerror(errno));
1189                 else {
1190                         initgroups(pw->pw_name, pw->pw_gid);
1191                         if (setgid(pw->pw_gid))
1192                                 lprintf(3, "setgid(%d): %s\n", pw->pw_gid,
1193                                         strerror(errno));
1194                 }
1195                 lprintf(7, "Changing uid to %d\n", BBSUID);
1196                 if (setuid(BBSUID) != 0) {
1197                         lprintf(3, "setuid() failed: %s\n", strerror(errno));
1198                 }
1199         }
1200
1201         /* We want to check for idle sessions once per minute */
1202         CtdlRegisterSessionHook(terminate_idle_sessions, EVT_TIMER);
1203
1204         /*
1205          * Now create a bunch of worker threads.
1206          */
1207         lprintf(9, "Starting %d worker threads\n", config.c_min_workers-1);
1208         begin_critical_section(S_WORKER_LIST);
1209         for (i=0; i<(config.c_min_workers-1); ++i) {
1210                 create_worker();
1211         }
1212         end_critical_section(S_WORKER_LIST);
1213
1214         /* Now this thread can become a worker as well. */
1215         initial_thread = pthread_self();
1216         worker_thread(NULL);
1217
1218         /* Server is exiting. Wait for workers to shutdown. */
1219         lprintf(7, "Waiting for worker threads to shut down\n");
1220
1221         begin_critical_section(S_WORKER_LIST);
1222         while (worker_list != NULL) {
1223                 wnp = worker_list;
1224                 worker_list = wnp->next;
1225
1226                 /* avoid deadlock with an exiting thread */
1227                 end_critical_section(S_WORKER_LIST);
1228                 if ((i = pthread_join(wnp->tid, NULL)))
1229                         lprintf(1, "pthread_join: %s\n", strerror(i));
1230                 phree(wnp);
1231                 begin_critical_section(S_WORKER_LIST);
1232         }
1233         end_critical_section(S_WORKER_LIST);
1234
1235         master_cleanup();
1236
1237         return(0);
1238 }
1239
1240
1241 /*
1242  * Bind a thread to a context.  (It's inline merely to speed things up.)
1243  */
1244 inline void become_session(struct CitContext *which_con) {
1245         pthread_setspecific(MyConKey, (void *)which_con );
1246 }
1247
1248
1249
1250 /* 
1251  * This loop just keeps going and going and going...
1252  */     
1253 void *worker_thread(void *arg) {
1254         int i;
1255         char junk;
1256         int highest;
1257         struct CitContext *ptr;
1258         struct CitContext *bind_me = NULL;
1259         fd_set readfds;
1260         int retval;
1261         struct CitContext *con= NULL;   /* Temporary context pointer */
1262         struct ServiceFunctionHook *serviceptr;
1263         struct sockaddr_in fsin;        /* Data for master socket */
1264         int alen;                       /* Data for master socket */
1265         int ssock;                      /* Descriptor for client socket */
1266         struct timeval tv;
1267
1268         num_threads++;
1269
1270         cdb_allocate_tsd();
1271
1272         while (!time_to_die) {
1273
1274                 /* 
1275                  * A naive implementation would have all idle threads
1276                  * calling select() and then they'd all wake up at once.  We
1277                  * solve this problem by putting the select() in a critical
1278                  * section, so only one thread has the opportunity to wake
1279                  * up.  If we wake up on a master socket, create a new
1280                  * session context; otherwise, just bind the thread to the
1281                  * context we want and go on our merry way.
1282                  */
1283
1284                 /* make doubly sure we're not holding any stale db handles
1285                  * which might cause a deadlock.
1286                  */
1287                 cdb_release_handles();
1288
1289                 begin_critical_section(S_I_WANNA_SELECT);
1290 SETUP_FD:       memcpy(&readfds, &masterfds, sizeof masterfds);
1291                 highest = masterhighest;
1292                 begin_critical_section(S_SESSION_TABLE);
1293                 for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
1294                         if (ptr->state == CON_IDLE) {
1295                                 FD_SET(ptr->client_socket, &readfds);
1296                                 if (ptr->client_socket > highest)
1297                                         highest = ptr->client_socket;
1298                         }
1299                 }
1300                 end_critical_section(S_SESSION_TABLE);
1301
1302                 tv.tv_sec = 1;          /* wake up every second if no input */
1303                 tv.tv_usec = 0;
1304
1305                 do_select:
1306                 if (!time_to_die)
1307                         retval = select(highest + 1, &readfds, NULL, NULL, &tv);
1308                 else {
1309                         end_critical_section(S_I_WANNA_SELECT);
1310                         break;
1311                 }
1312
1313                 /* Now figure out who made this select() unblock.
1314                  * First, check for an error or exit condition.
1315                  */
1316                 if (retval < 0) {
1317                         if (errno != EINTR) {
1318                                 lprintf(9, "Exiting (%s)\n", strerror(errno));
1319                                 time_to_die = 1;
1320                         } else if (!time_to_die)
1321                                 goto do_select;
1322                 }
1323
1324                 /* Next, check to see if it's a new client connecting
1325                  * on a master socket.
1326                  */
1327                 else for (serviceptr = ServiceHookTable; serviceptr != NULL;
1328                      serviceptr = serviceptr->next ) {
1329
1330                         if (FD_ISSET(serviceptr->msock, &readfds)) {
1331                                 alen = sizeof fsin;
1332                                 ssock = accept(serviceptr->msock,
1333                                         (struct sockaddr *)&fsin, &alen);
1334                                 if (ssock < 0) {
1335                                         lprintf(2, "citserver: accept(): %s\n",
1336                                                 strerror(errno));
1337                                 }
1338                                 else {
1339                                         lprintf(7, "citserver: "
1340                                                 "New client socket %d\n",
1341                                                 ssock);
1342
1343                                         /* New context will be created already
1344                                         * set up in the CON_EXECUTING state.
1345                                         */
1346                                         con = CreateNewContext();
1347
1348                                         /* Assign new socket number to it. */
1349                                         con->client_socket = ssock;
1350                                         con->h_command_function =
1351                                                 serviceptr->h_command_function;
1352
1353                                         /* Determine whether local socket */
1354                                         if (serviceptr->sockpath != NULL)
1355                                                 con->is_local_socket = 1;
1356         
1357                                         /* Set the SO_REUSEADDR socket option */
1358                                         i = 1;
1359                                         setsockopt(ssock, SOL_SOCKET,
1360                                                 SO_REUSEADDR,
1361                                                 &i, sizeof(i));
1362
1363                                         become_session(con);
1364                                         begin_session(con);
1365                                         serviceptr->h_greeting_function();
1366                                         become_session(NULL);
1367                                         con->state = CON_IDLE;
1368                                         goto SETUP_FD;
1369                                 }
1370                         }
1371                 }
1372
1373                 /* If the rescan pipe went active, someone is telling this
1374                  * thread that the &readfds needs to be refreshed with more
1375                  * current data.
1376                  */
1377                 if (time_to_die) {
1378                         end_critical_section(S_I_WANNA_SELECT);
1379                         break;
1380                 }
1381
1382                 if (FD_ISSET(rescan[0], &readfds)) {
1383                         read(rescan[0], &junk, 1);
1384                         goto SETUP_FD;
1385                 }
1386
1387                 /* It must be a client socket.  Find a context that has data
1388                  * waiting on its socket *and* is in the CON_IDLE state.
1389                  */
1390                 else {
1391                         bind_me = NULL;
1392                         begin_critical_section(S_SESSION_TABLE);
1393                         for (ptr = ContextList;
1394                             ( (ptr != NULL) && (bind_me == NULL) );
1395                             ptr = ptr->next) {
1396                                 if ( (FD_ISSET(ptr->client_socket, &readfds))
1397                                    && (ptr->state == CON_IDLE) ) {
1398                                         bind_me = ptr;
1399                                 }
1400                         }
1401                         if (bind_me != NULL) {
1402                                 /* Found one.  Stake a claim to it before
1403                                  * letting anyone else touch the context list.
1404                                  */
1405                                 bind_me->state = CON_EXECUTING;
1406                         }
1407
1408                         end_critical_section(S_SESSION_TABLE);
1409                         end_critical_section(S_I_WANNA_SELECT);
1410
1411                         /* We're bound to a session, now do *one* command */
1412                         if (bind_me != NULL) {
1413                                 become_session(bind_me);
1414                                 CC->h_command_function();
1415                                 become_session(NULL);
1416                                 bind_me->state = CON_IDLE;
1417                                 if (bind_me->kill_me == 1) {
1418                                         RemoveContext(bind_me);
1419                                 } 
1420                                 write(rescan[1], &junk, 1);
1421                         }
1422
1423                 }
1424                 dead_session_purge();
1425                 if ((time(NULL) - last_timer) > 60L) {
1426                         last_timer = time(NULL);
1427                         cdb_release_handles(); /* suggested by Justin Case */
1428                         PerformSessionHooks(EVT_TIMER);
1429                 }
1430
1431                 check_sched_shutdown();
1432         }
1433
1434         /* If control reaches this point, the server is shutting down */        
1435         --num_threads;
1436         return NULL;
1437 }
1438
1439
1440