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