4 * This module handles all "real time" communication between users. The
5 * modes of communication currently supported are Chat and Paging.
7 * Copyright (c) 1987-2009 by the citadel.org team
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 3 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
32 #include <sys/types.h>
34 #if TIME_WITH_SYS_TIME
35 # include <sys/time.h>
39 # include <sys/time.h>
48 #include <libcitadel.h>
51 #include "serv_chat.h"
52 #include "citserver.h"
62 #include "ctdl_module.h"
64 struct ChatLine *ChatQueue = NULL;
70 char usernames[2][128];
76 struct imlog *imlist = NULL;
82 * FIXME: OMG this module is realy horrible to the rest of the system when accessing contexts.
83 * It pays no regard at all to how long it may have the context list locked for.
84 * It carries out IO whilst the context list is locked.
85 * I'd recomend disabling this module altogether for the moment.
89 * This function handles the logging of instant messages to disk.
91 void log_instant_message(struct CitContext *me, struct CitContext *them, char *msgtext, int serial_number)
95 struct imlog *iptr = NULL;
96 struct imlog *this_im = NULL;
98 memset(usernums, 0, sizeof usernums);
99 usernums[0] = me->user.usernum;
100 usernums[1] = them->user.usernum;
102 /* Always put the lower user number first, so we can use the array as a hash value which
103 * represents a pair of users. For a broadcast message one of the users will be 0.
105 if (usernums[0] > usernums[1]) {
107 usernums[0] = usernums[1];
111 begin_critical_section(S_IM_LOGS);
113 /* Look for an existing conversation in the hash table.
114 * If not found, create a new one.
118 for (iptr = imlist; iptr != NULL; iptr = iptr->next) {
119 if ((iptr->usernums[0] == usernums[0]) && (iptr->usernums[1] == usernums[1])) {
120 /* Existing conversation */
124 if (this_im == NULL) {
125 /* New conversation */
126 this_im = malloc(sizeof(struct imlog));
127 memset(this_im, 0, sizeof (struct imlog));
128 this_im->usernums[0] = usernums[0];
129 this_im->usernums[1] = usernums[1];
130 /* usernames[] and usernums[] might not be in the same order. This is not an error. */
132 safestrncpy(this_im->usernames[0], me->user.fullname, sizeof this_im->usernames[0]);
135 safestrncpy(this_im->usernames[1], them->user.fullname, sizeof this_im->usernames[1]);
137 this_im->conversation = NewStrBuf();
138 this_im->next = imlist;
140 StrBufAppendBufPlain(this_im->conversation, HKEY(
141 "Content-type: text/html\r\n"
142 "Content-transfer-encoding: 7bit\r\n"
149 /* Since it's possible for this function to get called more than once if a user is logged
150 * in on multiple sessions, we use the message's serial number to keep track of whether
151 * we've already logged it.
153 if (this_im->last_serial != serial_number)
155 this_im->lastmsg = time(NULL); /* Touch the timestamp so we know when to flush */
156 this_im->last_serial = serial_number;
157 StrBufAppendBufPlain(this_im->conversation, HKEY("<p><b>"), 0);
158 StrBufAppendBufPlain(this_im->conversation, me->user.fullname, -1, 0);
159 StrBufAppendBufPlain(this_im->conversation, HKEY(":</b> "), 0);
160 StrEscAppend(this_im->conversation, NULL, msgtext, 0, 0);
161 StrBufAppendBufPlain(this_im->conversation, HKEY("</p>\r\n"), 0);
163 end_critical_section(S_IM_LOGS);
167 * This message can be set to anything you want, but it is
168 * checked for consistency so don't move it away from here.
170 #define KICKEDMSG "You have been kicked out of this room."
172 void allwrite(char *cmdbuf, int flag, char *username)
177 struct ChatLine *clptr, *clnew;
180 if (CC->fake_username[0])
181 un = CC->fake_username;
183 un = CC->user.fullname;
185 snprintf(bcast, sizeof bcast, ":|<%s %s>", un, cmdbuf);
186 } else if (flag == 0) {
187 snprintf(bcast, sizeof bcast, "%s|%s", un, cmdbuf);
188 } else if (flag == 2) {
189 snprintf(bcast, sizeof bcast, ":|<%s whispers %s>", un, cmdbuf);
190 } else if (flag == 3) {
191 snprintf(bcast, sizeof bcast, ":|%s", KICKEDMSG);
193 if ((strcasecmp(cmdbuf, "NOOP")) && (flag != 2)) {
194 fp = fopen(CHATLOG, "a");
196 fprintf(fp, "%s\n", bcast);
199 clnew = (struct ChatLine *) malloc(sizeof(struct ChatLine));
200 memset(clnew, 0, sizeof(struct ChatLine));
202 fprintf(stderr, "citserver: cannot alloc chat line: %s\n",
208 clnew->chat_time = now;
209 safestrncpy(clnew->chat_room, CC->room.QRname,
210 sizeof clnew->chat_room);
211 clnew->chat_room[sizeof clnew->chat_room - 1] = 0;
213 safestrncpy(clnew->chat_username, username,
214 sizeof clnew->chat_username);
215 clnew->chat_username[sizeof clnew->chat_username - 1] = 0;
217 clnew->chat_username[0] = '\0';
218 safestrncpy(clnew->chat_text, bcast, sizeof clnew->chat_text);
220 /* Here's the critical section.
221 * First, add the new message to the queue...
223 begin_critical_section(S_CHATQUEUE);
225 clnew->chat_seq = ChatLastMsg;
226 if (ChatQueue == NULL) {
229 for (clptr = ChatQueue; clptr->next != NULL; clptr = clptr->next);;
233 /* Then, before releasing the lock, free the expired messages */
234 while ((ChatQueue != NULL) && (now - ChatQueue->chat_time >= 120L)) {
236 ChatQueue = ChatQueue->next;
239 end_critical_section(S_CHATQUEUE);
243 CitContext *find_context(char **unstr)
245 CitContext *t_cc, *found_cc = NULL;
248 if ((!*unstr) || (!unstr))
251 begin_critical_section(S_SESSION_TABLE);
252 for (t_cc = ContextList; ((t_cc) && (!found_cc)); t_cc = t_cc->next) {
253 if (t_cc->fake_username[0])
254 name = t_cc->fake_username;
256 name = t_cc->curr_user;
258 if ((!strncasecmp(name, tptr, strlen(name))) && (tptr[strlen(name)] == ' ')) {
260 *unstr = &(tptr[strlen(name) + 1]);
263 end_critical_section(S_SESSION_TABLE);
269 * List users in chat.
270 * allflag == 0 = list users in chat
271 * 1 = list users in chat, followed by users not in chat
272 * 2 = display count only
275 void do_chat_listing(int allflag)
277 struct CitContext *ccptr;
279 int count_elsewhere = 0;
280 char roomname[ROOMNAMELEN];
282 if ((allflag == 0) || (allflag == 1))
283 cprintf(":|\n:| Users currently in chat:\n");
284 begin_critical_section(S_SESSION_TABLE);
285 for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
286 if (ccptr->cs_flags & CS_CHAT) {
287 if (!strcasecmp(ccptr->room.QRname,
296 GenerateRoomDisplay(roomname, ccptr, CC);
297 if ((CC->user.axlevel < 6) && (!IsEmptyStr(ccptr->fake_roomname))) {
298 strcpy(roomname, ccptr->fake_roomname);
301 if ((ccptr->cs_flags & CS_CHAT) && ((ccptr->cs_flags & CS_STEALTH) == 0)) {
302 if ((allflag == 0) || (allflag == 1)) {
303 cprintf(":| %-25s <%s>:\n",
304 (ccptr->fake_username[0]) ? ccptr->fake_username : ccptr->curr_user,
311 cprintf(":|\n:| Users not in chat:\n");
312 for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
314 GenerateRoomDisplay(roomname, ccptr, CC);
315 if ((CC->user.axlevel < 6)
316 && (!IsEmptyStr(ccptr->fake_roomname))) {
317 strcpy(roomname, ccptr->fake_roomname);
320 if (((ccptr->cs_flags & CS_CHAT) == 0)
321 && ((ccptr->cs_flags & CS_STEALTH) == 0)) {
322 cprintf(":| %-25s <%s>:\n",
323 (ccptr->fake_username[0]) ? ccptr->fake_username : ccptr->curr_user,
328 end_critical_section(S_SESSION_TABLE);
332 cprintf(":|There are %d users here.\n", count);
335 cprintf(":|Note: you are the only one here.\n");
337 if (count_elsewhere > 0) {
338 cprintf(":|There are %d users chatting in other rooms.\n", count_elsewhere);
346 void cmd_chat(char *argbuf)
351 int MyLastMsg, ThisLastMsg;
352 struct ChatLine *clptr;
353 struct CitContext *t_context;
356 if (!(CC->logged_in)) {
357 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
361 CC->cs_flags = CC->cs_flags | CS_CHAT;
362 cprintf("%d Entering chat mode (type '/help' for available commands)\n",
366 MyLastMsg = ChatLastMsg;
368 if ((CC->cs_flags & CS_STEALTH) == 0) {
369 allwrite("<entering chat>", 0, NULL);
380 linelen = strlen(cmdbuf);
381 if (linelen > 100) --linelen; /* truncate too-long lines */
382 cmdbuf[linelen + 1] = 0;
384 retval = client_read_to(&cmdbuf[linelen], 1, 2);
386 if (retval < 0 || CC->kill_me) { /* socket broken? */
387 if ((CC->cs_flags & CS_STEALTH) == 0) {
388 allwrite("<disconnected>", 0, NULL);
393 /* if we have a complete line, do send processing */
394 if (!IsEmptyStr(cmdbuf))
395 if (cmdbuf[strlen(cmdbuf) - 1] == 10) {
396 cmdbuf[strlen(cmdbuf) - 1] = 0;
400 if ((!strcasecmp(cmdbuf, "exit"))
401 || (!strcasecmp(cmdbuf, "/exit"))
402 || (!strcasecmp(cmdbuf, "quit"))
403 || (!strcasecmp(cmdbuf, "logout"))
404 || (!strcasecmp(cmdbuf, "logoff"))
405 || (!strcasecmp(cmdbuf, "/q"))
406 || (!strcasecmp(cmdbuf, ".q"))
407 || (!strcasecmp(cmdbuf, "/quit"))
409 strcpy(cmdbuf, "000");
411 if (!strcmp(cmdbuf, "000")) {
412 if ((CC->cs_flags & CS_STEALTH) == 0) {
413 allwrite("<exiting chat>", 0, NULL);
417 CC->cs_flags = CC->cs_flags - CS_CHAT;
420 if ((!strcasecmp(cmdbuf, "/help"))
421 || (!strcasecmp(cmdbuf, "help"))
422 || (!strcasecmp(cmdbuf, "/?"))
423 || (!strcasecmp(cmdbuf, "?"))) {
425 cprintf(":|Available commands: \n");
426 cprintf(":|/help (prints this message) \n");
427 cprintf(":|/who (list users currently in chat) \n");
428 cprintf(":|/whobbs (list users in chat -and- elsewhere) \n");
429 cprintf(":|/me ('action' line, ala irc) \n");
430 cprintf(":|/msg (send private message, ala irc) \n");
431 if (is_room_aide()) {
432 cprintf(":|/kick (kick another user out of this room) \n");
434 cprintf(":|/quit (exit from this chat) \n");
438 if (!strcasecmp(cmdbuf, "/who")) {
442 if (!strcasecmp(cmdbuf, "/whobbs")) {
446 if (!strncasecmp(cmdbuf, "/me ", 4)) {
447 allwrite(&cmdbuf[4], 1, NULL);
450 if (!strncasecmp(cmdbuf, "/msg ", 5)) {
452 strptr1 = &cmdbuf[5];
453 if ((t_context = find_context(&strptr1))) {
454 allwrite(strptr1, 2, CC->curr_user);
455 if (strcasecmp(CC->curr_user, t_context->curr_user))
456 allwrite(strptr1, 2, t_context->curr_user);
458 cprintf(":|User not found.\n");
461 /* The /kick function is implemented by sending a specific
462 * message to the kicked-out user's context. When that message
463 * is processed by the read loop, that context will exit.
465 if ( (!strncasecmp(cmdbuf, "/kick ", 6)) && (is_room_aide()) ) {
467 strptr1 = &cmdbuf[6];
468 strcat(strptr1, " ");
469 if ((t_context = find_context(&strptr1))) {
470 if (strcasecmp(CC->curr_user, t_context->curr_user))
471 allwrite(strptr1, 3, t_context->curr_user);
473 cprintf(":|User not found.\n");
476 if ((cmdbuf[0] != '/') && (strlen(cmdbuf) > 0)) {
478 allwrite(cmdbuf, 0, NULL);
480 if ((!ok_cmd) && (cmdbuf[0]) && (cmdbuf[0] != '\n'))
481 cprintf(":|Command %s is not understood.\n", cmdbuf);
486 /* now check the queue for new incoming stuff */
488 if (CC->fake_username[0])
489 un = CC->fake_username;
492 if (ChatLastMsg > MyLastMsg) {
493 ThisLastMsg = ChatLastMsg;
494 for (clptr = ChatQueue; clptr != NULL; clptr = clptr->next) {
495 if ((clptr->chat_seq > MyLastMsg) && ((!clptr->chat_username[0]) || (!strncasecmp(un, clptr->chat_username, 32)))) {
496 if ((!clptr->chat_room[0]) || (!strncasecmp(CC->room.QRname, clptr->chat_room, ROOMNAMELEN))) {
497 /* Output new chat data */
498 cprintf("%s\n", clptr->chat_text);
500 /* See if we've been force-quitted (kicked etc.) */
501 if (!strcmp(&clptr->chat_text[2], KICKEDMSG)) {
502 allwrite("<kicked out of this room>", 0, NULL);
504 CC->cs_flags = CC->cs_flags - CS_CHAT;
506 /* Kick user out of room */
507 CtdlInvtKick(CC->user.fullname, 0);
509 /* And return to the Lobby */
510 CtdlUserGoto(config.c_baseroom, 0, 0, NULL, NULL);
516 MyLastMsg = ThisLastMsg;
524 * Delete any remaining instant messages
526 void delete_instant_messages(void) {
527 struct ExpressMessage *ptr;
529 begin_critical_section(S_SESSION_TABLE);
530 while (CC->FirstExpressMessage != NULL) {
531 ptr = CC->FirstExpressMessage->next;
532 if (CC->FirstExpressMessage->text != NULL)
533 free(CC->FirstExpressMessage->text);
534 free(CC->FirstExpressMessage);
535 CC->FirstExpressMessage = ptr;
537 end_critical_section(S_SESSION_TABLE);
544 * Poll for instant messages (OLD METHOD -- ***DEPRECATED ***)
546 void cmd_pexp(char *argbuf)
548 struct ExpressMessage *ptr, *holdptr;
550 if (CC->FirstExpressMessage == NULL) {
551 cprintf("%d No instant messages waiting.\n", ERROR + MESSAGE_NOT_FOUND);
554 begin_critical_section(S_SESSION_TABLE);
555 ptr = CC->FirstExpressMessage;
556 CC->FirstExpressMessage = NULL;
557 end_critical_section(S_SESSION_TABLE);
559 cprintf("%d Express msgs:\n", LISTING_FOLLOWS);
560 while (ptr != NULL) {
561 if (ptr->flags && EM_BROADCAST)
562 cprintf("Broadcast message ");
563 else if (ptr->flags && EM_CHAT)
564 cprintf("Chat request ");
565 else if (ptr->flags && EM_GO_AWAY)
566 cprintf("Please logoff now, as requested ");
569 cprintf("from %s:\n", ptr->sender);
570 if (ptr->text != NULL)
571 memfmout(ptr->text, 0, "\n");
574 if (ptr->text != NULL) free(ptr->text);
583 * Get instant messages (new method)
585 void cmd_gexp(char *argbuf) {
586 struct ExpressMessage *ptr;
588 if (CC->FirstExpressMessage == NULL) {
589 cprintf("%d No instant messages waiting.\n", ERROR + MESSAGE_NOT_FOUND);
593 begin_critical_section(S_SESSION_TABLE);
594 ptr = CC->FirstExpressMessage;
595 CC->FirstExpressMessage = CC->FirstExpressMessage->next;
596 end_critical_section(S_SESSION_TABLE);
598 cprintf("%d %d|%ld|%d|%s|%s|%s\n",
600 ((ptr->next != NULL) ? 1 : 0), /* more msgs? */
601 (long)ptr->timestamp, /* time sent */
602 ptr->flags, /* flags */
603 ptr->sender, /* sender of msg */
604 config.c_nodename, /* static for now (and possibly deprecated) */
605 ptr->sender_email /* email or jid of sender */
608 if (ptr->text != NULL) {
609 memfmout(ptr->text, 0, "\n");
610 if (ptr->text[strlen(ptr->text)-1] != '\n') cprintf("\n");
619 * Asynchronously deliver instant messages
621 void cmd_gexp_async(void) {
623 /* Only do this if the session can handle asynchronous protocol */
624 if (CC->is_async == 0) return;
626 /* And don't do it if there's nothing to send. */
627 if (CC->FirstExpressMessage == NULL) return;
629 cprintf("%d instant msg\n", ASYNC_MSG + ASYNC_GEXP);
633 * Back end support function for send_instant_message() and company
635 void add_xmsg_to_context(struct CitContext *ccptr, struct ExpressMessage *newmsg)
637 struct ExpressMessage *findend;
639 if (ccptr->FirstExpressMessage == NULL) {
640 ccptr->FirstExpressMessage = newmsg;
643 findend = ccptr->FirstExpressMessage;
644 while (findend->next != NULL) {
645 findend = findend->next;
647 findend->next = newmsg;
650 /* If the target context is a session which can handle asynchronous
651 * messages, go ahead and set the flag for that.
653 if (ccptr->is_async) {
654 ccptr->async_waiting = 1;
655 if (ccptr->state == CON_IDLE) {
656 ccptr->state = CON_READY;
665 * This is the back end to the instant message sending function.
666 * Returns the number of users to which the message was sent.
667 * Sending a zero-length message tests for recipients without sending messages.
669 int send_instant_message(char *lun, char *lem, char *x_user, char *x_msg)
671 int message_sent = 0; /* number of successful sends */
672 struct CitContext *ccptr;
673 struct ExpressMessage *newmsg = NULL;
676 int do_send = 0; /* 1 = send message; 0 = only check for valid recipient */
677 static int serial_number = 0; /* this keeps messages from getting logged twice */
679 if (strlen(x_msg) > 0) {
680 msglen = strlen(x_msg) + 4;
684 /* find the target user's context and append the message */
685 begin_critical_section(S_SESSION_TABLE);
687 for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
689 if (ccptr->fake_username[0]) {
690 un = ccptr->fake_username;
693 un = ccptr->user.fullname;
696 if ( ((!strcasecmp(un, x_user))
697 || (!strcasecmp(x_user, "broadcast")))
698 && (ccptr->can_receive_im)
699 && ((ccptr->disable_exp == 0)
700 || (CC->user.axlevel >= 6)) ) {
702 newmsg = (struct ExpressMessage *) malloc(sizeof (struct ExpressMessage));
703 memset(newmsg, 0, sizeof (struct ExpressMessage));
704 time(&(newmsg->timestamp));
705 safestrncpy(newmsg->sender, lun, sizeof newmsg->sender);
706 safestrncpy(newmsg->sender_email, lem, sizeof newmsg->sender_email);
707 if (!strcasecmp(x_user, "broadcast")) {
708 newmsg->flags |= EM_BROADCAST;
710 newmsg->text = strdup(x_msg);
712 add_xmsg_to_context(ccptr, newmsg);
716 log_instant_message(CC, ccptr, newmsg->text, serial_number);
722 end_critical_section(S_SESSION_TABLE);
723 return (message_sent);
727 * send instant messages
729 void cmd_sexp(char *argbuf)
731 int message_sent = 0;
732 char x_user[USERNAME_SIZE];
736 char *x_big_msgbuf = NULL;
738 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
739 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
742 if (CC->fake_username[0])
743 lun = CC->fake_username;
745 lun = CC->user.fullname;
747 lem = CC->cs_inet_email;
749 extract_token(x_user, argbuf, 0, '|', sizeof x_user);
750 extract_token(x_msg, argbuf, 1, '|', sizeof x_msg);
753 cprintf("%d You were not previously paged.\n", ERROR + NO_SUCH_USER);
756 if ((!strcasecmp(x_user, "broadcast")) && (CC->user.axlevel < 6)) {
757 cprintf("%d Higher access required to send a broadcast.\n",
758 ERROR + HIGHER_ACCESS_REQUIRED);
761 /* This loop handles text-transfer pages */
762 if (!strcmp(x_msg, "-")) {
763 message_sent = PerformXmsgHooks(lun, lem, x_user, "");
764 if (message_sent == 0) {
765 if (CtdlGetUser(NULL, x_user))
766 cprintf("%d '%s' does not exist.\n",
767 ERROR + NO_SUCH_USER, x_user);
769 cprintf("%d '%s' is not logged in "
770 "or is not accepting pages.\n",
771 ERROR + RESOURCE_NOT_OPEN, x_user);
775 cprintf("%d Transmit message (will deliver to %d users)\n",
776 SEND_LISTING, message_sent);
777 x_big_msgbuf = malloc(SIZ);
778 memset(x_big_msgbuf, 0, SIZ);
779 while (client_getln(x_msg, sizeof x_msg) >= 0 && strcmp(x_msg, "000")) {
780 x_big_msgbuf = realloc(x_big_msgbuf,
781 strlen(x_big_msgbuf) + strlen(x_msg) + 4);
782 if (!IsEmptyStr(x_big_msgbuf))
783 if (x_big_msgbuf[strlen(x_big_msgbuf)] != '\n')
784 strcat(x_big_msgbuf, "\n");
785 strcat(x_big_msgbuf, x_msg);
787 PerformXmsgHooks(lun, lem, x_user, x_big_msgbuf);
790 /* This loop handles inline pages */
792 message_sent = PerformXmsgHooks(lun, lem, x_user, x_msg);
794 if (message_sent > 0) {
795 if (!IsEmptyStr(x_msg))
796 cprintf("%d Message sent", CIT_OK);
798 cprintf("%d Ok to send message", CIT_OK);
799 if (message_sent > 1)
800 cprintf(" to %d users", message_sent);
803 if (CtdlGetUser(NULL, x_user))
804 cprintf("%d '%s' does not exist.\n",
805 ERROR + NO_SUCH_USER, x_user);
807 cprintf("%d '%s' is not logged in "
808 "or is not accepting pages.\n",
809 ERROR + RESOURCE_NOT_OPEN, x_user);
819 * Enter or exit paging-disabled mode
821 void cmd_dexp(char *argbuf)
825 if (CtdlAccessCheck(ac_logged_in)) return;
827 new_state = extract_int(argbuf, 0);
828 if ((new_state == 0) || (new_state == 1)) {
829 CC->disable_exp = new_state;
832 cprintf("%d %d\n", CIT_OK, CC->disable_exp);
837 * Request client termination
839 void cmd_reqt(char *argbuf) {
840 struct CitContext *ccptr;
843 struct ExpressMessage *newmsg;
845 if (CtdlAccessCheck(ac_aide)) return;
846 which_session = extract_int(argbuf, 0);
848 begin_critical_section(S_SESSION_TABLE);
849 for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
850 if ((ccptr->cs_pid == which_session) || (which_session == 0)) {
852 newmsg = (struct ExpressMessage *)
853 malloc(sizeof (struct ExpressMessage));
855 sizeof (struct ExpressMessage));
856 time(&(newmsg->timestamp));
857 safestrncpy(newmsg->sender, CC->user.fullname,
858 sizeof newmsg->sender);
859 newmsg->flags |= EM_GO_AWAY;
860 newmsg->text = strdup("Automatic logoff requested.");
862 add_xmsg_to_context(ccptr, newmsg);
867 end_critical_section(S_SESSION_TABLE);
868 cprintf("%d Sent termination request to %d sessions.\n", CIT_OK, sessions);
873 * This is the back end for flush_conversations_to_disk()
874 * At this point we've isolated a single conversation (struct imlog)
875 * and are ready to write it to disk.
877 void flush_individual_conversation(struct imlog *im) {
878 struct CtdlMessage *msg;
880 char roomname[ROOMNAMELEN];
882 StrBufAppendBufPlain(im->conversation, HKEY(
888 msg = malloc(sizeof(struct CtdlMessage));
889 memset(msg, 0, sizeof(struct CtdlMessage));
890 msg->cm_magic = CTDLMESSAGE_MAGIC;
891 msg->cm_anon_type = MES_NORMAL;
892 msg->cm_format_type = FMT_RFC822;
893 if (!IsEmptyStr(im->usernames[0])) {
894 msg->cm_fields['A'] = strdup(im->usernames[0]);
896 msg->cm_fields['A'] = strdup("Citadel");
898 if (!IsEmptyStr(im->usernames[1])) {
899 msg->cm_fields['R'] = strdup(im->usernames[1]);
901 msg->cm_fields['O'] = strdup(PAGELOGROOM);
902 msg->cm_fields['N'] = strdup(NODENAME);
903 msg->cm_fields['M'] = SmashStrBuf(&im->conversation); /* we own this memory now */
905 /* Start with usernums[1] because it's guaranteed to be higher than usernums[0],
906 * so if there's only one party, usernums[0] will be zero but usernums[1] won't.
907 * Create the room if necessary. Note that we create as a type 5 room rather
908 * than 4, which indicates that it's a personal room but we've already supplied
909 * the namespace prefix.
911 * In the unlikely event that usernums[1] is zero, a room with an invalid namespace
912 * prefix will be created. That's ok because the auto-purger will clean it up later.
914 snprintf(roomname, sizeof roomname, "%010ld.%s", im->usernums[1], PAGELOGROOM);
915 CtdlCreateRoom(roomname, 5, "", 0, 1, 1, VIEW_BBS);
916 msgnum = CtdlSubmitMsg(msg, NULL, roomname, 0);
917 CtdlFreeMessage(msg);
919 /* If there is a valid user number in usernums[0], save a copy for them too. */
920 if (im->usernums[0] > 0) {
921 snprintf(roomname, sizeof roomname, "%010ld.%s", im->usernums[0], PAGELOGROOM);
922 CtdlCreateRoom(roomname, 5, "", 0, 1, 1, VIEW_BBS);
923 CtdlSaveMsgPointerInRoom(roomname, msgnum, 0, NULL);
926 /* Finally, if we're logging instant messages globally, do that now. */
927 if (!IsEmptyStr(config.c_logpages)) {
928 CtdlCreateRoom(config.c_logpages, 3, "", 0, 1, 1, VIEW_BBS);
929 CtdlSaveMsgPointerInRoom(config.c_logpages, msgnum, 0, NULL);
935 * Locate instant message conversations which have gone idle
936 * (or, if the server is shutting down, locate *all* conversations)
937 * and flush them to disk (in the participants' log rooms, etc.)
939 void flush_conversations_to_disk(time_t if_older_than) {
941 struct imlog *flush_these = NULL;
942 struct imlog *dont_flush_these = NULL;
943 struct imlog *imptr = NULL;
945 begin_critical_section(S_IM_LOGS);
949 imlist = imlist->next;
950 if ((time(NULL) - imptr->lastmsg) > if_older_than)
952 /* This conversation qualifies. Move it to the list of ones to flush. */
953 imptr->next = flush_these;
957 /* Move it to the list of ones not to flush. */
958 imptr->next = dont_flush_these;
959 dont_flush_these = imptr;
962 imlist = dont_flush_these;
963 end_critical_section(S_IM_LOGS);
965 /* We are now outside of the critical section, and we are the only thread holding a
966 * pointer to a linked list of conversations to be flushed to disk.
968 while (flush_these) {
970 flush_individual_conversation(flush_these); /* This will free the string buffer */
972 flush_these = flush_these->next;
979 void chat_timer(void) {
980 flush_conversations_to_disk(300); /* Anything that hasn't peeped in more than 5 minutes */
983 void chat_shutdown(void) {
984 flush_conversations_to_disk(0); /* Get it ALL onto disk NOW. */
987 CTDL_MODULE_INIT(chat)
991 CtdlRegisterProtoHook(cmd_chat, "CHAT", "Begin real-time chat");
992 CtdlRegisterProtoHook(cmd_pexp, "PEXP", "Poll for instant messages");
993 CtdlRegisterProtoHook(cmd_gexp, "GEXP", "Get instant messages");
994 CtdlRegisterProtoHook(cmd_sexp, "SEXP", "Send an instant message");
995 CtdlRegisterProtoHook(cmd_dexp, "DEXP", "Disable instant messages");
996 CtdlRegisterProtoHook(cmd_reqt, "REQT", "Request client termination");
997 CtdlRegisterSessionHook(cmd_gexp_async, EVT_ASYNC);
998 CtdlRegisterSessionHook(delete_instant_messages, EVT_STOP);
999 CtdlRegisterXmsgHook(send_instant_message, XMSG_PRI_LOCAL);
1000 CtdlRegisterSessionHook(chat_timer, EVT_TIMER);
1001 CtdlRegisterSessionHook(chat_shutdown, EVT_SHUTDOWN);
1004 /* return our Subversion id for the Log */