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