2fd90261c8d28873793a8cbe1aeb5645f82f559b
[citadel.git] / citadel / modules / chat / serv_chat.c
1 /*
2  * $Id$
3  * 
4  * This module handles all "real time" communication between users.  The
5  * modes of communication currently supported are Chat and Paging.
6  *
7  * Copyright (c) 1987-2009 by the citadel.org team
8  *
9  *  This program is free software; you can redistribute it and/or modify
10  *  it under the terms of the GNU General Public License as published by
11  *  the Free Software Foundation; either version 3 of the License, or
12  *  (at your option) any later version.
13  *
14  *  This program is distributed in the hope that it will be useful,
15  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  *  GNU General Public License for more details.
18  *
19  *  You should have received a copy of the GNU General Public License
20  *  along with this program; if not, write to the Free Software
21  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  *
23  */
24 #include "sysdep.h"
25 #include <stdlib.h>
26 #include <unistd.h>
27 #include <stdio.h>
28 #include <fcntl.h>
29 #include <signal.h>
30 #include <pwd.h>
31 #include <errno.h>
32 #include <sys/types.h>
33
34 #if TIME_WITH_SYS_TIME
35 # include <sys/time.h>
36 # include <time.h>
37 #else
38 # if HAVE_SYS_TIME_H
39 #  include <sys/time.h>
40 # else
41 #  include <time.h>
42 # endif
43 #endif
44
45 #include <sys/wait.h>
46 #include <string.h>
47 #include <limits.h>
48 #include <libcitadel.h>
49 #include "citadel.h"
50 #include "server.h"
51 #include "serv_chat.h"
52 #include "citserver.h"
53 #include "support.h"
54 #include "config.h"
55 #include "msgbase.h"
56 #include "user_ops.h"
57
58 #ifndef HAVE_SNPRINTF
59 #include "snprintf.h"
60 #endif
61
62 #include "ctdl_module.h"
63
64 struct ChatLine *ChatQueue = NULL;
65 int ChatLastMsg = 0;
66
67 struct imlog {
68         struct imlog *next;
69         long usernums[2];
70         char usernames[2][128];
71         time_t lastmsg;
72         int last_serial;
73         StrBuf *conversation;
74 };
75
76 struct imlog *imlist = NULL;
77
78
79
80
81 /*
82  * FIXME: OMG this module is realy horrible to the rest of the system when accessing contexts.
83  * It pays no regard at all to how long it may have the context list locked for. 
84  * It carries out IO whilst the context list is locked.
85  * I'd recomend disabling this module altogether for the moment.
86  */
87
88 /*
89  * This function handles the logging of instant messages to disk.
90  */
91 void log_instant_message(struct CitContext *me, struct CitContext *them, char *msgtext, int serial_number)
92 {
93         long usernums[2];
94         long t;
95         struct imlog *iptr = NULL;
96         struct imlog *this_im = NULL;
97         
98         memset(usernums, 0, sizeof usernums);
99         usernums[0] = me->user.usernum;
100         usernums[1] = them->user.usernum;
101
102         /* Always put the lower user number first, so we can use the array as a hash value which
103          * represents a pair of users.  For a broadcast message one of the users will be 0.
104          */
105         if (usernums[0] > usernums[1]) {
106                 t = usernums[0];
107                 usernums[0] = usernums[1];
108                 usernums[1] = t;
109         }
110
111         begin_critical_section(S_IM_LOGS);
112
113         /* Look for an existing conversation in the hash table.
114          * If not found, create a new one.
115          */
116
117         this_im = NULL;
118         for (iptr = imlist; iptr != NULL; iptr = iptr->next) {
119                 if ((iptr->usernums[0] == usernums[0]) && (iptr->usernums[1] == usernums[1])) {
120                         /* Existing conversation */
121                         this_im = iptr;
122                 }
123         }
124         if (this_im == NULL) {
125                 /* New conversation */
126                 this_im = malloc(sizeof(struct imlog));
127                 memset(this_im, 0, sizeof (struct imlog));
128                 this_im->usernums[0] = usernums[0];
129                 this_im->usernums[1] = usernums[1];
130                 /* usernames[] and usernums[] might not be in the same order.  This is not an error. */
131                 if (me) {
132                         safestrncpy(this_im->usernames[0], me->user.fullname, sizeof this_im->usernames[0]);
133                 }
134                 if (them) {
135                         safestrncpy(this_im->usernames[1], them->user.fullname, sizeof this_im->usernames[1]);
136                 }
137                 this_im->conversation = NewStrBuf();
138                 this_im->next = imlist;
139                 imlist = this_im;
140                 StrBufAppendBufPlain(this_im->conversation, HKEY(
141                         "Content-type: text/html\r\n"
142                         "Content-transfer-encoding: 7bit\r\n"
143                         "\r\n"
144                         "<html><body>\r\n"
145                         ), 0);
146         }
147
148
149         /* Since it's possible for this function to get called more than once if a user is logged
150          * in on multiple sessions, we use the message's serial number to keep track of whether
151          * we've already logged it.
152          */
153         if (this_im->last_serial != serial_number)
154         {
155                 this_im->lastmsg = time(NULL);          /* Touch the timestamp so we know when to flush */
156                 this_im->last_serial = serial_number;
157                 StrBufAppendBufPlain(this_im->conversation, HKEY("<p><b>"), 0);
158                 StrBufAppendBufPlain(this_im->conversation, me->user.fullname, -1, 0);
159                 StrBufAppendBufPlain(this_im->conversation, HKEY(":</b> "), 0);
160                 StrEscAppend(this_im->conversation, NULL, msgtext, 0, 0);
161                 StrBufAppendBufPlain(this_im->conversation, HKEY("</p>\r\n"), 0);
162         }
163         end_critical_section(S_IM_LOGS);
164 }
165
166 /*
167  * This message can be set to anything you want, but it is
168  * checked for consistency so don't move it away from here.
169  */
170 #define KICKEDMSG "You have been kicked out of this room."
171
172 void allwrite(char *cmdbuf, int flag, char *username)
173 {
174         FILE *fp;
175         char bcast[SIZ];
176         char *un;
177         struct ChatLine *clptr, *clnew;
178         time_t now;
179
180         if (CC->fake_username[0])
181                 un = CC->fake_username;
182         else
183                 un = CC->user.fullname;
184         if (flag == 1) {
185                 snprintf(bcast, sizeof bcast, ":|<%s %s>", un, cmdbuf);
186         } else if (flag == 0) {
187                 snprintf(bcast, sizeof bcast, "%s|%s", un, cmdbuf);
188         } else if (flag == 2) {
189                 snprintf(bcast, sizeof bcast, ":|<%s whispers %s>", un, cmdbuf);
190         } else if (flag == 3) {
191                 snprintf(bcast, sizeof bcast, ":|%s", KICKEDMSG);
192         }
193         if ((strcasecmp(cmdbuf, "NOOP")) && (flag != 2)) {
194                 fp = fopen(CHATLOG, "a");
195                 if (fp != NULL)
196                         fprintf(fp, "%s\n", bcast);
197                 fclose(fp);
198         }
199         clnew = (struct ChatLine *) malloc(sizeof(struct ChatLine));
200         memset(clnew, 0, sizeof(struct ChatLine));
201         if (clnew == NULL) {
202                 fprintf(stderr, "citserver: cannot alloc chat line: %s\n",
203                         strerror(errno));
204                 return;
205         }
206         time(&now);
207         clnew->next = NULL;
208         clnew->chat_time = now;
209         safestrncpy(clnew->chat_room, CC->room.QRname,
210                         sizeof clnew->chat_room);
211         clnew->chat_room[sizeof clnew->chat_room - 1] = 0;
212         if (username) {
213                 safestrncpy(clnew->chat_username, username,
214                         sizeof clnew->chat_username);
215                 clnew->chat_username[sizeof clnew->chat_username - 1] = 0;
216         } else
217                 clnew->chat_username[0] = '\0';
218         safestrncpy(clnew->chat_text, bcast, sizeof clnew->chat_text);
219
220         /* Here's the critical section.
221          * First, add the new message to the queue...
222          */
223         begin_critical_section(S_CHATQUEUE);
224         ++ChatLastMsg;
225         clnew->chat_seq = ChatLastMsg;
226         if (ChatQueue == NULL) {
227                 ChatQueue = clnew;
228         } else {
229                 for (clptr = ChatQueue; clptr->next != NULL; clptr = clptr->next);;
230                 clptr->next = clnew;
231         }
232
233         /* Then, before releasing the lock, free the expired messages */
234         while ((ChatQueue != NULL) && (now - ChatQueue->chat_time >= 120L)) {
235                 clptr = ChatQueue;
236                 ChatQueue = ChatQueue->next;
237                 free(clptr);
238         }
239         end_critical_section(S_CHATQUEUE);
240 }
241
242
243 CitContext *find_context(char **unstr)
244 {
245         CitContext *t_cc, *found_cc = NULL;
246         char *name, *tptr;
247
248         if ((!*unstr) || (!unstr))
249                 return (NULL);
250
251         begin_critical_section(S_SESSION_TABLE);
252         for (t_cc = ContextList; ((t_cc) && (!found_cc)); t_cc = t_cc->next) {
253                 if (t_cc->fake_username[0])
254                         name = t_cc->fake_username;
255                 else
256                         name = t_cc->curr_user;
257                 tptr = *unstr;
258                 if ((!strncasecmp(name, tptr, strlen(name))) && (tptr[strlen(name)] == ' ')) {
259                         found_cc = t_cc;
260                         *unstr = &(tptr[strlen(name) + 1]);
261                 }
262         }
263         end_critical_section(S_SESSION_TABLE);
264
265         return (found_cc);
266 }
267
268 /*
269  * List users in chat.
270  * allflag ==   0 = list users in chat
271  *              1 = list users in chat, followed by users not in chat
272  *              2 = display count only
273  */
274
275 void do_chat_listing(int allflag)
276 {
277         struct CitContext *ccptr;
278         int count = 0;
279         int count_elsewhere = 0;
280         char roomname[ROOMNAMELEN];
281
282         if ((allflag == 0) || (allflag == 1))
283                 cprintf(":|\n:| Users currently in chat:\n");
284         begin_critical_section(S_SESSION_TABLE);
285         for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
286                 if (ccptr->cs_flags & CS_CHAT) {
287                         if (!strcasecmp(ccptr->room.QRname,
288                            CC->room.QRname)) {
289                                 ++count;
290                         }
291                         else {
292                                 ++count_elsewhere;
293                         }
294                 }
295
296                 GenerateRoomDisplay(roomname, ccptr, CC);
297                 if ((CC->user.axlevel < 6) && (!IsEmptyStr(ccptr->fake_roomname))) {
298                         strcpy(roomname, ccptr->fake_roomname);
299                 }
300
301                 if ((ccptr->cs_flags & CS_CHAT) && ((ccptr->cs_flags & CS_STEALTH) == 0)) {
302                         if ((allflag == 0) || (allflag == 1)) {
303                                 cprintf(":| %-25s <%s>:\n",
304                                         (ccptr->fake_username[0]) ? ccptr->fake_username : ccptr->curr_user,
305                                         roomname);
306                         }
307                 }
308         }
309
310         if (allflag == 1) {
311                 cprintf(":|\n:| Users not in chat:\n");
312                 for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
313
314                         GenerateRoomDisplay(roomname, ccptr, CC);
315                         if ((CC->user.axlevel < 6)
316                         && (!IsEmptyStr(ccptr->fake_roomname))) {
317                                 strcpy(roomname, ccptr->fake_roomname);
318                         }
319
320                         if (((ccptr->cs_flags & CS_CHAT) == 0)
321                             && ((ccptr->cs_flags & CS_STEALTH) == 0)) {
322                                 cprintf(":| %-25s <%s>:\n",
323                                         (ccptr->fake_username[0]) ? ccptr->fake_username : ccptr->curr_user,
324                                         roomname);
325                         }
326                 }
327         }
328         end_critical_section(S_SESSION_TABLE);
329
330         if (allflag == 2) {
331                 if (count > 1) {
332                         cprintf(":|There are %d users here.\n", count);
333                 }
334                 else {
335                         cprintf(":|Note: you are the only one here.\n");
336                 }
337                 if (count_elsewhere > 0) {
338                         cprintf(":|There are %d users chatting in other rooms.\n", count_elsewhere);
339                 }
340         }
341
342         cprintf(":|\n");
343 }
344
345
346 void cmd_chat(char *argbuf)
347 {
348         char cmdbuf[SIZ];
349         char *un;
350         char *strptr1;
351         int MyLastMsg, ThisLastMsg;
352         struct ChatLine *clptr;
353         struct CitContext *t_context;
354         int retval;
355
356         if (!(CC->logged_in)) {
357                 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
358                 return;
359         }
360
361         CC->cs_flags = CC->cs_flags | CS_CHAT;
362         cprintf("%d Entering chat mode (type '/help' for available commands)\n",
363                 START_CHAT_MODE);
364         unbuffer_output();
365
366         MyLastMsg = ChatLastMsg;
367
368         if ((CC->cs_flags & CS_STEALTH) == 0) {
369                 allwrite("<entering chat>", 0, NULL);
370         }
371         strcpy(cmdbuf, "");
372
373         do_chat_listing(2);
374
375         while (1) {
376                 int ok_cmd;
377                 int linelen;
378
379                 ok_cmd = 0;
380                 linelen = strlen(cmdbuf);
381                 if (linelen > 100) --linelen;   /* truncate too-long lines */
382                 cmdbuf[linelen + 1] = 0;
383
384                 retval = client_read_to(&cmdbuf[linelen], 1, 2);
385
386                 if (retval < 0 || CC->kill_me) {        /* socket broken? */
387                         if ((CC->cs_flags & CS_STEALTH) == 0) {
388                                 allwrite("<disconnected>", 0, NULL);
389                         }
390                         return;
391                 }
392
393                 /* if we have a complete line, do send processing */
394                 if (!IsEmptyStr(cmdbuf))
395                         if (cmdbuf[strlen(cmdbuf) - 1] == 10) {
396                                 cmdbuf[strlen(cmdbuf) - 1] = 0;
397                                 time(&CC->lastcmd);
398                                 time(&CC->lastidle);
399
400                                 if ((!strcasecmp(cmdbuf, "exit"))
401                                     || (!strcasecmp(cmdbuf, "/exit"))
402                                     || (!strcasecmp(cmdbuf, "quit"))
403                                     || (!strcasecmp(cmdbuf, "logout"))
404                                     || (!strcasecmp(cmdbuf, "logoff"))
405                                     || (!strcasecmp(cmdbuf, "/q"))
406                                     || (!strcasecmp(cmdbuf, ".q"))
407                                     || (!strcasecmp(cmdbuf, "/quit"))
408                                     )
409                                         strcpy(cmdbuf, "000");
410
411                                 if (!strcmp(cmdbuf, "000")) {
412                                         if ((CC->cs_flags & CS_STEALTH) == 0) {
413                                                 allwrite("<exiting chat>", 0, NULL);
414                                         }
415                                         sleep(1);
416                                         cprintf("000\n");
417                                         CC->cs_flags = CC->cs_flags - CS_CHAT;
418                                         return;
419                                 }
420                                 if ((!strcasecmp(cmdbuf, "/help"))
421                                     || (!strcasecmp(cmdbuf, "help"))
422                                     || (!strcasecmp(cmdbuf, "/?"))
423                                     || (!strcasecmp(cmdbuf, "?"))) {
424                                         cprintf(":|\n");
425                                         cprintf(":|Available commands: \n");
426                                         cprintf(":|/help   (prints this message) \n");
427                                         cprintf(":|/who    (list users currently in chat) \n");
428                                         cprintf(":|/whobbs (list users in chat -and- elsewhere) \n");
429                                         cprintf(":|/me     ('action' line, ala irc) \n");
430                                         cprintf(":|/msg    (send private message, ala irc) \n");
431                                         if (is_room_aide()) {
432                                                 cprintf(":|/kick   (kick another user out of this room) \n");
433                                         }
434                                         cprintf(":|/quit   (exit from this chat) \n");
435                                         cprintf(":|\n");
436                                         ok_cmd = 1;
437                                 }
438                                 if (!strcasecmp(cmdbuf, "/who")) {
439                                         do_chat_listing(0);
440                                         ok_cmd = 1;
441                                 }
442                                 if (!strcasecmp(cmdbuf, "/whobbs")) {
443                                         do_chat_listing(1);
444                                         ok_cmd = 1;
445                                 }
446                                 if (!strncasecmp(cmdbuf, "/me ", 4)) {
447                                         allwrite(&cmdbuf[4], 1, NULL);
448                                         ok_cmd = 1;
449                                 }
450                                 if (!strncasecmp(cmdbuf, "/msg ", 5)) {
451                                         ok_cmd = 1;
452                                         strptr1 = &cmdbuf[5];
453                                         if ((t_context = find_context(&strptr1))) {
454                                                 allwrite(strptr1, 2, CC->curr_user);
455                                                 if (strcasecmp(CC->curr_user, t_context->curr_user))
456                                                         allwrite(strptr1, 2, t_context->curr_user);
457                                         } else
458                                                 cprintf(":|User not found.\n");
459                                         cprintf("\n");
460                                 }
461                                 /* The /kick function is implemented by sending a specific
462                                  * message to the kicked-out user's context.  When that message
463                                  * is processed by the read loop, that context will exit.
464                                  */
465                                 if ( (!strncasecmp(cmdbuf, "/kick ", 6)) && (is_room_aide()) ) {
466                                         ok_cmd = 1;
467                                         strptr1 = &cmdbuf[6];
468                                         strcat(strptr1, " ");
469                                         if ((t_context = find_context(&strptr1))) {
470                                                 if (strcasecmp(CC->curr_user, t_context->curr_user))
471                                                         allwrite(strptr1, 3, t_context->curr_user);
472                                         } else
473                                                 cprintf(":|User not found.\n");
474                                         cprintf("\n");
475                                 }
476                                 if ((cmdbuf[0] != '/') && (strlen(cmdbuf) > 0)) {
477                                         ok_cmd = 1;
478                                         allwrite(cmdbuf, 0, NULL);
479                                 }
480                                 if ((!ok_cmd) && (cmdbuf[0]) && (cmdbuf[0] != '\n'))
481                                         cprintf(":|Command %s is not understood.\n", cmdbuf);
482
483                                 strcpy(cmdbuf, "");
484
485                         }
486                 /* now check the queue for new incoming stuff */
487
488                 if (CC->fake_username[0])
489                         un = CC->fake_username;
490                 else
491                         un = CC->curr_user;
492                 if (ChatLastMsg > MyLastMsg) {
493                         ThisLastMsg = ChatLastMsg;
494                         for (clptr = ChatQueue; clptr != NULL; clptr = clptr->next) {
495                                 if ((clptr->chat_seq > MyLastMsg) && ((!clptr->chat_username[0]) || (!strncasecmp(un, clptr->chat_username, 32)))) {
496                                         if ((!clptr->chat_room[0]) || (!strncasecmp(CC->room.QRname, clptr->chat_room, ROOMNAMELEN))) {
497                                                 /* Output new chat data */
498                                                 cprintf("%s\n", clptr->chat_text);
499
500                                                 /* See if we've been force-quitted (kicked etc.) */
501                                                 if (!strcmp(&clptr->chat_text[2], KICKEDMSG)) {
502                                                         allwrite("<kicked out of this room>", 0, NULL);
503                                                         cprintf("000\n");
504                                                         CC->cs_flags = CC->cs_flags - CS_CHAT;
505
506                                                         /* Kick user out of room */
507                                                         CtdlInvtKick(CC->user.fullname, 0);
508
509                                                         /* And return to the Lobby */
510                                                         CtdlUserGoto(config.c_baseroom, 0, 0, NULL, NULL);
511                                                         return;
512                                                 }
513                                         }
514                                 }
515                         }
516                         MyLastMsg = ThisLastMsg;
517                 }
518         }
519 }
520
521
522
523 /*
524  * Delete any remaining instant messages
525  */
526 void delete_instant_messages(void) {
527         struct ExpressMessage *ptr;
528
529         begin_critical_section(S_SESSION_TABLE);
530         while (CC->FirstExpressMessage != NULL) {
531                 ptr = CC->FirstExpressMessage->next;
532                 if (CC->FirstExpressMessage->text != NULL)
533                         free(CC->FirstExpressMessage->text);
534                 free(CC->FirstExpressMessage);
535                 CC->FirstExpressMessage = ptr;
536         }
537         end_critical_section(S_SESSION_TABLE);
538 }
539
540
541
542
543 /*
544  * Poll for instant messages (OLD METHOD -- ***DEPRECATED ***)
545  */
546 void cmd_pexp(char *argbuf)
547 {
548         struct ExpressMessage *ptr, *holdptr;
549
550         if (CC->FirstExpressMessage == NULL) {
551                 cprintf("%d No instant messages waiting.\n", ERROR + MESSAGE_NOT_FOUND);
552                 return;
553         }
554         begin_critical_section(S_SESSION_TABLE);
555         ptr = CC->FirstExpressMessage;
556         CC->FirstExpressMessage = NULL;
557         end_critical_section(S_SESSION_TABLE);
558
559         cprintf("%d Express msgs:\n", LISTING_FOLLOWS);
560         while (ptr != NULL) {
561                 if (ptr->flags && EM_BROADCAST)
562                         cprintf("Broadcast message ");
563                 else if (ptr->flags && EM_CHAT)
564                         cprintf("Chat request ");
565                 else if (ptr->flags && EM_GO_AWAY)
566                         cprintf("Please logoff now, as requested ");
567                 else
568                         cprintf("Message ");
569                 cprintf("from %s:\n", ptr->sender);
570                 if (ptr->text != NULL)
571                         memfmout(ptr->text, 0, "\n");
572
573                 holdptr = ptr->next;
574                 if (ptr->text != NULL) free(ptr->text);
575                 free(ptr);
576                 ptr = holdptr;
577         }
578         cprintf("000\n");
579 }
580
581
582 /*
583  * Get instant messages (new method)
584  */
585 void cmd_gexp(char *argbuf) {
586         struct ExpressMessage *ptr;
587
588         if (CC->FirstExpressMessage == NULL) {
589                 cprintf("%d No instant messages waiting.\n", ERROR + MESSAGE_NOT_FOUND);
590                 return;
591         }
592
593         begin_critical_section(S_SESSION_TABLE);
594         ptr = CC->FirstExpressMessage;
595         CC->FirstExpressMessage = CC->FirstExpressMessage->next;
596         end_critical_section(S_SESSION_TABLE);
597
598         cprintf("%d %d|%ld|%d|%s|%s|%s\n",
599                 LISTING_FOLLOWS,
600                 ((ptr->next != NULL) ? 1 : 0),          /* more msgs? */
601                 (long)ptr->timestamp,                   /* time sent */
602                 ptr->flags,                             /* flags */
603                 ptr->sender,                            /* sender of msg */
604                 config.c_nodename,                      /* static for now (and possibly deprecated) */
605                 ptr->sender_email                       /* email or jid of sender */
606         );
607
608         if (ptr->text != NULL) {
609                 memfmout(ptr->text, 0, "\n");
610                 if (ptr->text[strlen(ptr->text)-1] != '\n') cprintf("\n");
611                 free(ptr->text);
612         }
613
614         cprintf("000\n");
615         free(ptr);
616 }
617
618 /*
619  * Asynchronously deliver instant messages
620  */
621 void cmd_gexp_async(void) {
622
623         /* Only do this if the session can handle asynchronous protocol */
624         if (CC->is_async == 0) return;
625
626         /* And don't do it if there's nothing to send. */
627         if (CC->FirstExpressMessage == NULL) return;
628
629         cprintf("%d instant msg\n", ASYNC_MSG + ASYNC_GEXP);
630 }
631
632 /*
633  * Back end support function for send_instant_message() and company
634  */
635 void add_xmsg_to_context(struct CitContext *ccptr, struct ExpressMessage *newmsg) 
636 {
637         struct ExpressMessage *findend;
638
639         if (ccptr->FirstExpressMessage == NULL) {
640                 ccptr->FirstExpressMessage = newmsg;
641         }
642         else {
643                 findend = ccptr->FirstExpressMessage;
644                 while (findend->next != NULL) {
645                         findend = findend->next;
646                 }
647                 findend->next = newmsg;
648         }
649
650         /* If the target context is a session which can handle asynchronous
651          * messages, go ahead and set the flag for that.
652          */
653         if (ccptr->is_async) {
654                 ccptr->async_waiting = 1;
655                 if (ccptr->state == CON_IDLE) {
656                         ccptr->state = CON_READY;
657                 }
658         }
659 }
660
661
662
663
664 /* 
665  * This is the back end to the instant message sending function.  
666  * Returns the number of users to which the message was sent.
667  * Sending a zero-length message tests for recipients without sending messages.
668  */
669 int send_instant_message(char *lun, char *lem, char *x_user, char *x_msg)
670 {
671         int message_sent = 0;           /* number of successful sends */
672         struct CitContext *ccptr;
673         struct ExpressMessage *newmsg = NULL;
674         char *un;
675         size_t msglen = 0;
676         int do_send = 0;                /* 1 = send message; 0 = only check for valid recipient */
677         static int serial_number = 0;   /* this keeps messages from getting logged twice */
678
679         if (strlen(x_msg) > 0) {
680                 msglen = strlen(x_msg) + 4;
681                 do_send = 1;
682         }
683
684         /* find the target user's context and append the message */
685         begin_critical_section(S_SESSION_TABLE);
686         ++serial_number;
687         for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
688
689                 if (ccptr->fake_username[0]) {
690                         un = ccptr->fake_username;
691                 }
692                 else {
693                         un = ccptr->user.fullname;
694                 }
695
696                 if ( ((!strcasecmp(un, x_user))
697                     || (!strcasecmp(x_user, "broadcast")))
698                     && (ccptr->can_receive_im)
699                     && ((ccptr->disable_exp == 0)
700                     || (CC->user.axlevel >= 6)) ) {
701                         if (do_send) {
702                                 newmsg = (struct ExpressMessage *) malloc(sizeof (struct ExpressMessage));
703                                 memset(newmsg, 0, sizeof (struct ExpressMessage));
704                                 time(&(newmsg->timestamp));
705                                 safestrncpy(newmsg->sender, lun, sizeof newmsg->sender);
706                                 safestrncpy(newmsg->sender_email, lem, sizeof newmsg->sender_email);
707                                 if (!strcasecmp(x_user, "broadcast")) {
708                                         newmsg->flags |= EM_BROADCAST;
709                                 }
710                                 newmsg->text = strdup(x_msg);
711
712                                 add_xmsg_to_context(ccptr, newmsg);
713
714                                 /* and log it ... */
715                                 if (ccptr != CC) {
716                                         log_instant_message(CC, ccptr, newmsg->text, serial_number);
717                                 }
718                         }
719                         ++message_sent;
720                 }
721         }
722         end_critical_section(S_SESSION_TABLE);
723         return (message_sent);
724 }
725
726 /*
727  * send instant messages
728  */
729 void cmd_sexp(char *argbuf)
730 {
731         int message_sent = 0;
732         char x_user[USERNAME_SIZE];
733         char x_msg[1024];
734         char *lun;
735         char *lem;
736         char *x_big_msgbuf = NULL;
737
738         if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
739                 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
740                 return;
741         }
742         if (CC->fake_username[0])
743                 lun = CC->fake_username;
744         else
745                 lun = CC->user.fullname;
746
747         lem = CC->cs_inet_email;
748
749         extract_token(x_user, argbuf, 0, '|', sizeof x_user);
750         extract_token(x_msg, argbuf, 1, '|', sizeof x_msg);
751
752         if (!x_user[0]) {
753                 cprintf("%d You were not previously paged.\n", ERROR + NO_SUCH_USER);
754                 return;
755         }
756         if ((!strcasecmp(x_user, "broadcast")) && (CC->user.axlevel < 6)) {
757                 cprintf("%d Higher access required to send a broadcast.\n",
758                         ERROR + HIGHER_ACCESS_REQUIRED);
759                 return;
760         }
761         /* This loop handles text-transfer pages */
762         if (!strcmp(x_msg, "-")) {
763                 message_sent = PerformXmsgHooks(lun, lem, x_user, "");
764                 if (message_sent == 0) {
765                         if (CtdlGetUser(NULL, x_user))
766                                 cprintf("%d '%s' does not exist.\n",
767                                                 ERROR + NO_SUCH_USER, x_user);
768                         else
769                                 cprintf("%d '%s' is not logged in "
770                                                 "or is not accepting pages.\n",
771                                                 ERROR + RESOURCE_NOT_OPEN, x_user);
772                         return;
773                 }
774                 unbuffer_output();
775                 cprintf("%d Transmit message (will deliver to %d users)\n",
776                         SEND_LISTING, message_sent);
777                 x_big_msgbuf = malloc(SIZ);
778                 memset(x_big_msgbuf, 0, SIZ);
779                 while (client_getln(x_msg, sizeof x_msg) >= 0 && strcmp(x_msg, "000")) {
780                         x_big_msgbuf = realloc(x_big_msgbuf,
781                                strlen(x_big_msgbuf) + strlen(x_msg) + 4);
782                         if (!IsEmptyStr(x_big_msgbuf))
783                            if (x_big_msgbuf[strlen(x_big_msgbuf)] != '\n')
784                                 strcat(x_big_msgbuf, "\n");
785                         strcat(x_big_msgbuf, x_msg);
786                 }
787                 PerformXmsgHooks(lun, lem, x_user, x_big_msgbuf);
788                 free(x_big_msgbuf);
789
790                 /* This loop handles inline pages */
791         } else {
792                 message_sent = PerformXmsgHooks(lun, lem, x_user, x_msg);
793
794                 if (message_sent > 0) {
795                         if (!IsEmptyStr(x_msg))
796                                 cprintf("%d Message sent", CIT_OK);
797                         else
798                                 cprintf("%d Ok to send message", CIT_OK);
799                         if (message_sent > 1)
800                                 cprintf(" to %d users", message_sent);
801                         cprintf(".\n");
802                 } else {
803                         if (CtdlGetUser(NULL, x_user))
804                                 cprintf("%d '%s' does not exist.\n",
805                                                 ERROR + NO_SUCH_USER, x_user);
806                         else
807                                 cprintf("%d '%s' is not logged in "
808                                                 "or is not accepting pages.\n",
809                                                 ERROR + RESOURCE_NOT_OPEN, x_user);
810                 }
811
812
813         }
814 }
815
816
817
818 /*
819  * Enter or exit paging-disabled mode
820  */
821 void cmd_dexp(char *argbuf)
822 {
823         int new_state;
824
825         if (CtdlAccessCheck(ac_logged_in)) return;
826
827         new_state = extract_int(argbuf, 0);
828         if ((new_state == 0) || (new_state == 1)) {
829                 CC->disable_exp = new_state;
830         }
831
832         cprintf("%d %d\n", CIT_OK, CC->disable_exp);
833 }
834
835
836 /*
837  * Request client termination
838  */
839 void cmd_reqt(char *argbuf) {
840         struct CitContext *ccptr;
841         int sessions = 0;
842         int which_session;
843         struct ExpressMessage *newmsg;
844
845         if (CtdlAccessCheck(ac_aide)) return;
846         which_session = extract_int(argbuf, 0);
847
848         begin_critical_section(S_SESSION_TABLE);
849         for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
850                 if ((ccptr->cs_pid == which_session) || (which_session == 0)) {
851
852                         newmsg = (struct ExpressMessage *)
853                                 malloc(sizeof (struct ExpressMessage));
854                         memset(newmsg, 0,
855                                 sizeof (struct ExpressMessage));
856                         time(&(newmsg->timestamp));
857                         safestrncpy(newmsg->sender, CC->user.fullname,
858                                     sizeof newmsg->sender);
859                         newmsg->flags |= EM_GO_AWAY;
860                         newmsg->text = strdup("Automatic logoff requested.");
861
862                         add_xmsg_to_context(ccptr, newmsg);
863                         ++sessions;
864
865                 }
866         }
867         end_critical_section(S_SESSION_TABLE);
868         cprintf("%d Sent termination request to %d sessions.\n", CIT_OK, sessions);
869 }
870
871
872 /*
873  * This is the back end for flush_conversations_to_disk()
874  * At this point we've isolated a single conversation (struct imlog)
875  * and are ready to write it to disk.
876  */
877 void flush_individual_conversation(struct imlog *im) {
878         struct CtdlMessage *msg;
879         long msgnum = 0;
880         char roomname[ROOMNAMELEN];
881
882         StrBufAppendBufPlain(im->conversation, HKEY(
883                 "</body>\r\n"
884                 "</html>\r\n"
885                 ), 0
886         );
887
888         msg = malloc(sizeof(struct CtdlMessage));
889         memset(msg, 0, sizeof(struct CtdlMessage));
890         msg->cm_magic = CTDLMESSAGE_MAGIC;
891         msg->cm_anon_type = MES_NORMAL;
892         msg->cm_format_type = FMT_RFC822;
893         if (!IsEmptyStr(im->usernames[0])) {
894                 msg->cm_fields['A'] = strdup(im->usernames[0]);
895         } else {
896                 msg->cm_fields['A'] = strdup("Citadel");
897         }
898         if (!IsEmptyStr(im->usernames[1])) {
899                 msg->cm_fields['R'] = strdup(im->usernames[1]);
900         }
901         msg->cm_fields['O'] = strdup(PAGELOGROOM);
902         msg->cm_fields['N'] = strdup(NODENAME);
903         msg->cm_fields['M'] = SmashStrBuf(&im->conversation);   /* we own this memory now */
904
905         /* Start with usernums[1] because it's guaranteed to be higher than usernums[0],
906          * so if there's only one party, usernums[0] will be zero but usernums[1] won't.
907          * Create the room if necessary.  Note that we create as a type 5 room rather
908          * than 4, which indicates that it's a personal room but we've already supplied
909          * the namespace prefix.
910          *
911          * In the unlikely event that usernums[1] is zero, a room with an invalid namespace
912          * prefix will be created.  That's ok because the auto-purger will clean it up later.
913          */
914         snprintf(roomname, sizeof roomname, "%010ld.%s", im->usernums[1], PAGELOGROOM);
915         CtdlCreateRoom(roomname, 5, "", 0, 1, 1, VIEW_BBS);
916         msgnum = CtdlSubmitMsg(msg, NULL, roomname, 0);
917         CtdlFreeMessage(msg);
918
919         /* If there is a valid user number in usernums[0], save a copy for them too. */
920         if (im->usernums[0] > 0) {
921                 snprintf(roomname, sizeof roomname, "%010ld.%s", im->usernums[0], PAGELOGROOM);
922                 CtdlCreateRoom(roomname, 5, "", 0, 1, 1, VIEW_BBS);
923                 CtdlSaveMsgPointerInRoom(roomname, msgnum, 0, NULL);
924         }
925
926         /* Finally, if we're logging instant messages globally, do that now. */
927         if (!IsEmptyStr(config.c_logpages)) {
928                 CtdlCreateRoom(config.c_logpages, 3, "", 0, 1, 1, VIEW_BBS);
929                 CtdlSaveMsgPointerInRoom(config.c_logpages, msgnum, 0, NULL);
930         }
931
932 }
933
934 /*
935  * Locate instant message conversations which have gone idle
936  * (or, if the server is shutting down, locate *all* conversations)
937  * and flush them to disk (in the participants' log rooms, etc.)
938  */
939 void flush_conversations_to_disk(time_t if_older_than) {
940
941         struct imlog *flush_these = NULL;
942         struct imlog *dont_flush_these = NULL;
943         struct imlog *imptr = NULL;
944
945         begin_critical_section(S_IM_LOGS);
946         while (imlist)
947         {
948                 imptr = imlist;
949                 imlist = imlist->next;
950                 if ((time(NULL) - imptr->lastmsg) > if_older_than)
951                 {
952                         /* This conversation qualifies.  Move it to the list of ones to flush. */
953                         imptr->next = flush_these;
954                         flush_these = imptr;
955                 }
956                 else  {
957                         /* Move it to the list of ones not to flush. */
958                         imptr->next = dont_flush_these;
959                         dont_flush_these = imptr;
960                 }
961         }
962         imlist = dont_flush_these;
963         end_critical_section(S_IM_LOGS);
964
965         /* We are now outside of the critical section, and we are the only thread holding a
966          * pointer to a linked list of conversations to be flushed to disk.
967          */
968         while (flush_these) {
969
970                 flush_individual_conversation(flush_these);     /* This will free the string buffer */
971                 imptr = flush_these;
972                 flush_these = flush_these->next;
973                 free(imptr);
974         }
975 }
976
977
978
979 void chat_timer(void) {
980         flush_conversations_to_disk(300);       /* Anything that hasn't peeped in more than 5 minutes */
981 }
982
983 void chat_shutdown(void) {
984         flush_conversations_to_disk(0);         /* Get it ALL onto disk NOW. */
985 }
986
987 CTDL_MODULE_INIT(chat)
988 {
989         if (!threading)
990         {
991                 CtdlRegisterProtoHook(cmd_chat, "CHAT", "Begin real-time chat");
992                 CtdlRegisterProtoHook(cmd_pexp, "PEXP", "Poll for instant messages");
993                 CtdlRegisterProtoHook(cmd_gexp, "GEXP", "Get instant messages");
994                 CtdlRegisterProtoHook(cmd_sexp, "SEXP", "Send an instant message");
995                 CtdlRegisterProtoHook(cmd_dexp, "DEXP", "Disable instant messages");
996                 CtdlRegisterProtoHook(cmd_reqt, "REQT", "Request client termination");
997                 CtdlRegisterSessionHook(cmd_gexp_async, EVT_ASYNC);
998                 CtdlRegisterSessionHook(delete_instant_messages, EVT_STOP);
999                 CtdlRegisterXmsgHook(send_instant_message, XMSG_PRI_LOCAL);
1000                 CtdlRegisterSessionHook(chat_timer, EVT_TIMER);
1001                 CtdlRegisterSessionHook(chat_shutdown, EVT_SHUTDOWN);
1002         }
1003         
1004         /* return our Subversion id for the Log */
1005         return "$Id$";
1006 }