4 * This module handles all "real time" communication between users. The
5 * modes of communication currently supported are Chat and Paging.
16 #include <sys/types.h>
18 #if TIME_WITH_SYS_TIME
19 # include <sys/time.h>
23 # include <sys/time.h>
32 #include <libcitadel.h>
35 #include "serv_chat.h"
36 #include "citserver.h"
47 #include "ctdl_module.h"
49 struct ChatLine *ChatQueue = NULL;
55 char usernames[2][128];
61 struct imlog *imlist = NULL;
64 * This function handles the logging of instant messages to disk.
66 void log_instant_message(struct CitContext *me, struct CitContext *them, char *msgtext, int serial_number)
70 struct imlog *iptr = NULL;
71 struct imlog *this_im = NULL;
73 memset(usernums, 0, sizeof usernums);
74 usernums[0] = me->user.usernum;
75 usernums[1] = them->user.usernum;
77 /* Always put the lower user number first, so we can use the array as a hash value which
78 * represents a pair of users. For a broadcast message one of the users will be 0.
80 if (usernums[0] > usernums[1]) {
82 usernums[0] = usernums[1];
86 begin_critical_section(S_IM_LOGS);
88 /* Look for an existing conversation in the hash table.
89 * If not found, create a new one.
93 for (iptr = imlist; iptr != NULL; iptr = iptr->next) {
94 if ((iptr->usernums[0] == usernums[0]) && (iptr->usernums[1] == usernums[1])) {
95 /* Existing conversation */
99 if (this_im == NULL) {
100 /* New conversation */
101 this_im = malloc(sizeof(struct imlog));
102 memset(this_im, 0, sizeof (struct imlog));
103 this_im->usernums[0] = usernums[0];
104 this_im->usernums[1] = usernums[1];
105 /* usernames[] and usernums[] might not be in the same order. This is not an error. */
107 safestrncpy(this_im->usernames[0], me->user.fullname, sizeof this_im->usernames[0]);
110 safestrncpy(this_im->usernames[1], them->user.fullname, sizeof this_im->usernames[1]);
112 this_im->conversation = NewStrBuf();
113 this_im->next = imlist;
115 StrBufAppendBufPlain(this_im->conversation, HKEY(
116 "Content-type: text/html\r\n"
117 "Content-transfer-encoding: 7bit\r\n"
124 /* Since it's possible for this function to get called more than once if a user is logged
125 * in on multiple sessions, we use the message's serial number to keep track of whether
126 * we've already logged it.
128 if (this_im->last_serial != serial_number)
130 this_im->lastmsg = time(NULL); /* Touch the timestamp so we know when to flush */
131 this_im->last_serial = serial_number;
132 StrBufAppendBufPlain(this_im->conversation, HKEY("<p><b>"), 0);
133 StrBufAppendBufPlain(this_im->conversation, me->user.fullname, -1, 0);
134 StrBufAppendBufPlain(this_im->conversation, HKEY(":</b> "), 0);
135 StrEscAppend(this_im->conversation, NULL, msgtext, 0, 0);
136 StrBufAppendBufPlain(this_im->conversation, HKEY("</p>\r\n"), 0);
138 end_critical_section(S_IM_LOGS);
142 * This message can be set to anything you want, but it is
143 * checked for consistency so don't move it away from here.
145 #define KICKEDMSG "You have been kicked out of this room."
147 void allwrite(char *cmdbuf, int flag, char *username)
152 struct ChatLine *clptr, *clnew;
155 if (CC->fake_username[0])
156 un = CC->fake_username;
158 un = CC->user.fullname;
160 snprintf(bcast, sizeof bcast, ":|<%s %s>", un, cmdbuf);
161 } else if (flag == 0) {
162 snprintf(bcast, sizeof bcast, "%s|%s", un, cmdbuf);
163 } else if (flag == 2) {
164 snprintf(bcast, sizeof bcast, ":|<%s whispers %s>", un, cmdbuf);
165 } else if (flag == 3) {
166 snprintf(bcast, sizeof bcast, ":|%s", KICKEDMSG);
168 if ((strcasecmp(cmdbuf, "NOOP")) && (flag != 2)) {
169 fp = fopen(CHATLOG, "a");
171 fprintf(fp, "%s\n", bcast);
174 clnew = (struct ChatLine *) malloc(sizeof(struct ChatLine));
175 memset(clnew, 0, sizeof(struct ChatLine));
177 fprintf(stderr, "citserver: cannot alloc chat line: %s\n",
183 clnew->chat_time = now;
184 safestrncpy(clnew->chat_room, CC->room.QRname,
185 sizeof clnew->chat_room);
186 clnew->chat_room[sizeof clnew->chat_room - 1] = 0;
188 safestrncpy(clnew->chat_username, username,
189 sizeof clnew->chat_username);
190 clnew->chat_username[sizeof clnew->chat_username - 1] = 0;
192 clnew->chat_username[0] = '\0';
193 safestrncpy(clnew->chat_text, bcast, sizeof clnew->chat_text);
195 /* Here's the critical section.
196 * First, add the new message to the queue...
198 begin_critical_section(S_CHATQUEUE);
200 clnew->chat_seq = ChatLastMsg;
201 if (ChatQueue == NULL) {
204 for (clptr = ChatQueue; clptr->next != NULL; clptr = clptr->next);;
208 /* Then, before releasing the lock, free the expired messages */
209 while ((ChatQueue != NULL) && (now - ChatQueue->chat_time >= 120L)) {
211 ChatQueue = ChatQueue->next;
214 end_critical_section(S_CHATQUEUE);
218 t_context *find_context(char **unstr)
220 t_context *t_cc, *found_cc = NULL;
223 if ((!*unstr) || (!unstr))
226 begin_critical_section(S_SESSION_TABLE);
227 for (t_cc = ContextList; ((t_cc) && (!found_cc)); t_cc = t_cc->next) {
228 if (t_cc->fake_username[0])
229 name = t_cc->fake_username;
231 name = t_cc->curr_user;
233 if ((!strncasecmp(name, tptr, strlen(name))) && (tptr[strlen(name)] == ' ')) {
235 *unstr = &(tptr[strlen(name) + 1]);
238 end_critical_section(S_SESSION_TABLE);
244 * List users in chat.
245 * allflag == 0 = list users in chat
246 * 1 = list users in chat, followed by users not in chat
247 * 2 = display count only
250 void do_chat_listing(int allflag)
252 struct CitContext *ccptr;
254 int count_elsewhere = 0;
255 char roomname[ROOMNAMELEN];
257 if ((allflag == 0) || (allflag == 1))
258 cprintf(":|\n:| Users currently in chat:\n");
259 begin_critical_section(S_SESSION_TABLE);
260 for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
261 if (ccptr->cs_flags & CS_CHAT) {
262 if (!strcasecmp(ccptr->room.QRname,
271 GenerateRoomDisplay(roomname, ccptr, CC);
272 if ((CC->user.axlevel < 6) && (!IsEmptyStr(ccptr->fake_roomname))) {
273 strcpy(roomname, ccptr->fake_roomname);
276 if ((ccptr->cs_flags & CS_CHAT) && ((ccptr->cs_flags & CS_STEALTH) == 0)) {
277 if ((allflag == 0) || (allflag == 1)) {
278 cprintf(":| %-25s <%s>:\n",
279 (ccptr->fake_username[0]) ? ccptr->fake_username : ccptr->curr_user,
286 cprintf(":|\n:| Users not in chat:\n");
287 for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
289 GenerateRoomDisplay(roomname, ccptr, CC);
290 if ((CC->user.axlevel < 6)
291 && (!IsEmptyStr(ccptr->fake_roomname))) {
292 strcpy(roomname, ccptr->fake_roomname);
295 if (((ccptr->cs_flags & CS_CHAT) == 0)
296 && ((ccptr->cs_flags & CS_STEALTH) == 0)) {
297 cprintf(":| %-25s <%s>:\n",
298 (ccptr->fake_username[0]) ? ccptr->fake_username : ccptr->curr_user,
303 end_critical_section(S_SESSION_TABLE);
307 cprintf(":|There are %d users here.\n", count);
310 cprintf(":|Note: you are the only one here.\n");
312 if (count_elsewhere > 0) {
313 cprintf(":|There are %d users chatting in other rooms.\n", count_elsewhere);
321 void cmd_chat(char *argbuf)
326 int MyLastMsg, ThisLastMsg;
327 struct ChatLine *clptr;
328 struct CitContext *t_context;
331 if (!(CC->logged_in)) {
332 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
336 CC->cs_flags = CC->cs_flags | CS_CHAT;
337 cprintf("%d Entering chat mode (type '/help' for available commands)\n",
341 MyLastMsg = ChatLastMsg;
343 if ((CC->cs_flags & CS_STEALTH) == 0) {
344 allwrite("<entering chat>", 0, NULL);
355 linelen = strlen(cmdbuf);
356 if (linelen > 100) --linelen; /* truncate too-long lines */
357 cmdbuf[linelen + 1] = 0;
359 retval = client_read_to(&cmdbuf[linelen], 1, 2);
361 if (retval < 0 || CC->kill_me) { /* socket broken? */
362 if ((CC->cs_flags & CS_STEALTH) == 0) {
363 allwrite("<disconnected>", 0, NULL);
368 /* if we have a complete line, do send processing */
369 if (!IsEmptyStr(cmdbuf))
370 if (cmdbuf[strlen(cmdbuf) - 1] == 10) {
371 cmdbuf[strlen(cmdbuf) - 1] = 0;
375 if ((!strcasecmp(cmdbuf, "exit"))
376 || (!strcasecmp(cmdbuf, "/exit"))
377 || (!strcasecmp(cmdbuf, "quit"))
378 || (!strcasecmp(cmdbuf, "logout"))
379 || (!strcasecmp(cmdbuf, "logoff"))
380 || (!strcasecmp(cmdbuf, "/q"))
381 || (!strcasecmp(cmdbuf, ".q"))
382 || (!strcasecmp(cmdbuf, "/quit"))
384 strcpy(cmdbuf, "000");
386 if (!strcmp(cmdbuf, "000")) {
387 if ((CC->cs_flags & CS_STEALTH) == 0) {
388 allwrite("<exiting chat>", 0, NULL);
392 CC->cs_flags = CC->cs_flags - CS_CHAT;
395 if ((!strcasecmp(cmdbuf, "/help"))
396 || (!strcasecmp(cmdbuf, "help"))
397 || (!strcasecmp(cmdbuf, "/?"))
398 || (!strcasecmp(cmdbuf, "?"))) {
400 cprintf(":|Available commands: \n");
401 cprintf(":|/help (prints this message) \n");
402 cprintf(":|/who (list users currently in chat) \n");
403 cprintf(":|/whobbs (list users in chat -and- elsewhere) \n");
404 cprintf(":|/me ('action' line, ala irc) \n");
405 cprintf(":|/msg (send private message, ala irc) \n");
406 if (is_room_aide()) {
407 cprintf(":|/kick (kick another user out of this room) \n");
409 cprintf(":|/quit (exit from this chat) \n");
413 if (!strcasecmp(cmdbuf, "/who")) {
417 if (!strcasecmp(cmdbuf, "/whobbs")) {
421 if (!strncasecmp(cmdbuf, "/me ", 4)) {
422 allwrite(&cmdbuf[4], 1, NULL);
425 if (!strncasecmp(cmdbuf, "/msg ", 5)) {
427 strptr1 = &cmdbuf[5];
428 if ((t_context = find_context(&strptr1))) {
429 allwrite(strptr1, 2, CC->curr_user);
430 if (strcasecmp(CC->curr_user, t_context->curr_user))
431 allwrite(strptr1, 2, t_context->curr_user);
433 cprintf(":|User not found.\n");
436 /* The /kick function is implemented by sending a specific
437 * message to the kicked-out user's context. When that message
438 * is processed by the read loop, that context will exit.
440 if ( (!strncasecmp(cmdbuf, "/kick ", 6)) && (is_room_aide()) ) {
442 strptr1 = &cmdbuf[6];
443 strcat(strptr1, " ");
444 if ((t_context = find_context(&strptr1))) {
445 if (strcasecmp(CC->curr_user, t_context->curr_user))
446 allwrite(strptr1, 3, t_context->curr_user);
448 cprintf(":|User not found.\n");
451 if ((cmdbuf[0] != '/') && (strlen(cmdbuf) > 0)) {
453 allwrite(cmdbuf, 0, NULL);
455 if ((!ok_cmd) && (cmdbuf[0]) && (cmdbuf[0] != '\n'))
456 cprintf(":|Command %s is not understood.\n", cmdbuf);
461 /* now check the queue for new incoming stuff */
463 if (CC->fake_username[0])
464 un = CC->fake_username;
467 if (ChatLastMsg > MyLastMsg) {
468 ThisLastMsg = ChatLastMsg;
469 for (clptr = ChatQueue; clptr != NULL; clptr = clptr->next) {
470 if ((clptr->chat_seq > MyLastMsg) && ((!clptr->chat_username[0]) || (!strncasecmp(un, clptr->chat_username, 32)))) {
471 if ((!clptr->chat_room[0]) || (!strncasecmp(CC->room.QRname, clptr->chat_room, ROOMNAMELEN))) {
472 /* Output new chat data */
473 cprintf("%s\n", clptr->chat_text);
475 /* See if we've been force-quitted (kicked etc.) */
476 if (!strcmp(&clptr->chat_text[2], KICKEDMSG)) {
477 allwrite("<kicked out of this room>", 0, NULL);
479 CC->cs_flags = CC->cs_flags - CS_CHAT;
481 /* Kick user out of room */
482 CtdlInvtKick(CC->user.fullname, 0);
484 /* And return to the Lobby */
485 usergoto(config.c_baseroom, 0, 0, NULL, NULL);
491 MyLastMsg = ThisLastMsg;
499 * Delete any remaining instant messages
501 void delete_instant_messages(void) {
502 struct ExpressMessage *ptr;
504 begin_critical_section(S_SESSION_TABLE);
505 while (CC->FirstExpressMessage != NULL) {
506 ptr = CC->FirstExpressMessage->next;
507 if (CC->FirstExpressMessage->text != NULL)
508 free(CC->FirstExpressMessage->text);
509 free(CC->FirstExpressMessage);
510 CC->FirstExpressMessage = ptr;
512 end_critical_section(S_SESSION_TABLE);
519 * Poll for instant messages (OLD METHOD -- ***DEPRECATED ***)
521 void cmd_pexp(char *argbuf)
523 struct ExpressMessage *ptr, *holdptr;
525 if (CC->FirstExpressMessage == NULL) {
526 cprintf("%d No instant messages waiting.\n", ERROR + MESSAGE_NOT_FOUND);
529 begin_critical_section(S_SESSION_TABLE);
530 ptr = CC->FirstExpressMessage;
531 CC->FirstExpressMessage = NULL;
532 end_critical_section(S_SESSION_TABLE);
534 cprintf("%d Express msgs:\n", LISTING_FOLLOWS);
535 while (ptr != NULL) {
536 if (ptr->flags && EM_BROADCAST)
537 cprintf("Broadcast message ");
538 else if (ptr->flags && EM_CHAT)
539 cprintf("Chat request ");
540 else if (ptr->flags && EM_GO_AWAY)
541 cprintf("Please logoff now, as requested ");
544 cprintf("from %s:\n", ptr->sender);
545 if (ptr->text != NULL)
546 memfmout(ptr->text, 0, "\n");
549 if (ptr->text != NULL) free(ptr->text);
558 * Get instant messages (new method)
560 void cmd_gexp(char *argbuf) {
561 struct ExpressMessage *ptr;
563 if (CC->FirstExpressMessage == NULL) {
564 cprintf("%d No instant messages waiting.\n", ERROR + MESSAGE_NOT_FOUND);
568 begin_critical_section(S_SESSION_TABLE);
569 ptr = CC->FirstExpressMessage;
570 CC->FirstExpressMessage = CC->FirstExpressMessage->next;
571 end_critical_section(S_SESSION_TABLE);
573 cprintf("%d %d|%ld|%d|%s|%s|%s\n",
575 ((ptr->next != NULL) ? 1 : 0), /* more msgs? */
576 (long)ptr->timestamp, /* time sent */
577 ptr->flags, /* flags */
578 ptr->sender, /* sender of msg */
579 config.c_nodename, /* static for now (and possibly deprecated) */
580 ptr->sender_email /* email or jid of sender */
583 if (ptr->text != NULL) {
584 memfmout(ptr->text, 0, "\n");
585 if (ptr->text[strlen(ptr->text)-1] != '\n') cprintf("\n");
594 * Asynchronously deliver instant messages
596 void cmd_gexp_async(void) {
598 /* Only do this if the session can handle asynchronous protocol */
599 if (CC->is_async == 0) return;
601 /* And don't do it if there's nothing to send. */
602 if (CC->FirstExpressMessage == NULL) return;
604 cprintf("%d instant msg\n", ASYNC_MSG + ASYNC_GEXP);
608 * Back end support function for send_instant_message() and company
610 void add_xmsg_to_context(struct CitContext *ccptr, struct ExpressMessage *newmsg)
612 struct ExpressMessage *findend;
614 if (ccptr->FirstExpressMessage == NULL) {
615 ccptr->FirstExpressMessage = newmsg;
618 findend = ccptr->FirstExpressMessage;
619 while (findend->next != NULL) {
620 findend = findend->next;
622 findend->next = newmsg;
625 /* If the target context is a session which can handle asynchronous
626 * messages, go ahead and set the flag for that.
628 if (ccptr->is_async) {
629 ccptr->async_waiting = 1;
630 if (ccptr->state == CON_IDLE) {
631 ccptr->state = CON_READY;
640 * This is the back end to the instant message sending function.
641 * Returns the number of users to which the message was sent.
642 * Sending a zero-length message tests for recipients without sending messages.
644 int send_instant_message(char *lun, char *lem, char *x_user, char *x_msg)
646 int message_sent = 0; /* number of successful sends */
647 struct CitContext *ccptr;
648 struct ExpressMessage *newmsg = NULL;
651 int do_send = 0; /* 1 = send message; 0 = only check for valid recipient */
652 static int serial_number = 0; /* this keeps messages from getting logged twice */
654 if (strlen(x_msg) > 0) {
655 msglen = strlen(x_msg) + 4;
659 /* find the target user's context and append the message */
660 begin_critical_section(S_SESSION_TABLE);
662 for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
664 if (ccptr->fake_username[0]) {
665 un = ccptr->fake_username;
668 un = ccptr->user.fullname;
671 if ( ((!strcasecmp(un, x_user))
672 || (!strcasecmp(x_user, "broadcast")))
673 && (ccptr->can_receive_im)
674 && ((ccptr->disable_exp == 0)
675 || (CC->user.axlevel >= 6)) ) {
677 newmsg = (struct ExpressMessage *) malloc(sizeof (struct ExpressMessage));
678 memset(newmsg, 0, sizeof (struct ExpressMessage));
679 time(&(newmsg->timestamp));
680 safestrncpy(newmsg->sender, lun, sizeof newmsg->sender);
681 safestrncpy(newmsg->sender_email, lem, sizeof newmsg->sender_email);
682 if (!strcasecmp(x_user, "broadcast")) {
683 newmsg->flags |= EM_BROADCAST;
685 newmsg->text = strdup(x_msg);
687 add_xmsg_to_context(ccptr, newmsg);
691 log_instant_message(CC, ccptr, newmsg->text, serial_number);
697 end_critical_section(S_SESSION_TABLE);
698 return (message_sent);
702 * send instant messages
704 void cmd_sexp(char *argbuf)
706 int message_sent = 0;
707 char x_user[USERNAME_SIZE];
711 char *x_big_msgbuf = NULL;
713 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
714 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
717 if (CC->fake_username[0])
718 lun = CC->fake_username;
720 lun = CC->user.fullname;
722 lem = CC->cs_inet_email;
724 extract_token(x_user, argbuf, 0, '|', sizeof x_user);
725 extract_token(x_msg, argbuf, 1, '|', sizeof x_msg);
728 cprintf("%d You were not previously paged.\n", ERROR + NO_SUCH_USER);
731 if ((!strcasecmp(x_user, "broadcast")) && (CC->user.axlevel < 6)) {
732 cprintf("%d Higher access required to send a broadcast.\n",
733 ERROR + HIGHER_ACCESS_REQUIRED);
736 /* This loop handles text-transfer pages */
737 if (!strcmp(x_msg, "-")) {
738 message_sent = PerformXmsgHooks(lun, lem, x_user, "");
739 if (message_sent == 0) {
740 if (getuser(NULL, x_user))
741 cprintf("%d '%s' does not exist.\n",
742 ERROR + NO_SUCH_USER, x_user);
744 cprintf("%d '%s' is not logged in "
745 "or is not accepting pages.\n",
746 ERROR + RESOURCE_NOT_OPEN, x_user);
750 cprintf("%d Transmit message (will deliver to %d users)\n",
751 SEND_LISTING, message_sent);
752 x_big_msgbuf = malloc(SIZ);
753 memset(x_big_msgbuf, 0, SIZ);
754 while (client_getln(x_msg, sizeof x_msg) >= 0 && strcmp(x_msg, "000")) {
755 x_big_msgbuf = realloc(x_big_msgbuf,
756 strlen(x_big_msgbuf) + strlen(x_msg) + 4);
757 if (!IsEmptyStr(x_big_msgbuf))
758 if (x_big_msgbuf[strlen(x_big_msgbuf)] != '\n')
759 strcat(x_big_msgbuf, "\n");
760 strcat(x_big_msgbuf, x_msg);
762 PerformXmsgHooks(lun, lem, x_user, x_big_msgbuf);
765 /* This loop handles inline pages */
767 message_sent = PerformXmsgHooks(lun, lem, x_user, x_msg);
769 if (message_sent > 0) {
770 if (!IsEmptyStr(x_msg))
771 cprintf("%d Message sent", CIT_OK);
773 cprintf("%d Ok to send message", CIT_OK);
774 if (message_sent > 1)
775 cprintf(" to %d users", message_sent);
778 if (getuser(NULL, x_user))
779 cprintf("%d '%s' does not exist.\n",
780 ERROR + NO_SUCH_USER, x_user);
782 cprintf("%d '%s' is not logged in "
783 "or is not accepting pages.\n",
784 ERROR + RESOURCE_NOT_OPEN, x_user);
794 * Enter or exit paging-disabled mode
796 void cmd_dexp(char *argbuf)
800 if (CtdlAccessCheck(ac_logged_in)) return;
802 new_state = extract_int(argbuf, 0);
803 if ((new_state == 0) || (new_state == 1)) {
804 CC->disable_exp = new_state;
807 cprintf("%d %d\n", CIT_OK, CC->disable_exp);
812 * Request client termination
814 void cmd_reqt(char *argbuf) {
815 struct CitContext *ccptr;
818 struct ExpressMessage *newmsg;
820 if (CtdlAccessCheck(ac_aide)) return;
821 which_session = extract_int(argbuf, 0);
823 begin_critical_section(S_SESSION_TABLE);
824 for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
825 if ((ccptr->cs_pid == which_session) || (which_session == 0)) {
827 newmsg = (struct ExpressMessage *)
828 malloc(sizeof (struct ExpressMessage));
830 sizeof (struct ExpressMessage));
831 time(&(newmsg->timestamp));
832 safestrncpy(newmsg->sender, CC->user.fullname,
833 sizeof newmsg->sender);
834 newmsg->flags |= EM_GO_AWAY;
835 newmsg->text = strdup("Automatic logoff requested.");
837 add_xmsg_to_context(ccptr, newmsg);
842 end_critical_section(S_SESSION_TABLE);
843 cprintf("%d Sent termination request to %d sessions.\n", CIT_OK, sessions);
848 * This is the back end for flush_conversations_to_disk()
849 * At this point we've isolated a single conversation (struct imlog)
850 * and are ready to write it to disk.
852 void flush_individual_conversation(struct imlog *im) {
853 struct CtdlMessage *msg;
855 char roomname[ROOMNAMELEN];
857 StrBufAppendBufPlain(im->conversation, HKEY(
863 msg = malloc(sizeof(struct CtdlMessage));
864 memset(msg, 0, sizeof(struct CtdlMessage));
865 msg->cm_magic = CTDLMESSAGE_MAGIC;
866 msg->cm_anon_type = MES_NORMAL;
867 msg->cm_format_type = FMT_RFC822;
868 if (!IsEmptyStr(im->usernames[0])) {
869 msg->cm_fields['A'] = strdup(im->usernames[0]);
871 msg->cm_fields['A'] = strdup("Citadel");
873 if (!IsEmptyStr(im->usernames[1])) {
874 msg->cm_fields['R'] = strdup(im->usernames[1]);
876 msg->cm_fields['O'] = strdup(PAGELOGROOM);
877 msg->cm_fields['N'] = strdup(NODENAME);
878 msg->cm_fields['M'] = SmashStrBuf(&im->conversation); /* we own this memory now */
880 /* Start with usernums[1] because it's guaranteed to be higher than usernums[0],
881 * so if there's only one party, usernums[0] will be zero but usernums[1] won't.
882 * Create the room if necessary. Note that we create as a type 5 room rather
883 * than 4, which indicates that it's a personal room but we've already supplied
884 * the namespace prefix.
886 * In the unlikely event that usernums[1] is zero, a room with an invalid namespace
887 * prefix will be created. That's ok because the auto-purger will clean it up later.
889 snprintf(roomname, sizeof roomname, "%010ld.%s", im->usernums[1], PAGELOGROOM);
890 create_room(roomname, 5, "", 0, 1, 1, VIEW_BBS);
891 msgnum = CtdlSubmitMsg(msg, NULL, roomname, 0);
892 CtdlFreeMessage(msg);
894 /* If there is a valid user number in usernums[0], save a copy for them too. */
895 if (im->usernums[0] > 0) {
896 snprintf(roomname, sizeof roomname, "%010ld.%s", im->usernums[0], PAGELOGROOM);
897 create_room(roomname, 5, "", 0, 1, 1, VIEW_BBS);
898 CtdlSaveMsgPointerInRoom(roomname, msgnum, 0, NULL);
901 /* Finally, if we're logging instant messages globally, do that now. */
902 if (!IsEmptyStr(config.c_logpages)) {
903 create_room(config.c_logpages, 3, "", 0, 1, 1, VIEW_BBS);
904 CtdlSaveMsgPointerInRoom(config.c_logpages, msgnum, 0, NULL);
910 * Locate instant message conversations which have gone idle
911 * (or, if the server is shutting down, locate *all* conversations)
912 * and flush them to disk (in the participants' log rooms, etc.)
914 void flush_conversations_to_disk(time_t if_older_than) {
916 struct imlog *flush_these = NULL;
917 struct imlog *dont_flush_these = NULL;
918 struct imlog *imptr = NULL;
920 begin_critical_section(S_IM_LOGS);
924 imlist = imlist->next;
925 if ((time(NULL) - imptr->lastmsg) > if_older_than)
927 /* This conversation qualifies. Move it to the list of ones to flush. */
928 imptr->next = flush_these;
932 /* Move it to the list of ones not to flush. */
933 imptr->next = dont_flush_these;
934 dont_flush_these = imptr;
937 imlist = dont_flush_these;
938 end_critical_section(S_IM_LOGS);
940 /* We are now outside of the critical section, and we are the only thread holding a
941 * pointer to a linked list of conversations to be flushed to disk.
943 while (flush_these) {
945 flush_individual_conversation(flush_these); /* This will free the string buffer */
947 flush_these = flush_these->next;
954 void chat_timer(void) {
955 flush_conversations_to_disk(300); /* Anything that hasn't peeped in more than 5 minutes */
958 void chat_shutdown(void) {
959 flush_conversations_to_disk(0); /* Get it ALL onto disk NOW. */
962 CTDL_MODULE_INIT(chat)
966 CtdlRegisterProtoHook(cmd_chat, "CHAT", "Begin real-time chat");
967 CtdlRegisterProtoHook(cmd_pexp, "PEXP", "Poll for instant messages");
968 CtdlRegisterProtoHook(cmd_gexp, "GEXP", "Get instant messages");
969 CtdlRegisterProtoHook(cmd_sexp, "SEXP", "Send an instant message");
970 CtdlRegisterProtoHook(cmd_dexp, "DEXP", "Disable instant messages");
971 CtdlRegisterProtoHook(cmd_reqt, "REQT", "Request client termination");
972 CtdlRegisterSessionHook(cmd_gexp_async, EVT_ASYNC);
973 CtdlRegisterSessionHook(delete_instant_messages, EVT_STOP);
974 CtdlRegisterXmsgHook(send_instant_message, XMSG_PRI_LOCAL);
975 CtdlRegisterSessionHook(chat_timer, EVT_TIMER);
976 CtdlRegisterSessionHook(chat_shutdown, EVT_SHUTDOWN);
979 /* return our Subversion id for the Log */