]> code.citadel.org Git - citadel.git/blob - citadel/serv_chat.c
* Protected cmd_move() from buffer overrun (no longer crashes the server)
[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:
120         end_critical_section(S_CHATQUEUE);
121 }
122
123
124 t_context *find_context(char **unstr)
125 {
126         t_context *t_cc, *found_cc = NULL;
127         char *name, *tptr;
128
129         if ((!*unstr) || (!unstr))
130                 return (NULL);
131
132         begin_critical_section(S_SESSION_TABLE);
133         for (t_cc = ContextList; ((t_cc) && (!found_cc)); t_cc = t_cc->next) {
134                 if (t_cc->fake_username[0])
135                         name = t_cc->fake_username;
136                 else
137                         name = t_cc->curr_user;
138                 tptr = *unstr;
139                 if ((!strncasecmp(name, tptr, strlen(name))) && (tptr[strlen(name)] == ' ')) {
140                         found_cc = t_cc;
141                         *unstr = &(tptr[strlen(name) + 1]);
142                 }
143         }
144         end_critical_section(S_SESSION_TABLE);
145
146         return (found_cc);
147 }
148
149 /*
150  * List users in chat.
151  * allflag ==   0 = list users in chat
152  *              1 = list users in chat, followed by users not in chat
153  *              2 = display count only
154  */
155
156 void do_chat_listing(int allflag)
157 {
158         struct CitContext *ccptr;
159         int count = 0;
160         char roomname[ROOMNAMELEN];
161
162         if ((allflag == 0) || (allflag == 1))
163                 cprintf(":|\n:| Users currently in chat:\n");
164         begin_critical_section(S_SESSION_TABLE);
165         for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
166                 if (ccptr->cs_flags & CS_CHAT) ++count;
167                 if ((ccptr->cs_flags & CS_CHAT)
168                     && ((ccptr->cs_flags & CS_STEALTH) == 0)) {
169                         if ((allflag == 0) || (allflag == 1))
170                                 cprintf(":| %-25s <%s>\n", (ccptr->fake_username[0]) ? ccptr->fake_username : ccptr->curr_user, ccptr->chat_room);
171                 }
172         }
173
174         if (allflag == 1) {
175                 cprintf(":|\n:| Users not in chat:\n");
176                 for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
177                         GenerateRoomDisplay(roomname, ccptr, CC);
178                         if (((ccptr->cs_flags & CS_CHAT) == 0)
179                             && ((ccptr->cs_flags & CS_STEALTH) == 0)) {
180                                 cprintf(":| %-25s <%s>:\n",
181                                         (ccptr->fake_username[0]) ? ccptr->fake_username : ccptr->curr_user,
182                                         roomname);
183                         }
184                 }
185         }
186         end_critical_section(S_SESSION_TABLE);
187
188         if (allflag == 2) {
189                 if (count > 1) 
190                         cprintf(":|There are %d users here.\n", count);
191                 else
192                         cprintf(":|Note: you are the only one here.\n");
193         }
194
195         cprintf(":|\n");
196 }
197
198
199 void cmd_chat(char *argbuf)
200 {
201         char cmdbuf[256];
202         char *un;
203         char *strptr1;
204         int MyLastMsg, ThisLastMsg;
205         struct ChatLine *clptr;
206         struct CitContext *t_context;
207         int retval;
208
209         if (!(CC->logged_in)) {
210                 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
211                 return;
212         }
213         strcpy(CC->chat_room, "Main room");
214
215         CC->cs_flags = CC->cs_flags | CS_CHAT;
216         cprintf("%d Entering chat mode (type '/help' for available commands)\n",
217                 START_CHAT_MODE);
218
219         MyLastMsg = ChatLastMsg;
220
221         if ((CC->cs_flags & CS_STEALTH) == 0) {
222                 allwrite("<entering chat>", 0, CC->chat_room, NULL);
223         }
224         strcpy(cmdbuf, "");
225
226         do_chat_listing(2);
227
228         while (1) {
229                 int ok_cmd;
230                 int linelen;
231
232                 ok_cmd = 0;
233                 linelen = strlen(cmdbuf);
234                 if (linelen > 100) --linelen;   /* truncate too-long lines */
235                 cmdbuf[linelen + 1] = 0;
236
237                 retval = client_read_to(&cmdbuf[linelen], 1, 2);
238
239                 if (retval < 0) {       /* socket broken? */
240                         if ((CC->cs_flags & CS_STEALTH) == 0) {
241                                 allwrite("<disconnected>", 0,
242                                         CC->chat_room, NULL);
243                         }
244                         return;
245                 }
246
247                 /* if we have a complete line, do send processing */
248                 if (strlen(cmdbuf) > 0)
249                         if (cmdbuf[strlen(cmdbuf) - 1] == 10) {
250                                 cmdbuf[strlen(cmdbuf) - 1] = 0;
251                                 time(&CC->lastcmd);
252                                 time(&CC->lastidle);
253
254                                 if ((!strcasecmp(cmdbuf, "exit"))
255                                     || (!strcasecmp(cmdbuf, "/exit"))
256                                     || (!strcasecmp(cmdbuf, "quit"))
257                                     || (!strcasecmp(cmdbuf, "logout"))
258                                     || (!strcasecmp(cmdbuf, "logoff"))
259                                     || (!strcasecmp(cmdbuf, "/q"))
260                                     || (!strcasecmp(cmdbuf, ".q"))
261                                     || (!strcasecmp(cmdbuf, "/quit"))
262                                     )
263                                         strcpy(cmdbuf, "000");
264
265                                 if (!strcmp(cmdbuf, "000")) {
266                                         if ((CC->cs_flags & CS_STEALTH) == 0) {
267                                                 allwrite("<exiting chat>", 0, CC->chat_room, NULL);
268                                         }
269                                         sleep(1);
270                                         cprintf("000\n");
271                                         CC->cs_flags = CC->cs_flags - CS_CHAT;
272                                         return;
273                                 }
274                                 if ((!strcasecmp(cmdbuf, "/help"))
275                                     || (!strcasecmp(cmdbuf, "help"))
276                                     || (!strcasecmp(cmdbuf, "/?"))
277                                     || (!strcasecmp(cmdbuf, "?"))) {
278                                         cprintf(":|\n");
279                                         cprintf(":|Available commands: \n");
280                                         cprintf(":|/help   (prints this message) \n");
281                                         cprintf(":|/who    (list users currently in chat) \n");
282                                         cprintf(":|/whobbs (list users in chat -and- elsewhere) \n");
283                                         cprintf(":|/me     ('action' line, ala irc) \n");
284                                         cprintf(":|/msg    (send private message, ala irc) \n");
285                                         cprintf(":|/join   (join new room) \n");
286                                         cprintf(":|/quit   (return to the BBS) \n");
287                                         cprintf(":|\n");
288                                         ok_cmd = 1;
289                                 }
290                                 if (!strcasecmp(cmdbuf, "/who")) {
291                                         do_chat_listing(0);
292                                         ok_cmd = 1;
293                                 }
294                                 if (!strcasecmp(cmdbuf, "/whobbs")) {
295                                         do_chat_listing(1);
296                                         ok_cmd = 1;
297                                 }
298                                 if (!strncasecmp(cmdbuf, "/me ", 4)) {
299                                         allwrite(&cmdbuf[4], 1, CC->chat_room, NULL);
300                                         ok_cmd = 1;
301                                 }
302                                 if (!strncasecmp(cmdbuf, "/msg ", 5)) {
303                                         ok_cmd = 1;
304                                         strptr1 = &cmdbuf[5];
305                                         if ((t_context = find_context(&strptr1))) {
306                                                 allwrite(strptr1, 2, "", CC->curr_user);
307                                                 if (strcasecmp(CC->curr_user, t_context->curr_user))
308                                                         allwrite(strptr1, 2, "", t_context->curr_user);
309                                         } else
310                                                 cprintf(":|User not found.\n", cmdbuf);
311                                         cprintf("\n");
312                                 }
313                                 if (!strncasecmp(cmdbuf, "/join ", 6)) {
314                                         ok_cmd = 1;
315                                         allwrite("<changing rooms>", 0, CC->chat_room, NULL);
316                                         if (!cmdbuf[6])
317                                                 strcpy(CC->chat_room, "Main room");
318                                         else {
319                                                 strncpy(CC->chat_room, &cmdbuf[6],
320                                                    sizeof CC->chat_room);
321                                                 CC->chat_room[sizeof CC->chat_room - 1] = 0;
322                                         }
323                                         allwrite("<joining room>", 0, CC->chat_room, NULL);
324                                         cprintf("\n");
325                                 }
326                                 if ((cmdbuf[0] != '/') && (strlen(cmdbuf) > 0)) {
327                                         ok_cmd = 1;
328                                         allwrite(cmdbuf, 0, CC->chat_room, NULL);
329                                 }
330                                 if ((!ok_cmd) && (cmdbuf[0]) && (cmdbuf[0] != '\n'))
331                                         cprintf(":|Command %s is not understood.\n", cmdbuf);
332
333                                 strcpy(cmdbuf, "");
334
335                         }
336                 /* now check the queue for new incoming stuff */
337
338                 if (CC->fake_username[0])
339                         un = CC->fake_username;
340                 else
341                         un = CC->curr_user;
342                 if (ChatLastMsg > MyLastMsg) {
343                         ThisLastMsg = ChatLastMsg;
344                         for (clptr = ChatQueue; clptr != NULL; clptr = clptr->next) {
345                                 if ((clptr->chat_seq > MyLastMsg) && ((!clptr->chat_username[0]) || (!strncasecmp(un, clptr->chat_username, 32)))) {
346                                         if ((!clptr->chat_room[0]) || (!strncasecmp(CC->chat_room, clptr->chat_room, ROOMNAMELEN))) {
347                                                 cprintf("%s\n", clptr->chat_text);
348                                         }
349                                 }
350                         }
351                         MyLastMsg = ThisLastMsg;
352                 }
353         }
354 }
355
356
357
358 /*
359  * Delete any remaining express messages
360  */
361 void delete_express_messages(void) {
362         struct ExpressMessage *ptr;
363
364         begin_critical_section(S_SESSION_TABLE);
365         while (CC->FirstExpressMessage != NULL) {
366                 ptr = CC->FirstExpressMessage->next;
367                 if (CC->FirstExpressMessage->text != NULL)
368                         phree(CC->FirstExpressMessage->text);
369                 phree(CC->FirstExpressMessage);
370                 CC->FirstExpressMessage = ptr;
371                 }
372         end_critical_section(S_SESSION_TABLE);
373         }
374
375
376
377
378 /*
379  * Poll for express messages (OLD METHOD -- ***DEPRECATED ***)
380  */
381 void cmd_pexp(char *argbuf)
382 {
383         struct ExpressMessage *ptr, *holdptr;
384
385         if (CC->FirstExpressMessage == NULL) {
386                 cprintf("%d No express messages waiting.\n", ERROR);
387                 return;
388         }
389         begin_critical_section(S_SESSION_TABLE);
390         ptr = CC->FirstExpressMessage;
391         CC->FirstExpressMessage = NULL;
392         end_critical_section(S_SESSION_TABLE);
393
394         cprintf("%d Express msgs:\n", LISTING_FOLLOWS);
395         while (ptr != NULL) {
396                 if (ptr->flags && EM_BROADCAST)
397                         cprintf("Broadcast message ");
398                 else if (ptr->flags && EM_CHAT)
399                         cprintf("Chat request ");
400                 else if (ptr->flags && EM_GO_AWAY)
401                         cprintf("Please logoff now, as requested ");
402                 else
403                         cprintf("Message ");
404                 cprintf("from %s:\n", ptr->sender);
405                 if (ptr->text != NULL)
406                         memfmout(80, ptr->text, 0);
407
408                 holdptr = ptr->next;
409                 if (ptr->text != NULL) phree(ptr->text);
410                 phree(ptr);
411                 ptr = holdptr;
412         }
413         cprintf("000\n");
414 }
415
416
417 /*
418  * Get express messages (new method)
419  */
420 void cmd_gexp(char *argbuf) {
421         struct ExpressMessage *ptr;
422
423         if (CC->FirstExpressMessage == NULL) {
424                 cprintf("%d No express messages waiting.\n", ERROR);
425                 return;
426         }
427
428         begin_critical_section(S_SESSION_TABLE);
429         ptr = CC->FirstExpressMessage;
430         CC->FirstExpressMessage = CC->FirstExpressMessage->next;
431         end_critical_section(S_SESSION_TABLE);
432
433         cprintf("%d %d|%ld|%d|%s|%s\n",
434                 LISTING_FOLLOWS,
435                 ((ptr->next != NULL) ? 1 : 0),          /* more msgs? */
436                 ptr->timestamp,                         /* time sent */
437                 ptr->flags,                             /* flags */
438                 ptr->sender,                            /* sender of msg */
439                 config.c_nodename);                     /* static for now */
440         if (ptr->text != NULL) {
441                 memfmout(80, ptr->text, 0);
442                 if (ptr->text[strlen(ptr->text)-1] != '\n') cprintf("\n");
443                 phree(ptr->text);
444                 }
445         cprintf("000\n");
446         phree(ptr);
447 }
448
449
450
451 /* 
452  * This is the back end to the express message sending function.  
453  * Returns the number of users to which the message was sent.
454  * Sending a zero-length message tests for recipients without sending messages.
455  */
456 int send_express_message(char *lun, char *x_user, char *x_msg)
457 {
458         int message_sent = 0;
459         struct CitContext *ccptr;
460         struct ExpressMessage *newmsg, *findend;
461         char *un;
462         size_t msglen = 0;
463         int do_send = 0;
464
465         if (strlen(x_msg) > 0) {
466                 msglen = strlen(x_msg) + 4;
467                 do_send = 1;
468                 }
469
470         /* find the target user's context and append the message */
471         begin_critical_section(S_SESSION_TABLE);
472         for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
473
474                 if (ccptr->fake_username[0])    /* <bc> */
475                         un = ccptr->fake_username;
476                 else
477                         un = ccptr->usersupp.fullname;
478
479                 if ((!strcasecmp(un, x_user))
480                     || (!strcasecmp(x_user, "broadcast"))) {
481                         if (do_send) {
482                                 newmsg = (struct ExpressMessage *)
483                                         mallok(sizeof (struct ExpressMessage));
484                                 memset(newmsg, 0,
485                                         sizeof (struct ExpressMessage));
486                                 safestrncpy(newmsg->sender, lun,
487                                             sizeof newmsg->sender);
488                                 if (!strcasecmp(x_user, "broadcast"))
489                                         newmsg->flags |= EM_BROADCAST;
490                                 newmsg->text = mallok(msglen);
491                                 safestrncpy(newmsg->text, x_msg, msglen);
492
493                                 if (ccptr->FirstExpressMessage == NULL)
494                                         ccptr->FirstExpressMessage = newmsg;
495                                 else {
496                                         findend = ccptr->FirstExpressMessage;
497                                         while (findend->next != NULL)
498                                                 findend = findend->next;
499                                         findend->next = newmsg;
500                                 }
501                         }
502                         ++message_sent;
503                 }
504         }
505         end_critical_section(S_SESSION_TABLE);
506
507         /* Log the page to disk if configured to do so  */
508         if ((strlen(config.c_logpages) > 0) && (do_send) ) {
509                 quickie_message(lun, x_user, config.c_logpages, x_msg);
510         }
511
512         return (message_sent);
513 }
514
515 /*
516  * send express messages  <bc>
517  */
518 void cmd_sexp(char *argbuf)
519 {
520         int message_sent = 0;
521         char x_user[256];
522         char x_msg[256];
523         char *lun;              /* <bc> */
524         char *x_big_msgbuf = NULL;
525
526         if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
527                 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
528                 return;
529         }
530         if (CC->fake_username[0])
531                 lun = CC->fake_username;
532         else
533                 lun = CC->usersupp.fullname;
534
535         extract(x_user, argbuf, 0);
536
537         extract(x_msg, argbuf, 1);
538
539         if (!x_user[0]) {
540                 cprintf("%d You were not previously paged.\n", ERROR);
541                 return;
542         }
543         if ((!strcasecmp(x_user, "broadcast")) && (CC->usersupp.axlevel < 6)) {
544                 cprintf("%d Higher access required to send a broadcast.\n",
545                         ERROR + HIGHER_ACCESS_REQUIRED);
546                 return;
547         }
548         /* This loop handles text-transfer pages */
549         if (!strcmp(x_msg, "-")) {
550                 message_sent = PerformXmsgHooks(lun, x_user, "");
551                 if (message_sent == 0) {
552                         cprintf("%d No user '%s' logged in.\n", ERROR, x_user);
553                         return;
554                 }
555                 cprintf("%d Transmit message (will deliver to %d users)\n",
556                         SEND_LISTING, message_sent);
557                 x_big_msgbuf = mallok(256);
558                 memset(x_big_msgbuf, 0, 256);
559                 while (client_gets(x_msg), strcmp(x_msg, "000")) {
560                         x_big_msgbuf = reallok(x_big_msgbuf,
561                                strlen(x_big_msgbuf) + strlen(x_msg) + 4);
562                         if (strlen(x_big_msgbuf) > 0)
563                            if (x_big_msgbuf[strlen(x_big_msgbuf)] != '\n')
564                                 strcat(x_big_msgbuf, "\n");
565                         strcat(x_big_msgbuf, x_msg);
566                 }
567                 PerformXmsgHooks(lun, x_user, x_big_msgbuf);
568                 phree(x_big_msgbuf);
569
570                 /* This loop handles inline pages */
571         } else {
572                 message_sent = PerformXmsgHooks(lun, x_user, x_msg);
573
574                 if (message_sent > 0) {
575                         if (strlen(x_msg) > 0)
576                                 cprintf("%d Message sent", OK);
577                         else
578                                 cprintf("%d Ok to send message", OK);
579                         if (message_sent > 1)
580                                 cprintf(" to %d users", message_sent);
581                         cprintf(".\n");
582                 } else {
583                         cprintf("%d No user '%s' logged in.\n", ERROR, x_user);
584                 }
585
586
587         }
588 }