2 * represent messages to the citadel clients
4 * Copyright (c) 1987-2015 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 cprintf("%ld|%s|%s|%s|%s|%s|\n",
52 (!CM_IsEmpty(msg, eTimestamp) ? msg->cm_fields[eTimestamp] : "0"),
53 (!CM_IsEmpty(msg, eAuthor) ? msg->cm_fields[eAuthor] : ""),
54 (!CM_IsEmpty(msg, eNodeName) ? msg->cm_fields[eNodeName] : ""),
55 (!CM_IsEmpty(msg, erFc822Addr) ? msg->cm_fields[erFc822Addr] : ""),
56 (!CM_IsEmpty(msg, eMsgSubject) ? msg->cm_fields[eMsgSubject] : "")
62 * Back end for the MSGS command: output EUID header.
64 void headers_euid(long msgnum, void *userdata)
66 struct CtdlMessage *msg;
68 msg = CtdlFetchMessage(msgnum, 0, 1);
70 cprintf("%ld||\n", msgnum);
74 cprintf("%ld|%s|%s\n",
76 (!CM_IsEmpty(msg, eExclusiveID) ? msg->cm_fields[eExclusiveID] : ""),
77 (!CM_IsEmpty(msg, eTimestamp) ? msg->cm_fields[eTimestamp] : "0"));
84 * cmd_msgs() - get list of message #'s in this room
85 * implements the MSGS server command using CtdlForEachMessage()
87 void cmd_msgs(char *cmdbuf)
95 int with_template = 0;
96 struct CtdlMessage *template = NULL;
97 char search_string[1024];
98 ForEachMsgCallback CallBack;
100 if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
102 extract_token(which, cmdbuf, 0, '|', sizeof which);
103 cm_ref = extract_int(cmdbuf, 1);
104 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
105 with_template = extract_int(cmdbuf, 2);
106 switch (extract_int(cmdbuf, 3))
110 CallBack = simple_listing;
113 CallBack = headers_listing;
116 CallBack = headers_euid;
121 if (!strncasecmp(which, "OLD", 3))
123 else if (!strncasecmp(which, "NEW", 3))
125 else if (!strncasecmp(which, "FIRST", 5))
127 else if (!strncasecmp(which, "LAST", 4))
129 else if (!strncasecmp(which, "GT", 2))
131 else if (!strncasecmp(which, "LT", 2))
133 else if (!strncasecmp(which, "SEARCH", 6))
138 if ( (mode == MSGS_SEARCH) && (!CtdlGetConfigInt("c_enable_fulltext")) ) {
139 cprintf("%d Full text index is not enabled on this server.\n",
140 ERROR + CMD_NOT_SUPPORTED);
146 cprintf("%d Send template then receive message list\n",
148 template = (struct CtdlMessage *)
149 malloc(sizeof(struct CtdlMessage));
150 memset(template, 0, sizeof(struct CtdlMessage));
151 template->cm_magic = CTDLMESSAGE_MAGIC;
152 template->cm_anon_type = MES_NORMAL;
154 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
158 tValueLen = extract_token(tfield, buf, 0, '|', sizeof tfield);
159 if ((tValueLen == 4) && GetFieldFromMnemonic(&f, tfield))
161 if (with_template == 1) {
162 tValueLen = extract_token(tvalue, buf, 1, '|', sizeof tvalue);
163 if (tValueLen >= 0) {
164 CM_SetField(template, f, tvalue, tValueLen);
175 cprintf("%d \n", LISTING_FOLLOWS);
178 CtdlForEachMessage(mode,
179 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
180 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
185 if (template != NULL) CM_Free(template);
190 * display a message (mode 0 - Citadel proprietary)
192 void cmd_msg0(char *cmdbuf)
195 int headers_only = HEADERS_ALL;
197 msgid = extract_long(cmdbuf, 0);
198 headers_only = extract_int(cmdbuf, 1);
200 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0, NULL, NULL, NULL);
206 * display a message (mode 2 - RFC822)
208 void cmd_msg2(char *cmdbuf)
211 int headers_only = HEADERS_ALL;
213 msgid = extract_long(cmdbuf, 0);
214 headers_only = extract_int(cmdbuf, 1);
216 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0, NULL, NULL, NULL);
222 * display a message (mode 3 - IGnet raw format - internal programs only)
224 void cmd_msg3(char *cmdbuf)
227 struct CtdlMessage *msg = NULL;
230 if (CC->internal_pgm == 0) {
231 cprintf("%d This command is for internal programs only.\n",
232 ERROR + HIGHER_ACCESS_REQUIRED);
236 msgnum = extract_long(cmdbuf, 0);
237 msg = CtdlFetchMessage(msgnum, 1, 1);
239 cprintf("%d Message %ld not found.\n",
240 ERROR + MESSAGE_NOT_FOUND, msgnum);
244 CtdlSerializeMessage(&smr, msg);
248 cprintf("%d Unable to serialize message\n",
249 ERROR + INTERNAL_ERROR);
253 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
254 client_write((char *)smr.ser, (int)smr.len);
261 * Display a message using MIME content types
263 void cmd_msg4(char *cmdbuf)
268 msgid = extract_long(cmdbuf, 0);
269 extract_token(section, cmdbuf, 1, '|', sizeof section);
270 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0, NULL, NULL, NULL);
276 * Client tells us its preferred message format(s)
278 void cmd_msgp(char *cmdbuf)
280 if (!strcasecmp(cmdbuf, "dont_decode")) {
281 CC->msg4_dont_decode = 1;
282 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
285 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
286 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
292 * Open a component of a MIME message as a download file
294 void cmd_opna(char *cmdbuf)
297 char desired_section[128];
299 msgid = extract_long(cmdbuf, 0);
300 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
301 safestrncpy(CC->download_desired_section, desired_section,
302 sizeof CC->download_desired_section);
303 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0, NULL, NULL, NULL);
308 * Open a component of a MIME message and transmit it all at once
310 void cmd_dlat(char *cmdbuf)
313 char desired_section[128];
315 msgid = extract_long(cmdbuf, 0);
316 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
317 safestrncpy(CC->download_desired_section, desired_section,
318 sizeof CC->download_desired_section);
319 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0, NULL, NULL, NULL);
323 * message entry - mode 0 (normal)
325 void cmd_ent0(char *entargs)
327 struct CitContext *CCC = CC;
332 char supplied_euid[128];
335 char newusername[256];
336 char newuseremail[256];
337 struct CtdlMessage *msg;
341 recptypes *valid = NULL;
342 recptypes *valid_to = NULL;
343 recptypes *valid_cc = NULL;
344 recptypes *valid_bcc = NULL;
346 int subject_required = 0;
351 int newuseremail_ok = 0;
352 char references[SIZ];
357 post = extract_int(entargs, 0);
358 extract_token(recp, entargs, 1, '|', sizeof recp);
359 anon_flag = extract_int(entargs, 2);
360 format_type = extract_int(entargs, 3);
361 extract_token(subject, entargs, 4, '|', sizeof subject);
362 extract_token(newusername, entargs, 5, '|', sizeof newusername);
363 do_confirm = extract_int(entargs, 6);
364 extract_token(cc, entargs, 7, '|', sizeof cc);
365 extract_token(bcc, entargs, 8, '|', sizeof bcc);
366 switch(CC->room.QRdefaultview) {
370 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
373 supplied_euid[0] = 0;
376 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
377 extract_token(references, entargs, 11, '|', sizeof references);
378 for (ptr=references; *ptr != 0; ++ptr) {
379 if (*ptr == '!') *ptr = '|';
382 /* first check to make sure the request is valid. */
384 err = CtdlDoIHavePermissionToPostInThisRoom(
389 (!IsEmptyStr(references)) /* is this a reply? or a top-level post? */
393 cprintf("%d %s\n", err, errmsg);
397 /* Check some other permission type things. */
399 if (IsEmptyStr(newusername)) {
400 strcpy(newusername, CCC->user.fullname);
402 if ( (CCC->user.axlevel < AxAideU)
403 && (strcasecmp(newusername, CCC->user.fullname))
404 && (strcasecmp(newusername, CCC->cs_inet_fn))
406 cprintf("%d You don't have permission to author messages as '%s'.\n",
407 ERROR + HIGHER_ACCESS_REQUIRED,
414 if (IsEmptyStr(newuseremail)) {
418 if (!IsEmptyStr(newuseremail)) {
419 if (!strcasecmp(newuseremail, CCC->cs_inet_email)) {
422 else if (!IsEmptyStr(CCC->cs_inet_other_emails)) {
423 j = num_tokens(CCC->cs_inet_other_emails, '|');
424 for (i=0; i<j; ++i) {
425 extract_token(buf, CCC->cs_inet_other_emails, i, '|', sizeof buf);
426 if (!strcasecmp(newuseremail, buf)) {
433 if (!newuseremail_ok) {
434 cprintf("%d You don't have permission to author messages as '%s'.\n",
435 ERROR + HIGHER_ACCESS_REQUIRED,
441 CCC->cs_flags |= CS_POSTING;
443 /* In mailbox rooms we have to behave a little differently --
444 * make sure the user has specified at least one recipient. Then
445 * validate the recipient(s). We do this for the Mail> room, as
446 * well as any room which has the "Mailbox" view set - unless it
447 * is the DRAFTS room which does not require recipients
450 if ( ( ( (CCC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CCC->room.QRname[11], MAILROOM)) )
451 || ( (CCC->room.QRflags & QR_MAILBOX) && (CCC->curr_view == VIEW_MAILBOX) )
452 ) && (strcasecmp(&CCC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
453 if (CCC->user.axlevel < AxProbU) {
454 strcpy(recp, "sysop");
459 valid_to = validate_recipients(recp, NULL, 0);
460 if (valid_to->num_error > 0) {
461 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
462 free_recipients(valid_to);
466 valid_cc = validate_recipients(cc, NULL, 0);
467 if (valid_cc->num_error > 0) {
468 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
469 free_recipients(valid_to);
470 free_recipients(valid_cc);
474 valid_bcc = validate_recipients(bcc, NULL, 0);
475 if (valid_bcc->num_error > 0) {
476 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
477 free_recipients(valid_to);
478 free_recipients(valid_cc);
479 free_recipients(valid_bcc);
483 /* Recipient required, but none were specified */
484 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
485 free_recipients(valid_to);
486 free_recipients(valid_cc);
487 free_recipients(valid_bcc);
488 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
492 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
493 if (CtdlCheckInternetMailPermission(&CCC->user)==0) {
494 cprintf("%d You do not have permission "
495 "to send Internet mail.\n",
496 ERROR + HIGHER_ACCESS_REQUIRED);
497 free_recipients(valid_to);
498 free_recipients(valid_cc);
499 free_recipients(valid_bcc);
504 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)
505 && (CCC->user.axlevel < AxNetU) ) {
506 cprintf("%d Higher access required for network mail.\n",
507 ERROR + HIGHER_ACCESS_REQUIRED);
508 free_recipients(valid_to);
509 free_recipients(valid_cc);
510 free_recipients(valid_bcc);
514 if ((RESTRICT_INTERNET == 1)
515 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
516 && ((CCC->user.flags & US_INTERNET) == 0)
517 && (!CCC->internal_pgm)) {
518 cprintf("%d You don't have access to Internet mail.\n",
519 ERROR + HIGHER_ACCESS_REQUIRED);
520 free_recipients(valid_to);
521 free_recipients(valid_cc);
522 free_recipients(valid_bcc);
528 /* Is this a room which has anonymous-only or anonymous-option? */
529 anonymous = MES_NORMAL;
530 if (CCC->room.QRflags & QR_ANONONLY) {
531 anonymous = MES_ANONONLY;
533 if (CCC->room.QRflags & QR_ANONOPT) {
534 if (anon_flag == 1) { /* only if the user requested it */
535 anonymous = MES_ANONOPT;
539 if ((CCC->room.QRflags & QR_MAILBOX) == 0) {
543 /* Recommend to the client that the use of a message subject is
544 * strongly recommended in this room, if either the SUBJECTREQ flag
545 * is set, or if there is one or more Internet email recipients.
547 if (CCC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
548 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
549 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
550 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
552 /* If we're only checking the validity of the request, return
553 * success without creating the message.
556 cprintf("%d %s|%d\n", CIT_OK,
557 ((valid_to != NULL) ? valid_to->display_recp : ""),
559 free_recipients(valid_to);
560 free_recipients(valid_cc);
561 free_recipients(valid_bcc);
565 /* We don't need these anymore because we'll do it differently below */
566 free_recipients(valid_to);
567 free_recipients(valid_cc);
568 free_recipients(valid_bcc);
570 /* Read in the message from the client. */
572 cprintf("%d send message\n", START_CHAT_MODE);
574 cprintf("%d send message\n", SEND_LISTING);
577 msg = CtdlMakeMessage(&CCC->user, recp, cc,
578 CCC->room.QRname, anonymous, format_type,
579 newusername, newuseremail, subject,
580 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
583 /* Put together one big recipients struct containing to/cc/bcc all in
584 * one. This is for the envelope.
586 char *all_recps = malloc(SIZ * 3);
587 strcpy(all_recps, recp);
588 if (!IsEmptyStr(cc)) {
589 if (!IsEmptyStr(all_recps)) {
590 strcat(all_recps, ",");
592 strcat(all_recps, cc);
594 if (!IsEmptyStr(bcc)) {
595 if (!IsEmptyStr(all_recps)) {
596 strcat(all_recps, ",");
598 strcat(all_recps, bcc);
600 if (!IsEmptyStr(all_recps)) {
601 valid = validate_recipients(all_recps, NULL, 0);
608 if ((valid != NULL) && (valid->num_room == 1) && !IsEmptyStr(valid->recp_orgroom))
610 /* posting into an ML room? set the envelope from
611 * to the actual mail address so others get a valid
614 CM_SetField(msg, eenVelopeTo, valid->recp_orgroom, strlen(valid->recp_orgroom));
618 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
620 cprintf("%ld\n", msgnum);
622 if (StrLength(CCC->StatusMessage) > 0) {
623 cprintf("%s\n", ChrPtr(CCC->StatusMessage));
625 else if (msgnum >= 0L) {
626 client_write(HKEY("Message accepted.\n"));
629 client_write(HKEY("Internal error.\n"));
632 if (!CM_IsEmpty(msg, eExclusiveID)) {
633 cprintf("%s\n", msg->cm_fields[eExclusiveID]);
643 free_recipients(valid);
649 * Delete message from current room
651 void cmd_dele(char *args)
660 extract_token(msgset, args, 0, '|', sizeof msgset);
661 num_msgs = num_tokens(msgset, ',');
663 cprintf("%d Nothing to do.\n", CIT_OK);
667 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
668 cprintf("%d Higher access required.\n",
669 ERROR + HIGHER_ACCESS_REQUIRED);
674 * Build our message set to be moved/copied
676 msgs = malloc(num_msgs * sizeof(long));
677 for (i=0; i<num_msgs; ++i) {
678 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
679 msgs[i] = atol(msgtok);
682 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
686 cprintf("%d %d message%s deleted.\n", CIT_OK,
687 num_deleted, ((num_deleted != 1) ? "s" : ""));
689 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
696 * move or copy a message to another room
698 void cmd_move(char *args)
705 char targ[ROOMNAMELEN];
706 struct ctdlroom qtemp;
713 extract_token(msgset, args, 0, '|', sizeof msgset);
714 num_msgs = num_tokens(msgset, ',');
716 cprintf("%d Nothing to do.\n", CIT_OK);
720 extract_token(targ, args, 1, '|', sizeof targ);
721 convert_room_name_macros(targ, sizeof targ);
722 targ[ROOMNAMELEN - 1] = 0;
723 is_copy = extract_int(args, 2);
725 if (CtdlGetRoom(&qtemp, targ) != 0) {
726 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
730 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
731 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
735 CtdlGetUser(&CC->user, CC->curr_user);
736 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
738 /* Check for permission to perform this operation.
739 * Remember: "CC->room" is source, "qtemp" is target.
743 /* Admins can move/copy */
744 if (CC->user.axlevel >= AxAideU) permit = 1;
746 /* Room aides can move/copy */
747 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
749 /* Permit move/copy from personal rooms */
750 if ((CC->room.QRflags & QR_MAILBOX)
751 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
753 /* Permit only copy from public to personal room */
755 && (!(CC->room.QRflags & QR_MAILBOX))
756 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
758 /* Permit message removal from collaborative delete rooms */
759 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
761 /* Users allowed to post into the target room may move into it too. */
762 if ((CC->room.QRflags & QR_MAILBOX) &&
763 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
765 /* User must have access to target room */
766 if (!(ra & UA_KNOWN)) permit = 0;
769 cprintf("%d Higher access required.\n",
770 ERROR + HIGHER_ACCESS_REQUIRED);
775 * Build our message set to be moved/copied
777 msgs = malloc(num_msgs * sizeof(long));
778 for (i=0; i<num_msgs; ++i) {
779 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
780 msgs[i] = atol(msgtok);
786 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
788 cprintf("%d Cannot store message(s) in %s: error %d\n",
794 /* Now delete the message from the source room,
795 * if this is a 'move' rather than a 'copy' operation.
798 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
802 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
806 /*****************************************************************************/
807 /* MODULE INITIALIZATION STUFF */
808 /*****************************************************************************/
809 CTDL_MODULE_INIT(ctdl_message)
813 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
814 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
815 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
816 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
817 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
818 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
819 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
820 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
821 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
822 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
823 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
826 /* return our Subversion id for the Log */
827 return "ctdl_message";