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')
665 if (i == 1024*1024) // set some obscene upper limit
670 sz *= 2; /* resize up */
671 b = reallok(b, sz+1);
675 /* Strip the trailing newline and any trailing nonprintables (cr's) */
677 while ((strlen(b)>0)&&(!isprint(*(b+strlen(b)-1))))
678 *(b+strlen(b)-1) = '\0';
682 CC->readbuf = b; /* faster if we do it once at the end */
683 CC->readbuf_alloc = sz;
690 int oldclient_gets(char *buf)
694 /* Read one character at a time.
697 retval = client_read(&buf[i], 1);
698 if (retval != 1 || buf[i] == '\n' || i == (SIZ-1))
702 /* If we got a long line, discard characters until the newline.
705 while (buf[i] != '\n' && retval == 1)
706 retval = client_read(&buf[i], 1);
708 /* Strip the trailing newline and any trailing nonprintables (cr's)
711 while ((strlen(buf)>0)&&(!isprint(buf[strlen(buf)-1])))
712 buf[strlen(buf)-1] = 0;
713 if (retval < 0) strcpy(buf, "000");
720 * The system-dependent part of master_cleanup() - close the master socket.
722 void sysdep_master_cleanup(void) {
723 struct ServiceFunctionHook *serviceptr;
726 * close all protocol master sockets
728 for (serviceptr = ServiceHookTable; serviceptr != NULL;
729 serviceptr = serviceptr->next ) {
731 if (serviceptr->tcp_port > 0)
732 lprintf(3, "Closing listener on port %d\n",
733 serviceptr->tcp_port);
735 if (serviceptr->sockpath != NULL)
736 lprintf(3, "Closing listener on '%s'\n",
737 serviceptr->sockpath);
739 close(serviceptr->msock);
741 /* If it's a Unix domain socket, remove the file. */
742 if (serviceptr->sockpath != NULL) {
743 unlink(serviceptr->sockpath);
750 * Terminate another session.
751 * (This could justifiably be moved out of sysdep.c because it
752 * no longer does anything that is system-dependent.)
754 void kill_session(int session_to_kill) {
755 struct CitContext *ptr;
757 begin_critical_section(S_SESSION_TABLE);
758 for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
759 if (ptr->cs_pid == session_to_kill) {
763 end_critical_section(S_SESSION_TABLE);
770 * Start running as a daemon. Only close stdio if do_close_stdio is set.
772 void start_daemon(int do_close_stdio) {
773 if (do_close_stdio) {
778 signal(SIGHUP,SIG_IGN);
779 signal(SIGINT,SIG_IGN);
780 signal(SIGQUIT,SIG_IGN);
781 if (fork()!=0) exit(0);
787 * Tie in to the 'netsetup' program.
789 * (We're going to hope that netsetup never feeds more than 4096 bytes back.)
791 void cmd_nset(char *cmdbuf)
798 char netsetup_args[3][SIZ];
800 if (CC->usersupp.axlevel < 6) {
801 cprintf("%d Higher access required.\n",
802 ERROR + HIGHER_ACCESS_REQUIRED);
806 for (a=1; a<=3; ++a) {
807 if (num_parms(cmdbuf) >= a) {
808 extract(netsetup_args[a-1], cmdbuf, a-1);
809 for (b=0; b<strlen(netsetup_args[a-1]); ++b) {
810 if (netsetup_args[a-1][b] == 34) {
811 netsetup_args[a-1][b] = '_';
816 netsetup_args[a-1][0] = 0;
820 sprintf(fbuf, "./netsetup \"%s\" \"%s\" \"%s\" </dev/null 2>&1",
821 netsetup_args[0], netsetup_args[1], netsetup_args[2]);
822 netsetup = popen(fbuf, "r");
823 if (netsetup == NULL) {
824 cprintf("%d %s\n", ERROR, strerror(errno));
829 while (ch = getc(netsetup), (ch > 0)) {
830 fbuf[strlen(fbuf)+1] = 0;
831 fbuf[strlen(fbuf)] = ch;
834 retcode = pclose(netsetup);
837 for (a=0; a<strlen(fbuf); ++a) {
838 if (fbuf[a] < 32) fbuf[a] = 32;
841 cprintf("%d %s\n", ERROR, fbuf);
845 cprintf("%d Command succeeded. Output follows:\n", LISTING_FOLLOWS);
847 if (fbuf[strlen(fbuf)-1] != 10) cprintf("\n");
854 * Generic routine to convert a login name to a full name (gecos)
855 * Returns nonzero if a conversion took place
857 int convert_login(char NameToConvert[]) {
861 pw = getpwnam(NameToConvert);
866 strcpy(NameToConvert, pw->pw_gecos);
867 for (a=0; a<strlen(NameToConvert); ++a) {
868 if (NameToConvert[a] == ',') NameToConvert[a] = 0;
874 static struct worker_node {
876 struct worker_node *next;
877 } *worker_list = NULL;
881 * create a worker thread. this function must always be called from within
882 * an S_WORKER_LIST critical section!
884 static void create_worker(void) {
886 struct worker_node *n = mallok(sizeof *n);
889 lprintf(1, "can't allocate worker_node, exiting\n");
894 if ((ret = pthread_create(&n->tid, NULL, worker_thread, NULL) != 0))
897 lprintf(1, "Can't create worker thread: %s\n",
901 n->next = worker_list;
908 * Purge all sessions which have the 'kill_me' flag set.
909 * This function has code to prevent it from running more than once every
910 * few seconds, because running it after every single unbind would waste a lot
911 * of CPU time and keep the context list locked too much.
913 * After that's done, we raise or lower the size of the worker thread pool
914 * if such an action is appropriate.
916 void dead_session_purge(void) {
917 struct CitContext *ptr, *rem;
918 struct worker_node **node, *tmp;
921 if ( (time(NULL) - last_purge) < 5 ) return; /* Too soon, go away */
926 begin_critical_section(S_SESSION_TABLE);
927 for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
928 if ( (ptr->state == CON_IDLE) && (ptr->kill_me) ) {
932 end_critical_section(S_SESSION_TABLE);
934 /* RemoveContext() enters its own S_SESSION_TABLE critical
935 * section, so we have to do it like this.
938 lprintf(9, "Purging session %d\n", rem->cs_pid);
942 } while (rem != NULL);
945 /* Raise or lower the size of the worker thread pool if such
946 * an action is appropriate.
949 self = pthread_self();
951 if ( (num_sessions > num_threads)
952 && (num_threads < config.c_max_workers) ) {
953 begin_critical_section(S_WORKER_LIST);
955 end_critical_section(S_WORKER_LIST);
958 /* don't let the initial thread die since it's responsible for
959 waiting for all the other threads to terminate. */
960 else if ( (num_sessions < num_threads)
961 && (num_threads > config.c_min_workers)
962 && (self != initial_thread) ) {
964 begin_critical_section(S_WORKER_LIST);
967 /* we're exiting before server shutdown... unlink ourself from
968 the worker list and detach our thread to avoid memory leaks
971 for (node = &worker_list; *node != NULL; node = &(*node)->next)
972 if ((*node)->tid == self) {
974 *node = (*node)->next;
979 pthread_detach(self);
980 end_critical_section(S_WORKER_LIST);
991 * Redirect a session's output to a file or socket.
992 * This function may be called with a file handle *or* a socket (but not
993 * both). Call with neither to return output to its normal client socket.
995 void CtdlRedirectOutput(FILE *fp, int sock) {
997 if (fp != NULL) CC->redirect_fp = fp;
998 else CC->redirect_fp = NULL;
1000 if (sock > 0) CC->redirect_sock = sock;
1001 else CC->redirect_sock = (-1);
1007 * masterCC is the context we use when not attached to a session. This
1008 * function initializes it.
1010 void InitializeMasterCC(void) {
1011 memset(&masterCC, 0, sizeof(struct CitContext));
1012 masterCC.internal_pgm = 1;
1013 masterCC.cs_pid = 0;
1019 * Set up a fd_set containing all the master sockets to which we
1020 * always listen. It's computationally less expensive to just copy
1021 * this to a local fd_set when starting a new select() and then add
1022 * the client sockets than it is to initialize a new one and then
1023 * figure out what to put there.
1025 void init_master_fdset(void) {
1026 struct ServiceFunctionHook *serviceptr;
1029 lprintf(9, "Initializing master fdset\n");
1031 FD_ZERO(&masterfds);
1034 lprintf(9, "Will listen on rescan pipe %d\n", rescan[0]);
1035 FD_SET(rescan[0], &masterfds);
1036 if (rescan[0] > masterhighest) masterhighest = rescan[0];
1038 for (serviceptr = ServiceHookTable; serviceptr != NULL;
1039 serviceptr = serviceptr->next ) {
1040 m = serviceptr->msock;
1041 lprintf(9, "Will listen on master socket %d\n", m);
1042 FD_SET(m, &masterfds);
1043 if (m > masterhighest) {
1047 lprintf(9, "masterhighest = %d\n", masterhighest);
1053 * Here's where it all begins.
1055 int main(int argc, char **argv)
1057 char tracefile[128]; /* Name of file to log traces to */
1058 int a, i; /* General-purpose variables */
1060 int drop_root_perms = 1;
1062 struct worker_node *wnp;
1064 /* specify default port name and trace file */
1065 strcpy(tracefile, "");
1067 /* initialize the master context */
1068 InitializeMasterCC();
1070 /* parse command-line arguments */
1071 for (a=1; a<argc; ++a) {
1073 /* -t specifies where to log trace messages to */
1074 if (!strncmp(argv[a], "-t", 2)) {
1075 strcpy(tracefile, argv[a]);
1076 strcpy(tracefile, &tracefile[2]);
1077 freopen(tracefile, "r", stdin);
1078 freopen(tracefile, "w", stdout);
1079 freopen(tracefile, "w", stderr);
1082 /* run in the background if -d was specified */
1083 else if (!strcmp(argv[a], "-d")) {
1084 start_daemon( (strlen(tracefile) > 0) ? 0 : 1 ) ;
1087 /* -x specifies the desired logging level */
1088 else if (!strncmp(argv[a], "-x", 2)) {
1089 verbosity = atoi(&argv[a][2]);
1092 else if (!strncmp(argv[a], "-h", 2)) {
1093 safestrncpy(bbs_home_directory, &argv[a][2],
1094 sizeof bbs_home_directory);
1098 else if (!strncmp(argv[a], "-f", 2)) {
1102 /* -r tells the server not to drop root permissions. don't use
1103 * this unless you know what you're doing. this should be
1104 * removed in the next release if it proves unnecessary. */
1105 else if (!strcmp(argv[a], "-r"))
1106 drop_root_perms = 0;
1108 /* any other parameter makes it crash and burn */
1110 lprintf(1, "citserver: usage: "
1111 "citserver [-tTraceFile] [-d] [-f]"
1112 " [-xLogLevel] [-hHomeDir]\n");
1118 /* Tell 'em who's in da house */
1120 "\nMultithreaded message server for Citadel/UX\n"
1121 "Copyright (C) 1987-2000 by the Citadel/UX development team.\n"
1122 "Citadel/UX is free software, covered by the GNU General Public License, and\n"
1123 "you are welcome to change it and/or distribute copies of it under certain\n"
1124 "conditions. There is absolutely no warranty for this software. Please\n"
1125 "read the 'COPYING.txt' file for details.\n\n");
1129 openlog("citserver", LOG_PID, LOG_USER);
1131 /* Load site-specific parameters */
1132 lprintf(7, "Loading citadel.config\n");
1137 * Do non system dependent startup functions.
1142 * Bind the server to a Unix-domain socket.
1144 CtdlRegisterServiceHook(0,
1146 citproto_begin_session,
1150 * Bind the server to our favorite TCP port (usually 504).
1152 CtdlRegisterServiceHook(config.c_port_number,
1154 citproto_begin_session,
1158 * Load any server-side modules (plugins) available here.
1160 lprintf(7, "Initializing loadable modules\n");
1161 if ((moddir = malloc(strlen(bbs_home_directory) + 9)) != NULL) {
1162 sprintf(moddir, "%s/modules", bbs_home_directory);
1163 DLoader_Init(moddir);
1168 * The rescan pipe exists so that worker threads can be woken up and
1169 * told to re-scan the context list for fd's to listen on. This is
1170 * necessary, for example, when a context is about to go idle and needs
1171 * to get back on that list.
1174 lprintf(1, "Can't create rescan pipe!\n");
1178 init_master_fdset();
1181 * Now that we've bound the sockets, change to the BBS user id and its
1182 * corresponding group ids
1184 if (drop_root_perms) {
1185 if ((pw = getpwuid(BBSUID)) == NULL)
1186 lprintf(1, "WARNING: getpwuid(%d): %s\n"
1187 "Group IDs will be incorrect.\n", BBSUID,
1190 initgroups(pw->pw_name, pw->pw_gid);
1191 if (setgid(pw->pw_gid))
1192 lprintf(3, "setgid(%d): %s\n", pw->pw_gid,
1195 lprintf(7, "Changing uid to %d\n", BBSUID);
1196 if (setuid(BBSUID) != 0) {
1197 lprintf(3, "setuid() failed: %s\n", strerror(errno));
1201 /* We want to check for idle sessions once per minute */
1202 CtdlRegisterSessionHook(terminate_idle_sessions, EVT_TIMER);
1205 * Now create a bunch of worker threads.
1207 lprintf(9, "Starting %d worker threads\n", config.c_min_workers-1);
1208 begin_critical_section(S_WORKER_LIST);
1209 for (i=0; i<(config.c_min_workers-1); ++i) {
1212 end_critical_section(S_WORKER_LIST);
1214 /* Now this thread can become a worker as well. */
1215 initial_thread = pthread_self();
1216 worker_thread(NULL);
1218 /* Server is exiting. Wait for workers to shutdown. */
1219 lprintf(7, "Waiting for worker threads to shut down\n");
1221 begin_critical_section(S_WORKER_LIST);
1222 while (worker_list != NULL) {
1224 worker_list = wnp->next;
1226 /* avoid deadlock with an exiting thread */
1227 end_critical_section(S_WORKER_LIST);
1228 if ((i = pthread_join(wnp->tid, NULL)))
1229 lprintf(1, "pthread_join: %s\n", strerror(i));
1231 begin_critical_section(S_WORKER_LIST);
1233 end_critical_section(S_WORKER_LIST);
1242 * Bind a thread to a context. (It's inline merely to speed things up.)
1244 inline void become_session(struct CitContext *which_con) {
1245 pthread_setspecific(MyConKey, (void *)which_con );
1251 * This loop just keeps going and going and going...
1253 void *worker_thread(void *arg) {
1257 struct CitContext *ptr;
1258 struct CitContext *bind_me = NULL;
1261 struct CitContext *con= NULL; /* Temporary context pointer */
1262 struct ServiceFunctionHook *serviceptr;
1263 struct sockaddr_in fsin; /* Data for master socket */
1264 int alen; /* Data for master socket */
1265 int ssock; /* Descriptor for client socket */
1272 while (!time_to_die) {
1275 * A naive implementation would have all idle threads
1276 * calling select() and then they'd all wake up at once. We
1277 * solve this problem by putting the select() in a critical
1278 * section, so only one thread has the opportunity to wake
1279 * up. If we wake up on a master socket, create a new
1280 * session context; otherwise, just bind the thread to the
1281 * context we want and go on our merry way.
1284 /* make doubly sure we're not holding any stale db handles
1285 * which might cause a deadlock.
1287 cdb_release_handles();
1289 begin_critical_section(S_I_WANNA_SELECT);
1290 SETUP_FD: memcpy(&readfds, &masterfds, sizeof masterfds);
1291 highest = masterhighest;
1292 begin_critical_section(S_SESSION_TABLE);
1293 for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
1294 if (ptr->state == CON_IDLE) {
1295 FD_SET(ptr->client_socket, &readfds);
1296 if (ptr->client_socket > highest)
1297 highest = ptr->client_socket;
1300 end_critical_section(S_SESSION_TABLE);
1302 tv.tv_sec = 1; /* wake up every second if no input */
1307 retval = select(highest + 1, &readfds, NULL, NULL, &tv);
1309 end_critical_section(S_I_WANNA_SELECT);
1313 /* Now figure out who made this select() unblock.
1314 * First, check for an error or exit condition.
1317 if (errno != EINTR) {
1318 lprintf(9, "Exiting (%s)\n", strerror(errno));
1320 } else if (!time_to_die)
1324 /* Next, check to see if it's a new client connecting
1325 * on a master socket.
1327 else for (serviceptr = ServiceHookTable; serviceptr != NULL;
1328 serviceptr = serviceptr->next ) {
1330 if (FD_ISSET(serviceptr->msock, &readfds)) {
1332 ssock = accept(serviceptr->msock,
1333 (struct sockaddr *)&fsin, &alen);
1335 lprintf(2, "citserver: accept(): %s\n",
1339 lprintf(7, "citserver: "
1340 "New client socket %d\n",
1343 /* New context will be created already
1344 * set up in the CON_EXECUTING state.
1346 con = CreateNewContext();
1348 /* Assign new socket number to it. */
1349 con->client_socket = ssock;
1350 con->h_command_function =
1351 serviceptr->h_command_function;
1353 /* Determine whether local socket */
1354 if (serviceptr->sockpath != NULL)
1355 con->is_local_socket = 1;
1357 /* Set the SO_REUSEADDR socket option */
1359 setsockopt(ssock, SOL_SOCKET,
1363 become_session(con);
1365 serviceptr->h_greeting_function();
1366 become_session(NULL);
1367 con->state = CON_IDLE;
1373 /* If the rescan pipe went active, someone is telling this
1374 * thread that the &readfds needs to be refreshed with more
1378 end_critical_section(S_I_WANNA_SELECT);
1382 if (FD_ISSET(rescan[0], &readfds)) {
1383 read(rescan[0], &junk, 1);
1387 /* It must be a client socket. Find a context that has data
1388 * waiting on its socket *and* is in the CON_IDLE state.
1392 begin_critical_section(S_SESSION_TABLE);
1393 for (ptr = ContextList;
1394 ( (ptr != NULL) && (bind_me == NULL) );
1396 if ( (FD_ISSET(ptr->client_socket, &readfds))
1397 && (ptr->state == CON_IDLE) ) {
1401 if (bind_me != NULL) {
1402 /* Found one. Stake a claim to it before
1403 * letting anyone else touch the context list.
1405 bind_me->state = CON_EXECUTING;
1408 end_critical_section(S_SESSION_TABLE);
1409 end_critical_section(S_I_WANNA_SELECT);
1411 /* We're bound to a session, now do *one* command */
1412 if (bind_me != NULL) {
1413 become_session(bind_me);
1414 CC->h_command_function();
1415 become_session(NULL);
1416 bind_me->state = CON_IDLE;
1417 if (bind_me->kill_me == 1) {
1418 RemoveContext(bind_me);
1420 write(rescan[1], &junk, 1);
1424 dead_session_purge();
1425 if ((time(NULL) - last_timer) > 60L) {
1426 last_timer = time(NULL);
1427 cdb_release_handles(); /* suggested by Justin Case */
1428 PerformSessionHooks(EVT_TIMER);
1431 check_sched_shutdown();
1434 /* If control reaches this point, the server is shutting down */