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);
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);
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 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
160 if (!strcasecmp(tfield, msgkeys[i])) {
161 CM_SetField(template, i, tvalue, tValueLen);
168 cprintf("%d \n", LISTING_FOLLOWS);
171 CtdlForEachMessage(mode,
172 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
173 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
178 if (template != NULL) CM_Free(template);
183 * display a message (mode 0 - Citadel proprietary)
185 void cmd_msg0(char *cmdbuf)
188 int headers_only = HEADERS_ALL;
190 msgid = extract_long(cmdbuf, 0);
191 headers_only = extract_int(cmdbuf, 1);
193 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0, NULL, NULL, NULL);
199 * display a message (mode 2 - RFC822)
201 void cmd_msg2(char *cmdbuf)
204 int headers_only = HEADERS_ALL;
206 msgid = extract_long(cmdbuf, 0);
207 headers_only = extract_int(cmdbuf, 1);
209 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0, NULL, NULL, NULL);
215 * display a message (mode 3 - IGnet raw format - internal programs only)
217 void cmd_msg3(char *cmdbuf)
220 struct CtdlMessage *msg = NULL;
223 if (CC->internal_pgm == 0) {
224 cprintf("%d This command is for internal programs only.\n",
225 ERROR + HIGHER_ACCESS_REQUIRED);
229 msgnum = extract_long(cmdbuf, 0);
230 msg = CtdlFetchMessage(msgnum, 1);
232 cprintf("%d Message %ld not found.\n",
233 ERROR + MESSAGE_NOT_FOUND, msgnum);
237 CtdlSerializeMessage(&smr, msg);
241 cprintf("%d Unable to serialize message\n",
242 ERROR + INTERNAL_ERROR);
246 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
247 client_write((char *)smr.ser, (int)smr.len);
254 * Display a message using MIME content types
256 void cmd_msg4(char *cmdbuf)
261 msgid = extract_long(cmdbuf, 0);
262 extract_token(section, cmdbuf, 1, '|', sizeof section);
263 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0, NULL, NULL, NULL);
269 * Client tells us its preferred message format(s)
271 void cmd_msgp(char *cmdbuf)
273 if (!strcasecmp(cmdbuf, "dont_decode")) {
274 CC->msg4_dont_decode = 1;
275 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
278 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
279 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
285 * Open a component of a MIME message as a download file
287 void cmd_opna(char *cmdbuf)
290 char desired_section[128];
292 msgid = extract_long(cmdbuf, 0);
293 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
294 safestrncpy(CC->download_desired_section, desired_section,
295 sizeof CC->download_desired_section);
296 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0, NULL, NULL, NULL);
301 * Open a component of a MIME message and transmit it all at once
303 void cmd_dlat(char *cmdbuf)
306 char desired_section[128];
308 msgid = extract_long(cmdbuf, 0);
309 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
310 safestrncpy(CC->download_desired_section, desired_section,
311 sizeof CC->download_desired_section);
312 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0, NULL, NULL, NULL);
316 * message entry - mode 0 (normal)
318 void cmd_ent0(char *entargs)
320 struct CitContext *CCC = CC;
325 char supplied_euid[128];
328 char newusername[256];
329 char newuseremail[256];
330 struct CtdlMessage *msg;
334 recptypes *valid = NULL;
335 recptypes *valid_to = NULL;
336 recptypes *valid_cc = NULL;
337 recptypes *valid_bcc = NULL;
339 int subject_required = 0;
344 int newuseremail_ok = 0;
345 char references[SIZ];
350 post = extract_int(entargs, 0);
351 extract_token(recp, entargs, 1, '|', sizeof recp);
352 anon_flag = extract_int(entargs, 2);
353 format_type = extract_int(entargs, 3);
354 extract_token(subject, entargs, 4, '|', sizeof subject);
355 extract_token(newusername, entargs, 5, '|', sizeof newusername);
356 do_confirm = extract_int(entargs, 6);
357 extract_token(cc, entargs, 7, '|', sizeof cc);
358 extract_token(bcc, entargs, 8, '|', sizeof bcc);
359 switch(CC->room.QRdefaultview) {
363 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
366 supplied_euid[0] = 0;
369 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
370 extract_token(references, entargs, 11, '|', sizeof references);
371 for (ptr=references; *ptr != 0; ++ptr) {
372 if (*ptr == '!') *ptr = '|';
375 /* first check to make sure the request is valid. */
377 err = CtdlDoIHavePermissionToPostInThisRoom(
382 (!IsEmptyStr(references)) /* is this a reply? or a top-level post? */
386 cprintf("%d %s\n", err, errmsg);
390 /* Check some other permission type things. */
392 if (IsEmptyStr(newusername)) {
393 strcpy(newusername, CCC->user.fullname);
395 if ( (CCC->user.axlevel < AxAideU)
396 && (strcasecmp(newusername, CCC->user.fullname))
397 && (strcasecmp(newusername, CCC->cs_inet_fn))
399 cprintf("%d You don't have permission to author messages as '%s'.\n",
400 ERROR + HIGHER_ACCESS_REQUIRED,
407 if (IsEmptyStr(newuseremail)) {
411 if (!IsEmptyStr(newuseremail)) {
412 if (!strcasecmp(newuseremail, CCC->cs_inet_email)) {
415 else if (!IsEmptyStr(CCC->cs_inet_other_emails)) {
416 j = num_tokens(CCC->cs_inet_other_emails, '|');
417 for (i=0; i<j; ++i) {
418 extract_token(buf, CCC->cs_inet_other_emails, i, '|', sizeof buf);
419 if (!strcasecmp(newuseremail, buf)) {
426 if (!newuseremail_ok) {
427 cprintf("%d You don't have permission to author messages as '%s'.\n",
428 ERROR + HIGHER_ACCESS_REQUIRED,
434 CCC->cs_flags |= CS_POSTING;
436 /* In mailbox rooms we have to behave a little differently --
437 * make sure the user has specified at least one recipient. Then
438 * validate the recipient(s). We do this for the Mail> room, as
439 * well as any room which has the "Mailbox" view set - unless it
440 * is the DRAFTS room which does not require recipients
443 if ( ( ( (CCC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CCC->room.QRname[11], MAILROOM)) )
444 || ( (CCC->room.QRflags & QR_MAILBOX) && (CCC->curr_view == VIEW_MAILBOX) )
445 ) && (strcasecmp(&CCC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
446 if (CCC->user.axlevel < AxProbU) {
447 strcpy(recp, "sysop");
452 valid_to = validate_recipients(recp, NULL, 0);
453 if (valid_to->num_error > 0) {
454 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
455 free_recipients(valid_to);
459 valid_cc = validate_recipients(cc, NULL, 0);
460 if (valid_cc->num_error > 0) {
461 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
462 free_recipients(valid_to);
463 free_recipients(valid_cc);
467 valid_bcc = validate_recipients(bcc, NULL, 0);
468 if (valid_bcc->num_error > 0) {
469 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
470 free_recipients(valid_to);
471 free_recipients(valid_cc);
472 free_recipients(valid_bcc);
476 /* Recipient required, but none were specified */
477 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
478 free_recipients(valid_to);
479 free_recipients(valid_cc);
480 free_recipients(valid_bcc);
481 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
485 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
486 if (CtdlCheckInternetMailPermission(&CCC->user)==0) {
487 cprintf("%d You do not have permission "
488 "to send Internet mail.\n",
489 ERROR + HIGHER_ACCESS_REQUIRED);
490 free_recipients(valid_to);
491 free_recipients(valid_cc);
492 free_recipients(valid_bcc);
497 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)
498 && (CCC->user.axlevel < AxNetU) ) {
499 cprintf("%d Higher access required for network mail.\n",
500 ERROR + HIGHER_ACCESS_REQUIRED);
501 free_recipients(valid_to);
502 free_recipients(valid_cc);
503 free_recipients(valid_bcc);
507 if ((RESTRICT_INTERNET == 1)
508 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
509 && ((CCC->user.flags & US_INTERNET) == 0)
510 && (!CCC->internal_pgm)) {
511 cprintf("%d You don't have access to Internet mail.\n",
512 ERROR + HIGHER_ACCESS_REQUIRED);
513 free_recipients(valid_to);
514 free_recipients(valid_cc);
515 free_recipients(valid_bcc);
521 /* Is this a room which has anonymous-only or anonymous-option? */
522 anonymous = MES_NORMAL;
523 if (CCC->room.QRflags & QR_ANONONLY) {
524 anonymous = MES_ANONONLY;
526 if (CCC->room.QRflags & QR_ANONOPT) {
527 if (anon_flag == 1) { /* only if the user requested it */
528 anonymous = MES_ANONOPT;
532 if ((CCC->room.QRflags & QR_MAILBOX) == 0) {
536 /* Recommend to the client that the use of a message subject is
537 * strongly recommended in this room, if either the SUBJECTREQ flag
538 * is set, or if there is one or more Internet email recipients.
540 if (CCC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
541 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
542 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
543 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
545 /* If we're only checking the validity of the request, return
546 * success without creating the message.
549 cprintf("%d %s|%d\n", CIT_OK,
550 ((valid_to != NULL) ? valid_to->display_recp : ""),
552 free_recipients(valid_to);
553 free_recipients(valid_cc);
554 free_recipients(valid_bcc);
558 /* We don't need these anymore because we'll do it differently below */
559 free_recipients(valid_to);
560 free_recipients(valid_cc);
561 free_recipients(valid_bcc);
563 /* Read in the message from the client. */
565 cprintf("%d send message\n", START_CHAT_MODE);
567 cprintf("%d send message\n", SEND_LISTING);
570 msg = CtdlMakeMessage(&CCC->user, recp, cc,
571 CCC->room.QRname, anonymous, format_type,
572 newusername, newuseremail, subject,
573 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
576 /* Put together one big recipients struct containing to/cc/bcc all in
577 * one. This is for the envelope.
579 char *all_recps = malloc(SIZ * 3);
580 strcpy(all_recps, recp);
581 if (!IsEmptyStr(cc)) {
582 if (!IsEmptyStr(all_recps)) {
583 strcat(all_recps, ",");
585 strcat(all_recps, cc);
587 if (!IsEmptyStr(bcc)) {
588 if (!IsEmptyStr(all_recps)) {
589 strcat(all_recps, ",");
591 strcat(all_recps, bcc);
593 if (!IsEmptyStr(all_recps)) {
594 valid = validate_recipients(all_recps, NULL, 0);
601 if ((valid != NULL) && (valid->num_room == 1))
603 /* posting into an ML room? set the envelope from
604 * to the actual mail address so others get a valid
607 CM_SetField(msg, eenVelopeTo, valid->recp_orgroom, strlen(valid->recp_orgroom));
611 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
613 cprintf("%ld\n", msgnum);
615 if (StrLength(CCC->StatusMessage) > 0) {
616 cprintf("%s\n", ChrPtr(CCC->StatusMessage));
618 else if (msgnum >= 0L) {
619 client_write(HKEY("Message accepted.\n"));
622 client_write(HKEY("Internal error.\n"));
625 if (!CM_IsEmpty(msg, eExclusiveID)) {
626 cprintf("%s\n", msg->cm_fields[eExclusiveID]);
636 free_recipients(valid);
642 * Delete message from current room
644 void cmd_dele(char *args)
653 extract_token(msgset, args, 0, '|', sizeof msgset);
654 num_msgs = num_tokens(msgset, ',');
656 cprintf("%d Nothing to do.\n", CIT_OK);
660 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
661 cprintf("%d Higher access required.\n",
662 ERROR + HIGHER_ACCESS_REQUIRED);
667 * Build our message set to be moved/copied
669 msgs = malloc(num_msgs * sizeof(long));
670 for (i=0; i<num_msgs; ++i) {
671 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
672 msgs[i] = atol(msgtok);
675 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
679 cprintf("%d %d message%s deleted.\n", CIT_OK,
680 num_deleted, ((num_deleted != 1) ? "s" : ""));
682 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
689 * move or copy a message to another room
691 void cmd_move(char *args)
698 char targ[ROOMNAMELEN];
699 struct ctdlroom qtemp;
706 extract_token(msgset, args, 0, '|', sizeof msgset);
707 num_msgs = num_tokens(msgset, ',');
709 cprintf("%d Nothing to do.\n", CIT_OK);
713 extract_token(targ, args, 1, '|', sizeof targ);
714 convert_room_name_macros(targ, sizeof targ);
715 targ[ROOMNAMELEN - 1] = 0;
716 is_copy = extract_int(args, 2);
718 if (CtdlGetRoom(&qtemp, targ) != 0) {
719 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
723 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
724 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
728 CtdlGetUser(&CC->user, CC->curr_user);
729 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
731 /* Check for permission to perform this operation.
732 * Remember: "CC->room" is source, "qtemp" is target.
736 /* Admins can move/copy */
737 if (CC->user.axlevel >= AxAideU) permit = 1;
739 /* Room aides can move/copy */
740 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
742 /* Permit move/copy from personal rooms */
743 if ((CC->room.QRflags & QR_MAILBOX)
744 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
746 /* Permit only copy from public to personal room */
748 && (!(CC->room.QRflags & QR_MAILBOX))
749 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
751 /* Permit message removal from collaborative delete rooms */
752 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
754 /* Users allowed to post into the target room may move into it too. */
755 if ((CC->room.QRflags & QR_MAILBOX) &&
756 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
758 /* User must have access to target room */
759 if (!(ra & UA_KNOWN)) permit = 0;
762 cprintf("%d Higher access required.\n",
763 ERROR + HIGHER_ACCESS_REQUIRED);
768 * Build our message set to be moved/copied
770 msgs = malloc(num_msgs * sizeof(long));
771 for (i=0; i<num_msgs; ++i) {
772 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
773 msgs[i] = atol(msgtok);
779 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
781 cprintf("%d Cannot store message(s) in %s: error %d\n",
787 /* Now delete the message from the source room,
788 * if this is a 'move' rather than a 'copy' operation.
791 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
795 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
799 /*****************************************************************************/
800 /* MODULE INITIALIZATION STUFF */
801 /*****************************************************************************/
802 CTDL_MODULE_INIT(ctdl_message)
806 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
807 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
808 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
809 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
810 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
811 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
812 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
813 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
814 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
815 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
816 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
819 /* return our Subversion id for the Log */
820 return "ctdl_message";