4 * Main source module for the Citadel server
6 * Copyright (c) 1987-2010 by the citadel.org team
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29 #include <sys/types.h>
32 #if TIME_WITH_SYS_TIME
33 # include <sys/time.h>
37 # include <sys/time.h>
53 #include <sys/types.h>
54 #include <sys/socket.h>
55 #include <netinet/in.h>
56 #include <arpa/inet.h>
57 #include <libcitadel.h>
60 #include "sysdep_decls.h"
62 #include "citserver.h"
65 #include "housekeeping.h"
69 #include "locate_host.h"
74 #include "euidindex.h"
76 #include "svn_revision.h"
82 #include "ctdl_module.h"
84 char *unique_session_numbers;
85 int ScheduledShutdown = 0;
86 time_t server_startup_time;
90 * print the actual stack frame.
92 void cit_backtrace(void)
95 void *stack_frames[50];
100 size = backtrace(stack_frames, sizeof(stack_frames) / sizeof(void*));
101 strings = backtrace_symbols(stack_frames, size);
102 for (i = 0; i < size; i++) {
104 CtdlLogPrintf(1, "%s\n", strings[i]);
106 CtdlLogPrintf(1, "%p\n", stack_frames[i]);
113 * print the actual stack frame.
115 void cit_panic_backtrace(int SigNum)
117 #ifdef HAVE_BACKTRACE
118 void *stack_frames[10];
122 printf("caught signal 11\n");
123 size = backtrace(stack_frames, sizeof(stack_frames) / sizeof(void*));
124 strings = backtrace_symbols(stack_frames, size);
125 for (i = 0; i < size; i++) {
127 CtdlLogPrintf(1, "%s\n", strings[i]);
129 CtdlLogPrintf(1, "%p\n", stack_frames[i]);
137 * Various things that need to be initialized at startup
139 void master_startup(void) {
143 struct ctdlroom qrbuf;
146 CtdlLogPrintf(CTDL_DEBUG, "master_startup() started\n");
147 time(&server_startup_time);
149 CtdlLogPrintf(CTDL_INFO, "Opening databases\n");
152 ctdl_thread_internal_init_tsd();
154 CtdlThreadAllocTSD();
158 CtdlLogPrintf(CTDL_INFO, "Creating base rooms (if necessary)\n");
159 CtdlCreateRoom(config.c_baseroom, 0, "", 0, 1, 0, VIEW_BBS);
160 CtdlCreateRoom(AIDEROOM, 3, "", 0, 1, 0, VIEW_BBS);
161 CtdlCreateRoom(SYSCONFIGROOM, 3, "", 0, 1, 0, VIEW_BBS);
162 CtdlCreateRoom(config.c_twitroom, 0, "", 0, 1, 0, VIEW_BBS);
164 /* The "Local System Configuration" room doesn't need to be visible */
165 if (CtdlGetRoomLock(&qrbuf, SYSCONFIGROOM) == 0) {
166 qrbuf.QRflags2 |= QR2_SYSTEM;
167 CtdlPutRoomLock(&qrbuf);
170 /* Aide needs to be public postable, else we're not RFC conformant. */
171 if (CtdlGetRoomLock(&qrbuf, AIDEROOM) == 0) {
172 qrbuf.QRflags2 |= QR2_SMTP_PUBLIC;
173 CtdlPutRoomLock(&qrbuf);
176 CtdlLogPrintf(CTDL_INFO, "Seeding the pseudo-random number generator...\n");
177 urandom = fopen("/dev/urandom", "r");
178 if (urandom != NULL) {
179 rv = fread(&seed, sizeof seed, 1, urandom);
183 gettimeofday(&tv, NULL);
189 CtdlLogPrintf(CTDL_INFO, "Initializing ipgm secret\n");
191 config.c_ipgm_secret = rand();
194 CtdlLogPrintf(CTDL_DEBUG, "master_startup() finished\n");
199 * Cleanup routine to be called when the server is shutting down.
201 void master_cleanup(int exitcode) {
202 struct CleanupFunctionHook *fcn;
203 static int already_cleaning_up = 0;
205 if (already_cleaning_up) while(1) sleep(1);
206 already_cleaning_up = 1;
208 /* Run any cleanup routines registered by loadable modules */
209 for (fcn = CleanupHookTable; fcn != NULL; fcn = fcn->next) {
210 (*fcn->h_function_pointer)();
213 /* Close the AdjRefCount queue file */
216 /* Do system-dependent stuff */
217 sysdep_master_cleanup();
219 /* Close databases */
220 CtdlLogPrintf(CTDL_INFO, "Closing databases\n");
223 #ifdef DEBUG_MEMORY_LEAKS
227 /* If the operator requested a halt but not an exit, halt here. */
228 if (shutdown_and_halt) {
229 CtdlLogPrintf(CTDL_NOTICE, "citserver: Halting server without exiting.\n");
230 fflush(stdout); fflush(stderr);
239 CtdlLogPrintf(CTDL_NOTICE, "citserver: Exiting with status %d\n", exitcode);
240 fflush(stdout); fflush(stderr);
242 if (restart_server != 0)
244 if ((running_as_daemon != 0) && ((exitcode == 0) ))
245 exitcode = CTDLEXIT_SHUTDOWN;
252 * cmd_info() - tell the client about this server
254 void cmd_info(char *cmdbuf) {
255 cprintf("%d Server info:\n", LISTING_FOLLOWS);
256 cprintf("%d\n", CC->cs_pid);
257 cprintf("%s\n", config.c_nodename);
258 cprintf("%s\n", config.c_humannode);
259 cprintf("%s\n", config.c_fqdn);
260 cprintf("%s\n", CITADEL);
261 cprintf("%d\n", REV_LEVEL);
262 cprintf("%s\n", config.c_site_location);
263 cprintf("%s\n", config.c_sysadm);
264 cprintf("%d\n", SERVER_TYPE);
265 cprintf("%s\n", config.c_moreprompt);
266 cprintf("1\n"); /* 1 = yes, this system supports floors */
267 cprintf("1\n"); /* 1 = we support the extended paging options */
268 cprintf("%s\n", CC->cs_nonce);
269 cprintf("1\n"); /* 1 = yes, this system supports the QNOP command */
272 cprintf("1\n"); /* 1 = yes, this server is LDAP-enabled */
274 cprintf("0\n"); /* 1 = no, this server is not LDAP-enabled */
277 if (config.c_auth_mode == AUTHMODE_NATIVE) {
278 cprintf("%d\n", config.c_disable_newu);
281 cprintf("1\n"); /* "create new user" does not work with non-native auth modes */
284 cprintf("%s\n", config.c_default_cal_zone);
286 /* Output load averages */
287 cprintf("%f\n", CtdlThreadLoadAvg);
288 cprintf("%f\n", CtdlThreadWorkerAvg);
289 cprintf("%d\n", CtdlThreadGetCount());
291 cprintf("1\n"); /* yes, Sieve mail filtering is supported */
292 cprintf("%d\n", config.c_enable_fulltext);
293 cprintf("%s\n", svn_revision());
295 if (config.c_auth_mode == AUTHMODE_NATIVE) {
296 cprintf("1\n"); /* OpenID is enabled when using native auth */
299 cprintf("0\n"); /* OpenID is disabled when using non-native auth */
307 * returns an asterisk if there are any instant messages waiting,
310 char CtdlCheckExpress(void) {
311 if (CC->FirstExpressMessage == NULL) {
319 void cmd_time(char *argbuf)
325 localtime_r(&tv, &tmp);
327 /* timezone and daylight global variables are not portable. */
328 #ifdef HAVE_STRUCT_TM_TM_GMTOFF
329 cprintf("%d %ld|%ld|%d\n", CIT_OK, (long)tv, tmp.tm_gmtoff, tmp.tm_isdst);
331 cprintf("%d %ld|%ld|%d\n", CIT_OK, (long)tv, timezone, tmp.tm_isdst);
337 * Check originating host against the public_clients file. This determines
338 * whether the client is allowed to change the hostname for this session
339 * (for example, to show the location of the user rather than the location
342 int is_public_client(void)
348 char *public_clientspos;
349 char *public_clientsend;
352 static time_t pc_timestamp = 0;
353 static char public_clients[SIZ];
354 static char public_clients_file[SIZ];
356 #define LOCALHOSTSTR "127.0.0.1"
358 snprintf(public_clients_file, sizeof public_clients_file, "%s/public_clients", ctdl_etc_dir);
361 * Check the time stamp on the public_clients file. If it's been
362 * updated since the last time we were here (or if this is the first
363 * time we've been through the loop), read its contents and learn
364 * the IP addresses of the listed hosts.
366 if (stat(public_clients_file, &statbuf) != 0) {
367 /* No public_clients file exists, so bail out */
368 CtdlLogPrintf(CTDL_WARNING, "Warning: '%s' does not exist\n",
369 public_clients_file);
373 if (statbuf.st_mtime > pc_timestamp) {
374 begin_critical_section(S_PUBLIC_CLIENTS);
375 CtdlLogPrintf(CTDL_INFO, "Loading %s\n", public_clients_file);
377 public_clientspos = &public_clients[0];
378 public_clientsend = public_clientspos + SIZ;
379 safestrncpy(public_clientspos, LOCALHOSTSTR, sizeof public_clients);
380 public_clientspos += sizeof(LOCALHOSTSTR) - 1;
382 if (hostname_to_dotted_quad(addrbuf, config.c_fqdn) == 0) {
383 *(public_clientspos++) = '|';
385 while (!IsEmptyStr (paddr) &&
386 (public_clientspos < public_clientsend))
387 *(public_clientspos++) = *(paddr++);
390 fp = fopen(public_clients_file, "r");
392 while ((fgets(buf, sizeof buf, fp)!=NULL) &&
393 (public_clientspos < public_clientsend)){
396 while (!IsEmptyStr(ptr)) {
404 while (ptr>buf && isspace(*ptr)) {
407 if (hostname_to_dotted_quad(addrbuf, buf) == 0) {
408 *(public_clientspos++) = '|';
410 while (!IsEmptyStr(paddr) &&
411 (public_clientspos < public_clientsend)){
412 *(public_clientspos++) = *(paddr++);
417 pc_timestamp = time(NULL);
418 end_critical_section(S_PUBLIC_CLIENTS);
421 CtdlLogPrintf(CTDL_DEBUG, "Checking whether %s is a local or public client\n",
423 for (i=0; i<num_parms(public_clients); ++i) {
424 extract_token(addrbuf, public_clients, i, '|', sizeof addrbuf);
425 if (!strcasecmp(CC->cs_addr, addrbuf)) {
426 CtdlLogPrintf(CTDL_DEBUG, "... yes it is.\n");
431 /* No hits. This is not a public client. */
432 CtdlLogPrintf(CTDL_DEBUG, "... no it isn't.\n");
438 * the client is identifying itself to the server
440 void cmd_iden(char *argbuf)
450 if (num_parms(argbuf)<4) {
451 cprintf("%d usage error\n", ERROR + ILLEGAL_VALUE);
455 dev_code = extract_int(argbuf,0);
456 cli_code = extract_int(argbuf,1);
457 rev_level = extract_int(argbuf,2);
458 extract_token(desc, argbuf, 3, '|', sizeof desc);
460 safestrncpy(from_host, config.c_fqdn, sizeof from_host);
461 from_host[sizeof from_host - 1] = 0;
462 if (num_parms(argbuf)>=5) extract_token(from_host, argbuf, 4, '|', sizeof from_host);
464 CC->cs_clientdev = dev_code;
465 CC->cs_clienttyp = cli_code;
466 CC->cs_clientver = rev_level;
467 safestrncpy(CC->cs_clientname, desc, sizeof CC->cs_clientname);
468 CC->cs_clientname[31] = 0;
470 if (!IsEmptyStr(from_host)) {
471 if (CC->is_local_socket) do_lookup = 1;
472 else if (is_public_client()) do_lookup = 1;
476 CtdlLogPrintf(CTDL_DEBUG, "Looking up hostname '%s'\n", from_host);
477 if ((addr.s_addr = inet_addr(from_host)) != -1) {
478 locate_host(CC->cs_host, sizeof CC->cs_host,
479 CC->cs_addr, sizeof CC->cs_addr,
483 safestrncpy(CC->cs_host, from_host, sizeof CC->cs_host);
484 CC->cs_host[sizeof CC->cs_host - 1] = 0;
488 CtdlLogPrintf(CTDL_NOTICE, "Client %d/%d/%01d.%02d (%s) from %s\n",
496 cprintf("%d Ok\n",CIT_OK);
501 * display system messages or help
503 void cmd_mesg(char *mname)
513 extract_token(buf, mname, 0, '|', sizeof buf);
515 dirs[0] = strdup(ctdl_message_dir);
516 dirs[1] = strdup(ctdl_hlp_dir);
518 snprintf(buf2, sizeof buf2, "%s.%d.%d",
519 buf, CC->cs_clientdev, CC->cs_clienttyp);
521 /* If the client requested "?" then produce a listing */
522 if (!strcmp(buf, "?")) {
523 cprintf("%d %s\n", LISTING_FOLLOWS, buf);
524 dp = opendir(dirs[1]);
526 while (d = readdir(dp), d != NULL) {
527 if (d->d_name[0] != '.') {
528 cprintf(" %s\n", d->d_name);
539 /* Otherwise, look for the requested file by name. */
541 mesg_locate(targ, sizeof targ, buf2, 2, (const char **)dirs);
542 if (IsEmptyStr(targ)) {
543 snprintf(buf2, sizeof buf2, "%s.%d",
544 buf, CC->cs_clientdev);
545 mesg_locate(targ, sizeof targ, buf2, 2,
546 (const char **)dirs);
547 if (IsEmptyStr(targ)) {
548 mesg_locate(targ, sizeof targ, buf, 2,
549 (const char **)dirs);
557 if (IsEmptyStr(targ)) {
558 cprintf("%d '%s' not found. (Searching in %s and %s)\n",
559 ERROR + FILE_NOT_FOUND,
567 mfp = fopen(targ, "r");
569 cprintf("%d Cannot open '%s': %s\n",
570 ERROR + INTERNAL_ERROR, targ, strerror(errno));
573 cprintf("%d %s\n", LISTING_FOLLOWS,buf);
575 while (fgets(buf, (sizeof buf - 1), mfp) != NULL) {
576 buf[strlen(buf)-1] = 0;
587 * enter system messages or help
589 void cmd_emsg(char *mname)
599 if (CtdlAccessCheck(ac_aide)) return;
601 extract_token(buf, mname, 0, '|', sizeof buf);
602 for (a=0; !IsEmptyStr(&buf[a]); ++a) { /* security measure */
603 if (buf[a] == '/') buf[a] = '.';
606 dirs[0] = strdup(ctdl_message_dir);
607 dirs[1] = strdup(ctdl_hlp_dir);
609 mesg_locate(targ, sizeof targ, buf, 2, (const char**)dirs);
613 if (IsEmptyStr(targ)) {
614 snprintf(targ, sizeof targ,
619 mfp = fopen(targ,"w");
621 cprintf("%d Cannot open '%s': %s\n",
622 ERROR + INTERNAL_ERROR, targ, strerror(errno));
625 cprintf("%d %s\n", SEND_LISTING, targ);
627 while (client_getln(buf, sizeof buf) >=0 && strcmp(buf, "000")) {
628 fprintf(mfp, "%s\n", buf);
635 /* Don't show the names of private rooms unless the viewing
636 * user also knows the rooms.
638 void GenerateRoomDisplay(char *real_room,
640 CitContext *viewer) {
644 strcpy(real_room, viewed->room.QRname);
645 if (viewed->room.QRflags & QR_MAILBOX) {
646 strcpy(real_room, &real_room[11]);
648 if (viewed->room.QRflags & QR_PRIVATE) {
649 CtdlRoomAccess(&viewed->room, &viewer->user, &ra, NULL);
650 if ( (ra & UA_KNOWN) == 0) {
651 strcpy(real_room, "<private room>");
655 if (viewed->cs_flags & CS_CHAT) {
656 while (strlen(real_room) < 14) {
657 strcat(real_room, " ");
659 strcpy(&real_room[14], "<chat>");
665 * Convenience function.
667 int CtdlAccessCheck(int required_level) {
669 if (CC->internal_pgm) return(0);
670 if (required_level >= ac_internal) {
671 cprintf("%d This is not a user-level command.\n",
672 ERROR + HIGHER_ACCESS_REQUIRED);
676 if ((required_level >= ac_logged_in) && (CC->logged_in == 0)) {
677 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
681 if (CC->user.axlevel >= AxAideU) return(0);
682 if (required_level >= ac_aide) {
683 cprintf("%d This command requires Aide access.\n",
684 ERROR + HIGHER_ACCESS_REQUIRED);
688 if (is_room_aide()) return(0);
689 if (required_level >= ac_room_aide) {
690 cprintf("%d This command requires Aide or Room Aide access.\n",
691 ERROR + HIGHER_ACCESS_REQUIRED);
695 /* shhh ... succeed quietly */
702 * Terminate another running session
704 void cmd_term(char *cmdbuf)
709 session_num = extract_int(cmdbuf, 0);
711 terminated = CtdlTerminateOtherSession(session_num);
713 if (terminated < 0) {
714 cprintf("%d You can't kill your own session.\n", ERROR + ILLEGAL_VALUE);
718 if (terminated & TERM_FOUND) {
719 if (terminated == TERM_KILLED) {
720 cprintf("%d Session terminated.\n", CIT_OK);
723 cprintf("%d You are not allowed to do that.\n",
724 ERROR + HIGHER_ACCESS_REQUIRED);
728 cprintf("%d No such session.\n", ERROR + ILLEGAL_VALUE);
734 * get the paginator prompt
736 void cmd_more(char *argbuf) {
737 cprintf("%d %s\n", CIT_OK, config.c_moreprompt);
744 void cmd_echo(char *etext)
746 cprintf("%d %s\n", CIT_OK, etext);
751 * Perform privilege escalation for an internal program
753 void cmd_ipgm(char *argbuf)
757 secret = extract_int(argbuf, 0);
759 /* For security reasons, we do NOT allow this command to run
760 * over the network. Local sockets only.
762 if (!CC->is_local_socket) {
764 cprintf("%d Authentication failed.\n", ERROR + PASSWORD_REQUIRED);
766 else if (secret == config.c_ipgm_secret) {
767 CC->internal_pgm = 1;
768 strcpy(CC->curr_user, "<internal program>");
769 CC->cs_flags = CC->cs_flags|CS_STEALTH;
770 cprintf("%d Authenticated as an internal program.\n", CIT_OK);
774 cprintf("%d Authentication failed.\n", ERROR + PASSWORD_REQUIRED);
775 CtdlLogPrintf(CTDL_ERR, "Warning: ipgm authentication failed.\n");
782 * Shut down the server
784 void cmd_down(char *argbuf) {
785 char *Reply ="%d Shutting down server. Goodbye.\n";
787 if (CtdlAccessCheck(ac_aide)) return;
789 if (!IsEmptyStr(argbuf))
792 restart_server = extract_int(argbuf, 0);
794 if (restart_server > 0)
796 Reply = "%d citserver will now shut down and automatically restart.\n";
798 if ((restart_server > 0) && !running_as_daemon)
800 CtdlLogPrintf(CTDL_ERR, "The user requested restart, but not running as daemon! Geronimooooooo!\n");
801 Reply = "%d Warning: citserver is not running in daemon mode and is therefore unlikely to restart automatically.\n";
804 cprintf(Reply, state);
808 cprintf(Reply, CIT_OK + SERVER_SHUTTING_DOWN);
810 CC->kill_me = 1; /* Even the DOWN command has to follow correct proceedure when disconecting */
816 * Halt the server without exiting the server process.
818 void cmd_halt(char *argbuf) {
820 if (CtdlAccessCheck(ac_aide)) return;
822 cprintf("%d Halting server. Goodbye.\n", CIT_OK);
824 shutdown_and_halt = 1;
829 * Schedule or cancel a server shutdown
831 void cmd_scdn(char *argbuf)
835 char *Reply = "%d %d\n";
837 if (CtdlAccessCheck(ac_aide)) return;
839 new_state = extract_int(argbuf, 0);
840 if ((new_state == 2) || (new_state == 3))
843 if (!running_as_daemon)
845 CtdlLogPrintf(CTDL_ERR, "The user requested restart, but not running as deamon! Geronimooooooo!\n");
846 Reply = "%d %d Warning, not running in deamon mode. maybe we will come up again, but don't lean on it.\n";
850 restart_server = extract_int(argbuf, 0);
853 if ((new_state == 0) || (new_state == 1)) {
854 ScheduledShutdown = new_state;
856 cprintf(Reply, state, ScheduledShutdown);
861 * Set or unset asynchronous protocol mode
863 void cmd_asyn(char *argbuf)
867 new_state = extract_int(argbuf, 0);
868 if ((new_state == 0) || (new_state == 1)) {
869 CC->is_async = new_state;
871 cprintf("%d %d\n", CIT_OK, CC->is_async);
876 * Generate a "nonce" for APOP-style authentication.
878 * RFC 1725 et al specify a PID to be placed in front of the nonce.
879 * Quoth BTX: That would be stupid.
881 void generate_nonce(CitContext *con) {
884 memset(con->cs_nonce, NONCE_SIZE, 0);
885 gettimeofday(&tv, NULL);
886 memset(con->cs_nonce, NONCE_SIZE, 0);
887 snprintf(con->cs_nonce, NONCE_SIZE, "<%d%ld@%s>",
888 rand(), (long)tv.tv_usec, config.c_fqdn);
893 * Back-end function for starting a session
895 void begin_session(CitContext *con)
898 struct sockaddr_in sin;
901 * Initialize some variables specific to our context.
904 con->internal_pgm = 0;
905 con->download_fp = NULL;
906 con->upload_fp = NULL;
907 con->FirstExpressMessage = NULL;
909 time(&con->lastidle);
910 strcpy(con->lastcmdname, " ");
911 strcpy(con->cs_clientname, "(unknown)");
912 strcpy(con->curr_user, NLI);
913 *con->net_node = '\0';
914 *con->fake_username = '\0';
915 *con->fake_hostname = '\0';
916 *con->fake_roomname = '\0';
918 safestrncpy(con->cs_host, config.c_fqdn, sizeof con->cs_host);
919 safestrncpy(con->cs_addr, "", sizeof con->cs_addr);
920 con->cs_UDSclientUID = -1;
921 con->cs_host[sizeof con->cs_host - 1] = 0;
923 if (!CC->is_local_socket) {
924 if (!getpeername(con->client_socket, (struct sockaddr *) &sin, &len)) {
925 locate_host(con->cs_host, sizeof con->cs_host,
926 con->cs_addr, sizeof con->cs_addr,
932 strcpy(con->cs_host, "");
933 #ifdef HAVE_STRUCT_UCRED
935 /* as http://www.wsinnovations.com/softeng/articles/uds.html told us... */
936 struct ucred credentials;
937 socklen_t ucred_length = sizeof(struct ucred);
939 /*fill in the user data structure */
940 if(getsockopt(con->client_socket, SOL_SOCKET, SO_PEERCRED, &credentials, &ucred_length)) {
941 CtdlLogPrintf(CTDL_NOTICE, "could obtain credentials from unix domain socket");
945 /* the process ID of the process on the other side of the socket */
946 /* credentials.pid; */
948 /* the effective UID of the process on the other side of the socket */
949 con->cs_UDSclientUID = credentials.uid;
951 /* the effective primary GID of the process on the other side of the socket */
952 /* credentials.gid; */
954 /* To get supplemental groups, we will have to look them up in our account
955 database, after a reverse lookup on the UID to get the account name.
956 We can take this opportunity to check to see if this is a legit account.
963 con->upload_type = UPL_FILE;
967 if (((config.c_maxsessions > 0)&&(num_sessions > config.c_maxsessions)) || CtdlWantSingleUser()) {
971 if (!CC->is_local_socket) {
972 CtdlLogPrintf(CTDL_NOTICE, "Session started from %s [%s].\n", con->cs_host, con->cs_addr);
975 CtdlLogPrintf(CTDL_NOTICE, "Session started via local socket.\n");
978 /* Run any session startup routines registered by loadable modules */
979 PerformSessionHooks(EVT_START);
983 void citproto_begin_session() {
984 if (CC->nologin==1) {
985 cprintf("%d %s: Too many users are already online (maximum is %d)\n",
986 ERROR + MAX_SESSIONS_EXCEEDED,
987 config.c_nodename, config.c_maxsessions
992 cprintf("%d %s Citadel server ready.\n", CIT_OK, config.c_nodename);
993 CC->can_receive_im = 1;
998 void cmd_noop(char *argbuf)
1000 cprintf("%d%cok\n", CIT_OK, CtdlCheckExpress() );
1004 void cmd_qnop(char *argbuf)
1006 /* do nothing, this command returns no response */
1010 void cmd_quit(char *argbuf)
1012 cprintf("%d Goodbye.\n", CIT_OK);
1017 void cmd_lout(char *argbuf)
1021 cprintf("%d logged out.\n", CIT_OK);
1026 * This loop recognizes all server commands.
1028 void do_command_loop(void) {
1030 const char *old_name = NULL;
1032 old_name = CtdlThreadName("do_command_loop");
1035 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
1036 if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
1037 CtdlLogPrintf(CTDL_ERR, "Client disconnected: ending session.\n");
1039 CtdlThreadName(old_name);
1043 /* Log the server command, but don't show passwords... */
1044 if ( (strncasecmp(cmdbuf, "PASS", 4)) && (strncasecmp(cmdbuf, "SETP", 4)) ) {
1045 CtdlLogPrintf(CTDL_INFO, "%s\n", cmdbuf);
1048 CtdlLogPrintf(CTDL_INFO, "<password command hidden from log>\n");
1054 * Let other clients see the last command we executed, and
1055 * update the idle time, but not NOOP, QNOP, PEXP, GEXP, RWHO, or TIME.
1057 if ( (strncasecmp(cmdbuf, "NOOP", 4))
1058 && (strncasecmp(cmdbuf, "QNOP", 4))
1059 && (strncasecmp(cmdbuf, "PEXP", 4))
1060 && (strncasecmp(cmdbuf, "GEXP", 4))
1061 && (strncasecmp(cmdbuf, "RWHO", 4))
1062 && (strncasecmp(cmdbuf, "TIME", 4)) ) {
1063 strcpy(CC->lastcmdname, " ");
1064 safestrncpy(CC->lastcmdname, cmdbuf, sizeof(CC->lastcmdname));
1065 time(&CC->lastidle);
1068 CtdlThreadName(cmdbuf);
1070 if ((strncasecmp(cmdbuf, "ENT0", 4))
1071 && (strncasecmp(cmdbuf, "MESG", 4))
1072 && (strncasecmp(cmdbuf, "MSGS", 4)))
1074 CC->cs_flags &= ~CS_POSTING;
1077 if (!DLoader_Exec_Cmd(cmdbuf)) {
1078 cprintf("%d Unrecognized or unsupported command.\n", ERROR + CMD_NOT_SUPPORTED);
1083 /* Run any after-each-command routines registered by modules */
1084 PerformSessionHooks(EVT_CMD);
1085 CtdlThreadName(old_name);
1090 * This loop performs all asynchronous functions.
1092 void do_async_loop(void) {
1093 PerformSessionHooks(EVT_ASYNC);
1097 /*****************************************************************************/
1098 /* MODULE INITIALIZATION STUFF */
1099 /*****************************************************************************/
1101 CTDL_MODULE_INIT(citserver)
1104 CtdlRegisterProtoHook(cmd_noop, "NOOP", "no operation");
1105 CtdlRegisterProtoHook(cmd_qnop, "QNOP", "no operation with no response");
1106 CtdlRegisterProtoHook(cmd_quit, "QUIT", "log out and disconnect from server");
1107 CtdlRegisterProtoHook(cmd_lout, "LOUT", "log out but do not disconnect from server");
1108 CtdlRegisterProtoHook(cmd_asyn, "ASYN", "enable asynchronous server responses");
1109 CtdlRegisterProtoHook(cmd_info, "INFO", "fetch server capabilities and configuration");
1110 CtdlRegisterProtoHook(cmd_mesg, "MESG", "fetch system banners");
1111 CtdlRegisterProtoHook(cmd_emsg, "EMSG", "submit system banners");
1112 CtdlRegisterProtoHook(cmd_echo, "ECHO", "echo text back to the client");
1113 CtdlRegisterProtoHook(cmd_more, "MORE", "fetch the paginator prompt");
1114 CtdlRegisterProtoHook(cmd_iden, "IDEN", "identify the client software and location");
1115 CtdlRegisterProtoHook(cmd_ipgm, "IPGM", "perform privilege escalation for internal programs");
1116 CtdlRegisterProtoHook(cmd_term, "TERM", "terminate another running session");
1117 CtdlRegisterProtoHook(cmd_down, "DOWN", "perform a server shutdown");
1118 CtdlRegisterProtoHook(cmd_halt, "HALT", "halt the server without exiting the server process");
1119 CtdlRegisterProtoHook(cmd_scdn, "SCDN", "schedule or cancel a server shutdown");
1120 CtdlRegisterProtoHook(cmd_time, "TIME", "fetch the date and time from the server");
1122 /* return our Subversion id for the Log */