6bde9f3961d93b173dd2d96490dd4630a8f9f4a8
[citadel.git] / citadel / sysdep.c
1 /*
2  * Citadel/UX "system dependent" stuff.
3  * See copyright.txt for copyright information.
4  *
5  * $Id$
6  *
7  * Here's where we (hopefully) have all the parts of the Citadel server that
8  * would need to be altered to run the server in a non-POSIX environment.
9  * 
10  * Eventually we'll try porting to a different platform and either have
11  * multiple variants of this file or simply load it up with #ifdefs.
12  */
13
14
15 #include "sysdep.h"
16 #include <stdlib.h>
17 #include <unistd.h>
18 #include <stdio.h>
19 #include <fcntl.h>
20 #include <ctype.h>
21 #include <signal.h>
22 #include <sys/types.h>
23 #include <sys/wait.h>
24 #include <sys/socket.h>
25 #include <sys/time.h>
26 #include <limits.h>
27 #include <netinet/in.h>
28 #include <netdb.h>
29 #include <string.h>
30 #include <pwd.h>
31 #include <errno.h>
32 #include <stdarg.h>
33 #include <syslog.h>
34 #include <grp.h>
35 #ifdef __GNUC__
36 #include <malloc.h>
37 #endif
38 #ifdef HAVE_PTHREAD_H
39 #include <pthread.h>
40 #endif
41 #include "citadel.h"
42 #include "server.h"
43 #include "sysdep_decls.h"
44 #include "citserver.h"
45 #include "support.h"
46 #include "config.h"
47 #include "database.h"
48 #include "housekeeping.h"
49 #include "dynloader.h"
50 #include "tools.h"
51
52 #ifdef HAVE_SYS_SELECT_H
53 #include <sys/select.h>
54 #endif
55
56 #ifndef HAVE_SNPRINTF
57 #include "snprintf.h"
58 #endif
59
60 #ifdef DEBUG_MEMORY_LEAKS
61 struct TheHeap *heap = NULL;
62 #endif
63
64 pthread_mutex_t Critters[MAX_SEMAPHORES];       /* Things needing locking */
65 pthread_key_t MyConKey;                         /* TSD key for MyContext() */
66
67 int verbosity = DEFAULT_VERBOSITY;              /* Logging level */
68
69 struct CitContext masterCC;
70 int rescan[2];                                  /* The Rescan Pipe */
71 time_t last_purge = 0;                          /* Last dead session purge */
72 int num_threads = 0;                            /* Current number of threads */
73 int num_sessions = 0;                           /* Current number of sessions */
74
75 fd_set masterfds;                               /* Master sockets etc. */
76 int masterhighest;
77
78
79 /*
80  * lprintf()  ...   Write logging information
81  */
82 void lprintf(int loglevel, const char *format, ...) {   
83         va_list arg_ptr;
84         char buf[512];
85   
86         va_start(arg_ptr, format);   
87         vsprintf(buf, format, arg_ptr);   
88         va_end(arg_ptr);   
89
90         if (loglevel <= verbosity) { 
91                 fprintf(stderr, "%s", buf);
92                 fflush(stderr);
93                 }
94
95         PerformLogHooks(loglevel, buf);
96         }   
97
98
99
100 #ifdef DEBUG_MEMORY_LEAKS
101 void *tracked_malloc(size_t tsize, char *tfile, int tline) {
102         void *ptr;
103         struct TheHeap *hptr;
104
105         ptr = malloc(tsize);
106         if (ptr == NULL) {
107                 lprintf(3, "DANGER!  mallok(%d) at %s:%d failed!\n",
108                         tsize, tfile, tline);
109                 return(NULL);
110         }
111
112         hptr = (struct TheHeap *) malloc(sizeof(struct TheHeap));
113         strcpy(hptr->h_file, tfile);
114         hptr->h_line = tline;
115         hptr->next = heap;
116         hptr->h_ptr = ptr;
117         heap = hptr;
118         return ptr;
119         }
120
121 char *tracked_strdup(const char *orig, char *tfile, int tline) {
122         char *s;
123
124         s = tracked_malloc( (strlen(orig)+1), tfile, tline);
125         if (s == NULL) return NULL;
126
127         strcpy(s, orig);
128         return s;
129 }
130
131 void tracked_free(void *ptr) {
132         struct TheHeap *hptr, *freeme;
133
134         if (heap->h_ptr == ptr) {
135                 hptr = heap->next;
136                 free(heap);
137                 heap = hptr;
138                 }
139         else {
140                 for (hptr=heap; hptr->next!=NULL; hptr=hptr->next) {
141                         if (hptr->next->h_ptr == ptr) {
142                                 freeme = hptr->next;
143                                 hptr->next = hptr->next->next;
144                                 free(freeme);
145                                 }
146                         }
147                 }
148
149         free(ptr);
150         }
151
152 void *tracked_realloc(void *ptr, size_t size) {
153         void *newptr;
154         struct TheHeap *hptr;
155         
156         newptr = realloc(ptr, size);
157
158         for (hptr=heap; hptr!=NULL; hptr=hptr->next) {
159                 if (hptr->h_ptr == ptr) hptr->h_ptr = newptr;
160                 }
161
162         return newptr;
163         }
164
165
166 void dump_tracked() {
167         struct TheHeap *hptr;
168
169         cprintf("%d Here's what's allocated...\n", LISTING_FOLLOWS);    
170         for (hptr=heap; hptr!=NULL; hptr=hptr->next) {
171                 cprintf("%20s %5d\n",
172                         hptr->h_file, hptr->h_line);
173                 }
174 #ifdef __GNUC__
175         malloc_stats();
176 #endif
177
178         cprintf("000\n");
179         }
180 #endif
181
182
183 /*
184  * we used to use master_cleanup() as a signal handler to shut down the server.
185  * however, master_cleanup() and the functions it calls do some things that
186  * aren't such a good idea to do from a signal handler: acquiring mutexes,
187  * playing with signal masks on BSDI systems, etc. so instead we install the
188  * following signal handler to set a global variable to inform the main loop
189  * that it's time to call master_cleanup() and exit.
190  */
191
192 static volatile int time_to_die = 0;
193
194 static RETSIGTYPE signal_cleanup(int signum) {
195         time_to_die = 1;
196 }
197
198
199 /*
200  * Some initialization stuff...
201  */
202 void init_sysdep(void) {
203         int a;
204
205         /* Set up a bunch of semaphores to be used for critical sections */
206         for (a=0; a<MAX_SEMAPHORES; ++a) {
207                 pthread_mutex_init(&Critters[a], NULL);
208         }
209
210         /*
211          * Set up a place to put thread-specific data.
212          * We only need a single pointer per thread - it points to the
213          * thread's CitContext structure in the ContextList linked list.
214          */
215         if (pthread_key_create(&MyConKey, NULL) != 0) {
216                 lprintf(1, "Can't create TSD key!!  %s\n", strerror(errno));
217         }
218
219         /*
220          * The action for unexpected signals and exceptions should be to
221          * call signal_cleanup() to gracefully shut down the server.
222          */
223         signal(SIGINT, signal_cleanup);
224         signal(SIGQUIT, signal_cleanup);
225         signal(SIGHUP, signal_cleanup);
226         signal(SIGTERM, signal_cleanup);
227
228         /*
229          * Do not shut down the server on broken pipe signals, otherwise the
230          * whole Citadel service would come down whenever a single client
231          * socket breaks.
232          */
233         signal(SIGPIPE, SIG_IGN);
234 }
235
236
237 /*
238  * Obtain a semaphore lock to begin a critical section.
239  */
240 void begin_critical_section(int which_one)
241 {
242         pthread_mutex_lock(&Critters[which_one]);
243 }
244
245 /*
246  * Release a semaphore lock to end a critical section.
247  */
248 void end_critical_section(int which_one)
249 {
250         pthread_mutex_unlock(&Critters[which_one]);
251 }
252
253
254
255 /*
256  * This is a generic function to set up a master socket for listening on
257  * a TCP port.  The server shuts down if the bind fails.
258  */
259 int ig_tcp_server(int port_number, int queue_len)
260 {
261         struct sockaddr_in sin;
262         int s, i;
263
264         memset(&sin, 0, sizeof(sin));
265         sin.sin_family = AF_INET;
266         sin.sin_addr.s_addr = INADDR_ANY;
267
268         if (port_number == 0) {
269                 lprintf(1, "citserver: illegal port number specified\n");
270                 return(-1);
271         }
272         
273         sin.sin_port = htons((u_short)port_number);
274
275         s = socket(PF_INET, SOCK_STREAM, (getprotobyname("tcp")->p_proto));
276         if (s < 0) {
277                 lprintf(1, "citserver: Can't create a socket: %s\n",
278                         strerror(errno));
279                 return(-1);
280         }
281
282         /* Set the SO_REUSEADDR socket option, because it makes sense. */
283         i = 1;
284         setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
285
286         if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
287                 lprintf(1, "citserver: Can't bind: %s\n", strerror(errno));
288                 sin.sin_port = 0;
289                 if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
290                         lprintf(1, "citserver: Can't bind: %s\n",
291                                 strerror(errno));
292                         return(-1);
293                 }
294                 else {
295                         lprintf(1, "bind to alternate port %d ok\n",
296                                 htons(sin.sin_port) );
297                 }
298         }
299
300         if (listen(s, queue_len) < 0) {
301                 lprintf(1, "citserver: Can't listen: %s\n", strerror(errno));
302                 return(-1);
303         }
304
305         return(s);
306 }
307
308
309
310 /*
311  * Return a pointer to the CitContext structure bound to the thread which
312  * called this function.  If there's no such binding (for example, if it's
313  * called by the housekeeper thread) then a generic 'master' CC is returned.
314  */
315 struct CitContext *MyContext(void) {
316         struct CitContext *retCC;
317         retCC = (struct CitContext *) pthread_getspecific(MyConKey);
318         if (retCC == NULL) retCC = &masterCC;
319         return(retCC);
320 }
321
322
323 /*
324  * Initialize a new context and place it in the list.
325  */
326 struct CitContext *CreateNewContext(void) {
327         struct CitContext *me, *ptr;
328         int num = 1;
329         int startover = 0;
330
331         me = (struct CitContext *) mallok(sizeof(struct CitContext));
332         if (me == NULL) {
333                 lprintf(1, "citserver: can't allocate memory!!\n");
334                 return NULL;
335         }
336         memset(me, 0, sizeof(struct CitContext));
337
338         /* The new context will be created already in the CON_EXECUTING state
339          * in order to prevent another thread from grabbing it while it's
340          * being set up.
341          */
342         me->state = CON_EXECUTING;
343
344         begin_critical_section(S_SESSION_TABLE);
345
346         /* obtain a unique session number */
347         do {
348                 startover = 0;
349                 for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
350                         if (ptr->cs_pid == num) {
351                                 ++num;
352                                 startover = 1;
353                         }
354                 }
355         } while (startover == 1);
356
357         me->cs_pid = num;
358         me->next = ContextList;
359         ContextList = me;
360         ++num_sessions;
361
362         end_critical_section(S_SESSION_TABLE);
363         return(me);
364 }
365
366
367
368 /*
369  * client_write()   ...    Send binary data to the client.
370  */
371 void client_write(char *buf, int nbytes)
372 {
373         int bytes_written = 0;
374         int retval;
375         while (bytes_written < nbytes) {
376                 retval = write(CC->client_socket, &buf[bytes_written],
377                         nbytes - bytes_written);
378                 if (retval < 1) {
379                         lprintf(2, "client_write() failed: %s\n",
380                                 strerror(errno));
381                         CC->kill_me = 1;
382                         return;
383                 }
384                 bytes_written = bytes_written + retval;
385         }
386 }
387
388
389 /*
390  * cprintf()  ...   Send formatted printable data to the client.   It is
391  *                  implemented in terms of client_write() but remains in
392  *                  sysdep.c in case we port to somewhere without va_args...
393  */
394 void cprintf(const char *format, ...) {   
395         va_list arg_ptr;   
396         char buf[256];   
397    
398         va_start(arg_ptr, format);   
399         if (vsnprintf(buf, sizeof buf, format, arg_ptr) == -1)
400                 buf[sizeof buf - 2] = '\n';
401         client_write(buf, strlen(buf)); 
402         va_end(arg_ptr);
403 }   
404
405
406 /*
407  * Read data from the client socket.
408  * Return values are:
409  *      1       Requested number of bytes has been read.
410  *      0       Request timed out.
411  *      -1      The socket is broken.
412  * If the socket breaks, the session will be terminated.
413  */
414 int client_read_to(char *buf, int bytes, int timeout)
415 {
416         int len,rlen;
417         fd_set rfds;
418         struct timeval tv;
419         int retval;
420
421         len = 0;
422         while(len<bytes) {
423                 FD_ZERO(&rfds);
424                 FD_SET(CC->client_socket, &rfds);
425                 tv.tv_sec = timeout;
426                 tv.tv_usec = 0;
427
428                 retval = select( (CC->client_socket)+1, 
429                                         &rfds, NULL, NULL, &tv);
430
431                 if (FD_ISSET(CC->client_socket, &rfds) == 0) {
432                         return(0);
433                 }
434
435                 rlen = read(CC->client_socket, &buf[len], bytes-len);
436                 if (rlen<1) {
437                         lprintf(2, "client_read() failed: %s\n",
438                                 strerror(errno));
439                         CC->kill_me = 1;
440                         return(-1);
441                 }
442                 len = len + rlen;
443         }
444         return(1);
445 }
446
447 /*
448  * Read data from the client socket with default timeout.
449  * (This is implemented in terms of client_read_to() and could be
450  * justifiably moved out of sysdep.c)
451  */
452 int client_read(char *buf, int bytes)
453 {
454         return(client_read_to(buf, bytes, config.c_sleeping));
455 }
456
457
458 /*
459  * client_gets()   ...   Get a LF-terminated line of text from the client.
460  * (This is implemented in terms of client_read() and could be
461  * justifiably moved out of sysdep.c)
462  */
463 int client_gets(char *buf)
464 {
465         int i, retval;
466
467         /* Read one character at a time.
468          */
469         for (i = 0;;i++) {
470                 retval = client_read(&buf[i], 1);
471                 if (retval != 1 || buf[i] == '\n' || i == 255)
472                         break;
473         }
474
475         /* If we got a long line, discard characters until the newline.
476          */
477         if (i == 255)
478                 while (buf[i] != '\n' && retval == 1)
479                         retval = client_read(&buf[i], 1);
480
481         /* Strip the trailing newline and any trailing nonprintables (cr's)
482          */
483         buf[i] = 0;
484         while ((strlen(buf)>0)&&(!isprint(buf[strlen(buf)-1])))
485                 buf[strlen(buf)-1] = 0;
486         return(retval);
487 }
488
489
490
491 /*
492  * The system-dependent part of master_cleanup() - close the master socket.
493  */
494 void sysdep_master_cleanup(void) {
495         /* FIX close all protocol master sockets here */
496 }
497
498
499 /*
500  * Terminate another session.
501  * (This could justifiably be moved out of sysdep.c because it
502  * no longer does anything that is system-dependent.)
503  */
504 void kill_session(int session_to_kill) {
505         struct CitContext *ptr;
506
507         begin_critical_section(S_SESSION_TABLE);
508         for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
509                 if (ptr->cs_pid == session_to_kill) {
510                         ptr->kill_me = 1;
511                 }
512         }
513         end_critical_section(S_SESSION_TABLE);
514 }
515
516
517
518
519 /*
520  * Start running as a daemon.  Only close stdio if do_close_stdio is set.
521  */
522 void start_daemon(int do_close_stdio) {
523         if (do_close_stdio) {
524                 /* close(0); */
525                 close(1);
526                 close(2);
527         }
528         signal(SIGHUP,SIG_IGN);
529         signal(SIGINT,SIG_IGN);
530         signal(SIGQUIT,SIG_IGN);
531         if (fork()!=0) exit(0);
532 }
533
534
535
536 /*
537  * Tie in to the 'netsetup' program.
538  *
539  * (We're going to hope that netsetup never feeds more than 4096 bytes back.)
540  */
541 void cmd_nset(char *cmdbuf)
542 {
543         int retcode;
544         char fbuf[4096];
545         FILE *netsetup;
546         int ch;
547         int a, b;
548         char netsetup_args[3][256];
549
550         if (CC->usersupp.axlevel < 6) {
551                 cprintf("%d Higher access required.\n", 
552                         ERROR + HIGHER_ACCESS_REQUIRED);
553                 return;
554         }
555
556         for (a=1; a<=3; ++a) {
557                 if (num_parms(cmdbuf) >= a) {
558                         extract(netsetup_args[a-1], cmdbuf, a-1);
559                         for (b=0; b<strlen(netsetup_args[a-1]); ++b) {
560                                 if (netsetup_args[a-1][b] == 34) {
561                                         netsetup_args[a-1][b] = '_';
562                                 }
563                         }
564                 }
565                 else {
566                         netsetup_args[a-1][0] = 0;
567                 }
568         }
569
570         sprintf(fbuf, "./netsetup \"%s\" \"%s\" \"%s\" </dev/null 2>&1",
571                 netsetup_args[0], netsetup_args[1], netsetup_args[2]);
572         netsetup = popen(fbuf, "r");
573         if (netsetup == NULL) {
574                 cprintf("%d %s\n", ERROR, strerror(errno));
575                 return;
576         }
577
578         fbuf[0] = 0;
579         while (ch = getc(netsetup), (ch > 0)) {
580                 fbuf[strlen(fbuf)+1] = 0;
581                 fbuf[strlen(fbuf)] = ch;
582         }
583
584         retcode = pclose(netsetup);
585
586         if (retcode != 0) {
587                 for (a=0; a<strlen(fbuf); ++a) {
588                         if (fbuf[a] < 32) fbuf[a] = 32;
589                 }
590                 fbuf[245] = 0;
591                 cprintf("%d %s\n", ERROR, fbuf);
592                 return;
593         }
594
595         cprintf("%d Command succeeded.  Output follows:\n", LISTING_FOLLOWS);
596         cprintf("%s", fbuf);
597         if (fbuf[strlen(fbuf)-1] != 10) cprintf("\n");
598         cprintf("000\n");
599 }
600
601
602
603 /*
604  * Generic routine to convert a login name to a full name (gecos)
605  * Returns nonzero if a conversion took place
606  */
607 int convert_login(char NameToConvert[]) {
608         struct passwd *pw;
609         int a;
610
611         pw = getpwnam(NameToConvert);
612         if (pw == NULL) {
613                 return(0);
614         }
615         else {
616                 strcpy(NameToConvert, pw->pw_gecos);
617                 for (a=0; a<strlen(NameToConvert); ++a) {
618                         if (NameToConvert[a] == ',') NameToConvert[a] = 0;
619                 }
620                 return(1);
621         }
622 }
623
624
625
626 /*
627  * Purge all sessions which have the 'kill_me' flag set.
628  * This function has code to prevent it from running more than once every
629  * few seconds, because running it after every single unbind would waste a lot
630  * of CPU time and keep the context list locked too much.
631  *
632  * After that's done, we raise or lower the size of the worker thread pool
633  * if such an action is appropriate.
634  */
635 void dead_session_purge(void) {
636         struct CitContext *ptr, *rem;
637         pthread_attr_t attr;
638         pthread_t newthread;
639
640         if ( (time(NULL) - last_purge) < 5 ) return;    /* Too soon, go away */
641         time(&last_purge);
642
643         do {
644                 rem = NULL;
645                 begin_critical_section(S_SESSION_TABLE);
646                 for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
647                         if ( (ptr->state == CON_IDLE) && (ptr->kill_me) ) {
648                                 rem = ptr;
649                         }
650                 }
651                 end_critical_section(S_SESSION_TABLE);
652
653                 /* RemoveContext() enters its own S_SESSION_TABLE critical
654                  * section, so we have to do it like this.
655                  */     
656                 if (rem != NULL) {
657                         lprintf(9, "Purging session %d\n", rem->cs_pid);
658                         RemoveContext(rem);
659                 }
660
661         } while (rem != NULL);
662
663
664         /* Raise or lower the size of the worker thread pool if such
665          * an action is appropriate.
666          */
667
668         if ( (num_sessions > num_threads)
669            && (num_threads < config.c_max_workers) ) {
670
671                 pthread_attr_init(&attr);
672                 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
673                 if (pthread_create(&newthread, &attr,
674                    (void* (*)(void*)) worker_thread, NULL) != 0) {
675                         lprintf(1, "Can't create worker thead: %s\n",
676                         strerror(errno));
677                 }
678
679         }
680         
681         else if ( (num_sessions < num_threads)
682            && (num_threads > config.c_min_workers) ) {
683                 --num_threads;
684                 pthread_exit(NULL);
685         }
686
687 }
688
689
690         
691
692 /*
693  * Here's where it all begins.
694  */
695 int main(int argc, char **argv)
696 {
697         pthread_t HousekeepingThread;   /* Thread descriptor */
698         pthread_attr_t attr;            /* Thread attributes */
699         char tracefile[128];            /* Name of file to log traces to */
700         int a, i;                       /* General-purpose variables */
701         struct passwd *pw;
702         int drop_root_perms = 1;
703         char *moddir;
704         struct ServiceFunctionHook *serviceptr;
705         
706         /* specify default port name and trace file */
707         strcpy(tracefile, "");
708
709         /* parse command-line arguments */
710         for (a=1; a<argc; ++a) {
711
712                 /* -t specifies where to log trace messages to */
713                 if (!strncmp(argv[a], "-t", 2)) {
714                         strcpy(tracefile, argv[a]);
715                         strcpy(tracefile, &tracefile[2]);
716                         freopen(tracefile, "r", stdin);
717                         freopen(tracefile, "w", stdout);
718                         freopen(tracefile, "w", stderr);
719                 }
720
721                 /* run in the background if -d was specified */
722                 else if (!strcmp(argv[a], "-d")) {
723                         start_daemon( (strlen(tracefile) > 0) ? 0 : 1 ) ;
724                 }
725
726                 /* -x specifies the desired logging level */
727                 else if (!strncmp(argv[a], "-x", 2)) {
728                         verbosity = atoi(&argv[a][2]);
729                 }
730
731                 else if (!strncmp(argv[a], "-h", 2)) {
732                         safestrncpy(bbs_home_directory, &argv[a][2],
733                                     sizeof bbs_home_directory);
734                         home_specified = 1;
735                 }
736
737                 else if (!strncmp(argv[a], "-f", 2)) {
738                         do_defrag = 1;
739                 }
740
741                 /* -r tells the server not to drop root permissions. don't use
742                  * this unless you know what you're doing. this should be
743                  * removed in the next release if it proves unnecessary. */
744                 else if (!strcmp(argv[a], "-r"))
745                         drop_root_perms = 0;
746
747                 /* any other parameter makes it crash and burn */
748                 else {
749                         lprintf(1,      "citserver: usage: "
750                                         "citserver [-tTraceFile] [-d] [-f]"
751                                         " [-xLogLevel] [-hHomeDir]\n");
752                         exit(1);
753                 }
754
755         }
756
757         /* Tell 'em who's in da house */
758         lprintf(1,
759 "\nMultithreaded message server for Citadel/UX\n"
760 "Copyright (C) 1987-1999 by the Citadel/UX development team.\n"
761 "Citadel/UX is free software, covered by the GNU General Public License, and\n"
762 "you are welcome to change it and/or distribute copies of it under certain\n"
763 "conditions.  There is absolutely no warranty for this software.  Please\n"
764 "read the 'COPYING.txt' file for details.\n\n");
765
766         /* Initialize... */
767         init_sysdep();
768         openlog("citserver",LOG_PID,LOG_USER);
769
770         /* Load site-specific parameters */
771         lprintf(7, "Loading citadel.config\n");
772         get_config();
773
774         /*
775          * Do non system dependent startup functions.
776          */
777         master_startup();
778
779         /*
780          * Bind the server to our favorite ports.
781          */
782         CtdlRegisterServiceHook(config.c_port_number,
783                                 citproto_begin_session,
784                                 do_command_loop);
785
786         /*
787          * Load any server-side modules (plugins) available here.
788          */
789         lprintf(7, "Initializing loadable modules\n");
790         if ((moddir = malloc(strlen(bbs_home_directory) + 9)) != NULL) {
791                 sprintf(moddir, "%s/modules", bbs_home_directory);
792                 DLoader_Init(moddir);
793                 free(moddir);
794         }
795
796         /*
797          * The rescan pipe exists so that worker threads can be woken up and
798          * told to re-scan the context list for fd's to listen on.  This is
799          * necessary, for example, when a context is about to go idle and needs
800          * to get back on that list.
801          */
802         if (pipe(rescan)) {
803                 lprintf(1, "Can't create rescan pipe!\n");
804                 exit(errno);
805         }
806
807         /*
808          * Set up a fd_set containing all the master sockets to which we
809          * always listen.  It's computationally less expensive to just copy
810          * this to a local fd_set when starting a new select() and then add
811          * the client sockets than it is to initialize a new one and then
812          * figure out what to put there.
813          */
814         FD_ZERO(&masterfds);
815         masterhighest = 0;
816         FD_SET(rescan[0], &masterfds);
817         if (rescan[0] > masterhighest) masterhighest = rescan[0];
818
819         for (serviceptr = ServiceHookTable; serviceptr != NULL;
820             serviceptr = serviceptr->next ) {
821                 serviceptr->msock = ig_tcp_server(
822                         serviceptr->tcp_port, config.c_maxsessions);
823                 if (serviceptr->msock >= 0) {
824                         FD_SET(serviceptr->msock, &masterfds);
825                         if (serviceptr->msock > masterhighest)
826                                 masterhighest = serviceptr->msock;
827                         lprintf(7, "Bound to port %-5d (socket %d)\n",
828                                 serviceptr->tcp_port,
829                                 serviceptr->msock);
830                 }
831                 else {
832                         lprintf(1, "Unable to bind to port %d\n",
833                                 serviceptr->tcp_port);
834                 }
835         }
836
837
838         /*
839          * Now that we've bound the sockets, change to the BBS user id and its
840          * corresponding group ids
841          */
842         if (drop_root_perms) {
843                 if ((pw = getpwuid(BBSUID)) == NULL)
844                         lprintf(1, "WARNING: getpwuid(%d): %s\n"
845                                    "Group IDs will be incorrect.\n", BBSUID,
846                                 strerror(errno));
847                 else {
848                         initgroups(pw->pw_name, pw->pw_gid);
849                         if (setgid(pw->pw_gid))
850                                 lprintf(3, "setgid(%d): %s\n", pw->pw_gid,
851                                         strerror(errno));
852                 }
853                 lprintf(7, "Changing uid to %d\n", BBSUID);
854                 if (setuid(BBSUID) != 0) {
855                         lprintf(3, "setuid() failed: %s\n", strerror(errno));
856                 }
857         }
858
859         /*
860          * Create the housekeeper thread
861          */
862         lprintf(7, "Starting housekeeper thread\n");
863         pthread_attr_init(&attr);
864         pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
865         if (pthread_create(&HousekeepingThread, &attr,
866            (void* (*)(void*)) housekeeping_loop, NULL) != 0) {
867                 lprintf(1, "Can't create housekeeping thead: %s\n",
868                         strerror(errno));
869         }
870
871
872         /*
873          * Now create a bunch of worker threads.
874          */
875         for (i=0; i<(config.c_min_workers-1); ++i) {
876                 pthread_attr_init(&attr);
877                 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
878                 if (pthread_create(&HousekeepingThread, &attr,
879                    (void* (*)(void*)) worker_thread, NULL) != 0) {
880                         lprintf(1, "Can't create worker thead: %s\n",
881                         strerror(errno));
882                 }
883         }
884
885         /* Now this thread can become a worker as well. */
886         worker_thread();
887
888         return(0);
889 }
890
891
892 /*
893  * Bind a thread to a context.
894  */
895 inline void become_session(struct CitContext *which_con) {
896         pthread_setspecific(MyConKey, (void *)which_con );
897 }
898
899
900
901 /* 
902  * This loop just keeps going and going and going...
903  */     
904 void worker_thread(void) {
905         int i;
906         char junk;
907         int highest;
908         struct CitContext *ptr;
909         struct CitContext *bind_me = NULL;
910         fd_set readfds;
911         int retval;
912         struct CitContext *con= NULL;   /* Temporary context pointer */
913         struct ServiceFunctionHook *serviceptr;
914         struct sockaddr_in fsin;        /* Data for master socket */
915         int alen;                       /* Data for master socket */
916         int ssock;                      /* Descriptor for client socket */
917
918         ++num_threads;
919         while (!time_to_die) {
920
921                 /* 
922                  * A naive implementation would have all idle threads
923                  * calling select() and then they'd all wake up at once.  We
924                  * solve this problem by putting the select() in a critical
925                  * section, so only one thread has the opportunity to wake
926                  * up.  If we wake up on the master socket, create a new
927                  * session context; otherwise, just bind the thread to the
928                  * context we want and go on our merry way.
929                  */
930
931                 begin_critical_section(S_I_WANNA_SELECT);
932 SETUP_FD:       memcpy(&readfds, &masterfds, sizeof(fd_set) );
933                 highest = masterhighest;
934                 begin_critical_section(S_SESSION_TABLE);
935                 for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
936                         if (ptr->state == CON_IDLE) {
937                                 FD_SET(ptr->client_socket, &readfds);
938                                 if (ptr->client_socket > highest)
939                                         highest = ptr->client_socket;
940                         }
941                 }
942                 end_critical_section(S_SESSION_TABLE);
943
944                 retval = select(highest + 1, &readfds, NULL, NULL, NULL);
945
946                 /* Now figure out who made this select() unblock.
947                  * First, check for an error or exit condition.
948                  */
949                 if (retval < 0) {
950                         end_critical_section(S_I_WANNA_SELECT);
951                         lprintf(9, "Exiting (%s)\n", strerror(errno));
952                         time_to_die = 1;
953                 }
954
955                 /* Next, check to see if it's a new client connecting
956                  * on the master socket.
957                  */
958                 else for (serviceptr = ServiceHookTable; serviceptr != NULL;
959                      serviceptr = serviceptr->next ) {
960
961                         if (FD_ISSET(serviceptr->msock, &readfds)) {
962                                 alen = sizeof fsin;
963                                 ssock = accept(serviceptr->msock,
964                                         (struct sockaddr *)&fsin, &alen);
965                                 if (ssock < 0) {
966                                         lprintf(2, "citserver: accept(): %s\n",
967                                                 strerror(errno));
968                                 }
969                                 else {
970                                         lprintf(7, "citserver: "
971                                                 "New client socket %d\n",
972                                                 ssock);
973
974                                         /* New context will be created already
975                                         * set up in the CON_EXECUTING state.
976                                         */
977                                         con = CreateNewContext();
978
979                                         /* Assign new socket number to it. */
980                                         con->client_socket = ssock;
981                                         con->h_command_function =
982                                                 serviceptr->h_command_function;
983         
984                                         /* Set the SO_REUSEADDR socket option */
985                                         i = 1;
986                                         setsockopt(ssock, SOL_SOCKET,
987                                                 SO_REUSEADDR,
988                                                 &i, sizeof(i));
989
990                                         become_session(con);
991                                         begin_session(con);
992                                         serviceptr->h_greeting_function();
993                                         become_session(NULL);
994                                         con->state = CON_IDLE;
995                                         goto SETUP_FD;
996                                 }
997                         }
998                 }
999
1000                 /* If the rescan pipe went active, someone is telling this
1001                  * thread that the &readfds needs to be refreshed with more
1002                  * current data.
1003                  */
1004                 if (!time_to_die) if (FD_ISSET(rescan[0], &readfds)) {
1005                         read(rescan[0], &junk, 1);
1006                         goto SETUP_FD;
1007                 }
1008
1009                 /* It must be a client socket.  Find a context that has data
1010                  * waiting on its socket *and* is in the CON_IDLE state.
1011                  */
1012                 else {
1013                         bind_me = NULL;
1014                         begin_critical_section(S_SESSION_TABLE);
1015                         for (ptr = ContextList;
1016                             ( (ptr != NULL) && (bind_me == NULL) );
1017                             ptr = ptr->next) {
1018                                 if ( (FD_ISSET(ptr->client_socket, &readfds))
1019                                    && (ptr->state == CON_IDLE) ) {
1020                                         bind_me = ptr;
1021                                 }
1022                         }
1023                         if (bind_me != NULL) {
1024                                 /* Found one.  Stake a claim to it before
1025                                  * letting anyone else touch the context list.
1026                                  */
1027                                 bind_me->state = CON_EXECUTING;
1028                         }
1029
1030                         end_critical_section(S_SESSION_TABLE);
1031                         end_critical_section(S_I_WANNA_SELECT);
1032
1033                         /* We're bound to a session, now do *one* command */
1034                         if (bind_me != NULL) {
1035                                 become_session(bind_me);
1036                                 CC->h_command_function();
1037                                 become_session(NULL);
1038                                 bind_me->state = CON_IDLE;
1039                                 if (bind_me->kill_me == 1) {
1040                                         RemoveContext(bind_me);
1041                                 } 
1042                                 write(rescan[1], &junk, 1);
1043                         }
1044                         else {
1045                                 lprintf(9, "Thread found nothing to do!\n");
1046                         }
1047
1048                 }
1049                 dead_session_purge();
1050         }
1051
1052         /* If control reaches this point, the server is shutting down */        
1053         master_cleanup();
1054         --num_threads;
1055         pthread_exit(NULL);
1056 }
1057