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) {
364 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
367 supplied_euid[0] = 0;
370 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
371 extract_token(references, entargs, 11, '|', sizeof references);
372 for (ptr=references; *ptr != 0; ++ptr) {
373 if (*ptr == '!') *ptr = '|';
376 /* first check to make sure the request is valid. */
378 err = CtdlDoIHavePermissionToPostInThisRoom(
383 (!IsEmptyStr(references)) /* is this a reply? or a top-level post? */
387 cprintf("%d %s\n", err, errmsg);
391 /* Check some other permission type things. */
393 if (IsEmptyStr(newusername)) {
394 strcpy(newusername, CCC->user.fullname);
396 if ( (CCC->user.axlevel < AxAideU)
397 && (strcasecmp(newusername, CCC->user.fullname))
398 && (strcasecmp(newusername, CCC->cs_inet_fn))
400 cprintf("%d You don't have permission to author messages as '%s'.\n",
401 ERROR + HIGHER_ACCESS_REQUIRED,
408 if (IsEmptyStr(newuseremail)) {
412 if (!IsEmptyStr(newuseremail)) {
413 if (!strcasecmp(newuseremail, CCC->cs_inet_email)) {
416 else if (!IsEmptyStr(CCC->cs_inet_other_emails)) {
417 j = num_tokens(CCC->cs_inet_other_emails, '|');
418 for (i=0; i<j; ++i) {
419 extract_token(buf, CCC->cs_inet_other_emails, i, '|', sizeof buf);
420 if (!strcasecmp(newuseremail, buf)) {
427 if (!newuseremail_ok) {
428 cprintf("%d You don't have permission to author messages as '%s'.\n",
429 ERROR + HIGHER_ACCESS_REQUIRED,
435 CCC->cs_flags |= CS_POSTING;
437 /* In mailbox rooms we have to behave a little differently --
438 * make sure the user has specified at least one recipient. Then
439 * validate the recipient(s). We do this for the Mail> room, as
440 * well as any room which has the "Mailbox" view set - unless it
441 * is the DRAFTS room which does not require recipients
444 if ( ( ( (CCC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CCC->room.QRname[11], MAILROOM)) )
445 || ( (CCC->room.QRflags & QR_MAILBOX) && (CCC->curr_view == VIEW_MAILBOX) )
446 ) && (strcasecmp(&CCC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
447 if (CCC->user.axlevel < AxProbU) {
448 strcpy(recp, "sysop");
453 valid_to = validate_recipients(recp, NULL, 0);
454 if (valid_to->num_error > 0) {
455 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
456 free_recipients(valid_to);
460 valid_cc = validate_recipients(cc, NULL, 0);
461 if (valid_cc->num_error > 0) {
462 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
463 free_recipients(valid_to);
464 free_recipients(valid_cc);
468 valid_bcc = validate_recipients(bcc, NULL, 0);
469 if (valid_bcc->num_error > 0) {
470 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
471 free_recipients(valid_to);
472 free_recipients(valid_cc);
473 free_recipients(valid_bcc);
477 /* Recipient required, but none were specified */
478 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
479 free_recipients(valid_to);
480 free_recipients(valid_cc);
481 free_recipients(valid_bcc);
482 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
486 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
487 if (CtdlCheckInternetMailPermission(&CCC->user)==0) {
488 cprintf("%d You do not have permission "
489 "to send Internet mail.\n",
490 ERROR + HIGHER_ACCESS_REQUIRED);
491 free_recipients(valid_to);
492 free_recipients(valid_cc);
493 free_recipients(valid_bcc);
498 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)
499 && (CCC->user.axlevel < AxNetU) ) {
500 cprintf("%d Higher access required for network mail.\n",
501 ERROR + HIGHER_ACCESS_REQUIRED);
502 free_recipients(valid_to);
503 free_recipients(valid_cc);
504 free_recipients(valid_bcc);
508 if ((RESTRICT_INTERNET == 1)
509 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
510 && ((CCC->user.flags & US_INTERNET) == 0)
511 && (!CCC->internal_pgm)) {
512 cprintf("%d You don't have access to Internet mail.\n",
513 ERROR + HIGHER_ACCESS_REQUIRED);
514 free_recipients(valid_to);
515 free_recipients(valid_cc);
516 free_recipients(valid_bcc);
522 /* Is this a room which has anonymous-only or anonymous-option? */
523 anonymous = MES_NORMAL;
524 if (CCC->room.QRflags & QR_ANONONLY) {
525 anonymous = MES_ANONONLY;
527 if (CCC->room.QRflags & QR_ANONOPT) {
528 if (anon_flag == 1) { /* only if the user requested it */
529 anonymous = MES_ANONOPT;
533 if ((CCC->room.QRflags & QR_MAILBOX) == 0) {
537 /* Recommend to the client that the use of a message subject is
538 * strongly recommended in this room, if either the SUBJECTREQ flag
539 * is set, or if there is one or more Internet email recipients.
541 if (CCC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
542 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
543 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
544 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
546 /* If we're only checking the validity of the request, return
547 * success without creating the message.
550 cprintf("%d %s|%d\n", CIT_OK,
551 ((valid_to != NULL) ? valid_to->display_recp : ""),
553 free_recipients(valid_to);
554 free_recipients(valid_cc);
555 free_recipients(valid_bcc);
559 /* We don't need these anymore because we'll do it differently below */
560 free_recipients(valid_to);
561 free_recipients(valid_cc);
562 free_recipients(valid_bcc);
564 /* Read in the message from the client. */
566 cprintf("%d send message\n", START_CHAT_MODE);
568 cprintf("%d send message\n", SEND_LISTING);
571 msg = CtdlMakeMessage(&CCC->user, recp, cc,
572 CCC->room.QRname, anonymous, format_type,
573 newusername, newuseremail, subject,
574 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
577 /* Put together one big recipients struct containing to/cc/bcc all in
578 * one. This is for the envelope.
580 char *all_recps = malloc(SIZ * 3);
581 strcpy(all_recps, recp);
582 if (!IsEmptyStr(cc)) {
583 if (!IsEmptyStr(all_recps)) {
584 strcat(all_recps, ",");
586 strcat(all_recps, cc);
588 if (!IsEmptyStr(bcc)) {
589 if (!IsEmptyStr(all_recps)) {
590 strcat(all_recps, ",");
592 strcat(all_recps, bcc);
594 if (!IsEmptyStr(all_recps)) {
595 valid = validate_recipients(all_recps, NULL, 0);
602 if ((valid != NULL) && (valid->num_room == 1))
604 /* posting into an ML room? set the envelope from
605 * to the actual mail address so others get a valid
608 CM_SetField(msg, eenVelopeTo, valid->recp_orgroom, strlen(valid->recp_orgroom));
612 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
614 cprintf("%ld\n", msgnum);
616 if (StrLength(CCC->StatusMessage) > 0) {
617 cprintf("%s\n", ChrPtr(CCC->StatusMessage));
619 else if (msgnum >= 0L) {
620 client_write(HKEY("Message accepted.\n"));
623 client_write(HKEY("Internal error.\n"));
626 if (!CM_IsEmpty(msg, eExclusiveID)) {
627 cprintf("%s\n", msg->cm_fields[eExclusiveID]);
637 free_recipients(valid);
643 * Delete message from current room
645 void cmd_dele(char *args)
654 extract_token(msgset, args, 0, '|', sizeof msgset);
655 num_msgs = num_tokens(msgset, ',');
657 cprintf("%d Nothing to do.\n", CIT_OK);
661 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
662 cprintf("%d Higher access required.\n",
663 ERROR + HIGHER_ACCESS_REQUIRED);
668 * Build our message set to be moved/copied
670 msgs = malloc(num_msgs * sizeof(long));
671 for (i=0; i<num_msgs; ++i) {
672 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
673 msgs[i] = atol(msgtok);
676 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
680 cprintf("%d %d message%s deleted.\n", CIT_OK,
681 num_deleted, ((num_deleted != 1) ? "s" : ""));
683 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
690 * move or copy a message to another room
692 void cmd_move(char *args)
699 char targ[ROOMNAMELEN];
700 struct ctdlroom qtemp;
707 extract_token(msgset, args, 0, '|', sizeof msgset);
708 num_msgs = num_tokens(msgset, ',');
710 cprintf("%d Nothing to do.\n", CIT_OK);
714 extract_token(targ, args, 1, '|', sizeof targ);
715 convert_room_name_macros(targ, sizeof targ);
716 targ[ROOMNAMELEN - 1] = 0;
717 is_copy = extract_int(args, 2);
719 if (CtdlGetRoom(&qtemp, targ) != 0) {
720 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
724 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
725 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
729 CtdlGetUser(&CC->user, CC->curr_user);
730 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
732 /* Check for permission to perform this operation.
733 * Remember: "CC->room" is source, "qtemp" is target.
737 /* Admins can move/copy */
738 if (CC->user.axlevel >= AxAideU) permit = 1;
740 /* Room aides can move/copy */
741 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
743 /* Permit move/copy from personal rooms */
744 if ((CC->room.QRflags & QR_MAILBOX)
745 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
747 /* Permit only copy from public to personal room */
749 && (!(CC->room.QRflags & QR_MAILBOX))
750 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
752 /* Permit message removal from collaborative delete rooms */
753 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
755 /* Users allowed to post into the target room may move into it too. */
756 if ((CC->room.QRflags & QR_MAILBOX) &&
757 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
759 /* User must have access to target room */
760 if (!(ra & UA_KNOWN)) permit = 0;
763 cprintf("%d Higher access required.\n",
764 ERROR + HIGHER_ACCESS_REQUIRED);
769 * Build our message set to be moved/copied
771 msgs = malloc(num_msgs * sizeof(long));
772 for (i=0; i<num_msgs; ++i) {
773 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
774 msgs[i] = atol(msgtok);
780 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
782 cprintf("%d Cannot store message(s) in %s: error %d\n",
788 /* Now delete the message from the source room,
789 * if this is a 'move' rather than a 'copy' operation.
792 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
796 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
800 /*****************************************************************************/
801 /* MODULE INITIALIZATION STUFF */
802 /*****************************************************************************/
803 CTDL_MODULE_INIT(ctdl_message)
807 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
808 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
809 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
810 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
811 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
812 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
813 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
814 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
815 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
816 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
817 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
820 /* return our Subversion id for the Log */
821 return "ctdl_message";