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