Mostly made changes to allow client_gets to handle reading a buffer
[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         i++;
666         if (i >= sz)
667           {
668             sz *= 2; /* resize up */
669             b = reallok(b, sz+1);
670           }
671       }
672
673         /* Strip the trailing newline and any trailing nonprintables (cr's) */
674         *(b+i) = '\0';
675         while ((strlen(b)>0)&&(!isprint(*(b+strlen(b)-1))))
676                 *(b+strlen(b)-1) = '\0';
677         if (retval < 0) 
678           strcpy(b, "000");
679
680     CC->readbuf = b; /* faster if we do it once at the end */
681     CC->readbuf_alloc = sz;
682
683
684     *retbuf = b;
685         return(retval);
686 }
687
688 int oldclient_gets(char *buf)
689 {
690         int i, retval;
691
692         /* Read one character at a time.
693          */
694         for (i = 0;;i++) {
695                 retval = client_read(&buf[i], 1);
696                 if (retval != 1 || buf[i] == '\n' || i == (SIZ-1))
697                         break;
698         }
699
700         /* If we got a long line, discard characters until the newline.
701          */
702         if (i == (SIZ-1))
703                 while (buf[i] != '\n' && retval == 1)
704                         retval = client_read(&buf[i], 1);
705
706         /* Strip the trailing newline and any trailing nonprintables (cr's)
707          */
708         buf[i] = 0;
709         while ((strlen(buf)>0)&&(!isprint(buf[strlen(buf)-1])))
710                 buf[strlen(buf)-1] = 0;
711         if (retval < 0) strcpy(buf, "000");
712         return(retval);
713 }
714
715
716
717 /*
718  * The system-dependent part of master_cleanup() - close the master socket.
719  */
720 void sysdep_master_cleanup(void) {
721         struct ServiceFunctionHook *serviceptr;
722
723         /*
724          * close all protocol master sockets
725          */
726         for (serviceptr = ServiceHookTable; serviceptr != NULL;
727             serviceptr = serviceptr->next ) {
728
729                 if (serviceptr->tcp_port > 0)
730                         lprintf(3, "Closing listener on port %d\n",
731                                 serviceptr->tcp_port);
732
733                 if (serviceptr->sockpath != NULL)
734                         lprintf(3, "Closing listener on '%s'\n",
735                                 serviceptr->sockpath);
736
737                 close(serviceptr->msock);
738
739                 /* If it's a Unix domain socket, remove the file. */
740                 if (serviceptr->sockpath != NULL) {
741                         unlink(serviceptr->sockpath);
742                 }
743         }
744 }
745
746
747 /*
748  * Terminate another session.
749  * (This could justifiably be moved out of sysdep.c because it
750  * no longer does anything that is system-dependent.)
751  */
752 void kill_session(int session_to_kill) {
753         struct CitContext *ptr;
754
755         begin_critical_section(S_SESSION_TABLE);
756         for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
757                 if (ptr->cs_pid == session_to_kill) {
758                         ptr->kill_me = 1;
759                 }
760         }
761         end_critical_section(S_SESSION_TABLE);
762 }
763
764
765
766
767 /*
768  * Start running as a daemon.  Only close stdio if do_close_stdio is set.
769  */
770 void start_daemon(int do_close_stdio) {
771         if (do_close_stdio) {
772                 /* close(0); */
773                 close(1);
774                 close(2);
775         }
776         signal(SIGHUP,SIG_IGN);
777         signal(SIGINT,SIG_IGN);
778         signal(SIGQUIT,SIG_IGN);
779         if (fork()!=0) exit(0);
780 }
781
782
783
784 /*
785  * Tie in to the 'netsetup' program.
786  *
787  * (We're going to hope that netsetup never feeds more than 4096 bytes back.)
788  */
789 void cmd_nset(char *cmdbuf)
790 {
791         int retcode;
792         char fbuf[4096];
793         FILE *netsetup;
794         int ch;
795         int a, b;
796         char netsetup_args[3][SIZ];
797
798         if (CC->usersupp.axlevel < 6) {
799                 cprintf("%d Higher access required.\n", 
800                         ERROR + HIGHER_ACCESS_REQUIRED);
801                 return;
802         }
803
804         for (a=1; a<=3; ++a) {
805                 if (num_parms(cmdbuf) >= a) {
806                         extract(netsetup_args[a-1], cmdbuf, a-1);
807                         for (b=0; b<strlen(netsetup_args[a-1]); ++b) {
808                                 if (netsetup_args[a-1][b] == 34) {
809                                         netsetup_args[a-1][b] = '_';
810                                 }
811                         }
812                 }
813                 else {
814                         netsetup_args[a-1][0] = 0;
815                 }
816         }
817
818         sprintf(fbuf, "./netsetup \"%s\" \"%s\" \"%s\" </dev/null 2>&1",
819                 netsetup_args[0], netsetup_args[1], netsetup_args[2]);
820         netsetup = popen(fbuf, "r");
821         if (netsetup == NULL) {
822                 cprintf("%d %s\n", ERROR, strerror(errno));
823                 return;
824         }
825
826         fbuf[0] = 0;
827         while (ch = getc(netsetup), (ch > 0)) {
828                 fbuf[strlen(fbuf)+1] = 0;
829                 fbuf[strlen(fbuf)] = ch;
830         }
831
832         retcode = pclose(netsetup);
833
834         if (retcode != 0) {
835                 for (a=0; a<strlen(fbuf); ++a) {
836                         if (fbuf[a] < 32) fbuf[a] = 32;
837                 }
838                 fbuf[245] = 0;
839                 cprintf("%d %s\n", ERROR, fbuf);
840                 return;
841         }
842
843         cprintf("%d Command succeeded.  Output follows:\n", LISTING_FOLLOWS);
844         cprintf("%s", fbuf);
845         if (fbuf[strlen(fbuf)-1] != 10) cprintf("\n");
846         cprintf("000\n");
847 }
848
849
850
851 /*
852  * Generic routine to convert a login name to a full name (gecos)
853  * Returns nonzero if a conversion took place
854  */
855 int convert_login(char NameToConvert[]) {
856         struct passwd *pw;
857         int a;
858
859         pw = getpwnam(NameToConvert);
860         if (pw == NULL) {
861                 return(0);
862         }
863         else {
864                 strcpy(NameToConvert, pw->pw_gecos);
865                 for (a=0; a<strlen(NameToConvert); ++a) {
866                         if (NameToConvert[a] == ',') NameToConvert[a] = 0;
867                 }
868                 return(1);
869         }
870 }
871
872 static struct worker_node {
873         pthread_t tid;
874         struct worker_node *next;
875 } *worker_list = NULL;
876
877
878 /*
879  * create a worker thread. this function must always be called from within
880  * an S_WORKER_LIST critical section!
881  */
882 static void create_worker(void) {
883         int ret;
884         struct worker_node *n = mallok(sizeof *n);
885
886         if (n == NULL) {
887                 lprintf(1, "can't allocate worker_node, exiting\n");
888                 time_to_die = -1;
889                 return;
890         }
891
892         if ((ret = pthread_create(&n->tid, NULL, worker_thread, NULL) != 0))
893         {
894
895                 lprintf(1, "Can't create worker thread: %s\n",
896                         strerror(ret));
897         }
898
899         n->next = worker_list;
900         worker_list = n;
901 }
902
903
904
905 /*
906  * Purge all sessions which have the 'kill_me' flag set.
907  * This function has code to prevent it from running more than once every
908  * few seconds, because running it after every single unbind would waste a lot
909  * of CPU time and keep the context list locked too much.
910  *
911  * After that's done, we raise or lower the size of the worker thread pool
912  * if such an action is appropriate.
913  */
914 void dead_session_purge(void) {
915         struct CitContext *ptr, *rem;
916         struct worker_node **node, *tmp;
917         pthread_t self;
918
919         if ( (time(NULL) - last_purge) < 5 ) return;    /* Too soon, go away */
920         time(&last_purge);
921
922         do {
923                 rem = NULL;
924                 begin_critical_section(S_SESSION_TABLE);
925                 for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
926                         if ( (ptr->state == CON_IDLE) && (ptr->kill_me) ) {
927                                 rem = ptr;
928                         }
929                 }
930                 end_critical_section(S_SESSION_TABLE);
931
932                 /* RemoveContext() enters its own S_SESSION_TABLE critical
933                  * section, so we have to do it like this.
934                  */     
935                 if (rem != NULL) {
936                         lprintf(9, "Purging session %d\n", rem->cs_pid);
937                         RemoveContext(rem);
938                 }
939
940         } while (rem != NULL);
941
942
943         /* Raise or lower the size of the worker thread pool if such
944          * an action is appropriate.
945          */
946
947         self = pthread_self();
948
949         if ( (num_sessions > num_threads)
950            && (num_threads < config.c_max_workers) ) {
951                 begin_critical_section(S_WORKER_LIST);
952                 create_worker();
953                 end_critical_section(S_WORKER_LIST);
954         }
955         
956         /* don't let the initial thread die since it's responsible for
957            waiting for all the other threads to terminate. */
958         else if ( (num_sessions < num_threads)
959            && (num_threads > config.c_min_workers)
960            && (self != initial_thread) ) {
961                 cdb_free_tsd();
962                 begin_critical_section(S_WORKER_LIST);
963                 --num_threads;
964
965                 /* we're exiting before server shutdown... unlink ourself from
966                    the worker list and detach our thread to avoid memory leaks
967                  */
968
969                 for (node = &worker_list; *node != NULL; node = &(*node)->next)
970                         if ((*node)->tid == self) {
971                                 tmp = *node;
972                                 *node = (*node)->next;
973                                 phree(tmp);
974                                 break;
975                         }
976
977                 pthread_detach(self);
978                 end_critical_section(S_WORKER_LIST);
979                 pthread_exit(NULL);
980         }
981
982 }
983
984
985
986
987
988 /*
989  * Redirect a session's output to a file or socket.
990  * This function may be called with a file handle *or* a socket (but not
991  * both).  Call with neither to return output to its normal client socket.
992  */
993 void CtdlRedirectOutput(FILE *fp, int sock) {
994
995         if (fp != NULL) CC->redirect_fp = fp;
996         else CC->redirect_fp = NULL;
997
998         if (sock > 0) CC->redirect_sock = sock;
999         else CC->redirect_sock = (-1);
1000
1001 }
1002
1003
1004 /*
1005  * masterCC is the context we use when not attached to a session.  This
1006  * function initializes it.
1007  */
1008 void InitializeMasterCC(void) {
1009         memset(&masterCC, 0, sizeof(struct CitContext));
1010         masterCC.internal_pgm = 1;
1011         masterCC.cs_pid = 0;
1012 }
1013
1014
1015
1016 /*
1017  * Set up a fd_set containing all the master sockets to which we
1018  * always listen.  It's computationally less expensive to just copy
1019  * this to a local fd_set when starting a new select() and then add
1020  * the client sockets than it is to initialize a new one and then
1021  * figure out what to put there.
1022  */
1023 void init_master_fdset(void) {
1024         struct ServiceFunctionHook *serviceptr;
1025         int m;
1026
1027         lprintf(9, "Initializing master fdset\n");
1028
1029         FD_ZERO(&masterfds);
1030         masterhighest = 0;
1031
1032         lprintf(9, "Will listen on rescan pipe %d\n", rescan[0]);
1033         FD_SET(rescan[0], &masterfds);
1034         if (rescan[0] > masterhighest) masterhighest = rescan[0];
1035
1036         for (serviceptr = ServiceHookTable; serviceptr != NULL;
1037             serviceptr = serviceptr->next ) {
1038                 m = serviceptr->msock;
1039                 lprintf(9, "Will listen on master socket %d\n", m);
1040                 FD_SET(m, &masterfds);
1041                 if (m > masterhighest) {
1042                         masterhighest = m;
1043                 }
1044         }
1045         lprintf(9, "masterhighest = %d\n", masterhighest);
1046 }
1047
1048
1049
1050 /*
1051  * Here's where it all begins.
1052  */
1053 int main(int argc, char **argv)
1054 {
1055         char tracefile[128];            /* Name of file to log traces to */
1056         int a, i;                       /* General-purpose variables */
1057         struct passwd *pw;
1058         int drop_root_perms = 1;
1059         char *moddir;
1060         struct worker_node *wnp;
1061         
1062         /* specify default port name and trace file */
1063         strcpy(tracefile, "");
1064
1065         /* initialize the master context */
1066         InitializeMasterCC();
1067
1068         /* parse command-line arguments */
1069         for (a=1; a<argc; ++a) {
1070
1071                 /* -t specifies where to log trace messages to */
1072                 if (!strncmp(argv[a], "-t", 2)) {
1073                         strcpy(tracefile, argv[a]);
1074                         strcpy(tracefile, &tracefile[2]);
1075                         freopen(tracefile, "r", stdin);
1076                         freopen(tracefile, "w", stdout);
1077                         freopen(tracefile, "w", stderr);
1078                 }
1079
1080                 /* run in the background if -d was specified */
1081                 else if (!strcmp(argv[a], "-d")) {
1082                         start_daemon( (strlen(tracefile) > 0) ? 0 : 1 ) ;
1083                 }
1084
1085                 /* -x specifies the desired logging level */
1086                 else if (!strncmp(argv[a], "-x", 2)) {
1087                         verbosity = atoi(&argv[a][2]);
1088                 }
1089
1090                 else if (!strncmp(argv[a], "-h", 2)) {
1091                         safestrncpy(bbs_home_directory, &argv[a][2],
1092                                     sizeof bbs_home_directory);
1093                         home_specified = 1;
1094                 }
1095
1096                 else if (!strncmp(argv[a], "-f", 2)) {
1097                         do_defrag = 1;
1098                 }
1099
1100                 /* -r tells the server not to drop root permissions. don't use
1101                  * this unless you know what you're doing. this should be
1102                  * removed in the next release if it proves unnecessary. */
1103                 else if (!strcmp(argv[a], "-r"))
1104                         drop_root_perms = 0;
1105
1106                 /* any other parameter makes it crash and burn */
1107                 else {
1108                         lprintf(1,      "citserver: usage: "
1109                                         "citserver [-tTraceFile] [-d] [-f]"
1110                                         " [-xLogLevel] [-hHomeDir]\n");
1111                         exit(1);
1112                 }
1113
1114         }
1115
1116         /* Tell 'em who's in da house */
1117         lprintf(1,
1118 "\nMultithreaded message server for Citadel/UX\n"
1119 "Copyright (C) 1987-2000 by the Citadel/UX development team.\n"
1120 "Citadel/UX is free software, covered by the GNU General Public License, and\n"
1121 "you are welcome to change it and/or distribute copies of it under certain\n"
1122 "conditions.  There is absolutely no warranty for this software.  Please\n"
1123 "read the 'COPYING.txt' file for details.\n\n");
1124
1125         /* Initialize... */
1126         init_sysdep();
1127         openlog("citserver", LOG_PID, LOG_USER);
1128
1129         /* Load site-specific parameters */
1130         lprintf(7, "Loading citadel.config\n");
1131         get_config();
1132
1133
1134         /*
1135          * Do non system dependent startup functions.
1136          */
1137         master_startup();
1138
1139         /*
1140          * Bind the server to a Unix-domain socket.
1141          */
1142         CtdlRegisterServiceHook(0,
1143                                 "citadel.socket",
1144                                 citproto_begin_session,
1145                                 do_command_loop);
1146
1147         /*
1148          * Bind the server to our favorite TCP port (usually 504).
1149          */
1150         CtdlRegisterServiceHook(config.c_port_number,
1151                                 NULL,
1152                                 citproto_begin_session,
1153                                 do_command_loop);
1154
1155         /*
1156          * Load any server-side modules (plugins) available here.
1157          */
1158         lprintf(7, "Initializing loadable modules\n");
1159         if ((moddir = malloc(strlen(bbs_home_directory) + 9)) != NULL) {
1160                 sprintf(moddir, "%s/modules", bbs_home_directory);
1161                 DLoader_Init(moddir);
1162                 free(moddir);
1163         }
1164
1165         /*
1166          * The rescan pipe exists so that worker threads can be woken up and
1167          * told to re-scan the context list for fd's to listen on.  This is
1168          * necessary, for example, when a context is about to go idle and needs
1169          * to get back on that list.
1170          */
1171         if (pipe(rescan)) {
1172                 lprintf(1, "Can't create rescan pipe!\n");
1173                 exit(errno);
1174         }
1175
1176         init_master_fdset();
1177
1178         /*
1179          * Now that we've bound the sockets, change to the BBS user id and its
1180          * corresponding group ids
1181          */
1182         if (drop_root_perms) {
1183                 if ((pw = getpwuid(BBSUID)) == NULL)
1184                         lprintf(1, "WARNING: getpwuid(%d): %s\n"
1185                                    "Group IDs will be incorrect.\n", BBSUID,
1186                                 strerror(errno));
1187                 else {
1188                         initgroups(pw->pw_name, pw->pw_gid);
1189                         if (setgid(pw->pw_gid))
1190                                 lprintf(3, "setgid(%d): %s\n", pw->pw_gid,
1191                                         strerror(errno));
1192                 }
1193                 lprintf(7, "Changing uid to %d\n", BBSUID);
1194                 if (setuid(BBSUID) != 0) {
1195                         lprintf(3, "setuid() failed: %s\n", strerror(errno));
1196                 }
1197         }
1198
1199         /* We want to check for idle sessions once per minute */
1200         CtdlRegisterSessionHook(terminate_idle_sessions, EVT_TIMER);
1201
1202         /*
1203          * Now create a bunch of worker threads.
1204          */
1205         lprintf(9, "Starting %d worker threads\n", config.c_min_workers-1);
1206         begin_critical_section(S_WORKER_LIST);
1207         for (i=0; i<(config.c_min_workers-1); ++i) {
1208                 create_worker();
1209         }
1210         end_critical_section(S_WORKER_LIST);
1211
1212         /* Now this thread can become a worker as well. */
1213         initial_thread = pthread_self();
1214         worker_thread(NULL);
1215
1216         /* Server is exiting. Wait for workers to shutdown. */
1217         lprintf(7, "Waiting for worker threads to shut down\n");
1218
1219         begin_critical_section(S_WORKER_LIST);
1220         while (worker_list != NULL) {
1221                 wnp = worker_list;
1222                 worker_list = wnp->next;
1223
1224                 /* avoid deadlock with an exiting thread */
1225                 end_critical_section(S_WORKER_LIST);
1226                 if ((i = pthread_join(wnp->tid, NULL)))
1227                         lprintf(1, "pthread_join: %s\n", strerror(i));
1228                 phree(wnp);
1229                 begin_critical_section(S_WORKER_LIST);
1230         }
1231         end_critical_section(S_WORKER_LIST);
1232
1233         master_cleanup();
1234
1235         return(0);
1236 }
1237
1238
1239 /*
1240  * Bind a thread to a context.  (It's inline merely to speed things up.)
1241  */
1242 inline void become_session(struct CitContext *which_con) {
1243         pthread_setspecific(MyConKey, (void *)which_con );
1244 }
1245
1246
1247
1248 /* 
1249  * This loop just keeps going and going and going...
1250  */     
1251 void *worker_thread(void *arg) {
1252         int i;
1253         char junk;
1254         int highest;
1255         struct CitContext *ptr;
1256         struct CitContext *bind_me = NULL;
1257         fd_set readfds;
1258         int retval;
1259         struct CitContext *con= NULL;   /* Temporary context pointer */
1260         struct ServiceFunctionHook *serviceptr;
1261         struct sockaddr_in fsin;        /* Data for master socket */
1262         int alen;                       /* Data for master socket */
1263         int ssock;                      /* Descriptor for client socket */
1264         struct timeval tv;
1265
1266         num_threads++;
1267
1268         cdb_allocate_tsd();
1269
1270         while (!time_to_die) {
1271
1272                 /* 
1273                  * A naive implementation would have all idle threads
1274                  * calling select() and then they'd all wake up at once.  We
1275                  * solve this problem by putting the select() in a critical
1276                  * section, so only one thread has the opportunity to wake
1277                  * up.  If we wake up on a master socket, create a new
1278                  * session context; otherwise, just bind the thread to the
1279                  * context we want and go on our merry way.
1280                  */
1281
1282                 /* make doubly sure we're not holding any stale db handles
1283                  * which might cause a deadlock.
1284                  */
1285                 cdb_release_handles();
1286
1287                 begin_critical_section(S_I_WANNA_SELECT);
1288 SETUP_FD:       memcpy(&readfds, &masterfds, sizeof masterfds);
1289                 highest = masterhighest;
1290                 begin_critical_section(S_SESSION_TABLE);
1291                 for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
1292                         if (ptr->state == CON_IDLE) {
1293                                 FD_SET(ptr->client_socket, &readfds);
1294                                 if (ptr->client_socket > highest)
1295                                         highest = ptr->client_socket;
1296                         }
1297                 }
1298                 end_critical_section(S_SESSION_TABLE);
1299
1300                 tv.tv_sec = 1;          /* wake up every second if no input */
1301                 tv.tv_usec = 0;
1302
1303                 do_select:
1304                 if (!time_to_die)
1305                         retval = select(highest + 1, &readfds, NULL, NULL, &tv);
1306                 else {
1307                         end_critical_section(S_I_WANNA_SELECT);
1308                         break;
1309                 }
1310
1311                 /* Now figure out who made this select() unblock.
1312                  * First, check for an error or exit condition.
1313                  */
1314                 if (retval < 0) {
1315                         if (errno != EINTR) {
1316                                 lprintf(9, "Exiting (%s)\n", strerror(errno));
1317                                 time_to_die = 1;
1318                         } else if (!time_to_die)
1319                                 goto do_select;
1320                 }
1321
1322                 /* Next, check to see if it's a new client connecting
1323                  * on a master socket.
1324                  */
1325                 else for (serviceptr = ServiceHookTable; serviceptr != NULL;
1326                      serviceptr = serviceptr->next ) {
1327
1328                         if (FD_ISSET(serviceptr->msock, &readfds)) {
1329                                 alen = sizeof fsin;
1330                                 ssock = accept(serviceptr->msock,
1331                                         (struct sockaddr *)&fsin, &alen);
1332                                 if (ssock < 0) {
1333                                         lprintf(2, "citserver: accept(): %s\n",
1334                                                 strerror(errno));
1335                                 }
1336                                 else {
1337                                         lprintf(7, "citserver: "
1338                                                 "New client socket %d\n",
1339                                                 ssock);
1340
1341                                         /* New context will be created already
1342                                         * set up in the CON_EXECUTING state.
1343                                         */
1344                                         con = CreateNewContext();
1345
1346                                         /* Assign new socket number to it. */
1347                                         con->client_socket = ssock;
1348                                         con->h_command_function =
1349                                                 serviceptr->h_command_function;
1350
1351                                         /* Determine whether local socket */
1352                                         if (serviceptr->sockpath != NULL)
1353                                                 con->is_local_socket = 1;
1354         
1355                                         /* Set the SO_REUSEADDR socket option */
1356                                         i = 1;
1357                                         setsockopt(ssock, SOL_SOCKET,
1358                                                 SO_REUSEADDR,
1359                                                 &i, sizeof(i));
1360
1361                                         become_session(con);
1362                                         begin_session(con);
1363                                         serviceptr->h_greeting_function();
1364                                         become_session(NULL);
1365                                         con->state = CON_IDLE;
1366                                         goto SETUP_FD;
1367                                 }
1368                         }
1369                 }
1370
1371                 /* If the rescan pipe went active, someone is telling this
1372                  * thread that the &readfds needs to be refreshed with more
1373                  * current data.
1374                  */
1375                 if (time_to_die) {
1376                         end_critical_section(S_I_WANNA_SELECT);
1377                         break;
1378                 }
1379
1380                 if (FD_ISSET(rescan[0], &readfds)) {
1381                         read(rescan[0], &junk, 1);
1382                         goto SETUP_FD;
1383                 }
1384
1385                 /* It must be a client socket.  Find a context that has data
1386                  * waiting on its socket *and* is in the CON_IDLE state.
1387                  */
1388                 else {
1389                         bind_me = NULL;
1390                         begin_critical_section(S_SESSION_TABLE);
1391                         for (ptr = ContextList;
1392                             ( (ptr != NULL) && (bind_me == NULL) );
1393                             ptr = ptr->next) {
1394                                 if ( (FD_ISSET(ptr->client_socket, &readfds))
1395                                    && (ptr->state == CON_IDLE) ) {
1396                                         bind_me = ptr;
1397                                 }
1398                         }
1399                         if (bind_me != NULL) {
1400                                 /* Found one.  Stake a claim to it before
1401                                  * letting anyone else touch the context list.
1402                                  */
1403                                 bind_me->state = CON_EXECUTING;
1404                         }
1405
1406                         end_critical_section(S_SESSION_TABLE);
1407                         end_critical_section(S_I_WANNA_SELECT);
1408
1409                         /* We're bound to a session, now do *one* command */
1410                         if (bind_me != NULL) {
1411                                 become_session(bind_me);
1412                                 CC->h_command_function();
1413                                 become_session(NULL);
1414                                 bind_me->state = CON_IDLE;
1415                                 if (bind_me->kill_me == 1) {
1416                                         RemoveContext(bind_me);
1417                                 } 
1418                                 write(rescan[1], &junk, 1);
1419                         }
1420
1421                 }
1422                 dead_session_purge();
1423                 if ((time(NULL) - last_timer) > 60L) {
1424                         last_timer = time(NULL);
1425                         cdb_release_handles(); /* suggested by Justin Case */
1426                         PerformSessionHooks(EVT_TIMER);
1427                 }
1428
1429                 check_sched_shutdown();
1430         }
1431
1432         /* If control reaches this point, the server is shutting down */        
1433         --num_threads;
1434         return NULL;
1435 }
1436
1437
1438