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