Changeover to new room structure. See ChangeLog for details.
[citadel.git] / citadel / serv_chat.c
1 #include <stdlib.h>
2 #include <unistd.h>
3 #include <stdio.h>
4 #include <fcntl.h>
5 #include <signal.h>
6 #include <pwd.h>
7 #include <errno.h>
8 #include <sys/types.h>
9 #include <sys/time.h>
10 #include <sys/wait.h>
11 #include <string.h>
12 #include <limits.h>
13 #include <pthread.h>
14 #include "citadel.h"
15 #include "server.h"
16 #include <syslog.h>
17 #ifdef NEED_SELECT_H
18 #include <sys/select.h>
19 #endif
20 #include "serv_chat.h"
21 #include "sysdep_decls.h"
22 #include "citserver.h"
23 #include "support.h"
24 #include "config.h"
25 #include "dynloader.h"
26
27 struct ChatLine *ChatQueue = NULL;
28 int ChatLastMsg = 0;
29
30 extern struct CitContext *ContextList;
31
32 #define MODULE_NAME     "Chat module"
33 #define MODULE_AUTHOR   "Art Cancro"
34 #define MODULE_EMAIL    "ajc@uncnsrd.mt-kisco.ny.us"
35 #define MAJOR_VERSION   0
36 #define MINOR_VERSION   2
37
38 static struct DLModule_Info info =
39 {
40   MODULE_NAME,
41   MODULE_AUTHOR,
42   MODULE_EMAIL,
43   MAJOR_VERSION,
44   MINOR_VERSION
45 };
46
47 struct DLModule_Info *Dynamic_Module_Init(void)
48 {
49    CtdlRegisterProtoHook(cmd_chat, "CHAT",
50                          "Initiates a real-time chat session");
51    CtdlRegisterProtoHook(cmd_pexp, "PEXP", "Poll for express messages");
52    CtdlRegisterProtoHook(cmd_sexp, "SEXP", "Send an express message");
53    return &info;
54 }
55
56 void allwrite(char *cmdbuf, int flag, char *roomname, char *username)
57 {       
58         FILE *fp;
59         char bcast[256];
60         char *un;
61         struct ChatLine *clptr, *clnew;
62         time_t now;
63
64         if (CC->fake_username[0])
65            un = CC->fake_username;
66         else
67            un = CC->usersupp.fullname;
68         if (flag == 1) 
69         {
70                 sprintf(bcast,":|<%s %s>",un,cmdbuf);
71         }
72         else
73         if (flag == 0)
74         {
75                 sprintf(bcast,"%s|%s",un,cmdbuf);
76         }
77         else
78         if (flag == 2)
79         {
80                 sprintf(bcast,":|<%s whispers %s>", un, cmdbuf);
81         }
82         if ((strcasecmp(cmdbuf,"NOOP")) && (flag !=2)) {
83                 fp = fopen(CHATLOG,"a");
84                 fprintf(fp,"%s\n",bcast);
85                 fclose(fp);
86                 }
87
88         clnew = (struct ChatLine *) malloc(sizeof(struct ChatLine));
89         bzero(clnew, sizeof(struct ChatLine));
90         if (clnew == NULL) {
91                 fprintf(stderr, "citserver: cannot alloc chat line: %s\n",
92                         strerror(errno));
93                 return;
94                 }
95
96         time(&now);
97         clnew->next = NULL;
98         clnew->chat_time = now;
99         strncpy(clnew->chat_room, roomname, ROOMNAMELEN-1);
100         if (username)
101            strncpy(clnew->chat_username, username, 31); 
102         else
103            clnew->chat_username[0] = '\0';
104         strcpy(clnew->chat_text, bcast);
105
106         /* Here's the critical section.
107          * First, add the new message to the queue...
108          */
109         begin_critical_section(S_CHATQUEUE);
110         ++ChatLastMsg;
111         clnew->chat_seq = ChatLastMsg;
112         if (ChatQueue == NULL) {
113                 ChatQueue = clnew;
114                 }
115         else {
116                 for (clptr=ChatQueue; clptr->next != NULL; clptr=clptr->next) ;;
117                 clptr->next = clnew;
118                 }
119
120         /* Then, before releasing the lock, free the expired messages */
121         while(1) {
122                 if (ChatQueue == NULL) goto DONE_FREEING;
123                 if ( (now - ChatQueue->chat_time) < 120L ) goto DONE_FREEING;
124                 clptr = ChatQueue;
125                 ChatQueue = ChatQueue->next;
126                 free(clptr);
127                 }
128 DONE_FREEING:   end_critical_section(S_CHATQUEUE);
129         }
130
131
132 t_context *find_context(char **unstr)
133 {
134    t_context *t_cc, *found_cc = NULL;
135    char *name, *tptr;
136    
137    if ((!*unstr) || (!unstr))
138       return(NULL);
139       
140    begin_critical_section(S_SESSION_TABLE);
141    for (t_cc = ContextList; ((t_cc) && (!found_cc)); t_cc = t_cc->next)
142    {
143       if (t_cc->fake_username[0])
144          name = t_cc->fake_username;
145       else
146          name = t_cc->curr_user;
147       tptr = *unstr;
148       if ((!strncasecmp(name, tptr, strlen(name))) && (tptr[strlen(name)] == ' '))
149       {
150          found_cc = t_cc;
151          *unstr = &(tptr[strlen(name)+1]);
152       }
153    }
154    end_critical_section(S_SESSION_TABLE);
155
156    return(found_cc);
157 }
158
159 /*
160  * List users in chat.  Setting allflag to 1 also lists users elsewhere.
161  */
162
163 void do_chat_listing(int allflag)
164 {
165         struct CitContext *ccptr;
166
167         cprintf(":|\n:| Users currently in chat:\n");
168         begin_critical_section(S_SESSION_TABLE);
169         for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
170                 if ( (!strcasecmp(ccptr->cs_room, "<chat>"))
171                    && ((ccptr->cs_flags & CS_STEALTH) == 0)) {
172                         cprintf(":| %-25s <%s>\n", (ccptr->fake_username[0]) ? ccptr->fake_username : ccptr->curr_user, ccptr->chat_room);
173                         }
174                 }
175
176         if (allflag == 1) 
177         {
178                 cprintf(":|\n:| Users not in chat:\n");
179                 for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) 
180                 {
181                         if ( (strcasecmp(ccptr->cs_room, "<chat>"))
182                            && ((ccptr->cs_flags & CS_STEALTH) == 0)) 
183                         {
184                                 cprintf(":| %-25s <%s>:\n", (ccptr->fake_username[0]) ? ccptr->fake_username : ccptr->curr_user, (ccptr->fake_roomname[0]) ? ccptr->fake_roomname : ccptr->cs_room);
185                         }
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
210         strcpy(CC->chat_room, "Main room");
211
212         strcpy(hold_cs_room,CC->cs_room);
213         CC->cs_flags = CC->cs_flags | CS_CHAT;
214         set_wtmpsupp("<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
224         strcpy(cmdbuf, "");
225
226         while(1) {
227                 int ok_cmd;
228                 
229                 ok_cmd = 0;
230                 cmdbuf[strlen(cmdbuf) + 1] = 0;
231                 retval = client_read_to(&cmdbuf[strlen(cmdbuf)], 1, 2);
232
233                 /* if we have a complete line, do send processing */
234                 if (strlen(cmdbuf) > 0) if (cmdbuf[strlen(cmdbuf)-1] == 10) {
235                         cmdbuf[strlen(cmdbuf) - 1] = 0;
236                         time(&CC->lastcmd);
237                         time(&CC->lastidle);
238
239                         if ( (!strcasecmp(cmdbuf,"exit"))
240                         ||(!strcasecmp(cmdbuf,"/exit"))
241                         ||(!strcasecmp(cmdbuf,"quit"))
242                         ||(!strcasecmp(cmdbuf,"logout"))
243                         ||(!strcasecmp(cmdbuf,"logoff"))
244                         ||(!strcasecmp(cmdbuf,"/q"))
245                         ||(!strcasecmp(cmdbuf,".q"))
246                         ||(!strcasecmp(cmdbuf,"/quit"))
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         
260                         if ((!strcasecmp(cmdbuf,"/help"))
261                         ||(!strcasecmp(cmdbuf,"help"))
262                         ||(!strcasecmp(cmdbuf,"/?"))
263                         ||(!strcasecmp(cmdbuf,"?"))) {
264                                 cprintf(":|\n");
265                                 cprintf(":|Available commands: \n");
266                                 cprintf(":|/help   (prints this message) \n");
267                                 cprintf(":|/who    (list users currently in chat) \n");
268                                 cprintf(":|/whobbs (list users in chat -and- elsewhere) \n");
269                                 cprintf(":|/me     ('action' line, ala irc) \n");
270                                 cprintf(":|/msg    (send private message, ala irc) \n");
271                                 cprintf(":|/join   (join new room) \n"); 
272                                 cprintf(":|/quit   (return to the BBS) \n");
273                                 cprintf(":|\n");
274                                 ok_cmd = 1;
275                                 }
276                         if (!strcasecmp(cmdbuf,"/who")) {
277                                 do_chat_listing(0);
278                                 ok_cmd = 1;
279                                 }
280                         if (!strcasecmp(cmdbuf,"/whobbs")) {
281                                 do_chat_listing(1);
282                                 ok_cmd = 1;
283                                 }
284                         if (!strncasecmp(cmdbuf,"/me ",4)) {
285                                 allwrite(&cmdbuf[4],1, CC->chat_room, NULL);
286                                 ok_cmd = 1;
287                                 }
288                                 
289                         if (!strncasecmp(cmdbuf,"/msg ", 5))
290                         {
291                            ok_cmd =1;
292                            strptr1 = &cmdbuf[5];
293                            if ((t_context = find_context(&strptr1)))
294                            {
295                               allwrite(strptr1, 2, "", CC->curr_user);
296                               if (strcasecmp(CC->curr_user, t_context->curr_user))
297                                  allwrite(strptr1, 2, "", t_context->curr_user);
298                            }
299                            else
300                               cprintf(":|User not found.\n", cmdbuf);
301                         cprintf("\n");
302                         }
303
304                         if (!strncasecmp(cmdbuf,"/join ", 6))
305                         {
306                            ok_cmd = 1;
307                            allwrite("<changing rooms>",0, CC->chat_room, NULL);
308                            if (!cmdbuf[6])
309                               strcpy(CC->chat_room, "Main room");
310                            else
311                            {
312                               strncpy(CC->chat_room, &cmdbuf[6], ROOMNAMELEN);
313                            }
314                            allwrite("<joining room>",0, CC->chat_room, NULL);
315                            cprintf("\n");
316                         }
317                         if ((cmdbuf[0]!='/')&&(strlen(cmdbuf)>0)) {
318                                 ok_cmd = 1;
319                                 allwrite(cmdbuf,0, CC->chat_room, NULL);
320                                 }
321
322                         if ((!ok_cmd) && (cmdbuf[0]) && (cmdbuf[0] != '\n'))
323                            cprintf(":|Command %s is not understood.\n", cmdbuf);
324                            
325                         strcpy(cmdbuf, "");
326
327                         }
328         
329                 /* now check the queue for new incoming stuff */
330                 
331                 if (CC->fake_username[0])
332                    un = CC->fake_username;
333                 else
334                    un = CC->curr_user;
335                 if (ChatLastMsg > MyLastMsg) {
336                         ThisLastMsg = ChatLastMsg;
337                         for (clptr=ChatQueue; clptr!=NULL; clptr=clptr->next) 
338                         {
339                            if ((clptr->chat_seq > MyLastMsg) && ((!clptr->chat_username[0]) || (!strncasecmp(un, clptr->chat_username, 32))))
340                            {
341                               if ((!clptr->chat_room[0]) || (!strncasecmp(CC->chat_room, clptr->chat_room, ROOMNAMELEN)))
342                               {
343                                  cprintf("%s\n", clptr->chat_text);
344                               }
345                            }
346                         }
347                         MyLastMsg = ThisLastMsg;
348                         }
349
350                 }
351         }
352
353
354 /*
355  * poll for express messages
356  */
357 void cmd_pexp(char *argbuf) /* arg unused */ {
358         struct ExpressMessage *emptr;
359
360         if (CC->FirstExpressMessage == NULL) {
361                 cprintf("%d No express messages waiting.\n",ERROR);
362                 return;
363                 }
364
365         cprintf("%d Express msgs:\n",LISTING_FOLLOWS);
366
367         while (CC->FirstExpressMessage != NULL) {
368                 cprintf("%s", CC->FirstExpressMessage->em_text);
369                 begin_critical_section(S_SESSION_TABLE);
370                 emptr = CC->FirstExpressMessage;
371                 CC->FirstExpressMessage = CC->FirstExpressMessage->next;
372                 free(emptr);
373                 end_critical_section(S_SESSION_TABLE);
374                 }
375         cprintf("000\n");
376         }
377
378
379 /*
380  * send express messages  <bc>
381  */
382 void cmd_sexp(char *argbuf)
383 {
384         char x_user[256];
385         char x_msg[256];
386         int message_sent = 0;
387         struct CitContext *ccptr;
388         struct ExpressMessage *emptr, *emnew;
389         char *lun;              /* <bc> */
390
391         if (!(CC->logged_in)) {
392                 cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
393                 return;
394                 }
395
396         if (num_parms(argbuf)!=2) {
397                 cprintf("%d usage error\n",ERROR);
398                 return; 
399                 }
400
401         if (CC->fake_username[0])
402            lun = CC->fake_username;
403         else
404            lun = CC->usersupp.fullname;
405
406         extract(x_user,argbuf,0);
407
408         if (!strcmp(x_user, "."))
409         {
410            strcpy(x_user, CC->last_pager);
411         }
412         extract(x_msg,argbuf,1);
413         
414         if (!x_user[0])
415         {
416            cprintf("%d You were not previously paged.\n", ERROR);
417            return;
418         }
419
420         if ( (!strcasecmp(x_user, "broadcast")) && (CC->usersupp.axlevel < 6) ) {
421                 cprintf("%d Higher access required to send a broadcast.\n",
422                         ERROR+HIGHER_ACCESS_REQUIRED);
423                 return;
424                 }
425
426         /* find the target user's context and append the message */
427         begin_critical_section(S_SESSION_TABLE);
428         for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
429                 char *un;
430                 
431                 if (ccptr->fake_username[0])            /* <bc> */
432                    un = ccptr->fake_username;
433                 else
434                    un = ccptr->usersupp.fullname;
435                    
436                 if ( (!strcasecmp(un, x_user))
437                    || (!strcasecmp(x_user, "broadcast")) ) {
438                         strcpy(ccptr->last_pager, CC->curr_user);
439                         emnew = (struct ExpressMessage *)
440                                 malloc(sizeof(struct ExpressMessage));
441                         emnew->next = NULL;
442                         sprintf(emnew->em_text, "%s from %s:\n %s\n",
443                                 ( (!strcasecmp(x_user, "broadcast")) ? "Broadcast message" : "Message" ),
444                                 lun, x_msg);
445
446                         if (ccptr->FirstExpressMessage == NULL) {
447                                 ccptr->FirstExpressMessage = emnew;
448                                 }
449                         else {
450                                 emptr = ccptr->FirstExpressMessage;
451                                 while (emptr->next != NULL) {
452                                         emptr = emptr->next;
453                                         }
454                                 emptr->next = emnew;
455                                 }
456
457                         ++message_sent;
458                         }
459                 }
460         end_critical_section(S_SESSION_TABLE);
461
462         if (message_sent > 0) {
463                 cprintf("%d Message sent.\n",OK);
464                 }
465         else {
466                 cprintf("%d No user '%s' logged in.\n",ERROR,x_user);
467                 }
468         }