* Removed all of the thread cancellation cruft that is no longer necessary
[citadel.git] / citadel / serv_chat.c
1 /*
2  * serv_chat.c
3  * 
4  * This module handles all "real time" communication between users.  The
5  * modes of communication currently supported are Chat and Paging.
6  *
7  * $Id$
8  */
9 #include "sysdep.h"
10 #include <stdlib.h>
11 #include <unistd.h>
12 #include <stdio.h>
13 #include <fcntl.h>
14 #include <signal.h>
15 #include <pwd.h>
16 #include <errno.h>
17 #include <sys/types.h>
18 #include <sys/time.h>
19 #include <sys/wait.h>
20 #include <string.h>
21 #include <limits.h>
22 #include "citadel.h"
23 #include "server.h"
24 #include <syslog.h>
25 #include "serv_chat.h"
26 #include "sysdep_decls.h"
27 #include "citserver.h"
28 #include "support.h"
29 #include "config.h"
30 #include "dynloader.h"
31 #include "tools.h"
32 #include "msgbase.h"
33
34 struct ChatLine *ChatQueue = NULL;
35 int ChatLastMsg = 0;
36
37 extern struct CitContext *ContextList;
38
39
40
41 char *Dynamic_Module_Init(void)
42 {
43         CtdlRegisterProtoHook(cmd_chat, "CHAT", "Begin real-time chat");
44         CtdlRegisterProtoHook(cmd_pexp, "PEXP", "Poll for express messages");
45         CtdlRegisterProtoHook(cmd_gexp, "GEXP", "Get express messages");
46         CtdlRegisterProtoHook(cmd_sexp, "SEXP", "Send an express message");
47         CtdlRegisterSessionHook(delete_express_messages, EVT_STOP);
48         CtdlRegisterXmsgHook(send_express_message, XMSG_PRI_LOCAL);
49         return "$Id$";
50 }
51
52 void allwrite(char *cmdbuf, int flag, char *roomname, char *username)
53 {
54         FILE *fp;
55         char bcast[256];
56         char *un;
57         struct ChatLine *clptr, *clnew;
58         time_t now;
59
60         if (CC->fake_username[0])
61                 un = CC->fake_username;
62         else
63                 un = CC->usersupp.fullname;
64         if (flag == 1) {
65                 snprintf(bcast, sizeof bcast, ":|<%s %s>", un, cmdbuf);
66         } else if (flag == 0) {
67                 snprintf(bcast, sizeof bcast, "%s|%s", un, cmdbuf);
68         } else if (flag == 2) {
69                 snprintf(bcast, sizeof bcast, ":|<%s whispers %s>", un, cmdbuf);
70         }
71         if ((strcasecmp(cmdbuf, "NOOP")) && (flag != 2)) {
72                 fp = fopen(CHATLOG, "a");
73                 fprintf(fp, "%s\n", bcast);
74                 fclose(fp);
75         }
76         clnew = (struct ChatLine *) mallok(sizeof(struct ChatLine));
77         memset(clnew, 0, sizeof(struct ChatLine));
78         if (clnew == NULL) {
79                 fprintf(stderr, "citserver: cannot alloc chat line: %s\n",
80                         strerror(errno));
81                 return;
82         }
83         time(&now);
84         clnew->next = NULL;
85         clnew->chat_time = now;
86         safestrncpy(clnew->chat_room, roomname, sizeof clnew->chat_room);
87         clnew->chat_room[sizeof clnew->chat_room - 1] = 0;
88         if (username) {
89                 safestrncpy(clnew->chat_username, username,
90                         sizeof clnew->chat_username);
91                 clnew->chat_username[sizeof clnew->chat_username - 1] = 0;
92         } else
93                 clnew->chat_username[0] = '\0';
94         safestrncpy(clnew->chat_text, bcast, sizeof clnew->chat_text);
95
96         /* Here's the critical section.
97          * First, add the new message to the queue...
98          */
99         begin_critical_section(S_CHATQUEUE);
100         ++ChatLastMsg;
101         clnew->chat_seq = ChatLastMsg;
102         if (ChatQueue == NULL) {
103                 ChatQueue = clnew;
104         } else {
105                 for (clptr = ChatQueue; clptr->next != NULL; clptr = clptr->next);;
106                 clptr->next = clnew;
107         }
108
109         /* Then, before releasing the lock, free the expired messages */
110         while (1) {
111                 if (ChatQueue == NULL)
112                         goto DONE_FREEING;
113                 if ((now - ChatQueue->chat_time) < 120L)
114                         goto DONE_FREEING;
115                 clptr = ChatQueue;
116                 ChatQueue = ChatQueue->next;
117                 phree(clptr);
118         }
119       DONE_FREEING:end_critical_section(S_CHATQUEUE);
120 }
121
122
123 t_context *find_context(char **unstr)
124 {
125         t_context *t_cc, *found_cc = NULL;
126         char *name, *tptr;
127
128         if ((!*unstr) || (!unstr))
129                 return (NULL);
130
131         begin_critical_section(S_SESSION_TABLE);
132         for (t_cc = ContextList; ((t_cc) && (!found_cc)); t_cc = t_cc->next) {
133                 if (t_cc->fake_username[0])
134                         name = t_cc->fake_username;
135                 else
136                         name = t_cc->curr_user;
137                 tptr = *unstr;
138                 if ((!strncasecmp(name, tptr, strlen(name))) && (tptr[strlen(name)] == ' ')) {
139                         found_cc = t_cc;
140                         *unstr = &(tptr[strlen(name) + 1]);
141                 }
142         }
143         end_critical_section(S_SESSION_TABLE);
144
145         return (found_cc);
146 }
147
148 /*
149  * List users in chat.
150  * allflag ==   0 = list users in chat
151  *              1 = list users in chat, followed by users not in chat
152  *              2 = display count only
153  */
154
155 void do_chat_listing(int allflag)
156 {
157         struct CitContext *ccptr;
158         int count = 0;
159         char roomname[ROOMNAMELEN];
160
161         if ((allflag == 0) || (allflag == 1))
162                 cprintf(":|\n:| Users currently in chat:\n");
163         begin_critical_section(S_SESSION_TABLE);
164         for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
165                 if (ccptr->cs_flags & CS_CHAT) ++count;
166                 if ((ccptr->cs_flags & CS_CHAT)
167                     && ((ccptr->cs_flags & CS_STEALTH) == 0)) {
168                         if ((allflag == 0) || (allflag == 1))
169                                 cprintf(":| %-25s <%s>\n", (ccptr->fake_username[0]) ? ccptr->fake_username : ccptr->curr_user, ccptr->chat_room);
170                 }
171         }
172
173         if (allflag == 1) {
174                 cprintf(":|\n:| Users not in chat:\n");
175                 for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
176                         GenerateRoomDisplay(roomname, ccptr, CC);
177                         if (((ccptr->cs_flags & CS_CHAT) == 0)
178                             && ((ccptr->cs_flags & CS_STEALTH) == 0)) {
179                                 cprintf(":| %-25s <%s>:\n",
180                                         (ccptr->fake_username[0]) ? ccptr->fake_username : ccptr->curr_user,
181                                         roomname);
182                         }
183                 }
184         }
185         end_critical_section(S_SESSION_TABLE);
186
187         if (allflag == 2) {
188                 if (count > 1) 
189                         cprintf(":|There are %d users here.\n", count);
190                 else
191                         cprintf(":|Note: you are the only one here.\n");
192         }
193
194         cprintf(":|\n");
195 }
196
197
198 void cmd_chat(char *argbuf)
199 {
200         char cmdbuf[256];
201         char *un;
202         char *strptr1;
203         int MyLastMsg, ThisLastMsg;
204         struct ChatLine *clptr;
205         struct CitContext *t_context;
206         int retval;
207
208         if (!(CC->logged_in)) {
209                 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
210                 return;
211         }
212         strcpy(CC->chat_room, "Main room");
213
214         CC->cs_flags = CC->cs_flags | CS_CHAT;
215         cprintf("%d Entering chat mode (type '/help' for available commands)\n",
216                 START_CHAT_MODE);
217
218         MyLastMsg = ChatLastMsg;
219
220         if ((CC->cs_flags & CS_STEALTH) == 0) {
221                 allwrite("<entering chat>", 0, CC->chat_room, NULL);
222         }
223         strcpy(cmdbuf, "");
224
225         do_chat_listing(2);
226
227         while (1) {
228                 int ok_cmd;
229
230                 ok_cmd = 0;
231                 cmdbuf[strlen(cmdbuf) + 1] = 0;
232                 retval = client_read_to(&cmdbuf[strlen(cmdbuf)], 1, 2);
233
234                 /* if we have a complete line, do send processing */
235                 if (strlen(cmdbuf) > 0)
236                         if (cmdbuf[strlen(cmdbuf) - 1] == 10) {
237                                 cmdbuf[strlen(cmdbuf) - 1] = 0;
238                                 time(&CC->lastcmd);
239                                 time(&CC->lastidle);
240
241                                 if ((!strcasecmp(cmdbuf, "exit"))
242                                     || (!strcasecmp(cmdbuf, "/exit"))
243                                     || (!strcasecmp(cmdbuf, "quit"))
244                                     || (!strcasecmp(cmdbuf, "logout"))
245                                     || (!strcasecmp(cmdbuf, "logoff"))
246                                     || (!strcasecmp(cmdbuf, "/q"))
247                                     || (!strcasecmp(cmdbuf, ".q"))
248                                     || (!strcasecmp(cmdbuf, "/quit"))
249                                     )
250                                         strcpy(cmdbuf, "000");
251
252                                 if (!strcmp(cmdbuf, "000")) {
253                                         if ((CC->cs_flags & CS_STEALTH) == 0) {
254                                                 allwrite("<exiting chat>", 0, CC->chat_room, NULL);
255                                         }
256                                         sleep(1);
257                                         cprintf("000\n");
258                                         CC->cs_flags = CC->cs_flags - CS_CHAT;
259                                         return;
260                                 }
261                                 if ((!strcasecmp(cmdbuf, "/help"))
262                                     || (!strcasecmp(cmdbuf, "help"))
263                                     || (!strcasecmp(cmdbuf, "/?"))
264                                     || (!strcasecmp(cmdbuf, "?"))) {
265                                         cprintf(":|\n");
266                                         cprintf(":|Available commands: \n");
267                                         cprintf(":|/help   (prints this message) \n");
268                                         cprintf(":|/who    (list users currently in chat) \n");
269                                         cprintf(":|/whobbs (list users in chat -and- elsewhere) \n");
270                                         cprintf(":|/me     ('action' line, ala irc) \n");
271                                         cprintf(":|/msg    (send private message, ala irc) \n");
272                                         cprintf(":|/join   (join new room) \n");
273                                         cprintf(":|/quit   (return to the BBS) \n");
274                                         cprintf(":|\n");
275                                         ok_cmd = 1;
276                                 }
277                                 if (!strcasecmp(cmdbuf, "/who")) {
278                                         do_chat_listing(0);
279                                         ok_cmd = 1;
280                                 }
281                                 if (!strcasecmp(cmdbuf, "/whobbs")) {
282                                         do_chat_listing(1);
283                                         ok_cmd = 1;
284                                 }
285                                 if (!strncasecmp(cmdbuf, "/me ", 4)) {
286                                         allwrite(&cmdbuf[4], 1, CC->chat_room, NULL);
287                                         ok_cmd = 1;
288                                 }
289                                 if (!strncasecmp(cmdbuf, "/msg ", 5)) {
290                                         ok_cmd = 1;
291                                         strptr1 = &cmdbuf[5];
292                                         if ((t_context = find_context(&strptr1))) {
293                                                 allwrite(strptr1, 2, "", CC->curr_user);
294                                                 if (strcasecmp(CC->curr_user, t_context->curr_user))
295                                                         allwrite(strptr1, 2, "", t_context->curr_user);
296                                         } else
297                                                 cprintf(":|User not found.\n", cmdbuf);
298                                         cprintf("\n");
299                                 }
300                                 if (!strncasecmp(cmdbuf, "/join ", 6)) {
301                                         ok_cmd = 1;
302                                         allwrite("<changing rooms>", 0, CC->chat_room, NULL);
303                                         if (!cmdbuf[6])
304                                                 strcpy(CC->chat_room, "Main room");
305                                         else {
306                                                 strncpy(CC->chat_room, &cmdbuf[6],
307                                                    sizeof CC->chat_room);
308                                                 CC->chat_room[sizeof CC->chat_room - 1] = 0;
309                                         }
310                                         allwrite("<joining room>", 0, CC->chat_room, NULL);
311                                         cprintf("\n");
312                                 }
313                                 if ((cmdbuf[0] != '/') && (strlen(cmdbuf) > 0)) {
314                                         ok_cmd = 1;
315                                         allwrite(cmdbuf, 0, CC->chat_room, NULL);
316                                 }
317                                 if ((!ok_cmd) && (cmdbuf[0]) && (cmdbuf[0] != '\n'))
318                                         cprintf(":|Command %s is not understood.\n", cmdbuf);
319
320                                 strcpy(cmdbuf, "");
321
322                         }
323                 /* now check the queue for new incoming stuff */
324
325                 if (CC->fake_username[0])
326                         un = CC->fake_username;
327                 else
328                         un = CC->curr_user;
329                 if (ChatLastMsg > MyLastMsg) {
330                         ThisLastMsg = ChatLastMsg;
331                         for (clptr = ChatQueue; clptr != NULL; clptr = clptr->next) {
332                                 if ((clptr->chat_seq > MyLastMsg) && ((!clptr->chat_username[0]) || (!strncasecmp(un, clptr->chat_username, 32)))) {
333                                         if ((!clptr->chat_room[0]) || (!strncasecmp(CC->chat_room, clptr->chat_room, ROOMNAMELEN))) {
334                                                 cprintf("%s\n", clptr->chat_text);
335                                         }
336                                 }
337                         }
338                         MyLastMsg = ThisLastMsg;
339                 }
340         }
341 }
342
343
344
345 /*
346  * Delete any remaining express messages
347  */
348 void delete_express_messages(void) {
349         struct ExpressMessage *ptr;
350
351         begin_critical_section(S_SESSION_TABLE);
352         while (CC->FirstExpressMessage != NULL) {
353                 ptr = CC->FirstExpressMessage->next;
354                 if (CC->FirstExpressMessage->text != NULL)
355                         phree(CC->FirstExpressMessage->text);
356                 phree(CC->FirstExpressMessage);
357                 CC->FirstExpressMessage = ptr;
358                 }
359         end_critical_section(S_SESSION_TABLE);
360         }
361
362
363
364
365 /*
366  * Poll for express messages (OLD METHOD -- ***DEPRECATED ***)
367  */
368 void cmd_pexp(char *argbuf)
369 {
370         struct ExpressMessage *ptr, *holdptr;
371
372         if (CC->FirstExpressMessage == NULL) {
373                 cprintf("%d No express messages waiting.\n", ERROR);
374                 return;
375         }
376         begin_critical_section(S_SESSION_TABLE);
377         ptr = CC->FirstExpressMessage;
378         CC->FirstExpressMessage = NULL;
379         end_critical_section(S_SESSION_TABLE);
380
381         cprintf("%d Express msgs:\n", LISTING_FOLLOWS);
382         while (ptr != NULL) {
383                 if (ptr->flags && EM_BROADCAST)
384                         cprintf("Broadcast message ");
385                 else if (ptr->flags && EM_CHAT)
386                         cprintf("Chat request ");
387                 else if (ptr->flags && EM_GO_AWAY)
388                         cprintf("Please logoff now, as requested ");
389                 else
390                         cprintf("Message ");
391                 cprintf("from %s:\n", ptr->sender);
392                 if (ptr->text != NULL)
393                         memfmout(80, ptr->text, 0);
394
395                 holdptr = ptr->next;
396                 if (ptr->text != NULL) phree(ptr->text);
397                 phree(ptr);
398                 ptr = holdptr;
399         }
400         cprintf("000\n");
401 }
402
403
404 /*
405  * Get express messages (new method)
406  */
407 void cmd_gexp(char *argbuf) {
408         struct ExpressMessage *ptr;
409
410         if (CC->FirstExpressMessage == NULL) {
411                 cprintf("%d No express messages waiting.\n", ERROR);
412                 return;
413         }
414
415         begin_critical_section(S_SESSION_TABLE);
416         ptr = CC->FirstExpressMessage;
417         CC->FirstExpressMessage = CC->FirstExpressMessage->next;
418         end_critical_section(S_SESSION_TABLE);
419
420         cprintf("%d %d|%ld|%d|%s|%s\n",
421                 LISTING_FOLLOWS,
422                 ((ptr->next != NULL) ? 1 : 0),          /* more msgs? */
423                 ptr->timestamp,                         /* time sent */
424                 ptr->flags,                             /* flags */
425                 ptr->sender,                            /* sender of msg */
426                 config.c_nodename);                     /* static for now */
427         if (ptr->text != NULL) {
428                 memfmout(80, ptr->text, 0);
429                 if (ptr->text[strlen(ptr->text)-1] != '\n') cprintf("\n");
430                 phree(ptr->text);
431                 }
432         cprintf("000\n");
433         phree(ptr);
434 }
435
436
437
438 /* 
439  * This is the back end to the express message sending function.  
440  * Returns the number of users to which the message was sent.
441  * Sending a zero-length message tests for recipients without sending messages.
442  */
443 int send_express_message(char *lun, char *x_user, char *x_msg)
444 {
445         int message_sent = 0;
446         struct CitContext *ccptr;
447         struct ExpressMessage *newmsg, *findend;
448         char *un;
449         size_t msglen = 0;
450         int do_send = 0;
451
452         if (strlen(x_msg) > 0) {
453                 msglen = strlen(x_msg) + 4;
454                 do_send = 1;
455                 }
456
457         /* find the target user's context and append the message */
458         begin_critical_section(S_SESSION_TABLE);
459         for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
460
461                 if (ccptr->fake_username[0])    /* <bc> */
462                         un = ccptr->fake_username;
463                 else
464                         un = ccptr->usersupp.fullname;
465
466                 if ((!strcasecmp(un, x_user))
467                     || (!strcasecmp(x_user, "broadcast"))) {
468                         if (do_send) {
469                                 newmsg = (struct ExpressMessage *)
470                                         mallok(sizeof (struct ExpressMessage));
471                                 memset(newmsg, 0,
472                                         sizeof (struct ExpressMessage));
473                                 safestrncpy(newmsg->sender, lun,
474                                             sizeof newmsg->sender);
475                                 if (!strcasecmp(x_user, "broadcast"))
476                                         newmsg->flags |= EM_BROADCAST;
477                                 newmsg->text = mallok(msglen);
478                                 safestrncpy(newmsg->text, x_msg, msglen);
479
480                                 if (ccptr->FirstExpressMessage == NULL)
481                                         ccptr->FirstExpressMessage = newmsg;
482                                 else {
483                                         findend = ccptr->FirstExpressMessage;
484                                         while (findend->next != NULL)
485                                                 findend = findend->next;
486                                         findend->next = newmsg;
487                                 }
488                         }
489                         ++message_sent;
490                 }
491         }
492         end_critical_section(S_SESSION_TABLE);
493
494         /* Log the page to disk if configured to do so  */
495         if ((strlen(config.c_logpages) > 0) && (do_send) ) {
496                 quickie_message(lun, x_user, config.c_logpages, x_msg);
497         }
498
499         return (message_sent);
500 }
501
502 /*
503  * send express messages  <bc>
504  */
505 void cmd_sexp(char *argbuf)
506 {
507         int message_sent = 0;
508         char x_user[256];
509         char x_msg[256];
510         char *lun;              /* <bc> */
511         char *x_big_msgbuf = NULL;
512
513         if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
514                 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
515                 return;
516         }
517         if (CC->fake_username[0])
518                 lun = CC->fake_username;
519         else
520                 lun = CC->usersupp.fullname;
521
522         extract(x_user, argbuf, 0);
523
524         extract(x_msg, argbuf, 1);
525
526         if (!x_user[0]) {
527                 cprintf("%d You were not previously paged.\n", ERROR);
528                 return;
529         }
530         if ((!strcasecmp(x_user, "broadcast")) && (CC->usersupp.axlevel < 6)) {
531                 cprintf("%d Higher access required to send a broadcast.\n",
532                         ERROR + HIGHER_ACCESS_REQUIRED);
533                 return;
534         }
535         /* This loop handles text-transfer pages */
536         if (!strcmp(x_msg, "-")) {
537                 message_sent = PerformXmsgHooks(lun, x_user, "");
538                 if (message_sent == 0) {
539                         cprintf("%d No user '%s' logged in.\n", ERROR, x_user);
540                         return;
541                 }
542                 cprintf("%d Transmit message (will deliver to %d users)\n",
543                         SEND_LISTING, message_sent);
544                 x_big_msgbuf = mallok(256);
545                 memset(x_big_msgbuf, 0, 256);
546                 while (client_gets(x_msg), strcmp(x_msg, "000")) {
547                         x_big_msgbuf = reallok(x_big_msgbuf,
548                                strlen(x_big_msgbuf) + strlen(x_msg) + 4);
549                         if (strlen(x_big_msgbuf) > 0)
550                            if (x_big_msgbuf[strlen(x_big_msgbuf)] != '\n')
551                                 strcat(x_big_msgbuf, "\n");
552                         strcat(x_big_msgbuf, x_msg);
553                 }
554                 PerformXmsgHooks(lun, x_user, x_big_msgbuf);
555                 phree(x_big_msgbuf);
556
557                 /* This loop handles inline pages */
558         } else {
559                 message_sent = PerformXmsgHooks(lun, x_user, x_msg);
560
561                 if (message_sent > 0) {
562                         if (strlen(x_msg) > 0)
563                                 cprintf("%d Message sent", OK);
564                         else
565                                 cprintf("%d Ok to send message", OK);
566                         if (message_sent > 1)
567                                 cprintf(" to %d users", message_sent);
568                         cprintf(".\n");
569                 } else {
570                         cprintf("%d No user '%s' logged in.\n", ERROR, x_user);
571                 }
572
573
574         }
575 }