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