2 * represent messages to the citadel clients
4 * Copyright (c) 1987-2012 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.
21 #if TIME_WITH_SYS_TIME
22 # include <sys/time.h>
26 # include <sys/time.h>
39 #include <sys/types.h>
44 #include <libcitadel.h>
47 #include "serv_extensions.h"
49 ///#include "msgbase.h"
51 #include "sysdep_decls.h"
52 #include "citserver.h"
59 #include "internet_addressing.h"
60 #include "euidindex.h"
61 #include "journaling.h"
62 #include "citadel_dirs.h"
63 #include "clientsocket.h"
66 #include "ctdl_module.h"
68 extern char *msgkeys[];
73 * Back end for the MSGS command: output message number only.
75 void simple_listing(long msgnum, void *userdata)
77 cprintf("%ld\n", msgnum);
83 * Back end for the MSGS command: output header summary.
85 void headers_listing(long msgnum, void *userdata)
87 struct CtdlMessage *msg;
89 msg = CtdlFetchMessage(msgnum, 0);
91 cprintf("%ld|0|||||\n", msgnum);
95 cprintf("%ld|%s|%s|%s|%s|%s|\n",
97 (!CM_IsEmpty(msg, eTimestamp) ? msg->cm_fields[eTimestamp] : "0"),
98 (!CM_IsEmpty(msg, eAuthor) ? msg->cm_fields[eAuthor] : ""),
99 (!CM_IsEmpty(msg, eNodeName) ? msg->cm_fields[eNodeName] : ""),
100 (!CM_IsEmpty(msg, erFc822Addr) ? msg->cm_fields[erFc822Addr] : ""),
101 (!CM_IsEmpty(msg, eMsgSubject) ? msg->cm_fields[eMsgSubject] : "")
107 * Back end for the MSGS command: output EUID header.
109 void headers_euid(long msgnum, void *userdata)
111 struct CtdlMessage *msg;
113 msg = CtdlFetchMessage(msgnum, 0);
115 cprintf("%ld||\n", msgnum);
119 cprintf("%ld|%s|%s\n",
121 (!CM_IsEmpty(msg, eExclusiveID) ? msg->cm_fields[eExclusiveID] : ""),
122 (!CM_IsEmpty(msg, eTimestamp) ? msg->cm_fields[eTimestamp] : "0"));
129 * cmd_msgs() - get list of message #'s in this room
130 * implements the MSGS server command using CtdlForEachMessage()
132 void cmd_msgs(char *cmdbuf)
141 int with_template = 0;
142 struct CtdlMessage *template = NULL;
143 char search_string[1024];
144 ForEachMsgCallback CallBack;
146 if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
148 extract_token(which, cmdbuf, 0, '|', sizeof which);
149 cm_ref = extract_int(cmdbuf, 1);
150 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
151 with_template = extract_int(cmdbuf, 2);
152 switch (extract_int(cmdbuf, 3))
156 CallBack = simple_listing;
159 CallBack = headers_listing;
162 CallBack = headers_euid;
167 if (!strncasecmp(which, "OLD", 3))
169 else if (!strncasecmp(which, "NEW", 3))
171 else if (!strncasecmp(which, "FIRST", 5))
173 else if (!strncasecmp(which, "LAST", 4))
175 else if (!strncasecmp(which, "GT", 2))
177 else if (!strncasecmp(which, "LT", 2))
179 else if (!strncasecmp(which, "SEARCH", 6))
184 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
185 cprintf("%d Full text index is not enabled on this server.\n",
186 ERROR + CMD_NOT_SUPPORTED);
192 cprintf("%d Send template then receive message list\n",
194 template = (struct CtdlMessage *)
195 malloc(sizeof(struct CtdlMessage));
196 memset(template, 0, sizeof(struct CtdlMessage));
197 template->cm_magic = CTDLMESSAGE_MAGIC;
198 template->cm_anon_type = MES_NORMAL;
200 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
202 extract_token(tfield, buf, 0, '|', sizeof tfield);
203 tValueLen = extract_token(tvalue, buf, 1, '|', sizeof tvalue);
204 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
205 if (!strcasecmp(tfield, msgkeys[i])) {
206 CM_SetField(template, i, tvalue, tValueLen);
213 cprintf("%d \n", LISTING_FOLLOWS);
216 CtdlForEachMessage(mode,
217 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
218 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
223 if (template != NULL) CM_Free(template);
228 * display a message (mode 0 - Citadel proprietary)
230 void cmd_msg0(char *cmdbuf)
233 int headers_only = HEADERS_ALL;
235 msgid = extract_long(cmdbuf, 0);
236 headers_only = extract_int(cmdbuf, 1);
238 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0, NULL, NULL);
244 * display a message (mode 2 - RFC822)
246 void cmd_msg2(char *cmdbuf)
249 int headers_only = HEADERS_ALL;
251 msgid = extract_long(cmdbuf, 0);
252 headers_only = extract_int(cmdbuf, 1);
254 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0, NULL, NULL);
260 * display a message (mode 3 - IGnet raw format - internal programs only)
262 void cmd_msg3(char *cmdbuf)
265 struct CtdlMessage *msg = NULL;
268 if (CC->internal_pgm == 0) {
269 cprintf("%d This command is for internal programs only.\n",
270 ERROR + HIGHER_ACCESS_REQUIRED);
274 msgnum = extract_long(cmdbuf, 0);
275 msg = CtdlFetchMessage(msgnum, 1);
277 cprintf("%d Message %ld not found.\n",
278 ERROR + MESSAGE_NOT_FOUND, msgnum);
282 CtdlSerializeMessage(&smr, msg);
286 cprintf("%d Unable to serialize message\n",
287 ERROR + INTERNAL_ERROR);
291 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
292 client_write((char *)smr.ser, (int)smr.len);
299 * Display a message using MIME content types
301 void cmd_msg4(char *cmdbuf)
306 msgid = extract_long(cmdbuf, 0);
307 extract_token(section, cmdbuf, 1, '|', sizeof section);
308 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0, NULL, NULL);
314 * Client tells us its preferred message format(s)
316 void cmd_msgp(char *cmdbuf)
318 if (!strcasecmp(cmdbuf, "dont_decode")) {
319 CC->msg4_dont_decode = 1;
320 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
323 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
324 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
330 * Open a component of a MIME message as a download file
332 void cmd_opna(char *cmdbuf)
335 char desired_section[128];
337 msgid = extract_long(cmdbuf, 0);
338 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
339 safestrncpy(CC->download_desired_section, desired_section,
340 sizeof CC->download_desired_section);
341 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0, NULL, NULL);
346 * Open a component of a MIME message and transmit it all at once
348 void cmd_dlat(char *cmdbuf)
351 char desired_section[128];
353 msgid = extract_long(cmdbuf, 0);
354 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
355 safestrncpy(CC->download_desired_section, desired_section,
356 sizeof CC->download_desired_section);
357 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0, NULL, NULL);
361 * message entry - mode 0 (normal)
363 void cmd_ent0(char *entargs)
365 struct CitContext *CCC = CC;
370 char supplied_euid[128];
373 char newusername[256];
374 char newuseremail[256];
375 struct CtdlMessage *msg;
379 struct recptypes *valid = NULL;
380 struct recptypes *valid_to = NULL;
381 struct recptypes *valid_cc = NULL;
382 struct recptypes *valid_bcc = NULL;
384 int subject_required = 0;
389 int newuseremail_ok = 0;
390 char references[SIZ];
395 post = extract_int(entargs, 0);
396 extract_token(recp, entargs, 1, '|', sizeof recp);
397 anon_flag = extract_int(entargs, 2);
398 format_type = extract_int(entargs, 3);
399 extract_token(subject, entargs, 4, '|', sizeof subject);
400 extract_token(newusername, entargs, 5, '|', sizeof newusername);
401 do_confirm = extract_int(entargs, 6);
402 extract_token(cc, entargs, 7, '|', sizeof cc);
403 extract_token(bcc, entargs, 8, '|', sizeof bcc);
404 switch(CC->room.QRdefaultview) {
407 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
410 supplied_euid[0] = 0;
413 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
414 extract_token(references, entargs, 11, '|', sizeof references);
415 for (ptr=references; *ptr != 0; ++ptr) {
416 if (*ptr == '!') *ptr = '|';
419 /* first check to make sure the request is valid. */
421 err = CtdlDoIHavePermissionToPostInThisRoom(
426 (!IsEmptyStr(references)) /* is this a reply? or a top-level post? */
430 cprintf("%d %s\n", err, errmsg);
434 /* Check some other permission type things. */
436 if (IsEmptyStr(newusername)) {
437 strcpy(newusername, CCC->user.fullname);
439 if ( (CCC->user.axlevel < AxAideU)
440 && (strcasecmp(newusername, CCC->user.fullname))
441 && (strcasecmp(newusername, CCC->cs_inet_fn))
443 cprintf("%d You don't have permission to author messages as '%s'.\n",
444 ERROR + HIGHER_ACCESS_REQUIRED,
451 if (IsEmptyStr(newuseremail)) {
455 if (!IsEmptyStr(newuseremail)) {
456 if (!strcasecmp(newuseremail, CCC->cs_inet_email)) {
459 else if (!IsEmptyStr(CCC->cs_inet_other_emails)) {
460 j = num_tokens(CCC->cs_inet_other_emails, '|');
461 for (i=0; i<j; ++i) {
462 extract_token(buf, CCC->cs_inet_other_emails, i, '|', sizeof buf);
463 if (!strcasecmp(newuseremail, buf)) {
470 if (!newuseremail_ok) {
471 cprintf("%d You don't have permission to author messages as '%s'.\n",
472 ERROR + HIGHER_ACCESS_REQUIRED,
478 CCC->cs_flags |= CS_POSTING;
480 /* In mailbox rooms we have to behave a little differently --
481 * make sure the user has specified at least one recipient. Then
482 * validate the recipient(s). We do this for the Mail> room, as
483 * well as any room which has the "Mailbox" view set - unless it
484 * is the DRAFTS room which does not require recipients
487 if ( ( ( (CCC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CCC->room.QRname[11], MAILROOM)) )
488 || ( (CCC->room.QRflags & QR_MAILBOX) && (CCC->curr_view == VIEW_MAILBOX) )
489 ) && (strcasecmp(&CCC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
490 if (CCC->user.axlevel < AxProbU) {
491 strcpy(recp, "sysop");
496 valid_to = validate_recipients(recp, NULL, 0);
497 if (valid_to->num_error > 0) {
498 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
499 free_recipients(valid_to);
503 valid_cc = validate_recipients(cc, NULL, 0);
504 if (valid_cc->num_error > 0) {
505 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
506 free_recipients(valid_to);
507 free_recipients(valid_cc);
511 valid_bcc = validate_recipients(bcc, NULL, 0);
512 if (valid_bcc->num_error > 0) {
513 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
514 free_recipients(valid_to);
515 free_recipients(valid_cc);
516 free_recipients(valid_bcc);
520 /* Recipient required, but none were specified */
521 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
522 free_recipients(valid_to);
523 free_recipients(valid_cc);
524 free_recipients(valid_bcc);
525 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
529 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
530 if (CtdlCheckInternetMailPermission(&CCC->user)==0) {
531 cprintf("%d You do not have permission "
532 "to send Internet mail.\n",
533 ERROR + HIGHER_ACCESS_REQUIRED);
534 free_recipients(valid_to);
535 free_recipients(valid_cc);
536 free_recipients(valid_bcc);
541 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)
542 && (CCC->user.axlevel < AxNetU) ) {
543 cprintf("%d Higher access required for network mail.\n",
544 ERROR + HIGHER_ACCESS_REQUIRED);
545 free_recipients(valid_to);
546 free_recipients(valid_cc);
547 free_recipients(valid_bcc);
551 if ((RESTRICT_INTERNET == 1)
552 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
553 && ((CCC->user.flags & US_INTERNET) == 0)
554 && (!CCC->internal_pgm)) {
555 cprintf("%d You don't have access to Internet mail.\n",
556 ERROR + HIGHER_ACCESS_REQUIRED);
557 free_recipients(valid_to);
558 free_recipients(valid_cc);
559 free_recipients(valid_bcc);
565 /* Is this a room which has anonymous-only or anonymous-option? */
566 anonymous = MES_NORMAL;
567 if (CCC->room.QRflags & QR_ANONONLY) {
568 anonymous = MES_ANONONLY;
570 if (CCC->room.QRflags & QR_ANONOPT) {
571 if (anon_flag == 1) { /* only if the user requested it */
572 anonymous = MES_ANONOPT;
576 if ((CCC->room.QRflags & QR_MAILBOX) == 0) {
580 /* Recommend to the client that the use of a message subject is
581 * strongly recommended in this room, if either the SUBJECTREQ flag
582 * is set, or if there is one or more Internet email recipients.
584 if (CCC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
585 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
586 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
587 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
589 /* If we're only checking the validity of the request, return
590 * success without creating the message.
593 cprintf("%d %s|%d\n", CIT_OK,
594 ((valid_to != NULL) ? valid_to->display_recp : ""),
596 free_recipients(valid_to);
597 free_recipients(valid_cc);
598 free_recipients(valid_bcc);
602 /* We don't need these anymore because we'll do it differently below */
603 free_recipients(valid_to);
604 free_recipients(valid_cc);
605 free_recipients(valid_bcc);
607 /* Read in the message from the client. */
609 cprintf("%d send message\n", START_CHAT_MODE);
611 cprintf("%d send message\n", SEND_LISTING);
614 msg = CtdlMakeMessage(&CCC->user, recp, cc,
615 CCC->room.QRname, anonymous, format_type,
616 newusername, newuseremail, subject,
617 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
620 /* Put together one big recipients struct containing to/cc/bcc all in
621 * one. This is for the envelope.
623 char *all_recps = malloc(SIZ * 3);
624 strcpy(all_recps, recp);
625 if (!IsEmptyStr(cc)) {
626 if (!IsEmptyStr(all_recps)) {
627 strcat(all_recps, ",");
629 strcat(all_recps, cc);
631 if (!IsEmptyStr(bcc)) {
632 if (!IsEmptyStr(all_recps)) {
633 strcat(all_recps, ",");
635 strcat(all_recps, bcc);
637 if (!IsEmptyStr(all_recps)) {
638 valid = validate_recipients(all_recps, NULL, 0);
645 if ((valid != NULL) && (valid->num_room == 1))
647 /* posting into an ML room? set the envelope from
648 * to the actual mail address so others get a valid
651 msg->cm_fields[eenVelopeTo] = strdup(valid->recp_orgroom);
655 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
657 cprintf("%ld\n", msgnum);
659 if (StrLength(CCC->StatusMessage) > 0) {
660 cprintf("%s\n", ChrPtr(CCC->StatusMessage));
662 else if (msgnum >= 0L) {
663 client_write(HKEY("Message accepted.\n"));
666 client_write(HKEY("Internal error.\n"));
669 if (!CM_IsEmpty(msg, eExclusiveID)) {
670 cprintf("%s\n", msg->cm_fields[eExclusiveID]);
680 free_recipients(valid);
686 * Delete message from current room
688 void cmd_dele(char *args)
697 extract_token(msgset, args, 0, '|', sizeof msgset);
698 num_msgs = num_tokens(msgset, ',');
700 cprintf("%d Nothing to do.\n", CIT_OK);
704 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
705 cprintf("%d Higher access required.\n",
706 ERROR + HIGHER_ACCESS_REQUIRED);
711 * Build our message set to be moved/copied
713 msgs = malloc(num_msgs * sizeof(long));
714 for (i=0; i<num_msgs; ++i) {
715 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
716 msgs[i] = atol(msgtok);
719 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
723 cprintf("%d %d message%s deleted.\n", CIT_OK,
724 num_deleted, ((num_deleted != 1) ? "s" : ""));
726 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
733 * move or copy a message to another room
735 void cmd_move(char *args)
742 char targ[ROOMNAMELEN];
743 struct ctdlroom qtemp;
750 extract_token(msgset, args, 0, '|', sizeof msgset);
751 num_msgs = num_tokens(msgset, ',');
753 cprintf("%d Nothing to do.\n", CIT_OK);
757 extract_token(targ, args, 1, '|', sizeof targ);
758 convert_room_name_macros(targ, sizeof targ);
759 targ[ROOMNAMELEN - 1] = 0;
760 is_copy = extract_int(args, 2);
762 if (CtdlGetRoom(&qtemp, targ) != 0) {
763 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
767 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
768 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
772 CtdlGetUser(&CC->user, CC->curr_user);
773 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
775 /* Check for permission to perform this operation.
776 * Remember: "CC->room" is source, "qtemp" is target.
780 /* Admins can move/copy */
781 if (CC->user.axlevel >= AxAideU) permit = 1;
783 /* Room aides can move/copy */
784 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
786 /* Permit move/copy from personal rooms */
787 if ((CC->room.QRflags & QR_MAILBOX)
788 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
790 /* Permit only copy from public to personal room */
792 && (!(CC->room.QRflags & QR_MAILBOX))
793 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
795 /* Permit message removal from collaborative delete rooms */
796 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
798 /* Users allowed to post into the target room may move into it too. */
799 if ((CC->room.QRflags & QR_MAILBOX) &&
800 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
802 /* User must have access to target room */
803 if (!(ra & UA_KNOWN)) permit = 0;
806 cprintf("%d Higher access required.\n",
807 ERROR + HIGHER_ACCESS_REQUIRED);
812 * Build our message set to be moved/copied
814 msgs = malloc(num_msgs * sizeof(long));
815 for (i=0; i<num_msgs; ++i) {
816 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
817 msgs[i] = atol(msgtok);
823 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
825 cprintf("%d Cannot store message(s) in %s: error %d\n",
831 /* Now delete the message from the source room,
832 * if this is a 'move' rather than a 'copy' operation.
835 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
839 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
843 /*****************************************************************************/
844 /* MODULE INITIALIZATION STUFF */
845 /*****************************************************************************/
846 CTDL_MODULE_INIT(ctdl_message)
850 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
851 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
852 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
853 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
854 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
855 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
856 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
857 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
858 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
859 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
860 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
863 /* return our Subversion id for the Log */
864 return "ctdl_message";