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;
43 int output_mode = *(int *)userdata;
45 msg = CtdlFetchMessage(msgnum, 0, 1);
47 cprintf("%ld|0|||||||\n", msgnum);
51 // output all fields except the references hash
52 cprintf("%ld|%s|%s|%s|%s|%s",
54 (!CM_IsEmpty(msg, eTimestamp) ? msg->cm_fields[eTimestamp] : "0"),
55 (!CM_IsEmpty(msg, eAuthor) ? msg->cm_fields[eAuthor] : ""),
56 (!CM_IsEmpty(msg, eNodeName) ? msg->cm_fields[eNodeName] : ""),
57 (!CM_IsEmpty(msg, erFc822Addr) ? msg->cm_fields[erFc822Addr] : ""),
58 (!CM_IsEmpty(msg, eMsgSubject) ? msg->cm_fields[eMsgSubject] : "")
61 if (output_mode == MSG_HDRS_THREADS) {
63 // output the references hash
65 (!CM_IsEmpty(msg, emessageId) ? HashLittle(msg->cm_fields[emessageId],strlen(msg->cm_fields[emessageId])) : 0)
68 // output the references hash (yes it's ok that we're trashing the source buffer by doing this)
69 if (!CM_IsEmpty(msg, eWeferences)) {
71 char *rest = msg->cm_fields[eWeferences];
73 while((token = strtok_r(rest, "|", &rest))) {
74 cprintf("%d%s", HashLittle(token,rest-prev-(*rest==0?0:1)), (*rest==0?"":","));
86 typedef struct _msg_filter{
92 void headers_brief_filter(long msgnum, void *userdata)
95 struct CtdlMessage *msg;
96 msg_filter *flt = (msg_filter*) userdata;
98 l = GetCount(flt->Filter);
99 msg = CtdlFetchMessage(msgnum, 0, 1);
100 StrBufPrintf(flt->buffer, "%ld", msgnum);
102 for (i = 0; i < l; i++) {
103 StrBufAppendBufPlain(flt->buffer, HKEY("|"), 0);
110 RewindHashPos(flt->Filter, flt->p, 0);
111 while (GetNextHashPos(flt->Filter, flt->p, &len, &k, &v)) {
112 eMsgField f = (eMsgField) v;
114 StrBufAppendBufPlain(flt->buffer, HKEY("|"), 0);
115 if (!CM_IsEmpty(msg, f)) {
116 StrBufAppendBufPlain(flt->buffer, CM_KEY(msg, f), 0);
120 StrBufAppendBufPlain(flt->buffer, HKEY("\n"), 0);
121 cputbuf(flt->buffer);
125 * Back end for the MSGS command: output EUID header.
127 void headers_euid(long msgnum, void *userdata)
129 struct CtdlMessage *msg;
131 msg = CtdlFetchMessage(msgnum, 0, 1);
133 cprintf("%ld||\n", msgnum);
137 cprintf("%ld|%s|%s\n",
139 (!CM_IsEmpty(msg, eExclusiveID) ? msg->cm_fields[eExclusiveID] : ""),
140 (!CM_IsEmpty(msg, eTimestamp) ? msg->cm_fields[eTimestamp] : "0"));
147 * cmd_msgs() - get list of message #'s in this room
148 * implements the MSGS server command using CtdlForEachMessage()
150 void cmd_msgs(char *cmdbuf)
158 int with_template = 0;
159 struct CtdlMessage *template = NULL;
161 char search_string[1024];
162 ForEachMsgCallback CallBack;
164 if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
166 extract_token(which, cmdbuf, 0, '|', sizeof which);
167 cm_ref = extract_int(cmdbuf, 1);
168 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
169 with_template = extract_int(cmdbuf, 2);
170 int output_mode = extract_int(cmdbuf, 3);
171 switch (output_mode) {
174 CallBack = simple_listing;
177 case MSG_HDRS_THREADS:
178 CallBack = headers_listing;
181 CallBack = headers_euid;
183 case MSG_HDRS_BRIEFFILTER:
185 CallBack = headers_brief_filter;
190 if (!strncasecmp(which, "OLD", 3))
192 else if (!strncasecmp(which, "NEW", 3))
194 else if (!strncasecmp(which, "FIRST", 5))
196 else if (!strncasecmp(which, "LAST", 4))
198 else if (!strncasecmp(which, "GT", 2))
200 else if (!strncasecmp(which, "LT", 2))
202 else if (!strncasecmp(which, "SEARCH", 6))
207 if ( (mode == MSGS_SEARCH) && (!CtdlGetConfigInt("c_enable_fulltext")) ) {
208 cprintf("%d Full text index is not enabled on this server.\n",
209 ERROR + CMD_NOT_SUPPORTED);
213 if (with_template == 1) {
216 cprintf("%d Send template then receive message list\n",
218 template = (struct CtdlMessage *)
219 malloc(sizeof(struct CtdlMessage));
220 memset(template, 0, sizeof(struct CtdlMessage));
221 template->cm_magic = CTDLMESSAGE_MAGIC;
222 template->cm_anon_type = MES_NORMAL;
224 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
228 tValueLen = extract_token(tfield, buf, 0, '|', sizeof tfield);
229 if ((tValueLen == 4) && GetFieldFromMnemonic(&f, tfield))
231 tValueLen = extract_token(tvalue, buf, 1, '|', sizeof tvalue);
232 if (tValueLen >= 0) {
233 CM_SetField(template, f, tvalue, tValueLen);
239 else if (with_template == 2) {
242 cprintf("%d Send list of headers\n",
244 filt.Filter = NewHash(1, lFlathash);
245 filt.buffer = NewStrBufPlain(NULL, 1024);
246 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
249 if (GetFieldFromMnemonic(&f, buf))
251 Put(filt.Filter, LKEY(i), (void*)f, reference_free_handler);
255 filt.p = GetNewHashPos(filt.Filter, 0);
259 cprintf("%d \n", LISTING_FOLLOWS);
262 if (with_template < 2) {
263 CtdlForEachMessage(mode,
264 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
265 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
270 if (template != NULL) CM_Free(template);
273 CtdlForEachMessage(mode,
274 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
275 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
280 DeleteHashPos(&filt.p);
281 DeleteHash(&filt.Filter);
282 FreeStrBuf(&filt.buffer);
289 * display a message (mode 0 - Citadel proprietary)
291 void cmd_msg0(char *cmdbuf)
294 int headers_only = HEADERS_ALL;
296 msgid = extract_long(cmdbuf, 0);
297 headers_only = extract_int(cmdbuf, 1);
299 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0, NULL, NULL, NULL);
305 * display a message (mode 2 - RFC822)
307 void cmd_msg2(char *cmdbuf)
310 int headers_only = HEADERS_ALL;
312 msgid = extract_long(cmdbuf, 0);
313 headers_only = extract_int(cmdbuf, 1);
315 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0, NULL, NULL, NULL);
321 * display a message (mode 3 - IGnet raw format - internal programs only)
323 void cmd_msg3(char *cmdbuf)
326 struct CtdlMessage *msg = NULL;
329 if (CC->internal_pgm == 0) {
330 cprintf("%d This command is for internal programs only.\n",
331 ERROR + HIGHER_ACCESS_REQUIRED);
335 msgnum = extract_long(cmdbuf, 0);
336 msg = CtdlFetchMessage(msgnum, 1, 1);
338 cprintf("%d Message %ld not found.\n",
339 ERROR + MESSAGE_NOT_FOUND, msgnum);
343 CtdlSerializeMessage(&smr, msg);
347 cprintf("%d Unable to serialize message\n",
348 ERROR + INTERNAL_ERROR);
352 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
353 client_write((char *)smr.ser, (int)smr.len);
360 * Display a message using MIME content types
362 void cmd_msg4(char *cmdbuf)
367 msgid = extract_long(cmdbuf, 0);
368 extract_token(section, cmdbuf, 1, '|', sizeof section);
369 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0, NULL, NULL, NULL);
375 * Client tells us its preferred message format(s)
377 void cmd_msgp(char *cmdbuf)
379 if (!strcasecmp(cmdbuf, "dont_decode")) {
380 CC->msg4_dont_decode = 1;
381 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
384 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
385 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
391 * Open a component of a MIME message as a download file
393 void cmd_opna(char *cmdbuf)
396 char desired_section[128];
398 msgid = extract_long(cmdbuf, 0);
399 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
400 safestrncpy(CC->download_desired_section, desired_section,
401 sizeof CC->download_desired_section);
402 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0, NULL, NULL, NULL);
407 * Open a component of a MIME message and transmit it all at once
409 void cmd_dlat(char *cmdbuf)
412 char desired_section[128];
414 msgid = extract_long(cmdbuf, 0);
415 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
416 safestrncpy(CC->download_desired_section, desired_section,
417 sizeof CC->download_desired_section);
418 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0, NULL, NULL, NULL);
422 * message entry - mode 0 (normal)
424 void cmd_ent0(char *entargs)
426 struct CitContext *CCC = CC;
431 char supplied_euid[128];
434 char newusername[256];
435 char newuseremail[256];
436 struct CtdlMessage *msg;
440 recptypes *valid = NULL;
441 recptypes *valid_to = NULL;
442 recptypes *valid_cc = NULL;
443 recptypes *valid_bcc = NULL;
445 int subject_required = 0;
450 int newuseremail_ok = 0;
451 char references[SIZ];
456 post = extract_int(entargs, 0);
457 extract_token(recp, entargs, 1, '|', sizeof recp);
458 anon_flag = extract_int(entargs, 2);
459 format_type = extract_int(entargs, 3);
460 extract_token(subject, entargs, 4, '|', sizeof subject);
461 extract_token(newusername, entargs, 5, '|', sizeof newusername);
462 do_confirm = extract_int(entargs, 6);
463 extract_token(cc, entargs, 7, '|', sizeof cc);
464 extract_token(bcc, entargs, 8, '|', sizeof bcc);
465 switch(CC->room.QRdefaultview) {
469 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
472 supplied_euid[0] = 0;
475 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
476 extract_token(references, entargs, 11, '|', sizeof references);
477 for (ptr=references; *ptr != 0; ++ptr) {
478 if (*ptr == '!') *ptr = '|';
481 /* first check to make sure the request is valid. */
483 err = CtdlDoIHavePermissionToPostInThisRoom(
488 (!IsEmptyStr(references)) /* is this a reply? or a top-level post? */
492 cprintf("%d %s\n", err, errmsg);
496 /* Check some other permission type things. */
498 if (IsEmptyStr(newusername)) {
499 strcpy(newusername, CCC->user.fullname);
501 if ( (CCC->user.axlevel < AxAideU)
502 && (strcasecmp(newusername, CCC->user.fullname))
503 && (strcasecmp(newusername, CCC->cs_inet_fn))
505 cprintf("%d You don't have permission to author messages as '%s'.\n",
506 ERROR + HIGHER_ACCESS_REQUIRED,
513 if (IsEmptyStr(newuseremail)) {
517 if (!IsEmptyStr(newuseremail)) {
518 if (!strcasecmp(newuseremail, CCC->cs_inet_email)) {
521 else if (!IsEmptyStr(CCC->cs_inet_other_emails)) {
522 j = num_tokens(CCC->cs_inet_other_emails, '|');
523 for (i=0; i<j; ++i) {
524 extract_token(buf, CCC->cs_inet_other_emails, i, '|', sizeof buf);
525 if (!strcasecmp(newuseremail, buf)) {
532 if (!newuseremail_ok) {
533 cprintf("%d You don't have permission to author messages as '%s'.\n",
534 ERROR + HIGHER_ACCESS_REQUIRED,
540 CCC->cs_flags |= CS_POSTING;
542 /* In mailbox rooms we have to behave a little differently --
543 * make sure the user has specified at least one recipient. Then
544 * validate the recipient(s). We do this for the Mail> room, as
545 * well as any room which has the "Mailbox" view set - unless it
546 * is the DRAFTS room which does not require recipients
549 if ( ( ( (CCC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CCC->room.QRname[11], MAILROOM)) )
550 || ( (CCC->room.QRflags & QR_MAILBOX) && (CCC->curr_view == VIEW_MAILBOX) )
551 ) && (strcasecmp(&CCC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
552 if (CCC->user.axlevel < AxProbU) {
553 strcpy(recp, "sysop");
558 valid_to = validate_recipients(recp, NULL, 0);
559 if (valid_to->num_error > 0) {
560 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
561 free_recipients(valid_to);
565 valid_cc = validate_recipients(cc, NULL, 0);
566 if (valid_cc->num_error > 0) {
567 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
568 free_recipients(valid_to);
569 free_recipients(valid_cc);
573 valid_bcc = validate_recipients(bcc, NULL, 0);
574 if (valid_bcc->num_error > 0) {
575 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
576 free_recipients(valid_to);
577 free_recipients(valid_cc);
578 free_recipients(valid_bcc);
582 /* Recipient required, but none were specified */
583 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
584 free_recipients(valid_to);
585 free_recipients(valid_cc);
586 free_recipients(valid_bcc);
587 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
591 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
592 if (CtdlCheckInternetMailPermission(&CCC->user)==0) {
593 cprintf("%d You do not have permission "
594 "to send Internet mail.\n",
595 ERROR + HIGHER_ACCESS_REQUIRED);
596 free_recipients(valid_to);
597 free_recipients(valid_cc);
598 free_recipients(valid_bcc);
603 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)
604 && (CCC->user.axlevel < AxNetU) ) {
605 cprintf("%d Higher access required for network mail.\n",
606 ERROR + HIGHER_ACCESS_REQUIRED);
607 free_recipients(valid_to);
608 free_recipients(valid_cc);
609 free_recipients(valid_bcc);
613 if ((RESTRICT_INTERNET == 1)
614 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
615 && ((CCC->user.flags & US_INTERNET) == 0)
616 && (!CCC->internal_pgm)) {
617 cprintf("%d You don't have access to Internet mail.\n",
618 ERROR + HIGHER_ACCESS_REQUIRED);
619 free_recipients(valid_to);
620 free_recipients(valid_cc);
621 free_recipients(valid_bcc);
627 /* Is this a room which has anonymous-only or anonymous-option? */
628 anonymous = MES_NORMAL;
629 if (CCC->room.QRflags & QR_ANONONLY) {
630 anonymous = MES_ANONONLY;
632 if (CCC->room.QRflags & QR_ANONOPT) {
633 if (anon_flag == 1) { /* only if the user requested it */
634 anonymous = MES_ANONOPT;
638 if ((CCC->room.QRflags & QR_MAILBOX) == 0) {
642 /* Recommend to the client that the use of a message subject is
643 * strongly recommended in this room, if either the SUBJECTREQ flag
644 * is set, or if there is one or more Internet email recipients.
646 if (CCC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
647 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
648 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
649 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
651 /* If we're only checking the validity of the request, return
652 * success without creating the message.
655 cprintf("%d %s|%d\n", CIT_OK,
656 ((valid_to != NULL) ? valid_to->display_recp : ""),
658 free_recipients(valid_to);
659 free_recipients(valid_cc);
660 free_recipients(valid_bcc);
664 /* We don't need these anymore because we'll do it differently below */
665 free_recipients(valid_to);
666 free_recipients(valid_cc);
667 free_recipients(valid_bcc);
669 /* Read in the message from the client. */
671 cprintf("%d send message\n", START_CHAT_MODE);
673 cprintf("%d send message\n", SEND_LISTING);
676 msg = CtdlMakeMessage(&CCC->user, recp, cc,
677 CCC->room.QRname, anonymous, format_type,
678 newusername, newuseremail, subject,
679 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
682 /* Put together one big recipients struct containing to/cc/bcc all in
683 * one. This is for the envelope.
685 char *all_recps = malloc(SIZ * 3);
686 strcpy(all_recps, recp);
687 if (!IsEmptyStr(cc)) {
688 if (!IsEmptyStr(all_recps)) {
689 strcat(all_recps, ",");
691 strcat(all_recps, cc);
693 if (!IsEmptyStr(bcc)) {
694 if (!IsEmptyStr(all_recps)) {
695 strcat(all_recps, ",");
697 strcat(all_recps, bcc);
699 if (!IsEmptyStr(all_recps)) {
700 valid = validate_recipients(all_recps, NULL, 0);
707 if ((valid != NULL) && (valid->num_room == 1) && !IsEmptyStr(valid->recp_orgroom))
709 /* posting into an ML room? set the envelope from
710 * to the actual mail address so others get a valid
713 CM_SetField(msg, eenVelopeTo, valid->recp_orgroom, strlen(valid->recp_orgroom));
717 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
719 cprintf("%ld\n", msgnum);
721 if (StrLength(CCC->StatusMessage) > 0) {
722 cprintf("%s\n", ChrPtr(CCC->StatusMessage));
724 else if (msgnum >= 0L) {
725 client_write(HKEY("Message accepted.\n"));
728 client_write(HKEY("Internal error.\n"));
731 if (!CM_IsEmpty(msg, eExclusiveID)) {
732 cprintf("%s\n", msg->cm_fields[eExclusiveID]);
742 free_recipients(valid);
748 * Delete message from current room
750 void cmd_dele(char *args)
759 extract_token(msgset, args, 0, '|', sizeof msgset);
760 num_msgs = num_tokens(msgset, ',');
762 cprintf("%d Nothing to do.\n", CIT_OK);
766 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
767 cprintf("%d Higher access required.\n",
768 ERROR + HIGHER_ACCESS_REQUIRED);
773 * Build our message set to be moved/copied
775 msgs = malloc(num_msgs * sizeof(long));
776 for (i=0; i<num_msgs; ++i) {
777 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
778 msgs[i] = atol(msgtok);
781 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
785 cprintf("%d %d message%s deleted.\n", CIT_OK,
786 num_deleted, ((num_deleted != 1) ? "s" : ""));
788 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
795 * move or copy a message to another room
797 void cmd_move(char *args)
804 char targ[ROOMNAMELEN];
805 struct ctdlroom qtemp;
812 extract_token(msgset, args, 0, '|', sizeof msgset);
813 num_msgs = num_tokens(msgset, ',');
815 cprintf("%d Nothing to do.\n", CIT_OK);
819 extract_token(targ, args, 1, '|', sizeof targ);
820 convert_room_name_macros(targ, sizeof targ);
821 targ[ROOMNAMELEN - 1] = 0;
822 is_copy = extract_int(args, 2);
824 if (CtdlGetRoom(&qtemp, targ) != 0) {
825 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
829 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
830 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
834 CtdlGetUser(&CC->user, CC->curr_user);
835 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
837 /* Check for permission to perform this operation.
838 * Remember: "CC->room" is source, "qtemp" is target.
842 /* Admins can move/copy */
843 if (CC->user.axlevel >= AxAideU) permit = 1;
845 /* Room aides can move/copy */
846 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
848 /* Permit move/copy from personal rooms */
849 if ((CC->room.QRflags & QR_MAILBOX)
850 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
852 /* Permit only copy from public to personal room */
854 && (!(CC->room.QRflags & QR_MAILBOX))
855 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
857 /* Permit message removal from collaborative delete rooms */
858 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
860 /* Users allowed to post into the target room may move into it too. */
861 if ((CC->room.QRflags & QR_MAILBOX) &&
862 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
864 /* User must have access to target room */
865 if (!(ra & UA_KNOWN)) permit = 0;
868 cprintf("%d Higher access required.\n",
869 ERROR + HIGHER_ACCESS_REQUIRED);
874 * Build our message set to be moved/copied
876 msgs = malloc(num_msgs * sizeof(long));
877 for (i=0; i<num_msgs; ++i) {
878 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
879 msgs[i] = atol(msgtok);
885 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
887 cprintf("%d Cannot store message(s) in %s: error %d\n",
893 /* Now delete the message from the source room,
894 * if this is a 'move' rather than a 'copy' operation.
897 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
901 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
905 /*****************************************************************************/
906 /* MODULE INITIALIZATION STUFF */
907 /*****************************************************************************/
908 CTDL_MODULE_INIT(ctdl_message)
912 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
913 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
914 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
915 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
916 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
917 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
918 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
919 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
920 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
921 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
922 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
925 /* return our Subversion id for the Log */
926 return "ctdl_message";