From: Art Cancro Date: Thu, 8 Apr 2010 04:33:45 +0000 (+0000) Subject: * Moved instant messaging into its own module -- serv_instmsg X-Git-Tag: v7.86~275 X-Git-Url: https://code.citadel.org/?a=commitdiff_plain;ds=sidebyside;h=c1ea6daf1e5c0a32265581410d6a45998b531440;p=citadel.git * Moved instant messaging into its own module -- serv_instmsg * Removed serv_chat and the CHAT command * Implemented serv_roomchat and the RCHT command, which implements a brand new chat protocol --- diff --git a/citadel/modules/chat/serv_chat.c b/citadel/modules/chat/serv_chat.c deleted file mode 100644 index 5fbd1db2d..000000000 --- a/citadel/modules/chat/serv_chat.c +++ /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 -#include -#include -#include -#include -#include -#include -#include - -#if TIME_WITH_SYS_TIME -# include -# include -#else -# if HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif - -#include -#include -#include -#include -#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" - "\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("

"), 0); - StrBufAppendBufPlain(this_im->conversation, me->user.fullname, -1, 0); - StrBufAppendBufPlain(this_im->conversation, HKEY(": "), 0); - StrEscAppend(this_im->conversation, NULL, msgtext, 0, 0); - StrBufAppendBufPlain(this_im->conversation, HKEY("

\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("", 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("", 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("", 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("", 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( - "\r\n" - "\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 index 6ceba2682..000000000 --- a/citadel/modules/chat/serv_chat.h +++ /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 index 000000000..fd0fa8e33 --- /dev/null +++ b/citadel/modules/instmsg/serv_instmsg.c @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +#include +#include +#include +#include +#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" + "\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("

"), 0); + StrBufAppendBufPlain(this_im->conversation, me->user.fullname, -1, 0); + StrBufAppendBufPlain(this_im->conversation, HKEY(": "), 0); + StrEscAppend(this_im->conversation, NULL, msgtext, 0, 0); + StrBufAppendBufPlain(this_im->conversation, HKEY("

\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( + "\r\n" + "\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 index 000000000..9f2bc9d8b --- /dev/null +++ b/citadel/modules/instmsg/serv_instmsg.h @@ -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 index 000000000..c1a08c566 --- /dev/null +++ b/citadel/modules/roomchat/serv_roomchat.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +#include +#include +#include +#include +#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$"; +} diff --git a/citadel/server.h b/citadel/server.h index d8161faea..d5755f0a6 100644 --- a/citadel/server.h +++ b/citadel/server.h @@ -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 */ diff --git a/citadel/textclient/client_chat.c b/citadel/textclient/client_chat.c index c86914f02..590d8b4c9 100644 --- a/citadel/textclient/client_chat.c +++ b/citadel/textclient/client_chat.c @@ -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" @@ -66,36 +66,33 @@ 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 */