* Moved instant messaging into its own module -- serv_instmsg
authorArt Cancro <ajc@citadel.org>
Thu, 8 Apr 2010 04:33:45 +0000 (04:33 +0000)
committerArt Cancro <ajc@citadel.org>
Thu, 8 Apr 2010 04:33:45 +0000 (04:33 +0000)
* Removed serv_chat and the CHAT command
* Implemented serv_roomchat and the RCHT command, which implements a brand new chat protocol

citadel/modules/chat/serv_chat.c [deleted file]
citadel/modules/chat/serv_chat.h [deleted file]
citadel/modules/instmsg/serv_instmsg.c [new file with mode: 0644]
citadel/modules/instmsg/serv_instmsg.h [new file with mode: 0644]
citadel/modules/roomchat/serv_roomchat.c [new file with mode: 0644]
citadel/server.h
citadel/textclient/client_chat.c

diff --git a/citadel/modules/chat/serv_chat.c b/citadel/modules/chat/serv_chat.c
deleted file mode 100644 (file)
index 5fbd1db..0000000
+++ /dev/null
@@ -1,964 +0,0 @@
-/*
- * $Id$
- * 
- * This module handles all "real time" communication between users.  The
- * modes of communication currently supported are Chat and Instant Messages.
- *
- * Copyright (c) 1987-2010 by the citadel.org team
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- *
- */
-#include "sysdep.h"
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <pwd.h>
-#include <errno.h>
-#include <sys/types.h>
-
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <time.h>
-#else
-# if HAVE_SYS_TIME_H
-#  include <sys/time.h>
-# else
-#  include <time.h>
-# endif
-#endif
-
-#include <sys/wait.h>
-#include <string.h>
-#include <limits.h>
-#include <libcitadel.h>
-#include "citadel.h"
-#include "server.h"
-#include "serv_chat.h"
-#include "citserver.h"
-#include "support.h"
-#include "config.h"
-#include "msgbase.h"
-#include "user_ops.h"
-
-#ifndef HAVE_SNPRINTF
-#include "snprintf.h"
-#endif
-
-#include "ctdl_module.h"
-
-struct ChatLine *ChatQueue = NULL;
-int ChatLastMsg = 0;
-
-struct imlog {
-       struct imlog *next;
-       long usernums[2];
-       char usernames[2][128];
-       time_t lastmsg;
-       int last_serial;
-       StrBuf *conversation;
-};
-
-struct imlog *imlist = NULL;
-
-
-
-
-/*
- * FIXME: OMG this module is realy horrible to the rest of the system when accessing contexts.
- * It pays no regard at all to how long it may have the context list locked for. 
- * It carries out IO whilst the context list is locked.
- * I'd recomend disabling this module altogether for the moment.
- */
-
-/*
- * This function handles the logging of instant messages to disk.
- */
-void log_instant_message(struct CitContext *me, struct CitContext *them, char *msgtext, int serial_number)
-{
-       long usernums[2];
-       long t;
-       struct imlog *iptr = NULL;
-       struct imlog *this_im = NULL;
-       
-       memset(usernums, 0, sizeof usernums);
-       usernums[0] = me->user.usernum;
-       usernums[1] = them->user.usernum;
-
-       /* Always put the lower user number first, so we can use the array as a hash value which
-        * represents a pair of users.  For a broadcast message one of the users will be 0.
-        */
-       if (usernums[0] > usernums[1]) {
-               t = usernums[0];
-               usernums[0] = usernums[1];
-               usernums[1] = t;
-       }
-
-       begin_critical_section(S_IM_LOGS);
-
-       /* Look for an existing conversation in the hash table.
-        * If not found, create a new one.
-        */
-
-       this_im = NULL;
-       for (iptr = imlist; iptr != NULL; iptr = iptr->next) {
-               if ((iptr->usernums[0] == usernums[0]) && (iptr->usernums[1] == usernums[1])) {
-                       /* Existing conversation */
-                       this_im = iptr;
-               }
-       }
-       if (this_im == NULL) {
-               /* New conversation */
-               this_im = malloc(sizeof(struct imlog));
-               memset(this_im, 0, sizeof (struct imlog));
-               this_im->usernums[0] = usernums[0];
-               this_im->usernums[1] = usernums[1];
-               /* usernames[] and usernums[] might not be in the same order.  This is not an error. */
-               if (me) {
-                       safestrncpy(this_im->usernames[0], me->user.fullname, sizeof this_im->usernames[0]);
-               }
-               if (them) {
-                       safestrncpy(this_im->usernames[1], them->user.fullname, sizeof this_im->usernames[1]);
-               }
-               this_im->conversation = NewStrBuf();
-               this_im->next = imlist;
-               imlist = this_im;
-               StrBufAppendBufPlain(this_im->conversation, HKEY(
-                       "Content-type: text/html\r\n"
-                       "Content-transfer-encoding: 7bit\r\n"
-                       "\r\n"
-                       "<html><body>\r\n"
-                       ), 0);
-       }
-
-
-       /* Since it's possible for this function to get called more than once if a user is logged
-        * in on multiple sessions, we use the message's serial number to keep track of whether
-        * we've already logged it.
-        */
-       if (this_im->last_serial != serial_number)
-       {
-               this_im->lastmsg = time(NULL);          /* Touch the timestamp so we know when to flush */
-               this_im->last_serial = serial_number;
-               StrBufAppendBufPlain(this_im->conversation, HKEY("<p><b>"), 0);
-               StrBufAppendBufPlain(this_im->conversation, me->user.fullname, -1, 0);
-               StrBufAppendBufPlain(this_im->conversation, HKEY(":</b> "), 0);
-               StrEscAppend(this_im->conversation, NULL, msgtext, 0, 0);
-               StrBufAppendBufPlain(this_im->conversation, HKEY("</p>\r\n"), 0);
-       }
-       end_critical_section(S_IM_LOGS);
-}
-
-/*
- * This message can be set to anything you want, but it is
- * checked for consistency so don't move it away from here.
- */
-#define KICKEDMSG "You have been kicked out of this room."
-
-void allwrite(char *cmdbuf, int flag, char *username)
-{
-       FILE *fp;
-       char bcast[SIZ];
-       char *un;
-       struct ChatLine *clptr, *clnew;
-       time_t now;
-
-       if (CC->fake_username[0])
-               un = CC->fake_username;
-       else
-               un = CC->user.fullname;
-       if (flag == 1) {
-               snprintf(bcast, sizeof bcast, ":|<%s %s>", un, cmdbuf);
-       } else if (flag == 0) {
-               snprintf(bcast, sizeof bcast, "%s|%s", un, cmdbuf);
-       } else if (flag == 2) {
-               snprintf(bcast, sizeof bcast, ":|<%s whispers %s>", un, cmdbuf);
-       } else if (flag == 3) {
-               snprintf(bcast, sizeof bcast, ":|%s", KICKEDMSG);
-       }
-       if ((strcasecmp(cmdbuf, "NOOP")) && (flag != 2)) {
-               fp = fopen(CHATLOG, "a");
-               if (fp != NULL)
-                       fprintf(fp, "%s\n", bcast);
-               fclose(fp);
-       }
-       clnew = (struct ChatLine *) malloc(sizeof(struct ChatLine));
-       memset(clnew, 0, sizeof(struct ChatLine));
-       if (clnew == NULL) {
-               fprintf(stderr, "citserver: cannot alloc chat line: %s\n",
-                       strerror(errno));
-               return;
-       }
-       time(&now);
-       clnew->next = NULL;
-       clnew->chat_time = now;
-       safestrncpy(clnew->chat_room, CC->room.QRname,
-                       sizeof clnew->chat_room);
-       clnew->chat_room[sizeof clnew->chat_room - 1] = 0;
-       if (username) {
-               safestrncpy(clnew->chat_username, username,
-                       sizeof clnew->chat_username);
-               clnew->chat_username[sizeof clnew->chat_username - 1] = 0;
-       } else
-               clnew->chat_username[0] = '\0';
-       safestrncpy(clnew->chat_text, bcast, sizeof clnew->chat_text);
-
-       /* Here's the critical section.
-        * First, add the new message to the queue...
-        */
-       begin_critical_section(S_CHATQUEUE);
-       ++ChatLastMsg;
-       clnew->chat_seq = ChatLastMsg;
-       if (ChatQueue == NULL) {
-               ChatQueue = clnew;
-       } else {
-               for (clptr = ChatQueue; clptr->next != NULL; clptr = clptr->next);;
-               clptr->next = clnew;
-       }
-
-       /* Then, before releasing the lock, free the expired messages */
-       while ((ChatQueue != NULL) && (now - ChatQueue->chat_time >= 120L)) {
-               clptr = ChatQueue;
-               ChatQueue = ChatQueue->next;
-               free(clptr);
-       }
-       end_critical_section(S_CHATQUEUE);
-}
-
-
-CitContext *find_context(char **unstr)
-{
-       CitContext *t_cc, *found_cc = NULL;
-       char *name, *tptr;
-
-       if ((!*unstr) || (!unstr))
-               return (NULL);
-
-       begin_critical_section(S_SESSION_TABLE);
-       for (t_cc = ContextList; ((t_cc) && (!found_cc)); t_cc = t_cc->next) {
-               if (t_cc->fake_username[0])
-                       name = t_cc->fake_username;
-               else
-                       name = t_cc->curr_user;
-               tptr = *unstr;
-               if ((!strncasecmp(name, tptr, strlen(name))) && (tptr[strlen(name)] == ' ')) {
-                       found_cc = t_cc;
-                       *unstr = &(tptr[strlen(name) + 1]);
-               }
-       }
-       end_critical_section(S_SESSION_TABLE);
-
-       return (found_cc);
-}
-
-/*
- * List users in chat.
- * allflag ==  0 = list users in chat
- *             1 = list users in chat, followed by users not in chat
- *             2 = display count only
- */
-
-void do_chat_listing(int allflag)
-{
-       struct CitContext *ccptr;
-       int count = 0;
-       int count_elsewhere = 0;
-       char roomname[ROOMNAMELEN];
-
-       if ((allflag == 0) || (allflag == 1))
-               cprintf(":|\n:| Users currently in chat:\n");
-       begin_critical_section(S_SESSION_TABLE);
-       for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
-               if (ccptr->cs_flags & CS_CHAT) {
-                       if (!strcasecmp(ccptr->room.QRname,
-                          CC->room.QRname)) {
-                               ++count;
-                       }
-                       else {
-                               ++count_elsewhere;
-                       }
-               }
-
-               GenerateRoomDisplay(roomname, ccptr, CC);
-               if ((CC->user.axlevel < AxAideU) && (!IsEmptyStr(ccptr->fake_roomname))) {
-                       strcpy(roomname, ccptr->fake_roomname);
-               }
-
-               if ((ccptr->cs_flags & CS_CHAT) && ((ccptr->cs_flags & CS_STEALTH) == 0)) {
-                       if ((allflag == 0) || (allflag == 1)) {
-                               cprintf(":| %-25s <%s>:\n",
-                                       (ccptr->fake_username[0]) ? ccptr->fake_username : ccptr->curr_user,
-                                       roomname);
-                       }
-               }
-       }
-
-       if (allflag == 1) {
-               cprintf(":|\n:| Users not in chat:\n");
-               for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
-
-                       GenerateRoomDisplay(roomname, ccptr, CC);
-                       if ((CC->user.axlevel < AxAideU)
-                       && (!IsEmptyStr(ccptr->fake_roomname))) {
-                               strcpy(roomname, ccptr->fake_roomname);
-                       }
-
-                       if (((ccptr->cs_flags & CS_CHAT) == 0)
-                           && ((ccptr->cs_flags & CS_STEALTH) == 0)) {
-                               cprintf(":| %-25s <%s>:\n",
-                                       (ccptr->fake_username[0]) ? ccptr->fake_username : ccptr->curr_user,
-                                       roomname);
-                       }
-               }
-       }
-       end_critical_section(S_SESSION_TABLE);
-
-       if (allflag == 2) {
-               if (count > 1) {
-                       cprintf(":|There are %d users here.\n", count);
-               }
-               else {
-                       cprintf(":|Note: you are the only one here.\n");
-               }
-               if (count_elsewhere > 0) {
-                       cprintf(":|There are %d users chatting in other rooms.\n", count_elsewhere);
-               }
-       }
-
-       cprintf(":|\n");
-}
-
-
-void cmd_chat(char *argbuf)
-{
-       /* FIXME chat has been broken by the underlying buffered I/O layer */
-       cprintf("%d Chat is currently disabled at this site.\n", ERROR);
-       return;
-
-#if 0
-       char cmdbuf[SIZ];
-       char *un;
-       char *strptr1;
-       int MyLastMsg, ThisLastMsg;
-       struct ChatLine *clptr;
-       struct CitContext *t_context;
-       int retval;
-       CitContext *CCC = CC;
-
-       if (!(CCC->logged_in)) {
-               cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
-               return;
-       }
-
-       CCC->cs_flags = CCC->cs_flags | CS_CHAT;
-       cprintf("%d Entering chat mode (type '/help' for available commands)\n",
-               START_CHAT_MODE);
-       unbuffer_output();
-
-       MyLastMsg = ChatLastMsg;
-
-       if ((CCC->cs_flags & CS_STEALTH) == 0) {
-               allwrite("<entering chat>", 0, NULL);
-       }
-       strcpy(cmdbuf, "");
-
-       do_chat_listing(2);
-
-       while (1) {
-               int ok_cmd;
-               int linelen;
-
-               ok_cmd = 0;
-               linelen = strlen(cmdbuf);
-               if (linelen > 100) --linelen;   /* truncate too-long lines */
-               cmdbuf[linelen + 1] = 0;
-
-               retval = client_read_to(&cmdbuf[linelen], 1, 2);
-
-               if (retval < 0 || CCC->kill_me) {       /* socket broken? */
-                       if ((CCC->cs_flags & CS_STEALTH) == 0) {
-                               allwrite("<disconnected>", 0, NULL);
-                       }
-                       return;
-               }
-
-               /* if we have a complete line, do send processing */
-               if (!IsEmptyStr(cmdbuf))
-                       if (cmdbuf[strlen(cmdbuf) - 1] == 10) {
-                               cmdbuf[strlen(cmdbuf) - 1] = 0;
-                               time(&CCC->lastcmd);
-                               time(&CCC->lastidle);
-
-                               if ((!strcasecmp(cmdbuf, "exit"))
-                                   || (!strcasecmp(cmdbuf, "/exit"))
-                                   || (!strcasecmp(cmdbuf, "quit"))
-                                   || (!strcasecmp(cmdbuf, "logout"))
-                                   || (!strcasecmp(cmdbuf, "logoff"))
-                                   || (!strcasecmp(cmdbuf, "/q"))
-                                   || (!strcasecmp(cmdbuf, ".q"))
-                                   || (!strcasecmp(cmdbuf, "/quit"))
-                                   )
-                                       strcpy(cmdbuf, "000");
-
-                               if (!strcmp(cmdbuf, "000")) {
-                                       if ((CCC->cs_flags & CS_STEALTH) == 0) {
-                                               allwrite("<exiting chat>", 0, NULL);
-                                       }
-                                       sleep(1);
-                                       cprintf("000\n");
-                                       CCC->cs_flags = CCC->cs_flags - CS_CHAT;
-                                       return;
-                               }
-                               if ((!strcasecmp(cmdbuf, "/help"))
-                                   || (!strcasecmp(cmdbuf, "help"))
-                                   || (!strcasecmp(cmdbuf, "/?"))
-                                   || (!strcasecmp(cmdbuf, "?"))) {
-                                       cprintf(":|\n");
-                                       cprintf(":|Available commands: \n");
-                                       cprintf(":|/help   (prints this message) \n");
-                                       cprintf(":|/who    (list users currently in chat) \n");
-                                       cprintf(":|/whobbs (list users in chat -and- elsewhere) \n");
-                                       cprintf(":|/me     ('action' line, ala irc) \n");
-                                       cprintf(":|/msg    (send private message, ala irc) \n");
-                                       if (is_room_aide()) {
-                                               cprintf(":|/kick   (kick another user out of this room) \n");
-                                       }
-                                       cprintf(":|/quit   (exit from this chat) \n");
-                                       cprintf(":|\n");
-                                       ok_cmd = 1;
-                               }
-                               if (!strcasecmp(cmdbuf, "/who")) {
-                                       do_chat_listing(0);
-                                       ok_cmd = 1;
-                               }
-                               if (!strcasecmp(cmdbuf, "/whobbs")) {
-                                       do_chat_listing(1);
-                                       ok_cmd = 1;
-                               }
-                               if (!strncasecmp(cmdbuf, "/me ", 4)) {
-                                       allwrite(&cmdbuf[4], 1, NULL);
-                                       ok_cmd = 1;
-                               }
-                               if (!strncasecmp(cmdbuf, "/msg ", 5)) {
-                                       ok_cmd = 1;
-                                       strptr1 = &cmdbuf[5];
-                                       if ((t_context = find_context(&strptr1))) {
-                                               allwrite(strptr1, 2, CCC->curr_user);
-                                               if (strcasecmp(CCC->curr_user, t_context->curr_user))
-                                                       allwrite(strptr1, 2, t_context->curr_user);
-                                       } else
-                                               cprintf(":|User not found.\n");
-                                       cprintf("\n");
-                               }
-                               /* The /kick function is implemented by sending a specific
-                                * message to the kicked-out user's context.  When that message
-                                * is processed by the read loop, that context will exit.
-                                */
-                               if ( (!strncasecmp(cmdbuf, "/kick ", 6)) && (is_room_aide()) ) {
-                                       ok_cmd = 1;
-                                       strptr1 = &cmdbuf[6];
-                                       strcat(strptr1, " ");
-                                       if ((t_context = find_context(&strptr1))) {
-                                               if (strcasecmp(CCC->curr_user, t_context->curr_user))
-                                                       allwrite(strptr1, 3, t_context->curr_user);
-                                       } else
-                                               cprintf(":|User not found.\n");
-                                       cprintf("\n");
-                               }
-                               if ((cmdbuf[0] != '/') && (strlen(cmdbuf) > 0)) {
-                                       ok_cmd = 1;
-                                       allwrite(cmdbuf, 0, NULL);
-                               }
-                               if ((!ok_cmd) && (cmdbuf[0]) && (cmdbuf[0] != '\n'))
-                                       cprintf(":|Command %s is not understood.\n", cmdbuf);
-
-                               strcpy(cmdbuf, "");
-
-                       }
-               /* now check the queue for new incoming stuff */
-
-               if (CCC->fake_username[0])
-                       un = CCC->fake_username;
-               else
-                       un = CCC->curr_user;
-               if (ChatLastMsg > MyLastMsg) {
-                       ThisLastMsg = ChatLastMsg;
-                       for (clptr = ChatQueue; clptr != NULL; clptr = clptr->next) {
-                               if ((clptr->chat_seq > MyLastMsg) && ((!clptr->chat_username[0]) || (!strncasecmp(un, clptr->chat_username, 32)))) {
-                                       if ((!clptr->chat_room[0]) || (!strncasecmp(CCC->room.QRname, clptr->chat_room, ROOMNAMELEN))) {
-                                               /* Output new chat data */
-                                               cprintf("%s\n", clptr->chat_text);
-
-                                               /* See if we've been force-quitted (kicked etc.) */
-                                               if (!strcmp(&clptr->chat_text[2], KICKEDMSG)) {
-                                                       allwrite("<kicked out of this room>", 0, NULL);
-                                                       cprintf("000\n");
-                                                       CCC->cs_flags = CCC->cs_flags - CS_CHAT;
-
-                                                       /* Kick user out of room */
-                                                       CtdlInvtKick(CCC->user.fullname, 0);
-
-                                                       /* And return to the Lobby */
-                                                       CtdlUserGoto(config.c_baseroom, 0, 0, NULL, NULL);
-                                                       return;
-                                               }
-                                       }
-                               }
-                       }
-                       MyLastMsg = ThisLastMsg;
-               }
-       }
-#endif
-}
-
-
-
-/*
- * Delete any remaining instant messages
- */
-void delete_instant_messages(void) {
-       struct ExpressMessage *ptr;
-
-       begin_critical_section(S_SESSION_TABLE);
-       while (CC->FirstExpressMessage != NULL) {
-               ptr = CC->FirstExpressMessage->next;
-               if (CC->FirstExpressMessage->text != NULL)
-                       free(CC->FirstExpressMessage->text);
-               free(CC->FirstExpressMessage);
-               CC->FirstExpressMessage = ptr;
-       }
-       end_critical_section(S_SESSION_TABLE);
-}
-
-
-
-/*
- * Retrieve instant messages
- */
-void cmd_gexp(char *argbuf) {
-       struct ExpressMessage *ptr;
-
-       if (CC->FirstExpressMessage == NULL) {
-               cprintf("%d No instant messages waiting.\n", ERROR + MESSAGE_NOT_FOUND);
-               return;
-       }
-
-       begin_critical_section(S_SESSION_TABLE);
-       ptr = CC->FirstExpressMessage;
-       CC->FirstExpressMessage = CC->FirstExpressMessage->next;
-       end_critical_section(S_SESSION_TABLE);
-
-       cprintf("%d %d|%ld|%d|%s|%s|%s\n",
-               LISTING_FOLLOWS,
-               ((ptr->next != NULL) ? 1 : 0),          /* more msgs? */
-               (long)ptr->timestamp,                   /* time sent */
-               ptr->flags,                             /* flags */
-               ptr->sender,                            /* sender of msg */
-               config.c_nodename,                      /* static for now (and possibly deprecated) */
-               ptr->sender_email                       /* email or jid of sender */
-       );
-
-       if (ptr->text != NULL) {
-               memfmout(ptr->text, "\n");
-               free(ptr->text);
-       }
-
-       cprintf("000\n");
-       free(ptr);
-}
-
-/*
- * Asynchronously deliver instant messages
- */
-void cmd_gexp_async(void) {
-
-       /* Only do this if the session can handle asynchronous protocol */
-       if (CC->is_async == 0) return;
-
-       /* And don't do it if there's nothing to send. */
-       if (CC->FirstExpressMessage == NULL) return;
-
-       cprintf("%d instant msg\n", ASYNC_MSG + ASYNC_GEXP);
-}
-
-/*
- * Back end support function for send_instant_message() and company
- */
-void add_xmsg_to_context(struct CitContext *ccptr, struct ExpressMessage *newmsg) 
-{
-       struct ExpressMessage *findend;
-
-       if (ccptr->FirstExpressMessage == NULL) {
-               ccptr->FirstExpressMessage = newmsg;
-       }
-       else {
-               findend = ccptr->FirstExpressMessage;
-               while (findend->next != NULL) {
-                       findend = findend->next;
-               }
-               findend->next = newmsg;
-       }
-
-       /* If the target context is a session which can handle asynchronous
-        * messages, go ahead and set the flag for that.
-        */
-       set_async_waiting(ccptr);
-}
-
-
-
-
-/* 
- * This is the back end to the instant message sending function.  
- * Returns the number of users to which the message was sent.
- * Sending a zero-length message tests for recipients without sending messages.
- */
-int send_instant_message(char *lun, char *lem, char *x_user, char *x_msg)
-{
-       int message_sent = 0;           /* number of successful sends */
-       struct CitContext *ccptr;
-       struct ExpressMessage *newmsg = NULL;
-       char *un;
-       int do_send = 0;                /* 1 = send message; 0 = only check for valid recipient */
-       static int serial_number = 0;   /* this keeps messages from getting logged twice */
-
-       if (strlen(x_msg) > 0) {
-               do_send = 1;
-       }
-
-       /* find the target user's context and append the message */
-       begin_critical_section(S_SESSION_TABLE);
-       ++serial_number;
-       for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
-
-               if (ccptr->fake_username[0]) {
-                       un = ccptr->fake_username;
-               }
-               else {
-                       un = ccptr->user.fullname;
-               }
-
-               if ( ((!strcasecmp(un, x_user))
-                   || (!strcasecmp(x_user, "broadcast")))
-                   && (ccptr->can_receive_im)
-                   && ((ccptr->disable_exp == 0)
-                   || (CC->user.axlevel >= AxAideU)) ) {
-                       if (do_send) {
-                               newmsg = (struct ExpressMessage *) malloc(sizeof (struct ExpressMessage));
-                               memset(newmsg, 0, sizeof (struct ExpressMessage));
-                               time(&(newmsg->timestamp));
-                               safestrncpy(newmsg->sender, lun, sizeof newmsg->sender);
-                               safestrncpy(newmsg->sender_email, lem, sizeof newmsg->sender_email);
-                               if (!strcasecmp(x_user, "broadcast")) {
-                                       newmsg->flags |= EM_BROADCAST;
-                               }
-                               newmsg->text = strdup(x_msg);
-
-                               add_xmsg_to_context(ccptr, newmsg);
-
-                               /* and log it ... */
-                               if (ccptr != CC) {
-                                       log_instant_message(CC, ccptr, newmsg->text, serial_number);
-                               }
-                       }
-                       ++message_sent;
-               }
-       }
-       end_critical_section(S_SESSION_TABLE);
-       return (message_sent);
-}
-
-/*
- * send instant messages
- */
-void cmd_sexp(char *argbuf)
-{
-       int message_sent = 0;
-       char x_user[USERNAME_SIZE];
-       char x_msg[1024];
-       char *lun;
-       char *lem;
-       char *x_big_msgbuf = NULL;
-
-       if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
-               cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
-               return;
-       }
-       if (CC->fake_username[0])
-               lun = CC->fake_username;
-       else
-               lun = CC->user.fullname;
-
-       lem = CC->cs_inet_email;
-
-       extract_token(x_user, argbuf, 0, '|', sizeof x_user);
-       extract_token(x_msg, argbuf, 1, '|', sizeof x_msg);
-
-       if (!x_user[0]) {
-               cprintf("%d You were not previously paged.\n", ERROR + NO_SUCH_USER);
-               return;
-       }
-       if ((!strcasecmp(x_user, "broadcast")) && (CC->user.axlevel < AxAideU)) {
-               cprintf("%d Higher access required to send a broadcast.\n",
-                       ERROR + HIGHER_ACCESS_REQUIRED);
-               return;
-       }
-       /* This loop handles text-transfer pages */
-       if (!strcmp(x_msg, "-")) {
-               message_sent = PerformXmsgHooks(lun, lem, x_user, "");
-               if (message_sent == 0) {
-                       if (CtdlGetUser(NULL, x_user))
-                               cprintf("%d '%s' does not exist.\n",
-                                               ERROR + NO_SUCH_USER, x_user);
-                       else
-                               cprintf("%d '%s' is not logged in "
-                                               "or is not accepting pages.\n",
-                                               ERROR + RESOURCE_NOT_OPEN, x_user);
-                       return;
-               }
-               unbuffer_output();
-               cprintf("%d Transmit message (will deliver to %d users)\n",
-                       SEND_LISTING, message_sent);
-               x_big_msgbuf = malloc(SIZ);
-               memset(x_big_msgbuf, 0, SIZ);
-               while (client_getln(x_msg, sizeof x_msg) >= 0 && strcmp(x_msg, "000")) {
-                       x_big_msgbuf = realloc(x_big_msgbuf,
-                              strlen(x_big_msgbuf) + strlen(x_msg) + 4);
-                       if (!IsEmptyStr(x_big_msgbuf))
-                          if (x_big_msgbuf[strlen(x_big_msgbuf)] != '\n')
-                               strcat(x_big_msgbuf, "\n");
-                       strcat(x_big_msgbuf, x_msg);
-               }
-               PerformXmsgHooks(lun, lem, x_user, x_big_msgbuf);
-               free(x_big_msgbuf);
-
-               /* This loop handles inline pages */
-       } else {
-               message_sent = PerformXmsgHooks(lun, lem, x_user, x_msg);
-
-               if (message_sent > 0) {
-                       if (!IsEmptyStr(x_msg))
-                               cprintf("%d Message sent", CIT_OK);
-                       else
-                               cprintf("%d Ok to send message", CIT_OK);
-                       if (message_sent > 1)
-                               cprintf(" to %d users", message_sent);
-                       cprintf(".\n");
-               } else {
-                       if (CtdlGetUser(NULL, x_user))
-                               cprintf("%d '%s' does not exist.\n",
-                                               ERROR + NO_SUCH_USER, x_user);
-                       else
-                               cprintf("%d '%s' is not logged in "
-                                               "or is not accepting pages.\n",
-                                               ERROR + RESOURCE_NOT_OPEN, x_user);
-               }
-
-
-       }
-}
-
-
-
-/*
- * Enter or exit paging-disabled mode
- */
-void cmd_dexp(char *argbuf)
-{
-       int new_state;
-
-       if (CtdlAccessCheck(ac_logged_in)) return;
-
-       new_state = extract_int(argbuf, 0);
-       if ((new_state == 0) || (new_state == 1)) {
-               CC->disable_exp = new_state;
-       }
-
-       cprintf("%d %d\n", CIT_OK, CC->disable_exp);
-}
-
-
-/*
- * Request client termination
- */
-void cmd_reqt(char *argbuf) {
-       struct CitContext *ccptr;
-       int sessions = 0;
-       int which_session;
-       struct ExpressMessage *newmsg;
-
-       if (CtdlAccessCheck(ac_aide)) return;
-       which_session = extract_int(argbuf, 0);
-
-       begin_critical_section(S_SESSION_TABLE);
-       for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
-               if ((ccptr->cs_pid == which_session) || (which_session == 0)) {
-
-                       newmsg = (struct ExpressMessage *)
-                               malloc(sizeof (struct ExpressMessage));
-                       memset(newmsg, 0,
-                               sizeof (struct ExpressMessage));
-                       time(&(newmsg->timestamp));
-                       safestrncpy(newmsg->sender, CC->user.fullname,
-                                   sizeof newmsg->sender);
-                       newmsg->flags |= EM_GO_AWAY;
-                       newmsg->text = strdup("Automatic logoff requested.");
-
-                       add_xmsg_to_context(ccptr, newmsg);
-                       ++sessions;
-
-               }
-       }
-       end_critical_section(S_SESSION_TABLE);
-       cprintf("%d Sent termination request to %d sessions.\n", CIT_OK, sessions);
-}
-
-
-/*
- * This is the back end for flush_conversations_to_disk()
- * At this point we've isolated a single conversation (struct imlog)
- * and are ready to write it to disk.
- */
-void flush_individual_conversation(struct imlog *im) {
-       struct CtdlMessage *msg;
-       long msgnum = 0;
-       char roomname[ROOMNAMELEN];
-
-       StrBufAppendBufPlain(im->conversation, HKEY(
-               "</body>\r\n"
-               "</html>\r\n"
-               ), 0
-       );
-
-       msg = malloc(sizeof(struct CtdlMessage));
-       memset(msg, 0, sizeof(struct CtdlMessage));
-       msg->cm_magic = CTDLMESSAGE_MAGIC;
-       msg->cm_anon_type = MES_NORMAL;
-       msg->cm_format_type = FMT_RFC822;
-       if (!IsEmptyStr(im->usernames[0])) {
-               msg->cm_fields['A'] = strdup(im->usernames[0]);
-       } else {
-               msg->cm_fields['A'] = strdup("Citadel");
-       }
-       if (!IsEmptyStr(im->usernames[1])) {
-               msg->cm_fields['R'] = strdup(im->usernames[1]);
-       }
-       msg->cm_fields['O'] = strdup(PAGELOGROOM);
-       msg->cm_fields['N'] = strdup(NODENAME);
-       msg->cm_fields['M'] = SmashStrBuf(&im->conversation);   /* we own this memory now */
-
-       /* Start with usernums[1] because it's guaranteed to be higher than usernums[0],
-        * so if there's only one party, usernums[0] will be zero but usernums[1] won't.
-        * Create the room if necessary.  Note that we create as a type 5 room rather
-        * than 4, which indicates that it's a personal room but we've already supplied
-        * the namespace prefix.
-        *
-        * In the unlikely event that usernums[1] is zero, a room with an invalid namespace
-        * prefix will be created.  That's ok because the auto-purger will clean it up later.
-        */
-       snprintf(roomname, sizeof roomname, "%010ld.%s", im->usernums[1], PAGELOGROOM);
-       CtdlCreateRoom(roomname, 5, "", 0, 1, 1, VIEW_BBS);
-       msgnum = CtdlSubmitMsg(msg, NULL, roomname, 0);
-       CtdlFreeMessage(msg);
-
-       /* If there is a valid user number in usernums[0], save a copy for them too. */
-       if (im->usernums[0] > 0) {
-               snprintf(roomname, sizeof roomname, "%010ld.%s", im->usernums[0], PAGELOGROOM);
-               CtdlCreateRoom(roomname, 5, "", 0, 1, 1, VIEW_BBS);
-               CtdlSaveMsgPointerInRoom(roomname, msgnum, 0, NULL);
-       }
-
-       /* Finally, if we're logging instant messages globally, do that now. */
-       if (!IsEmptyStr(config.c_logpages)) {
-               CtdlCreateRoom(config.c_logpages, 3, "", 0, 1, 1, VIEW_BBS);
-               CtdlSaveMsgPointerInRoom(config.c_logpages, msgnum, 0, NULL);
-       }
-
-}
-
-/*
- * Locate instant message conversations which have gone idle
- * (or, if the server is shutting down, locate *all* conversations)
- * and flush them to disk (in the participants' log rooms, etc.)
- */
-void flush_conversations_to_disk(time_t if_older_than) {
-
-       struct imlog *flush_these = NULL;
-       struct imlog *dont_flush_these = NULL;
-       struct imlog *imptr = NULL;
-
-       begin_critical_section(S_IM_LOGS);
-       while (imlist)
-       {
-               imptr = imlist;
-               imlist = imlist->next;
-               if ((time(NULL) - imptr->lastmsg) > if_older_than)
-               {
-                       /* This conversation qualifies.  Move it to the list of ones to flush. */
-                       imptr->next = flush_these;
-                       flush_these = imptr;
-               }
-               else  {
-                       /* Move it to the list of ones not to flush. */
-                       imptr->next = dont_flush_these;
-                       dont_flush_these = imptr;
-               }
-       }
-       imlist = dont_flush_these;
-       end_critical_section(S_IM_LOGS);
-
-       /* We are now outside of the critical section, and we are the only thread holding a
-        * pointer to a linked list of conversations to be flushed to disk.
-        */
-       while (flush_these) {
-
-               flush_individual_conversation(flush_these);     /* This will free the string buffer */
-               imptr = flush_these;
-               flush_these = flush_these->next;
-               free(imptr);
-       }
-}
-
-
-
-void chat_timer(void) {
-       flush_conversations_to_disk(300);       /* Anything that hasn't peeped in more than 5 minutes */
-}
-
-void chat_shutdown(void) {
-       flush_conversations_to_disk(0);         /* Get it ALL onto disk NOW. */
-}
-
-CTDL_MODULE_INIT(chat)
-{
-       if (!threading)
-       {
-               CtdlRegisterProtoHook(cmd_chat, "CHAT", "Begin real-time chat");
-               CtdlRegisterProtoHook(cmd_gexp, "GEXP", "Get instant messages");
-               CtdlRegisterProtoHook(cmd_sexp, "SEXP", "Send an instant message");
-               CtdlRegisterProtoHook(cmd_dexp, "DEXP", "Disable instant messages");
-               CtdlRegisterProtoHook(cmd_reqt, "REQT", "Request client termination");
-               CtdlRegisterSessionHook(cmd_gexp_async, EVT_ASYNC);
-               CtdlRegisterSessionHook(delete_instant_messages, EVT_STOP);
-               CtdlRegisterXmsgHook(send_instant_message, XMSG_PRI_LOCAL);
-               CtdlRegisterSessionHook(chat_timer, EVT_TIMER);
-               CtdlRegisterSessionHook(chat_shutdown, EVT_SHUTDOWN);
-       }
-       
-       /* return our Subversion id for the Log */
-       return "$Id$";
-}
diff --git a/citadel/modules/chat/serv_chat.h b/citadel/modules/chat/serv_chat.h
deleted file mode 100644 (file)
index 6ceba26..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-/* $Id$ */
-#include "ctdl_module.h"
-
-void ChatUnloadingTest(void);
-void allwrite (char *cmdbuf, int flag, char *username);
-CitContext *find_context (char **unstr);
-void do_chat_listing (int allflag);
-void cmd_chat (char *argbuf);
-void cmd_pexp (char *argbuf); /* arg unused */
-void cmd_sexp (char *argbuf);
-void delete_instant_messages(void);
-void cmd_gexp(char *);
-int send_instant_message(char *, char *, char *, char *);
diff --git a/citadel/modules/instmsg/serv_instmsg.c b/citadel/modules/instmsg/serv_instmsg.c
new file mode 100644 (file)
index 0000000..fd0fa8e
--- /dev/null
@@ -0,0 +1,586 @@
+/*
+ * $Id$
+ *
+ * This module handles instant messaging between users.
+ * 
+ * Copyright (c) 1987-2010 by the citadel.org team
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+#  include <sys/time.h>
+# else
+#  include <time.h>
+# endif
+#endif
+
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "citadel.h"
+#include "server.h"
+#include "serv_instmsg.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "msgbase.h"
+#include "user_ops.h"
+
+#ifndef HAVE_SNPRINTF
+#include "snprintf.h"
+#endif
+
+#include "ctdl_module.h"
+
+struct imlog {
+       struct imlog *next;
+       long usernums[2];
+       char usernames[2][128];
+       time_t lastmsg;
+       int last_serial;
+       StrBuf *conversation;
+};
+
+struct imlog *imlist = NULL;
+
+/*
+ * This function handles the logging of instant messages to disk.
+ */
+void log_instant_message(struct CitContext *me, struct CitContext *them, char *msgtext, int serial_number)
+{
+       long usernums[2];
+       long t;
+       struct imlog *iptr = NULL;
+       struct imlog *this_im = NULL;
+       
+       memset(usernums, 0, sizeof usernums);
+       usernums[0] = me->user.usernum;
+       usernums[1] = them->user.usernum;
+
+       /* Always put the lower user number first, so we can use the array as a hash value which
+        * represents a pair of users.  For a broadcast message one of the users will be 0.
+        */
+       if (usernums[0] > usernums[1]) {
+               t = usernums[0];
+               usernums[0] = usernums[1];
+               usernums[1] = t;
+       }
+
+       begin_critical_section(S_IM_LOGS);
+
+       /* Look for an existing conversation in the hash table.
+        * If not found, create a new one.
+        */
+
+       this_im = NULL;
+       for (iptr = imlist; iptr != NULL; iptr = iptr->next) {
+               if ((iptr->usernums[0] == usernums[0]) && (iptr->usernums[1] == usernums[1])) {
+                       /* Existing conversation */
+                       this_im = iptr;
+               }
+       }
+       if (this_im == NULL) {
+               /* New conversation */
+               this_im = malloc(sizeof(struct imlog));
+               memset(this_im, 0, sizeof (struct imlog));
+               this_im->usernums[0] = usernums[0];
+               this_im->usernums[1] = usernums[1];
+               /* usernames[] and usernums[] might not be in the same order.  This is not an error. */
+               if (me) {
+                       safestrncpy(this_im->usernames[0], me->user.fullname, sizeof this_im->usernames[0]);
+               }
+               if (them) {
+                       safestrncpy(this_im->usernames[1], them->user.fullname, sizeof this_im->usernames[1]);
+               }
+               this_im->conversation = NewStrBuf();
+               this_im->next = imlist;
+               imlist = this_im;
+               StrBufAppendBufPlain(this_im->conversation, HKEY(
+                       "Content-type: text/html\r\n"
+                       "Content-transfer-encoding: 7bit\r\n"
+                       "\r\n"
+                       "<html><body>\r\n"
+                       ), 0);
+       }
+
+
+       /* Since it's possible for this function to get called more than once if a user is logged
+        * in on multiple sessions, we use the message's serial number to keep track of whether
+        * we've already logged it.
+        */
+       if (this_im->last_serial != serial_number)
+       {
+               this_im->lastmsg = time(NULL);          /* Touch the timestamp so we know when to flush */
+               this_im->last_serial = serial_number;
+               StrBufAppendBufPlain(this_im->conversation, HKEY("<p><b>"), 0);
+               StrBufAppendBufPlain(this_im->conversation, me->user.fullname, -1, 0);
+               StrBufAppendBufPlain(this_im->conversation, HKEY(":</b> "), 0);
+               StrEscAppend(this_im->conversation, NULL, msgtext, 0, 0);
+               StrBufAppendBufPlain(this_im->conversation, HKEY("</p>\r\n"), 0);
+       }
+       end_critical_section(S_IM_LOGS);
+}
+
+
+/*
+ * Delete any remaining instant messages
+ */
+void delete_instant_messages(void) {
+       struct ExpressMessage *ptr;
+
+       begin_critical_section(S_SESSION_TABLE);
+       while (CC->FirstExpressMessage != NULL) {
+               ptr = CC->FirstExpressMessage->next;
+               if (CC->FirstExpressMessage->text != NULL)
+                       free(CC->FirstExpressMessage->text);
+               free(CC->FirstExpressMessage);
+               CC->FirstExpressMessage = ptr;
+       }
+       end_critical_section(S_SESSION_TABLE);
+}
+
+
+
+/*
+ * Retrieve instant messages
+ */
+void cmd_gexp(char *argbuf) {
+       struct ExpressMessage *ptr;
+
+       if (CC->FirstExpressMessage == NULL) {
+               cprintf("%d No instant messages waiting.\n", ERROR + MESSAGE_NOT_FOUND);
+               return;
+       }
+
+       begin_critical_section(S_SESSION_TABLE);
+       ptr = CC->FirstExpressMessage;
+       CC->FirstExpressMessage = CC->FirstExpressMessage->next;
+       end_critical_section(S_SESSION_TABLE);
+
+       cprintf("%d %d|%ld|%d|%s|%s|%s\n",
+               LISTING_FOLLOWS,
+               ((ptr->next != NULL) ? 1 : 0),          /* more msgs? */
+               (long)ptr->timestamp,                   /* time sent */
+               ptr->flags,                             /* flags */
+               ptr->sender,                            /* sender of msg */
+               config.c_nodename,                      /* static for now (and possibly deprecated) */
+               ptr->sender_email                       /* email or jid of sender */
+       );
+
+       if (ptr->text != NULL) {
+               memfmout(ptr->text, "\n");
+               free(ptr->text);
+       }
+
+       cprintf("000\n");
+       free(ptr);
+}
+
+/*
+ * Asynchronously deliver instant messages
+ */
+void cmd_gexp_async(void) {
+
+       /* Only do this if the session can handle asynchronous protocol */
+       if (CC->is_async == 0) return;
+
+       /* And don't do it if there's nothing to send. */
+       if (CC->FirstExpressMessage == NULL) return;
+
+       cprintf("%d instant msg\n", ASYNC_MSG + ASYNC_GEXP);
+}
+
+/*
+ * Back end support function for send_instant_message() and company
+ */
+void add_xmsg_to_context(struct CitContext *ccptr, struct ExpressMessage *newmsg) 
+{
+       struct ExpressMessage *findend;
+
+       if (ccptr->FirstExpressMessage == NULL) {
+               ccptr->FirstExpressMessage = newmsg;
+       }
+       else {
+               findend = ccptr->FirstExpressMessage;
+               while (findend->next != NULL) {
+                       findend = findend->next;
+               }
+               findend->next = newmsg;
+       }
+
+       /* If the target context is a session which can handle asynchronous
+        * messages, go ahead and set the flag for that.
+        */
+       set_async_waiting(ccptr);
+}
+
+
+
+
+/* 
+ * This is the back end to the instant message sending function.  
+ * Returns the number of users to which the message was sent.
+ * Sending a zero-length message tests for recipients without sending messages.
+ */
+int send_instant_message(char *lun, char *lem, char *x_user, char *x_msg)
+{
+       int message_sent = 0;           /* number of successful sends */
+       struct CitContext *ccptr;
+       struct ExpressMessage *newmsg = NULL;
+       char *un;
+       int do_send = 0;                /* 1 = send message; 0 = only check for valid recipient */
+       static int serial_number = 0;   /* this keeps messages from getting logged twice */
+
+       if (strlen(x_msg) > 0) {
+               do_send = 1;
+       }
+
+       /* find the target user's context and append the message */
+       begin_critical_section(S_SESSION_TABLE);
+       ++serial_number;
+       for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
+
+               if (ccptr->fake_username[0]) {
+                       un = ccptr->fake_username;
+               }
+               else {
+                       un = ccptr->user.fullname;
+               }
+
+               if ( ((!strcasecmp(un, x_user))
+                   || (!strcasecmp(x_user, "broadcast")))
+                   && (ccptr->can_receive_im)
+                   && ((ccptr->disable_exp == 0)
+                   || (CC->user.axlevel >= AxAideU)) ) {
+                       if (do_send) {
+                               newmsg = (struct ExpressMessage *) malloc(sizeof (struct ExpressMessage));
+                               memset(newmsg, 0, sizeof (struct ExpressMessage));
+                               time(&(newmsg->timestamp));
+                               safestrncpy(newmsg->sender, lun, sizeof newmsg->sender);
+                               safestrncpy(newmsg->sender_email, lem, sizeof newmsg->sender_email);
+                               if (!strcasecmp(x_user, "broadcast")) {
+                                       newmsg->flags |= EM_BROADCAST;
+                               }
+                               newmsg->text = strdup(x_msg);
+
+                               add_xmsg_to_context(ccptr, newmsg);
+
+                               /* and log it ... */
+                               if (ccptr != CC) {
+                                       log_instant_message(CC, ccptr, newmsg->text, serial_number);
+                               }
+                       }
+                       ++message_sent;
+               }
+       }
+       end_critical_section(S_SESSION_TABLE);
+       return (message_sent);
+}
+
+/*
+ * send instant messages
+ */
+void cmd_sexp(char *argbuf)
+{
+       int message_sent = 0;
+       char x_user[USERNAME_SIZE];
+       char x_msg[1024];
+       char *lun;
+       char *lem;
+       char *x_big_msgbuf = NULL;
+
+       if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
+               cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
+               return;
+       }
+       if (CC->fake_username[0])
+               lun = CC->fake_username;
+       else
+               lun = CC->user.fullname;
+
+       lem = CC->cs_inet_email;
+
+       extract_token(x_user, argbuf, 0, '|', sizeof x_user);
+       extract_token(x_msg, argbuf, 1, '|', sizeof x_msg);
+
+       if (!x_user[0]) {
+               cprintf("%d You were not previously paged.\n", ERROR + NO_SUCH_USER);
+               return;
+       }
+       if ((!strcasecmp(x_user, "broadcast")) && (CC->user.axlevel < AxAideU)) {
+               cprintf("%d Higher access required to send a broadcast.\n",
+                       ERROR + HIGHER_ACCESS_REQUIRED);
+               return;
+       }
+       /* This loop handles text-transfer pages */
+       if (!strcmp(x_msg, "-")) {
+               message_sent = PerformXmsgHooks(lun, lem, x_user, "");
+               if (message_sent == 0) {
+                       if (CtdlGetUser(NULL, x_user))
+                               cprintf("%d '%s' does not exist.\n",
+                                               ERROR + NO_SUCH_USER, x_user);
+                       else
+                               cprintf("%d '%s' is not logged in "
+                                               "or is not accepting pages.\n",
+                                               ERROR + RESOURCE_NOT_OPEN, x_user);
+                       return;
+               }
+               unbuffer_output();
+               cprintf("%d Transmit message (will deliver to %d users)\n",
+                       SEND_LISTING, message_sent);
+               x_big_msgbuf = malloc(SIZ);
+               memset(x_big_msgbuf, 0, SIZ);
+               while (client_getln(x_msg, sizeof x_msg) >= 0 && strcmp(x_msg, "000")) {
+                       x_big_msgbuf = realloc(x_big_msgbuf,
+                              strlen(x_big_msgbuf) + strlen(x_msg) + 4);
+                       if (!IsEmptyStr(x_big_msgbuf))
+                          if (x_big_msgbuf[strlen(x_big_msgbuf)] != '\n')
+                               strcat(x_big_msgbuf, "\n");
+                       strcat(x_big_msgbuf, x_msg);
+               }
+               PerformXmsgHooks(lun, lem, x_user, x_big_msgbuf);
+               free(x_big_msgbuf);
+
+               /* This loop handles inline pages */
+       } else {
+               message_sent = PerformXmsgHooks(lun, lem, x_user, x_msg);
+
+               if (message_sent > 0) {
+                       if (!IsEmptyStr(x_msg))
+                               cprintf("%d Message sent", CIT_OK);
+                       else
+                               cprintf("%d Ok to send message", CIT_OK);
+                       if (message_sent > 1)
+                               cprintf(" to %d users", message_sent);
+                       cprintf(".\n");
+               } else {
+                       if (CtdlGetUser(NULL, x_user))
+                               cprintf("%d '%s' does not exist.\n",
+                                               ERROR + NO_SUCH_USER, x_user);
+                       else
+                               cprintf("%d '%s' is not logged in "
+                                               "or is not accepting pages.\n",
+                                               ERROR + RESOURCE_NOT_OPEN, x_user);
+               }
+
+
+       }
+}
+
+
+
+/*
+ * Enter or exit paging-disabled mode
+ */
+void cmd_dexp(char *argbuf)
+{
+       int new_state;
+
+       if (CtdlAccessCheck(ac_logged_in)) return;
+
+       new_state = extract_int(argbuf, 0);
+       if ((new_state == 0) || (new_state == 1)) {
+               CC->disable_exp = new_state;
+       }
+
+       cprintf("%d %d\n", CIT_OK, CC->disable_exp);
+}
+
+
+/*
+ * Request client termination
+ */
+void cmd_reqt(char *argbuf) {
+       struct CitContext *ccptr;
+       int sessions = 0;
+       int which_session;
+       struct ExpressMessage *newmsg;
+
+       if (CtdlAccessCheck(ac_aide)) return;
+       which_session = extract_int(argbuf, 0);
+
+       begin_critical_section(S_SESSION_TABLE);
+       for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
+               if ((ccptr->cs_pid == which_session) || (which_session == 0)) {
+
+                       newmsg = (struct ExpressMessage *)
+                               malloc(sizeof (struct ExpressMessage));
+                       memset(newmsg, 0,
+                               sizeof (struct ExpressMessage));
+                       time(&(newmsg->timestamp));
+                       safestrncpy(newmsg->sender, CC->user.fullname,
+                                   sizeof newmsg->sender);
+                       newmsg->flags |= EM_GO_AWAY;
+                       newmsg->text = strdup("Automatic logoff requested.");
+
+                       add_xmsg_to_context(ccptr, newmsg);
+                       ++sessions;
+
+               }
+       }
+       end_critical_section(S_SESSION_TABLE);
+       cprintf("%d Sent termination request to %d sessions.\n", CIT_OK, sessions);
+}
+
+
+/*
+ * This is the back end for flush_conversations_to_disk()
+ * At this point we've isolated a single conversation (struct imlog)
+ * and are ready to write it to disk.
+ */
+void flush_individual_conversation(struct imlog *im) {
+       struct CtdlMessage *msg;
+       long msgnum = 0;
+       char roomname[ROOMNAMELEN];
+
+       StrBufAppendBufPlain(im->conversation, HKEY(
+               "</body>\r\n"
+               "</html>\r\n"
+               ), 0
+       );
+
+       msg = malloc(sizeof(struct CtdlMessage));
+       memset(msg, 0, sizeof(struct CtdlMessage));
+       msg->cm_magic = CTDLMESSAGE_MAGIC;
+       msg->cm_anon_type = MES_NORMAL;
+       msg->cm_format_type = FMT_RFC822;
+       if (!IsEmptyStr(im->usernames[0])) {
+               msg->cm_fields['A'] = strdup(im->usernames[0]);
+       } else {
+               msg->cm_fields['A'] = strdup("Citadel");
+       }
+       if (!IsEmptyStr(im->usernames[1])) {
+               msg->cm_fields['R'] = strdup(im->usernames[1]);
+       }
+       msg->cm_fields['O'] = strdup(PAGELOGROOM);
+       msg->cm_fields['N'] = strdup(NODENAME);
+       msg->cm_fields['M'] = SmashStrBuf(&im->conversation);   /* we own this memory now */
+
+       /* Start with usernums[1] because it's guaranteed to be higher than usernums[0],
+        * so if there's only one party, usernums[0] will be zero but usernums[1] won't.
+        * Create the room if necessary.  Note that we create as a type 5 room rather
+        * than 4, which indicates that it's a personal room but we've already supplied
+        * the namespace prefix.
+        *
+        * In the unlikely event that usernums[1] is zero, a room with an invalid namespace
+        * prefix will be created.  That's ok because the auto-purger will clean it up later.
+        */
+       snprintf(roomname, sizeof roomname, "%010ld.%s", im->usernums[1], PAGELOGROOM);
+       CtdlCreateRoom(roomname, 5, "", 0, 1, 1, VIEW_BBS);
+       msgnum = CtdlSubmitMsg(msg, NULL, roomname, 0);
+       CtdlFreeMessage(msg);
+
+       /* If there is a valid user number in usernums[0], save a copy for them too. */
+       if (im->usernums[0] > 0) {
+               snprintf(roomname, sizeof roomname, "%010ld.%s", im->usernums[0], PAGELOGROOM);
+               CtdlCreateRoom(roomname, 5, "", 0, 1, 1, VIEW_BBS);
+               CtdlSaveMsgPointerInRoom(roomname, msgnum, 0, NULL);
+       }
+
+       /* Finally, if we're logging instant messages globally, do that now. */
+       if (!IsEmptyStr(config.c_logpages)) {
+               CtdlCreateRoom(config.c_logpages, 3, "", 0, 1, 1, VIEW_BBS);
+               CtdlSaveMsgPointerInRoom(config.c_logpages, msgnum, 0, NULL);
+       }
+
+}
+
+/*
+ * Locate instant message conversations which have gone idle
+ * (or, if the server is shutting down, locate *all* conversations)
+ * and flush them to disk (in the participants' log rooms, etc.)
+ */
+void flush_conversations_to_disk(time_t if_older_than) {
+
+       struct imlog *flush_these = NULL;
+       struct imlog *dont_flush_these = NULL;
+       struct imlog *imptr = NULL;
+
+       begin_critical_section(S_IM_LOGS);
+       while (imlist)
+       {
+               imptr = imlist;
+               imlist = imlist->next;
+               if ((time(NULL) - imptr->lastmsg) > if_older_than)
+               {
+                       /* This conversation qualifies.  Move it to the list of ones to flush. */
+                       imptr->next = flush_these;
+                       flush_these = imptr;
+               }
+               else  {
+                       /* Move it to the list of ones not to flush. */
+                       imptr->next = dont_flush_these;
+                       dont_flush_these = imptr;
+               }
+       }
+       imlist = dont_flush_these;
+       end_critical_section(S_IM_LOGS);
+
+       /* We are now outside of the critical section, and we are the only thread holding a
+        * pointer to a linked list of conversations to be flushed to disk.
+        */
+       while (flush_these) {
+
+               flush_individual_conversation(flush_these);     /* This will free the string buffer */
+               imptr = flush_these;
+               flush_these = flush_these->next;
+               free(imptr);
+       }
+}
+
+
+
+void instmsg_timer(void) {
+       flush_conversations_to_disk(300);       /* Anything that hasn't peeped in more than 5 minutes */
+}
+
+void instmsg_shutdown(void) {
+       flush_conversations_to_disk(0);         /* Get it ALL onto disk NOW. */
+}
+
+CTDL_MODULE_INIT(instmsg)
+{
+       if (!threading)
+       {
+               CtdlRegisterProtoHook(cmd_gexp, "GEXP", "Get instant messages");
+               CtdlRegisterProtoHook(cmd_sexp, "SEXP", "Send an instant message");
+               CtdlRegisterProtoHook(cmd_dexp, "DEXP", "Disable instant messages");
+               CtdlRegisterProtoHook(cmd_reqt, "REQT", "Request client termination");
+               CtdlRegisterSessionHook(cmd_gexp_async, EVT_ASYNC);
+               CtdlRegisterSessionHook(delete_instant_messages, EVT_STOP);
+               CtdlRegisterXmsgHook(send_instant_message, XMSG_PRI_LOCAL);
+               CtdlRegisterSessionHook(instmsg_timer, EVT_TIMER);
+               CtdlRegisterSessionHook(instmsg_shutdown, EVT_SHUTDOWN);
+       }
+       
+       /* return our Subversion id for the Log */
+       return "$Id$";
+}
diff --git a/citadel/modules/instmsg/serv_instmsg.h b/citadel/modules/instmsg/serv_instmsg.h
new file mode 100644 (file)
index 0000000..9f2bc9d
--- /dev/null
@@ -0,0 +1,11 @@
+/* $Id$ */
+#include "ctdl_module.h"
+
+void ChatUnloadingTest(void);
+void allwrite (char *cmdbuf, int flag, char *username);
+CitContext *find_context (char **unstr);
+void cmd_pexp (char *argbuf); /* arg unused */
+void cmd_sexp (char *argbuf);
+void delete_instant_messages(void);
+void cmd_gexp(char *);
+int send_instant_message(char *, char *, char *, char *);
diff --git a/citadel/modules/roomchat/serv_roomchat.c b/citadel/modules/roomchat/serv_roomchat.c
new file mode 100644 (file)
index 0000000..c1a08c5
--- /dev/null
@@ -0,0 +1,243 @@
+/*
+ * $Id$
+ *
+ * This module handles instant messaging between users.
+ * 
+ * Copyright (c) 2010 by the citadel.org team
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+#include "sysdep.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <assert.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+#  include <sys/time.h>
+# else
+#  include <time.h>
+# endif
+#endif
+
+#include <sys/wait.h>
+#include <string.h>
+#include <limits.h>
+#include <libcitadel.h>
+#include "citadel.h"
+#include "server.h"
+#include "citserver.h"
+#include "support.h"
+#include "config.h"
+#include "msgbase.h"
+#include "user_ops.h"
+
+#ifndef HAVE_SNPRINTF
+#include "snprintf.h"
+#endif
+
+#include "ctdl_module.h"
+
+
+struct chatmsg {
+       struct chatmsg *next;
+       time_t timestamp;
+       int seq;
+       long roomnum;
+       char *sender;
+       char *msgtext;
+};
+
+struct chatmsg *first_chat_msg = NULL;
+struct chatmsg *last_chat_msg = NULL;
+
+
+/* 
+ * Periodically called for housekeeping.  Expire old chat messages so they don't take up memory forever.
+ */
+void roomchat_timer(void) {
+       struct chatmsg *ptr;
+
+       begin_critical_section(S_CHATQUEUE);
+
+       while ((first_chat_msg != NULL) && ((time(NULL) - first_chat_msg->timestamp) > 300)) {
+               ptr = first_chat_msg->next;
+               free(first_chat_msg->sender);
+               free(first_chat_msg->msgtext);
+               free(first_chat_msg);
+               first_chat_msg = ptr;
+               if (first_chat_msg == NULL) {
+                       last_chat_msg = NULL;
+               }
+       }
+
+       end_critical_section(S_CHATQUEUE);
+}
+
+
+/*
+ * Perform shutdown-related activities...
+ */
+void roomchat_shutdown(void) {
+       /* if we ever start logging chats, we have to flush them to disk here .*/
+}
+
+
+/*
+ * Add a message into the chat queue
+ */
+void add_to_chat_queue(char *msg) {
+       static int seq = 0;
+
+       struct chatmsg *m = malloc(sizeof(struct chatmsg));
+       if (!m) return;
+
+       m->next = NULL;
+       m->timestamp = time(NULL);
+       m->roomnum = CC->room.QRnumber;
+       m->sender = strdup(CC->user.fullname);
+       m->msgtext = strdup(msg);
+
+       if ((m->sender == NULL) || (m->msgtext == NULL)) {
+               free(m->sender);
+               free(m->msgtext);
+               free(m);
+               return;
+       }
+
+       begin_critical_section(S_CHATQUEUE);
+       m->seq = ++seq;
+
+       if (first_chat_msg == NULL) {
+               assert(last_chat_msg == NULL);
+               first_chat_msg = m;
+               last_chat_msg = m;
+       }
+       else {
+               assert(last_chat_msg != NULL);
+               assert(last_chat_msg->next == NULL);
+               last_chat_msg->next = m;
+               last_chat_msg = m;
+       }
+
+       end_critical_section(S_CHATQUEUE);
+}
+
+
+/*
+ * Transmit a message into a room chat
+ */
+void roomchat_send(char *argbuf) {
+       char buf[1024];
+
+       if ((CC->cs_flags & CS_CHAT) == 0) {
+               cprintf("%d Session is not in chat mode.\n", ERROR);
+               return;
+       }
+
+       cprintf("%d send now\n", SEND_LISTING);
+       while (client_getln(buf, sizeof buf) >= 0 && strcmp(buf, "000")) {
+               add_to_chat_queue(buf);
+       }
+}
+
+
+/*
+ * Poll room for incoming chat messages
+ */
+void roomchat_poll(char *argbuf) {
+       int newer_than = 0;
+       struct chatmsg *found = NULL;
+       struct chatmsg *ptr = NULL;
+
+       newer_than = extract_int(argbuf, 1);
+
+       if (!CC->cs_flags & CS_CHAT) {
+               cprintf("%d Session is not in chat mode.\n", ERROR);
+               return;
+       }
+
+       begin_critical_section(S_CHATQUEUE);
+       for (ptr = first_chat_msg; ((ptr != NULL) && (found == NULL)); ptr = ptr->next) {
+               if ((ptr->seq > newer_than) && (ptr->roomnum == CC->room.QRnumber)) {
+                       found = ptr;
+               }
+       }
+       end_critical_section(S_CHATQUEUE);
+
+       if (found == NULL) {
+               cprintf("%d no messages\n", ERROR + MESSAGE_NOT_FOUND);
+               return;
+       }
+
+       cprintf("%d %d|%ld|%s\n", LISTING_FOLLOWS, found->seq, found->timestamp, found->sender);
+       cprintf("%s\n", found->msgtext);
+       cprintf("000\n");
+}
+
+
+/*
+ * Participate in real time chat in a room
+ */
+void cmd_rcht(char *argbuf)
+{
+       char subcmd[16];
+
+       if (CtdlAccessCheck(ac_logged_in)) return;
+
+       extract_token(subcmd, argbuf, 0, '|', sizeof subcmd);
+
+       if (!strcasecmp(subcmd, "enter")) {
+               CC->cs_flags |= CS_CHAT;
+               cprintf("%d Entering chat mode.\n", CIT_OK);
+       }
+       else if (!strcasecmp(subcmd, "exit")) {
+               CC->cs_flags &= ~CS_CHAT;
+               cprintf("%d Exiting chat mode.\n", CIT_OK);
+       }
+       else if (!strcasecmp(subcmd, "send")) {
+               roomchat_send(argbuf);
+       }
+       else if (!strcasecmp(subcmd, "poll")) {
+               roomchat_poll(argbuf);
+       }
+       else {
+               cprintf("%d Invalid subcommand\n", ERROR + CMD_NOT_SUPPORTED);
+       }
+}
+
+
+CTDL_MODULE_INIT(roomchat)
+{
+       if (!threading)
+       {
+               CtdlRegisterProtoHook(cmd_rcht, "RCHT", "Participate in real time chat in a room");
+               CtdlRegisterSessionHook(roomchat_timer, EVT_TIMER);
+               CtdlRegisterSessionHook(roomchat_shutdown, EVT_SHUTDOWN);
+       }
+       
+       /* return our Subversion id for the Log */
+       return "$Id$";
+}
index d8161faeac2578be5504153beae3354c3f1c91a4..d5755f0a64916fc2feb096dfe16a953662964e7c 100644 (file)
@@ -84,15 +84,6 @@ struct ExpressMessage {
 #define EM_GO_AWAY     2       /* Server requests client log off */
 #define EM_CHAT                4       /* Server requests client enter chat */
 
-struct ChatLine {
-       struct ChatLine *next;
-       int chat_seq;
-       time_t chat_time;
-       char chat_text[SIZ];
-       char chat_username[USERNAME_SIZE];
-       char chat_room[ROOMNAMELEN];
-};
-
 /*
  * Various things we need to lock and unlock
  */
index c86914f02bc6e80574c87df9e142f79aab05f2ce..590d8b4c96445498a679a3b6331576f35722f2bd 100644 (file)
@@ -4,21 +4,21 @@
  * front end for chat mode
  * (the "single process" version - no more fork() anymore)
  *
- * Copyright (c) 1987-2009 by the citadel.org team
+ * Copyright (c) 1987-2010 by the citadel.org team
  *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 3 of the License, or
- *  (at your option) any later version.
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
  *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
  *
- *  You should have received a copy of the GNU General Public License
- *  along with this program; if not, write to the Free Software
- *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
 #include "sysdep.h"
 extern char temp[];
 void ctdl_getline(char *, int);
 
+
 char last_paged[SIZ] = "";
 
 void chatmode(CtdlIPC *ipc)
 {
        char wbuf[SIZ];
        char buf[SIZ];
+       char response[SIZ];
        char c_user[SIZ];
        char c_text[SIZ];
-       char c_room[SIZ];
        char last_user[SIZ];
        int send_complete_line;
-       int recv_complete_line;
        char ch;
        int a, pos;
-       time_t last_transmit;
+       int seq = 0;
 
        fd_set rfds;
        struct timeval tv;
        int retval;
 
-       CtdlIPC_chat_send(ipc, "CHAT");
+       CtdlIPC_chat_send(ipc, "RCHT enter");
        CtdlIPC_chat_recv(ipc, buf);
-       if (buf[0] != '8') {
+       if (buf[0] != '2') {
                scr_printf("%s\n", &buf[4]);
                return;
        }
-       scr_printf("Entering chat mode "
-               "(type /quit to exit, /help for other cmds)\n");
-       set_keepalives(KA_NO);
-       last_transmit = time(NULL);
+       scr_printf("Entering chat mode (type /quit to exit)\n");
 
        strcpy(buf, "");
        strcpy(wbuf, "");
@@ -104,24 +101,14 @@ void chatmode(CtdlIPC *ipc)
        sln_printf_if("\n");
        sln_printf("> ");
        send_complete_line = 0;
-       recv_complete_line = 0;
 
        while (1) {
                sln_flush();
                FD_ZERO(&rfds);
                FD_SET(0, &rfds);
-               FD_SET(CtdlIPC_getsockfd(ipc), &rfds);
-               tv.tv_sec = S_KEEPALIVE;
+               tv.tv_sec = 1;
                tv.tv_usec = 0;
-               retval = select(CtdlIPC_getsockfd(ipc) + 1, &rfds,
-                       NULL, NULL, &tv);
-
-               /* If there's data from the server... */
-               if (FD_ISSET(CtdlIPC_getsockfd(ipc), &rfds)) {
-                       CtdlIPC_chat_recv(ipc, buf);
-                       recv_complete_line = 1;
-                       goto RCL;       /* ugly, but we've gotta get out! */
-               }
+               retval = select(1, &rfds, NULL, NULL, &tv);
 
                /* If there's data from the keyboard... */
                if (FD_ISSET(0, &rfds)) {
@@ -141,9 +128,23 @@ void chatmode(CtdlIPC *ipc)
                }
 
                /* if the user hit return, send the line */
-RCL:           if (send_complete_line) {
-                       CtdlIPC_chat_send(ipc, wbuf);
-                       last_transmit = time(NULL);
+               if (send_complete_line) {
+
+                       if (!strcasecmp(wbuf, "/quit")) {
+                               CtdlIPC_chat_send(ipc, "RCHT exit");
+                               CtdlIPC_chat_recv(ipc, response);       /* don't care about the result */
+                               color(BRIGHT_WHITE);
+                               sln_printf("\rExiting chat mode\n");
+                               sln_flush();
+                               return;
+                       }
+
+                       CtdlIPC_chat_send(ipc, "RCHT send");
+                       CtdlIPC_chat_recv(ipc, response);
+                       if (response[0] == '4') {
+                               CtdlIPC_chat_send(ipc, wbuf);
+                               CtdlIPC_chat_send(ipc, "000");
+                       }
                        strcpy(wbuf, "");
                        send_complete_line = 0;
                }
@@ -156,90 +157,69 @@ RCL:              if (send_complete_line) {
                                        pos = a;
                        }
                        if (pos == 0) {
-                               CtdlIPC_chat_send(ipc, wbuf);
-                               last_transmit = time(NULL);
+                               CtdlIPC_chat_send(ipc, "RCHT send");
+                               CtdlIPC_chat_recv(ipc, response);
+                               if (response[0] == '4') {
+                                       CtdlIPC_chat_send(ipc, wbuf);
+                                       CtdlIPC_chat_send(ipc, "000");
+                               }
                                strcpy(wbuf, "");
                                send_complete_line = 0;
                        } else {
                                wbuf[pos] = 0;
-                               CtdlIPC_chat_send(ipc, wbuf);
-                               last_transmit = time(NULL);
+                               CtdlIPC_chat_send(ipc, "RCHT send");
+                               CtdlIPC_chat_recv(ipc, response);
+                               if (response[0] == '4') {
+                                       CtdlIPC_chat_send(ipc, wbuf);
+                                       CtdlIPC_chat_send(ipc, "000");
+                               }
                                strcpy(wbuf, &wbuf[pos + 1]);
                        }
                }
 
-               if (recv_complete_line) {
-                       sln_printf("\r%79s\r", "");
-                       if (!strcmp(buf, "000")) {
-                               color(BRIGHT_WHITE);
-                               sln_printf("\rExiting chat mode\n");
-                               sln_flush();
-                               set_keepalives(KA_YES);
-
-                               /* Some users complained about the client and
-                                * server losing protocol synchronization when
-                                * exiting chat.  This little dialog forces
-                                * everything to be hunky-dory.
-                                */
-                               CtdlIPC_chat_send(ipc, "ECHO __ExitingChat__");
-                               do {
-                                       CtdlIPC_chat_recv(ipc, buf);
-                               } while (strcmp(buf, "200 __ExitingChat__"));
-                               return;
-                       }
-                       if (num_parms(buf) >= 2) {
-                               extract_token(c_user, buf, 0, '|', sizeof c_user);
-                               extract_token(c_text, buf, 1, '|', sizeof c_text);
-                               if (num_parms(buf) > 2) {
-                                       extract_token(c_room, buf, 2, '|', sizeof c_room);
-                                       scr_printf("Got room %s\n", c_room);
+               /* poll for incoming chat messages */
+               snprintf(buf, sizeof buf, "RCHT poll|%d", seq);
+               CtdlIPC_chat_send(ipc, buf);
+               CtdlIPC_chat_recv(ipc, response);
+       
+               if (response[0] == '1') {
+                       seq = extract_int(&response[4], 0);
+                       extract_token(c_user, &response[4], 2, '|', sizeof c_user);
+                       while (CtdlIPC_chat_recv(ipc, c_text), strcmp(c_text, "000")) {
+                               sln_printf("\r%79s\r", "");
+                               if (!strcmp(c_user, fullname)) {
+                                       color(BRIGHT_YELLOW);
+                               } else if (!strcmp(c_user, ":")) {
+                                       color(BRIGHT_RED);
+                               } else {
+                                       color(BRIGHT_GREEN);
                                }
-                               if (strcasecmp(c_text, "NOOP")) {
-                                       if (!strcmp(c_user, fullname)) {
-                                               color(BRIGHT_YELLOW);
-                                       } else if (!strcmp(c_user, ":")) {
-                                               color(BRIGHT_RED);
-                                       } else {
-                                               color(BRIGHT_GREEN);
-                                       }
-                                       if (strcmp(c_user, last_user)) {
-                                               snprintf(buf, sizeof buf, "%s: %s", c_user, c_text);
-                                       } else {
-                                               size_t i = MIN(sizeof buf - 1,
-                                                    strlen(c_user) + 2);
-
-                                               memset(buf, ' ', i);
-                                               safestrncpy(&buf[i], c_text,
-                                                        sizeof buf - i);
-                                       }
-                                       while (strlen(buf) < 79)
-                                               strcat(buf, " ");
-                                       if (strcmp(c_user, last_user)) {
-                                               sln_printf("\r%79s\n", "");
-                                               strcpy(last_user, c_user);
-                                       }
-                                       scr_printf("\r%s\n", buf);
-                                       scr_flush();
+                               if (strcmp(c_user, last_user)) {
+                                       snprintf(buf, sizeof buf, "%s: %s", c_user, c_text);
+                               } else {
+                                       size_t i = MIN(sizeof buf - 1, strlen(c_user) + 2);
+                                       memset(buf, ' ', i);
+                                       safestrncpy(&buf[i], c_text, sizeof buf - i);
                                }
+                               while (strlen(buf) < 79) {
+                                       strcat(buf, " ");
+                               }
+                               if (strcmp(c_user, last_user)) {
+                                       sln_printf("\r%79s\n", "");
+                                       strcpy(last_user, c_user);
+                               }
+                               scr_printf("\r%s\n", buf);
+                               scr_flush();
                        }
-                       color(BRIGHT_YELLOW);
-                       sln_printf("\r> %s", wbuf);
-                       sln_flush();
-                       recv_complete_line = 0;
-                       strcpy(buf, "");
-               }
-
-               /* If the user is sitting idle, send a half-keepalive to the
-                * server to prevent session timeout.
-                */
-               if ((time(NULL) - last_transmit) >= S_KEEPALIVE) {
-                       CtdlIPC_chat_send(ipc, "NOOP");
-                       last_transmit = time(NULL);
                }
-
+               color(BRIGHT_YELLOW);
+               sln_printf("\r> %s", wbuf);
+               sln_flush();
+               strcpy(buf, "");
        }
 }
 
+
 /*
  * send an instant message
  */