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;
59 struct imlog *imlist = NULL;
62 * This function handles the logging of instant messages to disk.
64 void log_instant_message(struct CitContext *me, struct CitContext *them, char *msgtext)
68 struct imlog *iptr = NULL;
69 struct imlog *this_im = NULL;
71 memset(usernums, 0, sizeof usernums);
72 usernums[0] = me->user.usernum;
73 usernums[1] = them->user.usernum;
75 /* Always put the lower user number first, so we can use the array as a hash value which
76 * represents a pair of users. For a broadcast message one of the users will be 0.
78 if (usernums[0] > usernums[1]) {
80 usernums[0] = usernums[1];
84 begin_critical_section(S_IM_LOGS);
86 /* Look for an existing conversation in the hash table.
87 * If not found, create a new one.
91 for (iptr = imlist; iptr != NULL; iptr = iptr->next) {
92 if ((iptr->usernums[0] == usernums[0]) && (iptr->usernums[1] == usernums[1])) {
93 /* Existing conversation */
97 if (this_im == NULL) {
98 /* New conversation */
99 this_im = malloc(sizeof(struct imlog));
100 memset(this_im, 0, sizeof (struct imlog));
101 this_im->usernums[0] = usernums[0];
102 this_im->usernums[1] = usernums[1];
103 this_im->conversation = NewStrBuf();
104 this_im->next = imlist;
106 StrBufAppendBufPlain(this_im->conversation,
107 "Content-type: text/html\r\n"
108 "Content-transfer-encoding: 7bit\r\n\r\n"
109 "<html><head><title>instant message transcript</title></head>\r\n"
114 this_im->lastmsg = time(NULL); /* Touch the timestamp so we know when to flush */
115 StrBufAppendBufPlain(this_im->conversation, "<p><b>", -1, 0);
116 StrBufAppendBufPlain(this_im->conversation, me->user.fullname, -1, 0);
117 StrBufAppendBufPlain(this_im->conversation, ":</b> ", -1, 0);
118 StrEscAppend(this_im->conversation, NULL, msgtext, 0, 0);
119 StrBufAppendBufPlain(this_im->conversation, "</p>\r\n", -1, 0);
120 end_critical_section(S_IM_LOGS);
124 * This message can be set to anything you want, but it is
125 * checked for consistency so don't move it away from here.
127 #define KICKEDMSG "You have been kicked out of this room."
129 void allwrite(char *cmdbuf, int flag, char *username)
134 struct ChatLine *clptr, *clnew;
137 if (CC->fake_username[0])
138 un = CC->fake_username;
140 un = CC->user.fullname;
142 snprintf(bcast, sizeof bcast, ":|<%s %s>", un, cmdbuf);
143 } else if (flag == 0) {
144 snprintf(bcast, sizeof bcast, "%s|%s", un, cmdbuf);
145 } else if (flag == 2) {
146 snprintf(bcast, sizeof bcast, ":|<%s whispers %s>", un, cmdbuf);
147 } else if (flag == 3) {
148 snprintf(bcast, sizeof bcast, ":|%s", KICKEDMSG);
150 if ((strcasecmp(cmdbuf, "NOOP")) && (flag != 2)) {
151 fp = fopen(CHATLOG, "a");
153 fprintf(fp, "%s\n", bcast);
156 clnew = (struct ChatLine *) malloc(sizeof(struct ChatLine));
157 memset(clnew, 0, sizeof(struct ChatLine));
159 fprintf(stderr, "citserver: cannot alloc chat line: %s\n",
165 clnew->chat_time = now;
166 safestrncpy(clnew->chat_room, CC->room.QRname,
167 sizeof clnew->chat_room);
168 clnew->chat_room[sizeof clnew->chat_room - 1] = 0;
170 safestrncpy(clnew->chat_username, username,
171 sizeof clnew->chat_username);
172 clnew->chat_username[sizeof clnew->chat_username - 1] = 0;
174 clnew->chat_username[0] = '\0';
175 safestrncpy(clnew->chat_text, bcast, sizeof clnew->chat_text);
177 /* Here's the critical section.
178 * First, add the new message to the queue...
180 begin_critical_section(S_CHATQUEUE);
182 clnew->chat_seq = ChatLastMsg;
183 if (ChatQueue == NULL) {
186 for (clptr = ChatQueue; clptr->next != NULL; clptr = clptr->next);;
190 /* Then, before releasing the lock, free the expired messages */
191 while ((ChatQueue != NULL) && (now - ChatQueue->chat_time >= 120L)) {
193 ChatQueue = ChatQueue->next;
196 end_critical_section(S_CHATQUEUE);
200 t_context *find_context(char **unstr)
202 t_context *t_cc, *found_cc = NULL;
205 if ((!*unstr) || (!unstr))
208 begin_critical_section(S_SESSION_TABLE);
209 for (t_cc = ContextList; ((t_cc) && (!found_cc)); t_cc = t_cc->next) {
210 if (t_cc->fake_username[0])
211 name = t_cc->fake_username;
213 name = t_cc->curr_user;
215 if ((!strncasecmp(name, tptr, strlen(name))) && (tptr[strlen(name)] == ' ')) {
217 *unstr = &(tptr[strlen(name) + 1]);
220 end_critical_section(S_SESSION_TABLE);
226 * List users in chat.
227 * allflag == 0 = list users in chat
228 * 1 = list users in chat, followed by users not in chat
229 * 2 = display count only
232 void do_chat_listing(int allflag)
234 struct CitContext *ccptr;
236 int count_elsewhere = 0;
237 char roomname[ROOMNAMELEN];
239 if ((allflag == 0) || (allflag == 1))
240 cprintf(":|\n:| Users currently in chat:\n");
241 begin_critical_section(S_SESSION_TABLE);
242 for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
243 if (ccptr->cs_flags & CS_CHAT) {
244 if (!strcasecmp(ccptr->room.QRname,
253 GenerateRoomDisplay(roomname, ccptr, CC);
254 if ((CC->user.axlevel < 6)
255 && (!IsEmptyStr(ccptr->fake_roomname))) {
256 strcpy(roomname, ccptr->fake_roomname);
259 if ((ccptr->cs_flags & CS_CHAT)
260 && ((ccptr->cs_flags & CS_STEALTH) == 0)) {
261 if ((allflag == 0) || (allflag == 1)) {
262 cprintf(":| %-25s <%s>:\n",
263 (ccptr->fake_username[0]) ? ccptr->fake_username : ccptr->curr_user,
270 cprintf(":|\n:| Users not in chat:\n");
271 for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
273 GenerateRoomDisplay(roomname, ccptr, CC);
274 if ((CC->user.axlevel < 6)
275 && (!IsEmptyStr(ccptr->fake_roomname))) {
276 strcpy(roomname, ccptr->fake_roomname);
279 if (((ccptr->cs_flags & CS_CHAT) == 0)
280 && ((ccptr->cs_flags & CS_STEALTH) == 0)) {
281 cprintf(":| %-25s <%s>:\n",
282 (ccptr->fake_username[0]) ? ccptr->fake_username : ccptr->curr_user,
287 end_critical_section(S_SESSION_TABLE);
291 cprintf(":|There are %d users here.\n", count);
294 cprintf(":|Note: you are the only one here.\n");
296 if (count_elsewhere > 0) {
297 cprintf(":|There are %d users chatting in other rooms.\n", count_elsewhere);
305 void cmd_chat(char *argbuf)
310 int MyLastMsg, ThisLastMsg;
311 struct ChatLine *clptr;
312 struct CitContext *t_context;
315 if (!(CC->logged_in)) {
316 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
320 CC->cs_flags = CC->cs_flags | CS_CHAT;
321 cprintf("%d Entering chat mode (type '/help' for available commands)\n",
325 MyLastMsg = ChatLastMsg;
327 if ((CC->cs_flags & CS_STEALTH) == 0) {
328 allwrite("<entering chat>", 0, NULL);
339 linelen = strlen(cmdbuf);
340 if (linelen > 100) --linelen; /* truncate too-long lines */
341 cmdbuf[linelen + 1] = 0;
343 retval = client_read_to(&cmdbuf[linelen], 1, 2);
345 if (retval < 0 || CC->kill_me) { /* socket broken? */
346 if ((CC->cs_flags & CS_STEALTH) == 0) {
347 allwrite("<disconnected>", 0, NULL);
352 /* if we have a complete line, do send processing */
353 if (!IsEmptyStr(cmdbuf))
354 if (cmdbuf[strlen(cmdbuf) - 1] == 10) {
355 cmdbuf[strlen(cmdbuf) - 1] = 0;
359 if ((!strcasecmp(cmdbuf, "exit"))
360 || (!strcasecmp(cmdbuf, "/exit"))
361 || (!strcasecmp(cmdbuf, "quit"))
362 || (!strcasecmp(cmdbuf, "logout"))
363 || (!strcasecmp(cmdbuf, "logoff"))
364 || (!strcasecmp(cmdbuf, "/q"))
365 || (!strcasecmp(cmdbuf, ".q"))
366 || (!strcasecmp(cmdbuf, "/quit"))
368 strcpy(cmdbuf, "000");
370 if (!strcmp(cmdbuf, "000")) {
371 if ((CC->cs_flags & CS_STEALTH) == 0) {
372 allwrite("<exiting chat>", 0, NULL);
376 CC->cs_flags = CC->cs_flags - CS_CHAT;
379 if ((!strcasecmp(cmdbuf, "/help"))
380 || (!strcasecmp(cmdbuf, "help"))
381 || (!strcasecmp(cmdbuf, "/?"))
382 || (!strcasecmp(cmdbuf, "?"))) {
384 cprintf(":|Available commands: \n");
385 cprintf(":|/help (prints this message) \n");
386 cprintf(":|/who (list users currently in chat) \n");
387 cprintf(":|/whobbs (list users in chat -and- elsewhere) \n");
388 cprintf(":|/me ('action' line, ala irc) \n");
389 cprintf(":|/msg (send private message, ala irc) \n");
390 if (is_room_aide()) {
391 cprintf(":|/kick (kick another user out of this room) \n");
393 cprintf(":|/quit (exit from this chat) \n");
397 if (!strcasecmp(cmdbuf, "/who")) {
401 if (!strcasecmp(cmdbuf, "/whobbs")) {
405 if (!strncasecmp(cmdbuf, "/me ", 4)) {
406 allwrite(&cmdbuf[4], 1, NULL);
409 if (!strncasecmp(cmdbuf, "/msg ", 5)) {
411 strptr1 = &cmdbuf[5];
412 if ((t_context = find_context(&strptr1))) {
413 allwrite(strptr1, 2, CC->curr_user);
414 if (strcasecmp(CC->curr_user, t_context->curr_user))
415 allwrite(strptr1, 2, t_context->curr_user);
417 cprintf(":|User not found.\n");
420 /* The /kick function is implemented by sending a specific
421 * message to the kicked-out user's context. When that message
422 * is processed by the read loop, that context will exit.
424 if ( (!strncasecmp(cmdbuf, "/kick ", 6)) && (is_room_aide()) ) {
426 strptr1 = &cmdbuf[6];
427 strcat(strptr1, " ");
428 if ((t_context = find_context(&strptr1))) {
429 if (strcasecmp(CC->curr_user, t_context->curr_user))
430 allwrite(strptr1, 3, t_context->curr_user);
432 cprintf(":|User not found.\n");
435 if ((cmdbuf[0] != '/') && (strlen(cmdbuf) > 0)) {
437 allwrite(cmdbuf, 0, NULL);
439 if ((!ok_cmd) && (cmdbuf[0]) && (cmdbuf[0] != '\n'))
440 cprintf(":|Command %s is not understood.\n", cmdbuf);
445 /* now check the queue for new incoming stuff */
447 if (CC->fake_username[0])
448 un = CC->fake_username;
451 if (ChatLastMsg > MyLastMsg) {
452 ThisLastMsg = ChatLastMsg;
453 for (clptr = ChatQueue; clptr != NULL; clptr = clptr->next) {
454 if ((clptr->chat_seq > MyLastMsg) && ((!clptr->chat_username[0]) || (!strncasecmp(un, clptr->chat_username, 32)))) {
455 if ((!clptr->chat_room[0]) || (!strncasecmp(CC->room.QRname, clptr->chat_room, ROOMNAMELEN))) {
456 /* Output new chat data */
457 cprintf("%s\n", clptr->chat_text);
459 /* See if we've been force-quitted (kicked etc.) */
460 if (!strcmp(&clptr->chat_text[2], KICKEDMSG)) {
461 allwrite("<kicked out of this room>", 0, NULL);
463 CC->cs_flags = CC->cs_flags - CS_CHAT;
465 /* Kick user out of room */
466 CtdlInvtKick(CC->user.fullname, 0);
468 /* And return to the Lobby */
469 usergoto(config.c_baseroom, 0, 0, NULL, NULL);
475 MyLastMsg = ThisLastMsg;
483 * Delete any remaining instant messages
485 void delete_instant_messages(void) {
486 struct ExpressMessage *ptr;
488 begin_critical_section(S_SESSION_TABLE);
489 while (CC->FirstExpressMessage != NULL) {
490 ptr = CC->FirstExpressMessage->next;
491 if (CC->FirstExpressMessage->text != NULL)
492 free(CC->FirstExpressMessage->text);
493 free(CC->FirstExpressMessage);
494 CC->FirstExpressMessage = ptr;
496 end_critical_section(S_SESSION_TABLE);
503 * Poll for instant messages (OLD METHOD -- ***DEPRECATED ***)
505 void cmd_pexp(char *argbuf)
507 struct ExpressMessage *ptr, *holdptr;
509 if (CC->FirstExpressMessage == NULL) {
510 cprintf("%d No instant messages waiting.\n", ERROR + MESSAGE_NOT_FOUND);
513 begin_critical_section(S_SESSION_TABLE);
514 ptr = CC->FirstExpressMessage;
515 CC->FirstExpressMessage = NULL;
516 end_critical_section(S_SESSION_TABLE);
518 cprintf("%d Express msgs:\n", LISTING_FOLLOWS);
519 while (ptr != NULL) {
520 if (ptr->flags && EM_BROADCAST)
521 cprintf("Broadcast message ");
522 else if (ptr->flags && EM_CHAT)
523 cprintf("Chat request ");
524 else if (ptr->flags && EM_GO_AWAY)
525 cprintf("Please logoff now, as requested ");
528 cprintf("from %s:\n", ptr->sender);
529 if (ptr->text != NULL)
530 memfmout(ptr->text, 0, "\n");
533 if (ptr->text != NULL) free(ptr->text);
542 * Get instant messages (new method)
544 void cmd_gexp(char *argbuf) {
545 struct ExpressMessage *ptr;
547 if (CC->FirstExpressMessage == NULL) {
548 cprintf("%d No instant messages waiting.\n", ERROR + MESSAGE_NOT_FOUND);
552 begin_critical_section(S_SESSION_TABLE);
553 ptr = CC->FirstExpressMessage;
554 CC->FirstExpressMessage = CC->FirstExpressMessage->next;
555 end_critical_section(S_SESSION_TABLE);
557 cprintf("%d %d|%ld|%d|%s|%s|%s\n",
559 ((ptr->next != NULL) ? 1 : 0), /* more msgs? */
560 (long)ptr->timestamp, /* time sent */
561 ptr->flags, /* flags */
562 ptr->sender, /* sender of msg */
563 config.c_nodename, /* static for now (and possibly deprecated) */
564 ptr->sender_email /* email or jid of sender */
567 if (ptr->text != NULL) {
568 memfmout(ptr->text, 0, "\n");
569 if (ptr->text[strlen(ptr->text)-1] != '\n') cprintf("\n");
578 * Asynchronously deliver instant messages
580 void cmd_gexp_async(void) {
582 /* Only do this if the session can handle asynchronous protocol */
583 if (CC->is_async == 0) return;
585 /* And don't do it if there's nothing to send. */
586 if (CC->FirstExpressMessage == NULL) return;
588 cprintf("%d instant msg\n", ASYNC_MSG + ASYNC_GEXP);
592 * Back end support function for send_instant_message() and company
594 void add_xmsg_to_context(struct CitContext *ccptr, struct ExpressMessage *newmsg)
596 struct ExpressMessage *findend;
598 if (ccptr->FirstExpressMessage == NULL) {
599 ccptr->FirstExpressMessage = newmsg;
602 findend = ccptr->FirstExpressMessage;
603 while (findend->next != NULL) {
604 findend = findend->next;
606 findend->next = newmsg;
609 /* If the target context is a session which can handle asynchronous
610 * messages, go ahead and set the flag for that.
612 if (ccptr->is_async) {
613 ccptr->async_waiting = 1;
614 if (ccptr->state == CON_IDLE) {
615 ccptr->state = CON_READY;
624 * This is the back end to the instant message sending function.
625 * Returns the number of users to which the message was sent.
626 * Sending a zero-length message tests for recipients without sending messages.
628 int send_instant_message(char *lun, char *lem, char *x_user, char *x_msg)
630 int message_sent = 0; /* number of successful sends */
631 struct CitContext *ccptr;
632 struct ExpressMessage *newmsg = NULL;
635 int do_send = 0; /* 1 = send message; 0 = only check for valid recipient */
637 if (strlen(x_msg) > 0) {
638 msglen = strlen(x_msg) + 4;
642 /* find the target user's context and append the message */
643 begin_critical_section(S_SESSION_TABLE);
644 for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
646 if (ccptr->fake_username[0]) {
647 un = ccptr->fake_username;
650 un = ccptr->user.fullname;
653 if ( ((!strcasecmp(un, x_user))
654 || (!strcasecmp(x_user, "broadcast")))
655 && (ccptr->can_receive_im)
656 && ((ccptr->disable_exp == 0)
657 || (CC->user.axlevel >= 6)) ) {
659 newmsg = (struct ExpressMessage *)
660 malloc(sizeof (struct ExpressMessage));
662 sizeof (struct ExpressMessage));
663 time(&(newmsg->timestamp));
664 safestrncpy(newmsg->sender, lun, sizeof newmsg->sender);
665 safestrncpy(newmsg->sender_email, lem, sizeof newmsg->sender_email);
666 if (!strcasecmp(x_user, "broadcast")) {
667 newmsg->flags |= EM_BROADCAST;
669 newmsg->text = strdup(x_msg);
671 add_xmsg_to_context(ccptr, newmsg);
675 log_instant_message(CC, ccptr, newmsg->text);
681 end_critical_section(S_SESSION_TABLE);
682 return (message_sent);
686 * send instant messages
688 void cmd_sexp(char *argbuf)
690 int message_sent = 0;
691 char x_user[USERNAME_SIZE];
695 char *x_big_msgbuf = NULL;
697 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
698 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
701 if (CC->fake_username[0])
702 lun = CC->fake_username;
704 lun = CC->user.fullname;
706 lem = CC->cs_inet_email;
708 extract_token(x_user, argbuf, 0, '|', sizeof x_user);
709 extract_token(x_msg, argbuf, 1, '|', sizeof x_msg);
712 cprintf("%d You were not previously paged.\n", ERROR + NO_SUCH_USER);
715 if ((!strcasecmp(x_user, "broadcast")) && (CC->user.axlevel < 6)) {
716 cprintf("%d Higher access required to send a broadcast.\n",
717 ERROR + HIGHER_ACCESS_REQUIRED);
720 /* This loop handles text-transfer pages */
721 if (!strcmp(x_msg, "-")) {
722 message_sent = PerformXmsgHooks(lun, lem, x_user, "");
723 if (message_sent == 0) {
724 if (getuser(NULL, x_user))
725 cprintf("%d '%s' does not exist.\n",
726 ERROR + NO_SUCH_USER, x_user);
728 cprintf("%d '%s' is not logged in "
729 "or is not accepting pages.\n",
730 ERROR + RESOURCE_NOT_OPEN, x_user);
734 cprintf("%d Transmit message (will deliver to %d users)\n",
735 SEND_LISTING, message_sent);
736 x_big_msgbuf = malloc(SIZ);
737 memset(x_big_msgbuf, 0, SIZ);
738 while (client_getln(x_msg, sizeof x_msg) >= 0 && strcmp(x_msg, "000")) {
739 x_big_msgbuf = realloc(x_big_msgbuf,
740 strlen(x_big_msgbuf) + strlen(x_msg) + 4);
741 if (!IsEmptyStr(x_big_msgbuf))
742 if (x_big_msgbuf[strlen(x_big_msgbuf)] != '\n')
743 strcat(x_big_msgbuf, "\n");
744 strcat(x_big_msgbuf, x_msg);
746 PerformXmsgHooks(lun, lem, x_user, x_big_msgbuf);
749 /* This loop handles inline pages */
751 message_sent = PerformXmsgHooks(lun, lem, x_user, x_msg);
753 if (message_sent > 0) {
754 if (!IsEmptyStr(x_msg))
755 cprintf("%d Message sent", CIT_OK);
757 cprintf("%d Ok to send message", CIT_OK);
758 if (message_sent > 1)
759 cprintf(" to %d users", message_sent);
762 if (getuser(NULL, x_user))
763 cprintf("%d '%s' does not exist.\n",
764 ERROR + NO_SUCH_USER, x_user);
766 cprintf("%d '%s' is not logged in "
767 "or is not accepting pages.\n",
768 ERROR + RESOURCE_NOT_OPEN, x_user);
778 * Enter or exit paging-disabled mode
780 void cmd_dexp(char *argbuf)
784 if (CtdlAccessCheck(ac_logged_in)) return;
786 new_state = extract_int(argbuf, 0);
787 if ((new_state == 0) || (new_state == 1)) {
788 CC->disable_exp = new_state;
791 cprintf("%d %d\n", CIT_OK, CC->disable_exp);
796 * Request client termination
798 void cmd_reqt(char *argbuf) {
799 struct CitContext *ccptr;
802 struct ExpressMessage *newmsg;
804 if (CtdlAccessCheck(ac_aide)) return;
805 which_session = extract_int(argbuf, 0);
807 begin_critical_section(S_SESSION_TABLE);
808 for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
809 if ((ccptr->cs_pid == which_session) || (which_session == 0)) {
811 newmsg = (struct ExpressMessage *)
812 malloc(sizeof (struct ExpressMessage));
814 sizeof (struct ExpressMessage));
815 time(&(newmsg->timestamp));
816 safestrncpy(newmsg->sender, CC->user.fullname,
817 sizeof newmsg->sender);
818 newmsg->flags |= EM_GO_AWAY;
819 newmsg->text = strdup("Automatic logoff requested.");
821 add_xmsg_to_context(ccptr, newmsg);
826 end_critical_section(S_SESSION_TABLE);
827 cprintf("%d Sent termination request to %d sessions.\n", CIT_OK, sessions);
832 * This is the back end for flush_conversations_to_disk()
833 * At this point we've isolated a single conversation (struct imlog)
834 * and are ready to write it to disk.
836 void flush_individual_conversation(struct imlog *im) {
837 struct CtdlMessage *msg;
839 char roomname[ROOMNAMELEN];
841 StrBufAppendBufPlain(im->conversation,
847 msg = malloc(sizeof(struct CtdlMessage));
848 memset(msg, 0, sizeof(struct CtdlMessage));
849 msg->cm_magic = CTDLMESSAGE_MAGIC;
850 msg->cm_anon_type = MES_NORMAL;
851 msg->cm_format_type = FMT_RFC822;
852 msg->cm_fields['A'] = strdup("Citadel");
853 msg->cm_fields['O'] = strdup(PAGELOGROOM);
854 msg->cm_fields['N'] = strdup(NODENAME);
855 msg->cm_fields['M'] = strdup(ChrPtr(im->conversation));
857 /* Start with usernums[1] because it's guaranteed to be higher than usernums[0],
858 * so if there's only one party, usernums[0] will be zero but usernums[1] won't.
859 * Create the room if necessary. Note that we create as a type 5 room rather
860 * than 4, which indicates that it's a personal room but we've already supplied
861 * the namespace prefix.
863 * In the unlikely event that usernums[1] is zero, a room with an invalid namespace
864 * prefix will be created. That's ok because the auto-purger will clean it up later.
866 snprintf(roomname, sizeof roomname, "%010ld.%s", im->usernums[1], PAGELOGROOM);
867 create_room(roomname, 5, "", 0, 1, 1, VIEW_BBS);
868 msgnum = CtdlSubmitMsg(msg, NULL, roomname, 0);
869 CtdlFreeMessage(msg);
871 /* If there is a valid user number in usernums[0], save a copy for them too. */
872 if (im->usernums[0] > 0) {
873 snprintf(roomname, sizeof roomname, "%010ld.%s", im->usernums[0], PAGELOGROOM);
874 create_room(roomname, 5, "", 0, 1, 1, VIEW_BBS);
875 CtdlSaveMsgPointerInRoom(roomname, msgnum, 0, NULL);
878 /* Finally, if we're logging instant messages globally, do that now. */
879 if (!IsEmptyStr(config.c_logpages)) {
880 create_room(config.c_logpages, 3, "", 0, 1, 1, VIEW_BBS);
881 CtdlSaveMsgPointerInRoom(config.c_logpages, msgnum, 0, NULL);
887 * Locate instant message conversations which have gone idle
888 * (or, if the server is shutting down, locate *all* conversations)
889 * and flush them to disk (in the participants' log rooms, etc.)
891 void flush_conversations_to_disk(time_t if_older_than) {
893 struct imlog *flush_these = NULL;
894 struct imlog *dont_flush_these = NULL;
895 struct imlog *imptr = NULL;
897 begin_critical_section(S_IM_LOGS);
901 imlist = imlist->next;
902 if ((time(NULL) - imptr->lastmsg) > if_older_than)
904 /* This conversation qualifies. Move it to the list of ones to flush. */
905 imptr->next = flush_these;
909 /* Move it to the list of ones not to flush. */
910 imptr->next = dont_flush_these;
911 dont_flush_these = imptr;
914 imlist = dont_flush_these;
915 end_critical_section(S_IM_LOGS);
917 /* We are now outside of the critical section, and we are the only thread holding a
918 * pointer to a linked list of conversations to be flushed to disk.
920 while (flush_these) {
922 flush_individual_conversation(flush_these);
924 flush_these = flush_these->next;
925 FreeStrBuf(&imptr->conversation);
932 void chat_timer(void) {
933 flush_conversations_to_disk(300); /* Anything that hasn't peeped in more than 5 minutes */
936 void chat_shutdown(void) {
937 flush_conversations_to_disk(0); /* Get it ALL onto disk NOW. */
940 CTDL_MODULE_INIT(chat)
944 CtdlRegisterProtoHook(cmd_chat, "CHAT", "Begin real-time chat");
945 CtdlRegisterProtoHook(cmd_pexp, "PEXP", "Poll for instant messages");
946 CtdlRegisterProtoHook(cmd_gexp, "GEXP", "Get instant messages");
947 CtdlRegisterProtoHook(cmd_sexp, "SEXP", "Send an instant message");
948 CtdlRegisterProtoHook(cmd_dexp, "DEXP", "Disable instant messages");
949 CtdlRegisterProtoHook(cmd_reqt, "REQT", "Request client termination");
950 CtdlRegisterSessionHook(cmd_gexp_async, EVT_ASYNC);
951 CtdlRegisterSessionHook(delete_instant_messages, EVT_STOP);
952 CtdlRegisterXmsgHook(send_instant_message, XMSG_PRI_LOCAL);
953 CtdlRegisterSessionHook(chat_timer, EVT_TIMER);
954 CtdlRegisterSessionHook(chat_shutdown, EVT_SHUTDOWN);
957 /* return our Subversion id for the Log */