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)
96 int with_template = 0;
97 struct CtdlMessage *template = NULL;
98 char search_string[1024];
99 ForEachMsgCallback CallBack;
101 if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
103 extract_token(which, cmdbuf, 0, '|', sizeof which);
104 cm_ref = extract_int(cmdbuf, 1);
105 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
106 with_template = extract_int(cmdbuf, 2);
107 switch (extract_int(cmdbuf, 3))
111 CallBack = simple_listing;
114 CallBack = headers_listing;
117 CallBack = headers_euid;
122 if (!strncasecmp(which, "OLD", 3))
124 else if (!strncasecmp(which, "NEW", 3))
126 else if (!strncasecmp(which, "FIRST", 5))
128 else if (!strncasecmp(which, "LAST", 4))
130 else if (!strncasecmp(which, "GT", 2))
132 else if (!strncasecmp(which, "LT", 2))
134 else if (!strncasecmp(which, "SEARCH", 6))
139 if ( (mode == MSGS_SEARCH) && (!CtdlGetConfigInt("c_enable_fulltext")) ) {
140 cprintf("%d Full text index is not enabled on this server.\n",
141 ERROR + CMD_NOT_SUPPORTED);
147 cprintf("%d Send template then receive message list\n",
149 template = (struct CtdlMessage *)
150 malloc(sizeof(struct CtdlMessage));
151 memset(template, 0, sizeof(struct CtdlMessage));
152 template->cm_magic = CTDLMESSAGE_MAGIC;
153 template->cm_anon_type = MES_NORMAL;
155 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
157 extract_token(tfield, buf, 0, '|', sizeof tfield);
158 tValueLen = extract_token(tvalue, buf, 1, '|', sizeof tvalue);
159 if (tValueLen >= 0) {
160 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
161 if (!strcasecmp(tfield, msgkeys[i])) {
162 CM_SetField(template, i, tvalue, tValueLen);
170 cprintf("%d \n", LISTING_FOLLOWS);
173 CtdlForEachMessage(mode,
174 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
175 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
180 if (template != NULL) CM_Free(template);
185 * display a message (mode 0 - Citadel proprietary)
187 void cmd_msg0(char *cmdbuf)
190 int headers_only = HEADERS_ALL;
192 msgid = extract_long(cmdbuf, 0);
193 headers_only = extract_int(cmdbuf, 1);
195 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0, NULL, NULL, NULL);
201 * display a message (mode 2 - RFC822)
203 void cmd_msg2(char *cmdbuf)
206 int headers_only = HEADERS_ALL;
208 msgid = extract_long(cmdbuf, 0);
209 headers_only = extract_int(cmdbuf, 1);
211 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0, NULL, NULL, NULL);
217 * display a message (mode 3 - IGnet raw format - internal programs only)
219 void cmd_msg3(char *cmdbuf)
222 struct CtdlMessage *msg = NULL;
225 if (CC->internal_pgm == 0) {
226 cprintf("%d This command is for internal programs only.\n",
227 ERROR + HIGHER_ACCESS_REQUIRED);
231 msgnum = extract_long(cmdbuf, 0);
232 msg = CtdlFetchMessage(msgnum, 1, 1);
234 cprintf("%d Message %ld not found.\n",
235 ERROR + MESSAGE_NOT_FOUND, msgnum);
239 CtdlSerializeMessage(&smr, msg);
243 cprintf("%d Unable to serialize message\n",
244 ERROR + INTERNAL_ERROR);
248 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
249 client_write((char *)smr.ser, (int)smr.len);
256 * Display a message using MIME content types
258 void cmd_msg4(char *cmdbuf)
263 msgid = extract_long(cmdbuf, 0);
264 extract_token(section, cmdbuf, 1, '|', sizeof section);
265 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0, NULL, NULL, NULL);
271 * Client tells us its preferred message format(s)
273 void cmd_msgp(char *cmdbuf)
275 if (!strcasecmp(cmdbuf, "dont_decode")) {
276 CC->msg4_dont_decode = 1;
277 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
280 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
281 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
287 * Open a component of a MIME message as a download file
289 void cmd_opna(char *cmdbuf)
292 char desired_section[128];
294 msgid = extract_long(cmdbuf, 0);
295 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
296 safestrncpy(CC->download_desired_section, desired_section,
297 sizeof CC->download_desired_section);
298 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0, NULL, NULL, NULL);
303 * Open a component of a MIME message and transmit it all at once
305 void cmd_dlat(char *cmdbuf)
308 char desired_section[128];
310 msgid = extract_long(cmdbuf, 0);
311 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
312 safestrncpy(CC->download_desired_section, desired_section,
313 sizeof CC->download_desired_section);
314 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0, NULL, NULL, NULL);
318 * message entry - mode 0 (normal)
320 void cmd_ent0(char *entargs)
322 struct CitContext *CCC = CC;
327 char supplied_euid[128];
330 char newusername[256];
331 char newuseremail[256];
332 struct CtdlMessage *msg;
336 recptypes *valid = NULL;
337 recptypes *valid_to = NULL;
338 recptypes *valid_cc = NULL;
339 recptypes *valid_bcc = NULL;
341 int subject_required = 0;
346 int newuseremail_ok = 0;
347 char references[SIZ];
352 post = extract_int(entargs, 0);
353 extract_token(recp, entargs, 1, '|', sizeof recp);
354 anon_flag = extract_int(entargs, 2);
355 format_type = extract_int(entargs, 3);
356 extract_token(subject, entargs, 4, '|', sizeof subject);
357 extract_token(newusername, entargs, 5, '|', sizeof newusername);
358 do_confirm = extract_int(entargs, 6);
359 extract_token(cc, entargs, 7, '|', sizeof cc);
360 extract_token(bcc, entargs, 8, '|', sizeof bcc);
361 switch(CC->room.QRdefaultview) {
365 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
368 supplied_euid[0] = 0;
371 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
372 extract_token(references, entargs, 11, '|', sizeof references);
373 for (ptr=references; *ptr != 0; ++ptr) {
374 if (*ptr == '!') *ptr = '|';
377 /* first check to make sure the request is valid. */
379 err = CtdlDoIHavePermissionToPostInThisRoom(
384 (!IsEmptyStr(references)) /* is this a reply? or a top-level post? */
388 cprintf("%d %s\n", err, errmsg);
392 /* Check some other permission type things. */
394 if (IsEmptyStr(newusername)) {
395 strcpy(newusername, CCC->user.fullname);
397 if ( (CCC->user.axlevel < AxAideU)
398 && (strcasecmp(newusername, CCC->user.fullname))
399 && (strcasecmp(newusername, CCC->cs_inet_fn))
401 cprintf("%d You don't have permission to author messages as '%s'.\n",
402 ERROR + HIGHER_ACCESS_REQUIRED,
409 if (IsEmptyStr(newuseremail)) {
413 if (!IsEmptyStr(newuseremail)) {
414 if (!strcasecmp(newuseremail, CCC->cs_inet_email)) {
417 else if (!IsEmptyStr(CCC->cs_inet_other_emails)) {
418 j = num_tokens(CCC->cs_inet_other_emails, '|');
419 for (i=0; i<j; ++i) {
420 extract_token(buf, CCC->cs_inet_other_emails, i, '|', sizeof buf);
421 if (!strcasecmp(newuseremail, buf)) {
428 if (!newuseremail_ok) {
429 cprintf("%d You don't have permission to author messages as '%s'.\n",
430 ERROR + HIGHER_ACCESS_REQUIRED,
436 CCC->cs_flags |= CS_POSTING;
438 /* In mailbox rooms we have to behave a little differently --
439 * make sure the user has specified at least one recipient. Then
440 * validate the recipient(s). We do this for the Mail> room, as
441 * well as any room which has the "Mailbox" view set - unless it
442 * is the DRAFTS room which does not require recipients
445 if ( ( ( (CCC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CCC->room.QRname[11], MAILROOM)) )
446 || ( (CCC->room.QRflags & QR_MAILBOX) && (CCC->curr_view == VIEW_MAILBOX) )
447 ) && (strcasecmp(&CCC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
448 if (CCC->user.axlevel < AxProbU) {
449 strcpy(recp, "sysop");
454 valid_to = validate_recipients(recp, NULL, 0);
455 if (valid_to->num_error > 0) {
456 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
457 free_recipients(valid_to);
461 valid_cc = validate_recipients(cc, NULL, 0);
462 if (valid_cc->num_error > 0) {
463 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
464 free_recipients(valid_to);
465 free_recipients(valid_cc);
469 valid_bcc = validate_recipients(bcc, NULL, 0);
470 if (valid_bcc->num_error > 0) {
471 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
472 free_recipients(valid_to);
473 free_recipients(valid_cc);
474 free_recipients(valid_bcc);
478 /* Recipient required, but none were specified */
479 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
480 free_recipients(valid_to);
481 free_recipients(valid_cc);
482 free_recipients(valid_bcc);
483 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
487 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
488 if (CtdlCheckInternetMailPermission(&CCC->user)==0) {
489 cprintf("%d You do not have permission "
490 "to send Internet mail.\n",
491 ERROR + HIGHER_ACCESS_REQUIRED);
492 free_recipients(valid_to);
493 free_recipients(valid_cc);
494 free_recipients(valid_bcc);
499 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)
500 && (CCC->user.axlevel < AxNetU) ) {
501 cprintf("%d Higher access required for network mail.\n",
502 ERROR + HIGHER_ACCESS_REQUIRED);
503 free_recipients(valid_to);
504 free_recipients(valid_cc);
505 free_recipients(valid_bcc);
509 if ((RESTRICT_INTERNET == 1)
510 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
511 && ((CCC->user.flags & US_INTERNET) == 0)
512 && (!CCC->internal_pgm)) {
513 cprintf("%d You don't have access to Internet mail.\n",
514 ERROR + HIGHER_ACCESS_REQUIRED);
515 free_recipients(valid_to);
516 free_recipients(valid_cc);
517 free_recipients(valid_bcc);
523 /* Is this a room which has anonymous-only or anonymous-option? */
524 anonymous = MES_NORMAL;
525 if (CCC->room.QRflags & QR_ANONONLY) {
526 anonymous = MES_ANONONLY;
528 if (CCC->room.QRflags & QR_ANONOPT) {
529 if (anon_flag == 1) { /* only if the user requested it */
530 anonymous = MES_ANONOPT;
534 if ((CCC->room.QRflags & QR_MAILBOX) == 0) {
538 /* Recommend to the client that the use of a message subject is
539 * strongly recommended in this room, if either the SUBJECTREQ flag
540 * is set, or if there is one or more Internet email recipients.
542 if (CCC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
543 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
544 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
545 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
547 /* If we're only checking the validity of the request, return
548 * success without creating the message.
551 cprintf("%d %s|%d\n", CIT_OK,
552 ((valid_to != NULL) ? valid_to->display_recp : ""),
554 free_recipients(valid_to);
555 free_recipients(valid_cc);
556 free_recipients(valid_bcc);
560 /* We don't need these anymore because we'll do it differently below */
561 free_recipients(valid_to);
562 free_recipients(valid_cc);
563 free_recipients(valid_bcc);
565 /* Read in the message from the client. */
567 cprintf("%d send message\n", START_CHAT_MODE);
569 cprintf("%d send message\n", SEND_LISTING);
572 msg = CtdlMakeMessage(&CCC->user, recp, cc,
573 CCC->room.QRname, anonymous, format_type,
574 newusername, newuseremail, subject,
575 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
578 /* Put together one big recipients struct containing to/cc/bcc all in
579 * one. This is for the envelope.
581 char *all_recps = malloc(SIZ * 3);
582 strcpy(all_recps, recp);
583 if (!IsEmptyStr(cc)) {
584 if (!IsEmptyStr(all_recps)) {
585 strcat(all_recps, ",");
587 strcat(all_recps, cc);
589 if (!IsEmptyStr(bcc)) {
590 if (!IsEmptyStr(all_recps)) {
591 strcat(all_recps, ",");
593 strcat(all_recps, bcc);
595 if (!IsEmptyStr(all_recps)) {
596 valid = validate_recipients(all_recps, NULL, 0);
603 if ((valid != NULL) && (valid->num_room == 1) && !IsEmptyStr(valid->recp_orgroom))
605 /* posting into an ML room? set the envelope from
606 * to the actual mail address so others get a valid
609 CM_SetField(msg, eenVelopeTo, valid->recp_orgroom, strlen(valid->recp_orgroom));
613 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
615 cprintf("%ld\n", msgnum);
617 if (StrLength(CCC->StatusMessage) > 0) {
618 cprintf("%s\n", ChrPtr(CCC->StatusMessage));
620 else if (msgnum >= 0L) {
621 client_write(HKEY("Message accepted.\n"));
624 client_write(HKEY("Internal error.\n"));
627 if (!CM_IsEmpty(msg, eExclusiveID)) {
628 cprintf("%s\n", msg->cm_fields[eExclusiveID]);
638 free_recipients(valid);
644 * Delete message from current room
646 void cmd_dele(char *args)
655 extract_token(msgset, args, 0, '|', sizeof msgset);
656 num_msgs = num_tokens(msgset, ',');
658 cprintf("%d Nothing to do.\n", CIT_OK);
662 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
663 cprintf("%d Higher access required.\n",
664 ERROR + HIGHER_ACCESS_REQUIRED);
669 * Build our message set to be moved/copied
671 msgs = malloc(num_msgs * sizeof(long));
672 for (i=0; i<num_msgs; ++i) {
673 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
674 msgs[i] = atol(msgtok);
677 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
681 cprintf("%d %d message%s deleted.\n", CIT_OK,
682 num_deleted, ((num_deleted != 1) ? "s" : ""));
684 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
691 * move or copy a message to another room
693 void cmd_move(char *args)
700 char targ[ROOMNAMELEN];
701 struct ctdlroom qtemp;
708 extract_token(msgset, args, 0, '|', sizeof msgset);
709 num_msgs = num_tokens(msgset, ',');
711 cprintf("%d Nothing to do.\n", CIT_OK);
715 extract_token(targ, args, 1, '|', sizeof targ);
716 convert_room_name_macros(targ, sizeof targ);
717 targ[ROOMNAMELEN - 1] = 0;
718 is_copy = extract_int(args, 2);
720 if (CtdlGetRoom(&qtemp, targ) != 0) {
721 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
725 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
726 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
730 CtdlGetUser(&CC->user, CC->curr_user);
731 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
733 /* Check for permission to perform this operation.
734 * Remember: "CC->room" is source, "qtemp" is target.
738 /* Admins can move/copy */
739 if (CC->user.axlevel >= AxAideU) permit = 1;
741 /* Room aides can move/copy */
742 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
744 /* Permit move/copy from personal rooms */
745 if ((CC->room.QRflags & QR_MAILBOX)
746 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
748 /* Permit only copy from public to personal room */
750 && (!(CC->room.QRflags & QR_MAILBOX))
751 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
753 /* Permit message removal from collaborative delete rooms */
754 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
756 /* Users allowed to post into the target room may move into it too. */
757 if ((CC->room.QRflags & QR_MAILBOX) &&
758 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
760 /* User must have access to target room */
761 if (!(ra & UA_KNOWN)) permit = 0;
764 cprintf("%d Higher access required.\n",
765 ERROR + HIGHER_ACCESS_REQUIRED);
770 * Build our message set to be moved/copied
772 msgs = malloc(num_msgs * sizeof(long));
773 for (i=0; i<num_msgs; ++i) {
774 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
775 msgs[i] = atol(msgtok);
781 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
783 cprintf("%d Cannot store message(s) in %s: error %d\n",
789 /* Now delete the message from the source room,
790 * if this is a 'move' rather than a 'copy' operation.
793 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
797 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
801 /*****************************************************************************/
802 /* MODULE INITIALIZATION STUFF */
803 /*****************************************************************************/
804 CTDL_MODULE_INIT(ctdl_message)
808 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
809 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
810 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
811 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
812 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
813 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
814 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
815 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
816 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
817 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
818 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
821 /* return our Subversion id for the Log */
822 return "ctdl_message";