2 * represent messages to the citadel clients
4 * Copyright (c) 1987-2017 by the citadel.org team
6 * This program is open source software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 3.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
16 #include <libcitadel.h>
18 #include "citserver.h"
19 #include "ctdl_module.h"
20 #include "internet_addressing.h"
25 extern char *msgkeys[];
29 * Back end for the MSGS command: output message number only.
31 void simple_listing(long msgnum, void *userdata)
33 cprintf("%ld\n", msgnum);
38 * Back end for the MSGS command: output header summary.
40 void headers_listing(long msgnum, void *userdata)
42 struct CtdlMessage *msg;
44 msg = CtdlFetchMessage(msgnum, 0, 1);
46 cprintf("%ld|0|||||||\n", msgnum);
50 // output all fields except the references hash
51 cprintf("%ld|%s|%s|%s|%s|%s|%d|",
53 (!CM_IsEmpty(msg, eTimestamp) ? msg->cm_fields[eTimestamp] : "0"),
54 (!CM_IsEmpty(msg, eAuthor) ? msg->cm_fields[eAuthor] : ""),
55 (!CM_IsEmpty(msg, eNodeName) ? msg->cm_fields[eNodeName] : ""),
56 (!CM_IsEmpty(msg, erFc822Addr) ? msg->cm_fields[erFc822Addr] : ""),
57 (!CM_IsEmpty(msg, eMsgSubject) ? msg->cm_fields[eMsgSubject] : ""),
58 (!CM_IsEmpty(msg, emessageId) ? HashLittle(msg->cm_fields[emessageId],strlen(msg->cm_fields[emessageId])) : 0)
61 // output the references hash (yes it's ok that we're trashing the source buffer by doing this)
62 if (!CM_IsEmpty(msg, eWeferences)) {
64 char *rest = msg->cm_fields[eWeferences];
66 while((token = strtok_r(rest, "|", &rest))) {
67 cprintf("%d%s", HashLittle(token,rest-prev-(*rest==0?0:1)), (*rest==0?"":","));
76 typedef struct _msg_filter{
82 void headers_brief_filter(long msgnum, void *userdata)
85 struct CtdlMessage *msg;
86 msg_filter *flt = (msg_filter*) userdata;
88 l = GetCount(flt->Filter);
89 msg = CtdlFetchMessage(msgnum, 0, 1);
90 StrBufPrintf(flt->buffer, "%ld", msgnum);
92 for (i = 0; i < l; i++) {
93 StrBufAppendBufPlain(flt->buffer, HKEY("|"), 0);
100 RewindHashPos(flt->Filter, flt->p, 0);
101 while (GetNextHashPos(flt->Filter, flt->p, &len, &k, &v)) {
102 eMsgField f = (eMsgField) v;
104 StrBufAppendBufPlain(flt->buffer, HKEY("|"), 0);
105 if (!CM_IsEmpty(msg, f)) {
106 StrBufAppendBufPlain(flt->buffer, CM_KEY(msg, f), 0);
110 StrBufAppendBufPlain(flt->buffer, HKEY("\n"), 0);
111 cputbuf(flt->buffer);
115 * Back end for the MSGS command: output EUID header.
117 void headers_euid(long msgnum, void *userdata)
119 struct CtdlMessage *msg;
121 msg = CtdlFetchMessage(msgnum, 0, 1);
123 cprintf("%ld||\n", msgnum);
127 cprintf("%ld|%s|%s\n",
129 (!CM_IsEmpty(msg, eExclusiveID) ? msg->cm_fields[eExclusiveID] : ""),
130 (!CM_IsEmpty(msg, eTimestamp) ? msg->cm_fields[eTimestamp] : "0"));
137 * cmd_msgs() - get list of message #'s in this room
138 * implements the MSGS server command using CtdlForEachMessage()
140 void cmd_msgs(char *cmdbuf)
148 int with_template = 0;
149 struct CtdlMessage *template = NULL;
151 char search_string[1024];
152 ForEachMsgCallback CallBack;
154 if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
156 extract_token(which, cmdbuf, 0, '|', sizeof which);
157 cm_ref = extract_int(cmdbuf, 1);
158 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
159 with_template = extract_int(cmdbuf, 2);
160 switch (extract_int(cmdbuf, 3))
164 CallBack = simple_listing;
167 CallBack = headers_listing;
170 CallBack = headers_euid;
172 case MSG_HDRS_BRIEFFILTER:
174 CallBack = headers_brief_filter;
179 if (!strncasecmp(which, "OLD", 3))
181 else if (!strncasecmp(which, "NEW", 3))
183 else if (!strncasecmp(which, "FIRST", 5))
185 else if (!strncasecmp(which, "LAST", 4))
187 else if (!strncasecmp(which, "GT", 2))
189 else if (!strncasecmp(which, "LT", 2))
191 else if (!strncasecmp(which, "SEARCH", 6))
196 if ( (mode == MSGS_SEARCH) && (!CtdlGetConfigInt("c_enable_fulltext")) ) {
197 cprintf("%d Full text index is not enabled on this server.\n",
198 ERROR + CMD_NOT_SUPPORTED);
202 if (with_template == 1) {
205 cprintf("%d Send template then receive message list\n",
207 template = (struct CtdlMessage *)
208 malloc(sizeof(struct CtdlMessage));
209 memset(template, 0, sizeof(struct CtdlMessage));
210 template->cm_magic = CTDLMESSAGE_MAGIC;
211 template->cm_anon_type = MES_NORMAL;
213 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
217 tValueLen = extract_token(tfield, buf, 0, '|', sizeof tfield);
218 if ((tValueLen == 4) && GetFieldFromMnemonic(&f, tfield))
220 tValueLen = extract_token(tvalue, buf, 1, '|', sizeof tvalue);
221 if (tValueLen >= 0) {
222 CM_SetField(template, f, tvalue, tValueLen);
228 else if (with_template == 2) {
231 cprintf("%d Send list of headers\n",
233 filt.Filter = NewHash(1, lFlathash);
234 filt.buffer = NewStrBufPlain(NULL, 1024);
235 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
238 if (GetFieldFromMnemonic(&f, buf))
240 Put(filt.Filter, LKEY(i), (void*)f, reference_free_handler);
244 filt.p = GetNewHashPos(filt.Filter, 0);
248 cprintf("%d \n", LISTING_FOLLOWS);
251 if (with_template < 2) {
252 CtdlForEachMessage(mode,
253 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
254 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
259 if (template != NULL) CM_Free(template);
262 CtdlForEachMessage(mode,
263 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
264 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
269 DeleteHashPos(&filt.p);
270 DeleteHash(&filt.Filter);
271 FreeStrBuf(&filt.buffer);
278 * display a message (mode 0 - Citadel proprietary)
280 void cmd_msg0(char *cmdbuf)
283 int headers_only = HEADERS_ALL;
285 msgid = extract_long(cmdbuf, 0);
286 headers_only = extract_int(cmdbuf, 1);
288 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0, NULL, NULL, NULL);
294 * display a message (mode 2 - RFC822)
296 void cmd_msg2(char *cmdbuf)
299 int headers_only = HEADERS_ALL;
301 msgid = extract_long(cmdbuf, 0);
302 headers_only = extract_int(cmdbuf, 1);
304 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0, NULL, NULL, NULL);
310 * display a message (mode 3 - IGnet raw format - internal programs only)
312 void cmd_msg3(char *cmdbuf)
315 struct CtdlMessage *msg = NULL;
318 if (CC->internal_pgm == 0) {
319 cprintf("%d This command is for internal programs only.\n",
320 ERROR + HIGHER_ACCESS_REQUIRED);
324 msgnum = extract_long(cmdbuf, 0);
325 msg = CtdlFetchMessage(msgnum, 1, 1);
327 cprintf("%d Message %ld not found.\n",
328 ERROR + MESSAGE_NOT_FOUND, msgnum);
332 CtdlSerializeMessage(&smr, msg);
336 cprintf("%d Unable to serialize message\n",
337 ERROR + INTERNAL_ERROR);
341 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
342 client_write((char *)smr.ser, (int)smr.len);
349 * Display a message using MIME content types
351 void cmd_msg4(char *cmdbuf)
356 msgid = extract_long(cmdbuf, 0);
357 extract_token(section, cmdbuf, 1, '|', sizeof section);
358 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0, NULL, NULL, NULL);
364 * Client tells us its preferred message format(s)
366 void cmd_msgp(char *cmdbuf)
368 if (!strcasecmp(cmdbuf, "dont_decode")) {
369 CC->msg4_dont_decode = 1;
370 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
373 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
374 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
380 * Open a component of a MIME message as a download file
382 void cmd_opna(char *cmdbuf)
385 char desired_section[128];
387 msgid = extract_long(cmdbuf, 0);
388 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
389 safestrncpy(CC->download_desired_section, desired_section,
390 sizeof CC->download_desired_section);
391 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0, NULL, NULL, NULL);
396 * Open a component of a MIME message and transmit it all at once
398 void cmd_dlat(char *cmdbuf)
401 char desired_section[128];
403 msgid = extract_long(cmdbuf, 0);
404 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
405 safestrncpy(CC->download_desired_section, desired_section,
406 sizeof CC->download_desired_section);
407 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0, NULL, NULL, NULL);
411 * message entry - mode 0 (normal)
413 void cmd_ent0(char *entargs)
415 struct CitContext *CCC = CC;
420 char supplied_euid[128];
423 char newusername[256];
424 char newuseremail[256];
425 struct CtdlMessage *msg;
429 recptypes *valid = NULL;
430 recptypes *valid_to = NULL;
431 recptypes *valid_cc = NULL;
432 recptypes *valid_bcc = NULL;
434 int subject_required = 0;
439 int newuseremail_ok = 0;
440 char references[SIZ];
445 post = extract_int(entargs, 0);
446 extract_token(recp, entargs, 1, '|', sizeof recp);
447 anon_flag = extract_int(entargs, 2);
448 format_type = extract_int(entargs, 3);
449 extract_token(subject, entargs, 4, '|', sizeof subject);
450 extract_token(newusername, entargs, 5, '|', sizeof newusername);
451 do_confirm = extract_int(entargs, 6);
452 extract_token(cc, entargs, 7, '|', sizeof cc);
453 extract_token(bcc, entargs, 8, '|', sizeof bcc);
454 switch(CC->room.QRdefaultview) {
458 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
461 supplied_euid[0] = 0;
464 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
465 extract_token(references, entargs, 11, '|', sizeof references);
466 for (ptr=references; *ptr != 0; ++ptr) {
467 if (*ptr == '!') *ptr = '|';
470 /* first check to make sure the request is valid. */
472 err = CtdlDoIHavePermissionToPostInThisRoom(
477 (!IsEmptyStr(references)) /* is this a reply? or a top-level post? */
481 cprintf("%d %s\n", err, errmsg);
485 /* Check some other permission type things. */
487 if (IsEmptyStr(newusername)) {
488 strcpy(newusername, CCC->user.fullname);
490 if ( (CCC->user.axlevel < AxAideU)
491 && (strcasecmp(newusername, CCC->user.fullname))
492 && (strcasecmp(newusername, CCC->cs_inet_fn))
494 cprintf("%d You don't have permission to author messages as '%s'.\n",
495 ERROR + HIGHER_ACCESS_REQUIRED,
502 if (IsEmptyStr(newuseremail)) {
506 if (!IsEmptyStr(newuseremail)) {
507 if (!strcasecmp(newuseremail, CCC->cs_inet_email)) {
510 else if (!IsEmptyStr(CCC->cs_inet_other_emails)) {
511 j = num_tokens(CCC->cs_inet_other_emails, '|');
512 for (i=0; i<j; ++i) {
513 extract_token(buf, CCC->cs_inet_other_emails, i, '|', sizeof buf);
514 if (!strcasecmp(newuseremail, buf)) {
521 if (!newuseremail_ok) {
522 cprintf("%d You don't have permission to author messages as '%s'.\n",
523 ERROR + HIGHER_ACCESS_REQUIRED,
529 CCC->cs_flags |= CS_POSTING;
531 /* In mailbox rooms we have to behave a little differently --
532 * make sure the user has specified at least one recipient. Then
533 * validate the recipient(s). We do this for the Mail> room, as
534 * well as any room which has the "Mailbox" view set - unless it
535 * is the DRAFTS room which does not require recipients
538 if ( ( ( (CCC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CCC->room.QRname[11], MAILROOM)) )
539 || ( (CCC->room.QRflags & QR_MAILBOX) && (CCC->curr_view == VIEW_MAILBOX) )
540 ) && (strcasecmp(&CCC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
541 if (CCC->user.axlevel < AxProbU) {
542 strcpy(recp, "sysop");
547 valid_to = validate_recipients(recp, NULL, 0);
548 if (valid_to->num_error > 0) {
549 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
550 free_recipients(valid_to);
554 valid_cc = validate_recipients(cc, NULL, 0);
555 if (valid_cc->num_error > 0) {
556 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
557 free_recipients(valid_to);
558 free_recipients(valid_cc);
562 valid_bcc = validate_recipients(bcc, NULL, 0);
563 if (valid_bcc->num_error > 0) {
564 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
565 free_recipients(valid_to);
566 free_recipients(valid_cc);
567 free_recipients(valid_bcc);
571 /* Recipient required, but none were specified */
572 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
573 free_recipients(valid_to);
574 free_recipients(valid_cc);
575 free_recipients(valid_bcc);
576 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
580 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
581 if (CtdlCheckInternetMailPermission(&CCC->user)==0) {
582 cprintf("%d You do not have permission "
583 "to send Internet mail.\n",
584 ERROR + HIGHER_ACCESS_REQUIRED);
585 free_recipients(valid_to);
586 free_recipients(valid_cc);
587 free_recipients(valid_bcc);
592 if ( ( (valid_to->num_internet + valid_to->num_ignet + valid_cc->num_internet + valid_cc->num_ignet + valid_bcc->num_internet + valid_bcc->num_ignet) > 0)
593 && (CCC->user.axlevel < AxNetU) ) {
594 cprintf("%d Higher access required for network mail.\n",
595 ERROR + HIGHER_ACCESS_REQUIRED);
596 free_recipients(valid_to);
597 free_recipients(valid_cc);
598 free_recipients(valid_bcc);
602 if ((RESTRICT_INTERNET == 1)
603 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
604 && ((CCC->user.flags & US_INTERNET) == 0)
605 && (!CCC->internal_pgm)) {
606 cprintf("%d You don't have access to Internet mail.\n",
607 ERROR + HIGHER_ACCESS_REQUIRED);
608 free_recipients(valid_to);
609 free_recipients(valid_cc);
610 free_recipients(valid_bcc);
616 /* Is this a room which has anonymous-only or anonymous-option? */
617 anonymous = MES_NORMAL;
618 if (CCC->room.QRflags & QR_ANONONLY) {
619 anonymous = MES_ANONONLY;
621 if (CCC->room.QRflags & QR_ANONOPT) {
622 if (anon_flag == 1) { /* only if the user requested it */
623 anonymous = MES_ANONOPT;
627 if ((CCC->room.QRflags & QR_MAILBOX) == 0) {
631 /* Recommend to the client that the use of a message subject is
632 * strongly recommended in this room, if either the SUBJECTREQ flag
633 * is set, or if there is one or more Internet email recipients.
635 if (CCC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
636 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
637 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
638 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
640 /* If we're only checking the validity of the request, return
641 * success without creating the message.
644 cprintf("%d %s|%d\n", CIT_OK,
645 ((valid_to != NULL) ? valid_to->display_recp : ""),
647 free_recipients(valid_to);
648 free_recipients(valid_cc);
649 free_recipients(valid_bcc);
653 /* We don't need these anymore because we'll do it differently below */
654 free_recipients(valid_to);
655 free_recipients(valid_cc);
656 free_recipients(valid_bcc);
658 /* Read in the message from the client. */
660 cprintf("%d send message\n", START_CHAT_MODE);
662 cprintf("%d send message\n", SEND_LISTING);
665 msg = CtdlMakeMessage(&CCC->user, recp, cc,
666 CCC->room.QRname, anonymous, format_type,
667 newusername, newuseremail, subject,
668 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
671 /* Put together one big recipients struct containing to/cc/bcc all in
672 * one. This is for the envelope.
674 char *all_recps = malloc(SIZ * 3);
675 strcpy(all_recps, recp);
676 if (!IsEmptyStr(cc)) {
677 if (!IsEmptyStr(all_recps)) {
678 strcat(all_recps, ",");
680 strcat(all_recps, cc);
682 if (!IsEmptyStr(bcc)) {
683 if (!IsEmptyStr(all_recps)) {
684 strcat(all_recps, ",");
686 strcat(all_recps, bcc);
688 if (!IsEmptyStr(all_recps)) {
689 valid = validate_recipients(all_recps, NULL, 0);
696 if ((valid != NULL) && (valid->num_room == 1) && !IsEmptyStr(valid->recp_orgroom))
698 /* posting into an ML room? set the envelope from
699 * to the actual mail address so others get a valid
702 CM_SetField(msg, eenVelopeTo, valid->recp_orgroom, strlen(valid->recp_orgroom));
706 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
708 cprintf("%ld\n", msgnum);
710 if (StrLength(CCC->StatusMessage) > 0) {
711 cprintf("%s\n", ChrPtr(CCC->StatusMessage));
713 else if (msgnum >= 0L) {
714 client_write(HKEY("Message accepted.\n"));
717 client_write(HKEY("Internal error.\n"));
720 if (!CM_IsEmpty(msg, eExclusiveID)) {
721 cprintf("%s\n", msg->cm_fields[eExclusiveID]);
731 free_recipients(valid);
737 * Delete message from current room
739 void cmd_dele(char *args)
748 extract_token(msgset, args, 0, '|', sizeof msgset);
749 num_msgs = num_tokens(msgset, ',');
751 cprintf("%d Nothing to do.\n", CIT_OK);
755 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
756 cprintf("%d Higher access required.\n",
757 ERROR + HIGHER_ACCESS_REQUIRED);
762 * Build our message set to be moved/copied
764 msgs = malloc(num_msgs * sizeof(long));
765 for (i=0; i<num_msgs; ++i) {
766 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
767 msgs[i] = atol(msgtok);
770 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
774 cprintf("%d %d message%s deleted.\n", CIT_OK,
775 num_deleted, ((num_deleted != 1) ? "s" : ""));
777 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
784 * move or copy a message to another room
786 void cmd_move(char *args)
793 char targ[ROOMNAMELEN];
794 struct ctdlroom qtemp;
801 extract_token(msgset, args, 0, '|', sizeof msgset);
802 num_msgs = num_tokens(msgset, ',');
804 cprintf("%d Nothing to do.\n", CIT_OK);
808 extract_token(targ, args, 1, '|', sizeof targ);
809 convert_room_name_macros(targ, sizeof targ);
810 targ[ROOMNAMELEN - 1] = 0;
811 is_copy = extract_int(args, 2);
813 if (CtdlGetRoom(&qtemp, targ) != 0) {
814 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
818 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
819 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
823 CtdlGetUser(&CC->user, CC->curr_user);
824 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
826 /* Check for permission to perform this operation.
827 * Remember: "CC->room" is source, "qtemp" is target.
831 /* Admins can move/copy */
832 if (CC->user.axlevel >= AxAideU) permit = 1;
834 /* Room aides can move/copy */
835 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
837 /* Permit move/copy from personal rooms */
838 if ((CC->room.QRflags & QR_MAILBOX)
839 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
841 /* Permit only copy from public to personal room */
843 && (!(CC->room.QRflags & QR_MAILBOX))
844 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
846 /* Permit message removal from collaborative delete rooms */
847 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
849 /* Users allowed to post into the target room may move into it too. */
850 if ((CC->room.QRflags & QR_MAILBOX) &&
851 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
853 /* User must have access to target room */
854 if (!(ra & UA_KNOWN)) permit = 0;
857 cprintf("%d Higher access required.\n",
858 ERROR + HIGHER_ACCESS_REQUIRED);
863 * Build our message set to be moved/copied
865 msgs = malloc(num_msgs * sizeof(long));
866 for (i=0; i<num_msgs; ++i) {
867 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
868 msgs[i] = atol(msgtok);
874 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
876 cprintf("%d Cannot store message(s) in %s: error %d\n",
882 /* Now delete the message from the source room,
883 * if this is a 'move' rather than a 'copy' operation.
886 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
890 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
894 /*****************************************************************************/
895 /* MODULE INITIALIZATION STUFF */
896 /*****************************************************************************/
897 CTDL_MODULE_INIT(ctdl_message)
901 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
902 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
903 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
904 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
905 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
906 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
907 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
908 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
909 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
910 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
911 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
914 /* return our Subversion id for the Log */
915 return "ctdl_message";