4 * Citadel/UX "system dependent" stuff.
5 * See copyright.txt for copyright information.
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.
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.
23 #include <sys/types.h>
26 #include <sys/socket.h>
29 #include <netinet/in.h>
46 #include "sysdep_decls.h"
47 #include "citserver.h"
51 #include "housekeeping.h"
52 #include "dynloader.h"
55 #ifdef HAVE_SYS_SELECT_H
56 #include <sys/select.h>
63 #ifdef DEBUG_MEMORY_LEAKS
64 struct TheHeap *heap = NULL;
67 pthread_mutex_t Critters[MAX_SEMAPHORES]; /* Things needing locking */
68 pthread_key_t MyConKey; /* TSD key for MyContext() */
70 int verbosity = DEFAULT_VERBOSITY; /* Logging level */
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 */
78 fd_set masterfds; /* Master sockets etc. */
81 time_t last_timer = 0L; /* Last timer hook processing */
83 static pthread_t initial_thread; /* tid for main() thread */
87 * lprintf() ... Write logging information
89 * Note: the variable "buf" below needs to be large enough to handle any
90 * log data sent through this function. BE CAREFUL!
93 static int /* I stole this from snprintf.c because it might not get configed in */
94 neededalso (const char *fmt, va_list argp)
96 static FILE *sink = NULL;
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 :-) */
104 if ((sink = fopen("/dev/null", "w")) == NULL)
111 return vfprintf(sink, fmt, argp);
114 void lprintf(int loglevel, const char *format, ...) {
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);
123 va_start(arg_ptr, format);
124 vsprintf(buf, format, arg_ptr);
127 if (loglevel <= verbosity) {
131 gettimeofday(&tv, NULL);
132 tim = localtime(&(tv.tv_sec));
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.
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);
145 PerformLogHooks(loglevel, buf);
151 #ifdef DEBUG_MEMORY_LEAKS
153 /* These functions presume a long is 32 bits. */
155 void *getmem (long m)
157 /* must zero out data */
161 z = malloc(m+8); /* make room for size and "CITX" */
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 */
171 void freemem (void *m)
173 /* check to see if we overran */
176 memcpy (&sz, m-4, 4); /* get long back */
178 if (memcmp (m + sz, "CITX", 4) != 0)
180 lprintf(3, "DANGER! Memory overrun!\n", "", "", "");
182 free(m - 4); // nobody tells me these things
185 void *reallocmem(void *m, ULONG newsize)
187 /* check to see if we overran */
191 memcpy (&sz, m-4, 4); /* get long back */
193 if (memcmp (m + sz, "CITX", 4) != 0)
194 lprintf(3, "DANGER! Memory overrun!\n", "", "", "");
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 */
204 void *tracked_malloc(size_t tsize, char *tfile, int tline) {
206 struct TheHeap *hptr;
208 ptr = getmem(tsize); /* stu, thought you might like this for debugging */
210 lprintf(3, "DANGER! mallok(%d) at %s:%d failed!\n",
211 tsize, tfile, tline);
215 hptr = (struct TheHeap *) getmem(sizeof(struct TheHeap));
216 strcpy(hptr->h_file, tfile);
217 hptr->h_line = tline;
224 char *tracked_strdup(const char *orig, char *tfile, int tline) {
227 s = tracked_malloc( (strlen(orig)+1), tfile, tline);
228 if (s == NULL) return NULL;
234 void tracked_free(void *ptr) {
235 struct TheHeap *hptr, *freeme;
237 if (heap->h_ptr == ptr) {
243 for (hptr=heap; hptr->next!=NULL; hptr=hptr->next) {
244 if (hptr->next->h_ptr == ptr) {
246 hptr->next = hptr->next->next;
255 void *tracked_realloc(void *ptr, size_t size) {
257 struct TheHeap *hptr;
259 newptr = reallocmem(ptr, size);
261 for (hptr=heap; hptr!=NULL; hptr=hptr->next) {
262 if (hptr->h_ptr == ptr) hptr->h_ptr = newptr;
269 void dump_tracked() {
270 struct TheHeap *hptr;
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);
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.
295 volatile int time_to_die = 0;
297 static RETSIGTYPE signal_cleanup(int signum) {
303 * Some initialization stuff...
305 void init_sysdep(void) {
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);
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.
319 if (pthread_key_create(&MyConKey, NULL) != 0) {
320 lprintf(1, "Can't create TSD key!! %s\n", strerror(errno));
324 * The action for unexpected signals and exceptions should be to
325 * call signal_cleanup() to gracefully shut down the server.
327 signal(SIGINT, signal_cleanup);
328 signal(SIGQUIT, signal_cleanup);
329 signal(SIGHUP, signal_cleanup);
330 signal(SIGTERM, signal_cleanup);
333 * Do not shut down the server on broken pipe signals, otherwise the
334 * whole Citadel service would come down whenever a single client
337 signal(SIGPIPE, SIG_IGN);
342 * Obtain a semaphore lock to begin a critical section.
344 void begin_critical_section(int which_one)
346 /* lprintf(9, "begin_critical_section(%d)\n", which_one); */
347 pthread_mutex_lock(&Critters[which_one]);
351 * Release a semaphore lock to end a critical section.
353 void end_critical_section(int which_one)
355 /* lprintf(9, "end_critical_section(%d)\n", which_one); */
356 pthread_mutex_unlock(&Critters[which_one]);
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.
366 int ig_tcp_server(int port_number, int queue_len)
368 struct sockaddr_in sin;
370 int actual_queue_len;
372 actual_queue_len = queue_len;
373 if (actual_queue_len < 5) actual_queue_len = 5;
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);
380 s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
383 lprintf(1, "citserver: Can't create a socket: %s\n",
389 setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
391 if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
392 lprintf(1, "citserver: Can't bind: %s\n",
398 if (listen(s, actual_queue_len) < 0) {
399 lprintf(1, "citserver: Can't listen: %s\n", strerror(errno));
410 * Create a Unix domain socket and listen on it
412 int ig_uds_server(char *sockpath, int queue_len)
414 struct sockaddr_un addr;
417 int actual_queue_len;
419 actual_queue_len = queue_len;
420 if (actual_queue_len < 5) actual_queue_len = 5;
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));
429 memset(&addr, 0, sizeof(addr));
430 addr.sun_family = AF_UNIX;
431 safestrncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
433 s = socket(AF_UNIX, SOCK_STREAM, 0);
435 lprintf(1, "citserver: Can't create a socket: %s\n",
440 if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
441 lprintf(1, "citserver: Can't bind: %s\n",
446 if (listen(s, actual_queue_len) < 0) {
447 lprintf(1, "citserver: Can't listen: %s\n", strerror(errno));
451 chmod(sockpath, 0777);
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.
462 struct CitContext *MyContext(void) {
463 struct CitContext *retCC;
464 retCC = (struct CitContext *) pthread_getspecific(MyConKey);
465 if (retCC == NULL) retCC = &masterCC;
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.
477 struct CitContext *CreateNewContext(void) {
478 struct CitContext *me, *ptr;
482 me = (struct CitContext *) mallok(sizeof(struct CitContext));
484 lprintf(1, "citserver: can't allocate memory!!\n");
487 memset(me, 0, sizeof(struct CitContext));
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
493 me->state = CON_EXECUTING;
495 begin_critical_section(S_SESSION_TABLE);
497 /* obtain a unique session number */
500 for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
501 if (ptr->cs_pid == num) {
506 } while (startover == 1);
509 me->next = ContextList;
513 end_critical_section(S_SESSION_TABLE);
520 * client_write() ... Send binary data to the client.
522 void client_write(char *buf, int nbytes)
524 int bytes_written = 0;
528 if (CC->redirect_fp != NULL) {
529 fwrite(buf, nbytes, 1, CC->redirect_fp);
533 if (CC->redirect_sock > 0) {
534 sock = CC->redirect_sock; /* and continue below... */
537 sock = CC->client_socket;
540 while (bytes_written < nbytes) {
541 retval = write(sock, &buf[bytes_written],
542 nbytes - bytes_written);
544 lprintf(2, "client_write() failed: %s\n",
546 if (sock == CC->client_socket) CC->kill_me = 1;
549 bytes_written = bytes_written + retval;
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...
559 void cprintf(const char *format, ...) {
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));
572 * Read data from the client socket.
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.
579 int client_read_to(char *buf, int bytes, int timeout)
589 FD_SET(CC->client_socket, &rfds);
593 retval = select( (CC->client_socket)+1,
594 &rfds, NULL, NULL, &tv);
596 if (FD_ISSET(CC->client_socket, &rfds) == 0) {
600 rlen = read(CC->client_socket, &buf[len], bytes-len);
602 lprintf(2, "client_read() failed: %s\n",
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)
617 int client_read(char *buf, int bytes)
619 return(client_read_to(buf, bytes, config.c_sleeping));
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)
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. */
637 int client_gets(char **retbuf)
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? */
646 b = mallok(SIZ+1); /* start with something */
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 */
654 b = reallok(b, SIZ*2+1); /* resize down */
658 *b = '\0'; /* in case we bail early */
662 retval = client_read(b+i, 1);
663 if (retval != 1 || b[i] == '\n')
668 sz *= 2; /* resize up */
669 b = reallok(b, sz+1);
673 /* Strip the trailing newline and any trailing nonprintables (cr's) */
675 while ((strlen(b)>0)&&(!isprint(*(b+strlen(b)-1))))
676 *(b+strlen(b)-1) = '\0';
680 CC->readbuf = b; /* faster if we do it once at the end */
681 CC->readbuf_alloc = sz;
688 int oldclient_gets(char *buf)
692 /* Read one character at a time.
695 retval = client_read(&buf[i], 1);
696 if (retval != 1 || buf[i] == '\n' || i == (SIZ-1))
700 /* If we got a long line, discard characters until the newline.
703 while (buf[i] != '\n' && retval == 1)
704 retval = client_read(&buf[i], 1);
706 /* Strip the trailing newline and any trailing nonprintables (cr's)
709 while ((strlen(buf)>0)&&(!isprint(buf[strlen(buf)-1])))
710 buf[strlen(buf)-1] = 0;
711 if (retval < 0) strcpy(buf, "000");
718 * The system-dependent part of master_cleanup() - close the master socket.
720 void sysdep_master_cleanup(void) {
721 struct ServiceFunctionHook *serviceptr;
724 * close all protocol master sockets
726 for (serviceptr = ServiceHookTable; serviceptr != NULL;
727 serviceptr = serviceptr->next ) {
729 if (serviceptr->tcp_port > 0)
730 lprintf(3, "Closing listener on port %d\n",
731 serviceptr->tcp_port);
733 if (serviceptr->sockpath != NULL)
734 lprintf(3, "Closing listener on '%s'\n",
735 serviceptr->sockpath);
737 close(serviceptr->msock);
739 /* If it's a Unix domain socket, remove the file. */
740 if (serviceptr->sockpath != NULL) {
741 unlink(serviceptr->sockpath);
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.)
752 void kill_session(int session_to_kill) {
753 struct CitContext *ptr;
755 begin_critical_section(S_SESSION_TABLE);
756 for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
757 if (ptr->cs_pid == session_to_kill) {
761 end_critical_section(S_SESSION_TABLE);
768 * Start running as a daemon. Only close stdio if do_close_stdio is set.
770 void start_daemon(int do_close_stdio) {
771 if (do_close_stdio) {
776 signal(SIGHUP,SIG_IGN);
777 signal(SIGINT,SIG_IGN);
778 signal(SIGQUIT,SIG_IGN);
779 if (fork()!=0) exit(0);
785 * Tie in to the 'netsetup' program.
787 * (We're going to hope that netsetup never feeds more than 4096 bytes back.)
789 void cmd_nset(char *cmdbuf)
796 char netsetup_args[3][SIZ];
798 if (CC->usersupp.axlevel < 6) {
799 cprintf("%d Higher access required.\n",
800 ERROR + HIGHER_ACCESS_REQUIRED);
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] = '_';
814 netsetup_args[a-1][0] = 0;
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));
827 while (ch = getc(netsetup), (ch > 0)) {
828 fbuf[strlen(fbuf)+1] = 0;
829 fbuf[strlen(fbuf)] = ch;
832 retcode = pclose(netsetup);
835 for (a=0; a<strlen(fbuf); ++a) {
836 if (fbuf[a] < 32) fbuf[a] = 32;
839 cprintf("%d %s\n", ERROR, fbuf);
843 cprintf("%d Command succeeded. Output follows:\n", LISTING_FOLLOWS);
845 if (fbuf[strlen(fbuf)-1] != 10) cprintf("\n");
852 * Generic routine to convert a login name to a full name (gecos)
853 * Returns nonzero if a conversion took place
855 int convert_login(char NameToConvert[]) {
859 pw = getpwnam(NameToConvert);
864 strcpy(NameToConvert, pw->pw_gecos);
865 for (a=0; a<strlen(NameToConvert); ++a) {
866 if (NameToConvert[a] == ',') NameToConvert[a] = 0;
872 static struct worker_node {
874 struct worker_node *next;
875 } *worker_list = NULL;
879 * create a worker thread. this function must always be called from within
880 * an S_WORKER_LIST critical section!
882 static void create_worker(void) {
884 struct worker_node *n = mallok(sizeof *n);
887 lprintf(1, "can't allocate worker_node, exiting\n");
892 if ((ret = pthread_create(&n->tid, NULL, worker_thread, NULL) != 0))
895 lprintf(1, "Can't create worker thread: %s\n",
899 n->next = worker_list;
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.
911 * After that's done, we raise or lower the size of the worker thread pool
912 * if such an action is appropriate.
914 void dead_session_purge(void) {
915 struct CitContext *ptr, *rem;
916 struct worker_node **node, *tmp;
919 if ( (time(NULL) - last_purge) < 5 ) return; /* Too soon, go away */
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) ) {
930 end_critical_section(S_SESSION_TABLE);
932 /* RemoveContext() enters its own S_SESSION_TABLE critical
933 * section, so we have to do it like this.
936 lprintf(9, "Purging session %d\n", rem->cs_pid);
940 } while (rem != NULL);
943 /* Raise or lower the size of the worker thread pool if such
944 * an action is appropriate.
947 self = pthread_self();
949 if ( (num_sessions > num_threads)
950 && (num_threads < config.c_max_workers) ) {
951 begin_critical_section(S_WORKER_LIST);
953 end_critical_section(S_WORKER_LIST);
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) ) {
962 begin_critical_section(S_WORKER_LIST);
965 /* we're exiting before server shutdown... unlink ourself from
966 the worker list and detach our thread to avoid memory leaks
969 for (node = &worker_list; *node != NULL; node = &(*node)->next)
970 if ((*node)->tid == self) {
972 *node = (*node)->next;
977 pthread_detach(self);
978 end_critical_section(S_WORKER_LIST);
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.
993 void CtdlRedirectOutput(FILE *fp, int sock) {
995 if (fp != NULL) CC->redirect_fp = fp;
996 else CC->redirect_fp = NULL;
998 if (sock > 0) CC->redirect_sock = sock;
999 else CC->redirect_sock = (-1);
1005 * masterCC is the context we use when not attached to a session. This
1006 * function initializes it.
1008 void InitializeMasterCC(void) {
1009 memset(&masterCC, 0, sizeof(struct CitContext));
1010 masterCC.internal_pgm = 1;
1011 masterCC.cs_pid = 0;
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.
1023 void init_master_fdset(void) {
1024 struct ServiceFunctionHook *serviceptr;
1027 lprintf(9, "Initializing master fdset\n");
1029 FD_ZERO(&masterfds);
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];
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) {
1045 lprintf(9, "masterhighest = %d\n", masterhighest);
1051 * Here's where it all begins.
1053 int main(int argc, char **argv)
1055 char tracefile[128]; /* Name of file to log traces to */
1056 int a, i; /* General-purpose variables */
1058 int drop_root_perms = 1;
1060 struct worker_node *wnp;
1062 /* specify default port name and trace file */
1063 strcpy(tracefile, "");
1065 /* initialize the master context */
1066 InitializeMasterCC();
1068 /* parse command-line arguments */
1069 for (a=1; a<argc; ++a) {
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);
1080 /* run in the background if -d was specified */
1081 else if (!strcmp(argv[a], "-d")) {
1082 start_daemon( (strlen(tracefile) > 0) ? 0 : 1 ) ;
1085 /* -x specifies the desired logging level */
1086 else if (!strncmp(argv[a], "-x", 2)) {
1087 verbosity = atoi(&argv[a][2]);
1090 else if (!strncmp(argv[a], "-h", 2)) {
1091 safestrncpy(bbs_home_directory, &argv[a][2],
1092 sizeof bbs_home_directory);
1096 else if (!strncmp(argv[a], "-f", 2)) {
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;
1106 /* any other parameter makes it crash and burn */
1108 lprintf(1, "citserver: usage: "
1109 "citserver [-tTraceFile] [-d] [-f]"
1110 " [-xLogLevel] [-hHomeDir]\n");
1116 /* Tell 'em who's in da house */
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");
1127 openlog("citserver", LOG_PID, LOG_USER);
1129 /* Load site-specific parameters */
1130 lprintf(7, "Loading citadel.config\n");
1135 * Do non system dependent startup functions.
1140 * Bind the server to a Unix-domain socket.
1142 CtdlRegisterServiceHook(0,
1144 citproto_begin_session,
1148 * Bind the server to our favorite TCP port (usually 504).
1150 CtdlRegisterServiceHook(config.c_port_number,
1152 citproto_begin_session,
1156 * Load any server-side modules (plugins) available here.
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);
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.
1172 lprintf(1, "Can't create rescan pipe!\n");
1176 init_master_fdset();
1179 * Now that we've bound the sockets, change to the BBS user id and its
1180 * corresponding group ids
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,
1188 initgroups(pw->pw_name, pw->pw_gid);
1189 if (setgid(pw->pw_gid))
1190 lprintf(3, "setgid(%d): %s\n", pw->pw_gid,
1193 lprintf(7, "Changing uid to %d\n", BBSUID);
1194 if (setuid(BBSUID) != 0) {
1195 lprintf(3, "setuid() failed: %s\n", strerror(errno));
1199 /* We want to check for idle sessions once per minute */
1200 CtdlRegisterSessionHook(terminate_idle_sessions, EVT_TIMER);
1203 * Now create a bunch of worker threads.
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) {
1210 end_critical_section(S_WORKER_LIST);
1212 /* Now this thread can become a worker as well. */
1213 initial_thread = pthread_self();
1214 worker_thread(NULL);
1216 /* Server is exiting. Wait for workers to shutdown. */
1217 lprintf(7, "Waiting for worker threads to shut down\n");
1219 begin_critical_section(S_WORKER_LIST);
1220 while (worker_list != NULL) {
1222 worker_list = wnp->next;
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));
1229 begin_critical_section(S_WORKER_LIST);
1231 end_critical_section(S_WORKER_LIST);
1240 * Bind a thread to a context. (It's inline merely to speed things up.)
1242 inline void become_session(struct CitContext *which_con) {
1243 pthread_setspecific(MyConKey, (void *)which_con );
1249 * This loop just keeps going and going and going...
1251 void *worker_thread(void *arg) {
1255 struct CitContext *ptr;
1256 struct CitContext *bind_me = NULL;
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 */
1270 while (!time_to_die) {
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.
1282 /* make doubly sure we're not holding any stale db handles
1283 * which might cause a deadlock.
1285 cdb_release_handles();
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;
1298 end_critical_section(S_SESSION_TABLE);
1300 tv.tv_sec = 1; /* wake up every second if no input */
1305 retval = select(highest + 1, &readfds, NULL, NULL, &tv);
1307 end_critical_section(S_I_WANNA_SELECT);
1311 /* Now figure out who made this select() unblock.
1312 * First, check for an error or exit condition.
1315 if (errno != EINTR) {
1316 lprintf(9, "Exiting (%s)\n", strerror(errno));
1318 } else if (!time_to_die)
1322 /* Next, check to see if it's a new client connecting
1323 * on a master socket.
1325 else for (serviceptr = ServiceHookTable; serviceptr != NULL;
1326 serviceptr = serviceptr->next ) {
1328 if (FD_ISSET(serviceptr->msock, &readfds)) {
1330 ssock = accept(serviceptr->msock,
1331 (struct sockaddr *)&fsin, &alen);
1333 lprintf(2, "citserver: accept(): %s\n",
1337 lprintf(7, "citserver: "
1338 "New client socket %d\n",
1341 /* New context will be created already
1342 * set up in the CON_EXECUTING state.
1344 con = CreateNewContext();
1346 /* Assign new socket number to it. */
1347 con->client_socket = ssock;
1348 con->h_command_function =
1349 serviceptr->h_command_function;
1351 /* Determine whether local socket */
1352 if (serviceptr->sockpath != NULL)
1353 con->is_local_socket = 1;
1355 /* Set the SO_REUSEADDR socket option */
1357 setsockopt(ssock, SOL_SOCKET,
1361 become_session(con);
1363 serviceptr->h_greeting_function();
1364 become_session(NULL);
1365 con->state = CON_IDLE;
1371 /* If the rescan pipe went active, someone is telling this
1372 * thread that the &readfds needs to be refreshed with more
1376 end_critical_section(S_I_WANNA_SELECT);
1380 if (FD_ISSET(rescan[0], &readfds)) {
1381 read(rescan[0], &junk, 1);
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.
1390 begin_critical_section(S_SESSION_TABLE);
1391 for (ptr = ContextList;
1392 ( (ptr != NULL) && (bind_me == NULL) );
1394 if ( (FD_ISSET(ptr->client_socket, &readfds))
1395 && (ptr->state == CON_IDLE) ) {
1399 if (bind_me != NULL) {
1400 /* Found one. Stake a claim to it before
1401 * letting anyone else touch the context list.
1403 bind_me->state = CON_EXECUTING;
1406 end_critical_section(S_SESSION_TABLE);
1407 end_critical_section(S_I_WANNA_SELECT);
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);
1418 write(rescan[1], &junk, 1);
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);
1429 check_sched_shutdown();
1432 /* If control reaches this point, the server is shutting down */