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.
16 #include <libcitadel.h>
18 #include "citserver.h"
19 #include "ctdl_module.h"
20 #include "internet_addressing.h"
24 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);
39 * Back end for the MSGS command: output header summary.
41 void headers_listing(long msgnum, void *userdata)
43 struct CtdlMessage *msg;
45 msg = CtdlFetchMessage(msgnum, 0);
47 cprintf("%ld|0|||||\n", msgnum);
51 cprintf("%ld|%s|%s|%s|%s|%s|\n",
53 (!CM_IsEmpty(msg, eTimestamp) ? msg->cm_fields[eTimestamp] : "0"),
54 (!CM_IsEmpty(msg, eAuthor) ? msg->cm_fields[eAuthor] : ""),
55 (!CM_IsEmpty(msg, eNodeName) ? msg->cm_fields[eNodeName] : ""),
56 (!CM_IsEmpty(msg, erFc822Addr) ? msg->cm_fields[erFc822Addr] : ""),
57 (!CM_IsEmpty(msg, eMsgSubject) ? msg->cm_fields[eMsgSubject] : "")
63 * Back end for the MSGS command: output EUID header.
65 void headers_euid(long msgnum, void *userdata)
67 struct CtdlMessage *msg;
69 msg = CtdlFetchMessage(msgnum, 0);
71 cprintf("%ld||\n", msgnum);
75 cprintf("%ld|%s|%s\n",
77 (!CM_IsEmpty(msg, eExclusiveID) ? msg->cm_fields[eExclusiveID] : ""),
78 (!CM_IsEmpty(msg, eTimestamp) ? msg->cm_fields[eTimestamp] : "0"));
85 * cmd_msgs() - get list of message #'s in this room
86 * implements the MSGS server command using CtdlForEachMessage()
88 void cmd_msgs(char *cmdbuf)
97 int with_template = 0;
98 struct CtdlMessage *template = NULL;
99 char search_string[1024];
100 ForEachMsgCallback CallBack;
102 if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
104 extract_token(which, cmdbuf, 0, '|', sizeof which);
105 cm_ref = extract_int(cmdbuf, 1);
106 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
107 with_template = extract_int(cmdbuf, 2);
108 switch (extract_int(cmdbuf, 3))
112 CallBack = simple_listing;
115 CallBack = headers_listing;
118 CallBack = headers_euid;
123 if (!strncasecmp(which, "OLD", 3))
125 else if (!strncasecmp(which, "NEW", 3))
127 else if (!strncasecmp(which, "FIRST", 5))
129 else if (!strncasecmp(which, "LAST", 4))
131 else if (!strncasecmp(which, "GT", 2))
133 else if (!strncasecmp(which, "LT", 2))
135 else if (!strncasecmp(which, "SEARCH", 6))
140 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
141 cprintf("%d Full text index is not enabled on this server.\n",
142 ERROR + CMD_NOT_SUPPORTED);
148 cprintf("%d Send template then receive message list\n",
150 template = (struct CtdlMessage *)
151 malloc(sizeof(struct CtdlMessage));
152 memset(template, 0, sizeof(struct CtdlMessage));
153 template->cm_magic = CTDLMESSAGE_MAGIC;
154 template->cm_anon_type = MES_NORMAL;
156 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
158 extract_token(tfield, buf, 0, '|', sizeof tfield);
159 tValueLen = extract_token(tvalue, buf, 1, '|', sizeof tvalue);
160 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
161 if (!strcasecmp(tfield, msgkeys[i])) {
162 CM_SetField(template, i, tvalue, tValueLen);
169 cprintf("%d \n", LISTING_FOLLOWS);
172 CtdlForEachMessage(mode,
173 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
174 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
179 if (template != NULL) CM_Free(template);
184 * display a message (mode 0 - Citadel proprietary)
186 void cmd_msg0(char *cmdbuf)
189 int headers_only = HEADERS_ALL;
191 msgid = extract_long(cmdbuf, 0);
192 headers_only = extract_int(cmdbuf, 1);
194 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0, NULL, NULL);
200 * display a message (mode 2 - RFC822)
202 void cmd_msg2(char *cmdbuf)
205 int headers_only = HEADERS_ALL;
207 msgid = extract_long(cmdbuf, 0);
208 headers_only = extract_int(cmdbuf, 1);
210 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0, NULL, NULL);
216 * display a message (mode 3 - IGnet raw format - internal programs only)
218 void cmd_msg3(char *cmdbuf)
221 struct CtdlMessage *msg = NULL;
224 if (CC->internal_pgm == 0) {
225 cprintf("%d This command is for internal programs only.\n",
226 ERROR + HIGHER_ACCESS_REQUIRED);
230 msgnum = extract_long(cmdbuf, 0);
231 msg = CtdlFetchMessage(msgnum, 1);
233 cprintf("%d Message %ld not found.\n",
234 ERROR + MESSAGE_NOT_FOUND, msgnum);
238 CtdlSerializeMessage(&smr, msg);
242 cprintf("%d Unable to serialize message\n",
243 ERROR + INTERNAL_ERROR);
247 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
248 client_write((char *)smr.ser, (int)smr.len);
255 * Display a message using MIME content types
257 void cmd_msg4(char *cmdbuf)
262 msgid = extract_long(cmdbuf, 0);
263 extract_token(section, cmdbuf, 1, '|', sizeof section);
264 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0, NULL, NULL);
270 * Client tells us its preferred message format(s)
272 void cmd_msgp(char *cmdbuf)
274 if (!strcasecmp(cmdbuf, "dont_decode")) {
275 CC->msg4_dont_decode = 1;
276 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
279 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
280 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
286 * Open a component of a MIME message as a download file
288 void cmd_opna(char *cmdbuf)
291 char desired_section[128];
293 msgid = extract_long(cmdbuf, 0);
294 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
295 safestrncpy(CC->download_desired_section, desired_section,
296 sizeof CC->download_desired_section);
297 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0, NULL, NULL);
302 * Open a component of a MIME message and transmit it all at once
304 void cmd_dlat(char *cmdbuf)
307 char desired_section[128];
309 msgid = extract_long(cmdbuf, 0);
310 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
311 safestrncpy(CC->download_desired_section, desired_section,
312 sizeof CC->download_desired_section);
313 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0, NULL, NULL);
317 * message entry - mode 0 (normal)
319 void cmd_ent0(char *entargs)
321 struct CitContext *CCC = CC;
326 char supplied_euid[128];
329 char newusername[256];
330 char newuseremail[256];
331 struct CtdlMessage *msg;
335 recptypes *valid = NULL;
336 recptypes *valid_to = NULL;
337 recptypes *valid_cc = NULL;
338 recptypes *valid_bcc = NULL;
340 int subject_required = 0;
345 int newuseremail_ok = 0;
346 char references[SIZ];
351 post = extract_int(entargs, 0);
352 extract_token(recp, entargs, 1, '|', sizeof recp);
353 anon_flag = extract_int(entargs, 2);
354 format_type = extract_int(entargs, 3);
355 extract_token(subject, entargs, 4, '|', sizeof subject);
356 extract_token(newusername, entargs, 5, '|', sizeof newusername);
357 do_confirm = extract_int(entargs, 6);
358 extract_token(cc, entargs, 7, '|', sizeof cc);
359 extract_token(bcc, entargs, 8, '|', sizeof bcc);
360 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";