4 * Implements the message store.
14 #if TIME_WITH_SYS_TIME
15 # include <sys/time.h>
19 # include <sys/time.h>
34 #include "serv_extensions.h"
38 #include "sysdep_decls.h"
39 #include "citserver.h"
46 #include "mime_parser.h"
49 #include "internet_addressing.h"
50 #include "serv_fulltext.h"
52 #include "euidindex.h"
53 #include "journaling.h"
54 #include "citadel_dirs.h"
57 struct addresses_to_be_filed *atbf = NULL;
60 * This really belongs in serv_network.c, but I don't know how to export
61 * symbols between modules.
63 struct FilterList *filterlist = NULL;
67 * These are the four-character field headers we use when outputting
68 * messages in Citadel format (as opposed to RFC822 format).
71 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
72 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
73 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
74 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
75 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
76 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
77 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
78 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
106 * This function is self explanatory.
107 * (What can I say, I'm in a weird mood today...)
109 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
113 for (i = 0; i < strlen(name); ++i) {
114 if (name[i] == '@') {
115 while (isspace(name[i - 1]) && i > 0) {
116 strcpy(&name[i - 1], &name[i]);
119 while (isspace(name[i + 1])) {
120 strcpy(&name[i + 1], &name[i + 2]);
128 * Aliasing for network mail.
129 * (Error messages have been commented out, because this is a server.)
131 int alias(char *name)
132 { /* process alias and routing info for mail */
135 char aaa[SIZ], bbb[SIZ];
136 char *ignetcfg = NULL;
137 char *ignetmap = NULL;
144 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
145 stripallbut(name, '<', '>');
147 fp = fopen(file_mail_aliases, "r");
149 fp = fopen("/dev/null", "r");
156 while (fgets(aaa, sizeof aaa, fp) != NULL) {
157 while (isspace(name[0]))
158 strcpy(name, &name[1]);
159 aaa[strlen(aaa) - 1] = 0;
161 for (a = 0; a < strlen(aaa); ++a) {
163 strcpy(bbb, &aaa[a + 1]);
167 if (!strcasecmp(name, aaa))
172 /* Hit the Global Address Book */
173 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
177 lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name);
179 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
180 for (a=0; a<strlen(name); ++a) {
181 if (name[a] == '@') {
182 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
184 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
189 /* determine local or remote type, see citadel.h */
190 at = haschar(name, '@');
191 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
192 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
193 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
195 /* figure out the delivery mode */
196 extract_token(node, name, 1, '@', sizeof node);
198 /* If there are one or more dots in the nodename, we assume that it
199 * is an FQDN and will attempt SMTP delivery to the Internet.
201 if (haschar(node, '.') > 0) {
202 return(MES_INTERNET);
205 /* Otherwise we look in the IGnet maps for a valid Citadel node.
206 * Try directly-connected nodes first...
208 ignetcfg = CtdlGetSysConfig(IGNETCFG);
209 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
210 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
211 extract_token(testnode, buf, 0, '|', sizeof testnode);
212 if (!strcasecmp(node, testnode)) {
220 * Then try nodes that are two or more hops away.
222 ignetmap = CtdlGetSysConfig(IGNETMAP);
223 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
224 extract_token(buf, ignetmap, i, '\n', sizeof buf);
225 extract_token(testnode, buf, 0, '|', sizeof testnode);
226 if (!strcasecmp(node, testnode)) {
233 /* If we get to this point it's an invalid node name */
242 fp = fopen(file_citadel_control, "r");
244 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
245 file_citadel_control,
249 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
255 * Back end for the MSGS command: output message number only.
257 void simple_listing(long msgnum, void *userdata)
259 cprintf("%ld\n", msgnum);
265 * Back end for the MSGS command: output header summary.
267 void headers_listing(long msgnum, void *userdata)
269 struct CtdlMessage *msg;
271 msg = CtdlFetchMessage(msgnum, 0);
273 cprintf("%ld|0|||||\n", msgnum);
277 cprintf("%ld|%s|%s|%s|%s|%s|\n",
279 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
280 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
281 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
282 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
283 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
285 CtdlFreeMessage(msg);
290 /* Determine if a given message matches the fields in a message template.
291 * Return 0 for a successful match.
293 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
296 /* If there aren't any fields in the template, all messages will
299 if (template == NULL) return(0);
301 /* Null messages are bogus. */
302 if (msg == NULL) return(1);
304 for (i='A'; i<='Z'; ++i) {
305 if (template->cm_fields[i] != NULL) {
306 if (msg->cm_fields[i] == NULL) {
309 if (strcasecmp(msg->cm_fields[i],
310 template->cm_fields[i])) return 1;
314 /* All compares succeeded: we have a match! */
321 * Retrieve the "seen" message list for the current room.
323 void CtdlGetSeen(char *buf, int which_set) {
326 /* Learn about the user and room in question */
327 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
329 if (which_set == ctdlsetseen_seen)
330 safestrncpy(buf, vbuf.v_seen, SIZ);
331 if (which_set == ctdlsetseen_answered)
332 safestrncpy(buf, vbuf.v_answered, SIZ);
338 * Manipulate the "seen msgs" string (or other message set strings)
340 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
341 int target_setting, int which_set,
342 struct ctdluser *which_user, struct ctdlroom *which_room) {
343 struct cdbdata *cdbfr;
355 char *is_set; /* actually an array of booleans */
358 char setstr[SIZ], lostr[SIZ], histr[SIZ];
361 lprintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %d, %d)\n",
362 num_target_msgnums, target_msgnums[0],
363 target_setting, which_set);
365 /* Learn about the user and room in question */
366 CtdlGetRelationship(&vbuf,
367 ((which_user != NULL) ? which_user : &CC->user),
368 ((which_room != NULL) ? which_room : &CC->room)
371 /* Load the message list */
372 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
374 msglist = (long *) cdbfr->ptr;
375 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
376 num_msgs = cdbfr->len / sizeof(long);
379 return; /* No messages at all? No further action. */
382 is_set = malloc(num_msgs * sizeof(char));
383 memset(is_set, 0, (num_msgs * sizeof(char)) );
385 /* Decide which message set we're manipulating */
387 case ctdlsetseen_seen:
388 safestrncpy(vset, vbuf.v_seen, sizeof vset);
390 case ctdlsetseen_answered:
391 safestrncpy(vset, vbuf.v_answered, sizeof vset);
395 /* lprintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
397 /* Translate the existing sequence set into an array of booleans */
398 num_sets = num_tokens(vset, ',');
399 for (s=0; s<num_sets; ++s) {
400 extract_token(setstr, vset, s, ',', sizeof setstr);
402 extract_token(lostr, setstr, 0, ':', sizeof lostr);
403 if (num_tokens(setstr, ':') >= 2) {
404 extract_token(histr, setstr, 1, ':', sizeof histr);
405 if (!strcmp(histr, "*")) {
406 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
410 strcpy(histr, lostr);
415 for (i = 0; i < num_msgs; ++i) {
416 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
422 /* Now translate the array of booleans back into a sequence set */
427 for (i=0; i<num_msgs; ++i) {
429 is_seen = is_set[i]; /* Default to existing setting */
431 for (k=0; k<num_target_msgnums; ++k) {
432 if (msglist[i] == target_msgnums[k]) {
433 is_seen = target_setting;
438 if (lo < 0L) lo = msglist[i];
442 if ( ((is_seen == 0) && (was_seen == 1))
443 || ((is_seen == 1) && (i == num_msgs-1)) ) {
445 /* begin trim-o-matic code */
448 while ( (strlen(vset) + 20) > sizeof vset) {
449 remove_token(vset, 0, ',');
451 if (j--) break; /* loop no more than 9 times */
453 if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
457 snprintf(lostr, sizeof lostr,
458 "1:%ld,%s", t, vset);
459 safestrncpy(vset, lostr, sizeof vset);
461 /* end trim-o-matic code */
469 snprintf(&vset[tmp], (sizeof vset) - tmp,
473 snprintf(&vset[tmp], (sizeof vset) - tmp,
482 /* Decide which message set we're manipulating */
484 case ctdlsetseen_seen:
485 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
487 case ctdlsetseen_answered:
488 safestrncpy(vbuf.v_answered, vset,
489 sizeof vbuf.v_answered);
494 /* lprintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
496 CtdlSetRelationship(&vbuf,
497 ((which_user != NULL) ? which_user : &CC->user),
498 ((which_room != NULL) ? which_room : &CC->room)
504 * API function to perform an operation for each qualifying message in the
505 * current room. (Returns the number of messages processed.)
507 int CtdlForEachMessage(int mode, long ref,
509 struct CtdlMessage *compare,
510 void (*CallBack) (long, void *),
516 struct cdbdata *cdbfr;
517 long *msglist = NULL;
519 int num_processed = 0;
522 struct CtdlMessage *msg;
525 int printed_lastold = 0;
527 /* Learn about the user and room in question */
529 getuser(&CC->user, CC->curr_user);
530 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
532 /* Load the message list */
533 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
535 msglist = (long *) cdbfr->ptr;
536 cdbfr->ptr = NULL; /* CtdlForEachMessage() now owns this memory */
537 num_msgs = cdbfr->len / sizeof(long);
540 return 0; /* No messages at all? No further action. */
545 * Now begin the traversal.
547 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
549 /* If the caller is looking for a specific MIME type, filter
550 * out all messages which are not of the type requested.
552 if (content_type != NULL) if (strlen(content_type) > 0) {
554 /* This call to GetMetaData() sits inside this loop
555 * so that we only do the extra database read per msg
556 * if we need to. Doing the extra read all the time
557 * really kills the server. If we ever need to use
558 * metadata for another search criterion, we need to
559 * move the read somewhere else -- but still be smart
560 * enough to only do the read if the caller has
561 * specified something that will need it.
563 GetMetaData(&smi, msglist[a]);
565 if (strcasecmp(smi.meta_content_type, content_type)) {
571 num_msgs = sort_msglist(msglist, num_msgs);
573 /* If a template was supplied, filter out the messages which
574 * don't match. (This could induce some delays!)
577 if (compare != NULL) {
578 for (a = 0; a < num_msgs; ++a) {
579 msg = CtdlFetchMessage(msglist[a], 1);
581 if (CtdlMsgCmp(msg, compare)) {
584 CtdlFreeMessage(msg);
592 * Now iterate through the message list, according to the
593 * criteria supplied by the caller.
596 for (a = 0; a < num_msgs; ++a) {
597 thismsg = msglist[a];
598 if (mode == MSGS_ALL) {
602 is_seen = is_msg_in_sequence_set(
603 vbuf.v_seen, thismsg);
604 if (is_seen) lastold = thismsg;
610 || ((mode == MSGS_OLD) && (is_seen))
611 || ((mode == MSGS_NEW) && (!is_seen))
612 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
613 || ((mode == MSGS_FIRST) && (a < ref))
614 || ((mode == MSGS_GT) && (thismsg > ref))
615 || ((mode == MSGS_EQ) && (thismsg == ref))
618 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
620 CallBack(lastold, userdata);
624 if (CallBack) CallBack(thismsg, userdata);
628 free(msglist); /* Clean up */
629 return num_processed;
635 * cmd_msgs() - get list of message #'s in this room
636 * implements the MSGS server command using CtdlForEachMessage()
638 void cmd_msgs(char *cmdbuf)
647 int with_template = 0;
648 struct CtdlMessage *template = NULL;
649 int with_headers = 0;
651 extract_token(which, cmdbuf, 0, '|', sizeof which);
652 cm_ref = extract_int(cmdbuf, 1);
653 with_template = extract_int(cmdbuf, 2);
654 with_headers = extract_int(cmdbuf, 3);
658 if (!strncasecmp(which, "OLD", 3))
660 else if (!strncasecmp(which, "NEW", 3))
662 else if (!strncasecmp(which, "FIRST", 5))
664 else if (!strncasecmp(which, "LAST", 4))
666 else if (!strncasecmp(which, "GT", 2))
669 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
670 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
676 cprintf("%d Send template then receive message list\n",
678 template = (struct CtdlMessage *)
679 malloc(sizeof(struct CtdlMessage));
680 memset(template, 0, sizeof(struct CtdlMessage));
681 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
682 extract_token(tfield, buf, 0, '|', sizeof tfield);
683 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
684 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
685 if (!strcasecmp(tfield, msgkeys[i])) {
686 template->cm_fields[i] =
694 cprintf("%d \n", LISTING_FOLLOWS);
697 CtdlForEachMessage(mode,
701 (with_headers ? headers_listing : simple_listing),
704 if (template != NULL) CtdlFreeMessage(template);
712 * help_subst() - support routine for help file viewer
714 void help_subst(char *strbuf, char *source, char *dest)
719 while (p = pattern2(strbuf, source), (p >= 0)) {
720 strcpy(workbuf, &strbuf[p + strlen(source)]);
721 strcpy(&strbuf[p], dest);
722 strcat(strbuf, workbuf);
727 void do_help_subst(char *buffer)
731 help_subst(buffer, "^nodename", config.c_nodename);
732 help_subst(buffer, "^humannode", config.c_humannode);
733 help_subst(buffer, "^fqdn", config.c_fqdn);
734 help_subst(buffer, "^username", CC->user.fullname);
735 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
736 help_subst(buffer, "^usernum", buf2);
737 help_subst(buffer, "^sysadm", config.c_sysadm);
738 help_subst(buffer, "^variantname", CITADEL);
739 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
740 help_subst(buffer, "^maxsessions", buf2);
741 help_subst(buffer, "^bbsdir", ctdl_bbsbase_dir);
747 * memfmout() - Citadel text formatter and paginator.
748 * Although the original purpose of this routine was to format
749 * text to the reader's screen width, all we're really using it
750 * for here is to format text out to 80 columns before sending it
751 * to the client. The client software may reformat it again.
754 char *mptr, /* where are we going to get our text from? */
755 char subst, /* nonzero if we should do substitutions */
756 char *nl) /* string to terminate lines with */
764 static int width = 80;
769 c = 1; /* c is the current pos */
773 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
775 buffer[strlen(buffer) + 1] = 0;
776 buffer[strlen(buffer)] = ch;
779 if (buffer[0] == '^')
780 do_help_subst(buffer);
782 buffer[strlen(buffer) + 1] = 0;
784 strcpy(buffer, &buffer[1]);
792 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
795 if (((old == 13) || (old == 10)) && (isspace(real))) {
800 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
801 cprintf("%s%s", nl, aaa);
810 if ((strlen(aaa) + c) > (width - 5)) {
819 if ((ch == 13) || (ch == 10)) {
820 cprintf("%s%s", aaa, nl);
827 cprintf("%s%s", aaa, nl);
833 * Callback function for mime parser that simply lists the part
835 void list_this_part(char *name, char *filename, char *partnum, char *disp,
836 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
841 ma = (struct ma_info *)cbuserdata;
842 if (ma->is_ma == 0) {
843 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
844 name, filename, partnum, disp, cbtype, (long)length);
849 * Callback function for multipart prefix
851 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
852 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
857 ma = (struct ma_info *)cbuserdata;
858 if (!strcasecmp(cbtype, "multipart/alternative")) {
862 if (ma->is_ma == 0) {
863 cprintf("pref=%s|%s\n", partnum, cbtype);
868 * Callback function for multipart sufffix
870 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
871 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
876 ma = (struct ma_info *)cbuserdata;
877 if (ma->is_ma == 0) {
878 cprintf("suff=%s|%s\n", partnum, cbtype);
880 if (!strcasecmp(cbtype, "multipart/alternative")) {
887 * Callback function for mime parser that opens a section for downloading
889 void mime_download(char *name, char *filename, char *partnum, char *disp,
890 void *content, char *cbtype, char *cbcharset, size_t length,
891 char *encoding, void *cbuserdata)
894 /* Silently go away if there's already a download open... */
895 if (CC->download_fp != NULL)
898 /* ...or if this is not the desired section */
899 if (strcasecmp(CC->download_desired_section, partnum))
902 CC->download_fp = tmpfile();
903 if (CC->download_fp == NULL)
906 fwrite(content, length, 1, CC->download_fp);
907 fflush(CC->download_fp);
908 rewind(CC->download_fp);
910 OpenCmdResult(filename, cbtype);
916 * Load a message from disk into memory.
917 * This is used by CtdlOutputMsg() and other fetch functions.
919 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
920 * using the CtdlMessageFree() function.
922 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
924 struct cdbdata *dmsgtext;
925 struct CtdlMessage *ret = NULL;
929 cit_uint8_t field_header;
931 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
933 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
934 if (dmsgtext == NULL) {
937 mptr = dmsgtext->ptr;
938 upper_bound = mptr + dmsgtext->len;
940 /* Parse the three bytes that begin EVERY message on disk.
941 * The first is always 0xFF, the on-disk magic number.
942 * The second is the anonymous/public type byte.
943 * The third is the format type byte (vari, fixed, or MIME).
948 "Message %ld appears to be corrupted.\n",
953 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
954 memset(ret, 0, sizeof(struct CtdlMessage));
956 ret->cm_magic = CTDLMESSAGE_MAGIC;
957 ret->cm_anon_type = *mptr++; /* Anon type byte */
958 ret->cm_format_type = *mptr++; /* Format type byte */
961 * The rest is zero or more arbitrary fields. Load them in.
962 * We're done when we encounter either a zero-length field or
963 * have just processed the 'M' (message text) field.
966 if (mptr >= upper_bound) {
969 field_header = *mptr++;
970 ret->cm_fields[field_header] = strdup(mptr);
972 while (*mptr++ != 0); /* advance to next field */
974 } while ((mptr < upper_bound) && (field_header != 'M'));
978 /* Always make sure there's something in the msg text field. If
979 * it's NULL, the message text is most likely stored separately,
980 * so go ahead and fetch that. Failing that, just set a dummy
981 * body so other code doesn't barf.
983 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
984 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
985 if (dmsgtext != NULL) {
986 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
990 if (ret->cm_fields['M'] == NULL) {
991 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
994 /* Perform "before read" hooks (aborting if any return nonzero) */
995 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
996 CtdlFreeMessage(ret);
1005 * Returns 1 if the supplied pointer points to a valid Citadel message.
1006 * If the pointer is NULL or the magic number check fails, returns 0.
1008 int is_valid_message(struct CtdlMessage *msg) {
1011 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1012 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1020 * 'Destructor' for struct CtdlMessage
1022 void CtdlFreeMessage(struct CtdlMessage *msg)
1026 if (is_valid_message(msg) == 0) return;
1028 for (i = 0; i < 256; ++i)
1029 if (msg->cm_fields[i] != NULL) {
1030 free(msg->cm_fields[i]);
1033 msg->cm_magic = 0; /* just in case */
1039 * Pre callback function for multipart/alternative
1041 * NOTE: this differs from the standard behavior for a reason. Normally when
1042 * displaying multipart/alternative you want to show the _last_ usable
1043 * format in the message. Here we show the _first_ one, because it's
1044 * usually text/plain. Since this set of functions is designed for text
1045 * output to non-MIME-aware clients, this is the desired behavior.
1048 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1049 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1054 ma = (struct ma_info *)cbuserdata;
1055 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1056 if (!strcasecmp(cbtype, "multipart/alternative")) {
1060 if (!strcasecmp(cbtype, "message/rfc822")) {
1066 * Post callback function for multipart/alternative
1068 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1069 void *content, char *cbtype, char *cbcharset, size_t length,
1070 char *encoding, void *cbuserdata)
1074 ma = (struct ma_info *)cbuserdata;
1075 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1076 if (!strcasecmp(cbtype, "multipart/alternative")) {
1080 if (!strcasecmp(cbtype, "message/rfc822")) {
1086 * Inline callback function for mime parser that wants to display text
1088 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1089 void *content, char *cbtype, char *cbcharset, size_t length,
1090 char *encoding, void *cbuserdata)
1097 ma = (struct ma_info *)cbuserdata;
1100 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1101 partnum, filename, cbtype, (long)length);
1104 * If we're in the middle of a multipart/alternative scope and
1105 * we've already printed another section, skip this one.
1107 if ( (ma->is_ma) && (ma->did_print) ) {
1108 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n",
1114 if ( (!strcasecmp(cbtype, "text/plain"))
1115 || (strlen(cbtype)==0) ) {
1118 client_write(wptr, length);
1119 if (wptr[length-1] != '\n') {
1124 else if (!strcasecmp(cbtype, "text/html")) {
1125 ptr = html_to_ascii(content, length, 80, 0);
1127 client_write(ptr, wlen);
1128 if (ptr[wlen-1] != '\n') {
1133 else if (PerformFixedOutputHooks(cbtype, content, length)) {
1134 /* above function returns nonzero if it handled the part */
1136 else if (strncasecmp(cbtype, "multipart/", 10)) {
1137 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1138 partnum, filename, cbtype, (long)length);
1143 * The client is elegant and sophisticated and wants to be choosy about
1144 * MIME content types, so figure out which multipart/alternative part
1145 * we're going to send.
1147 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1148 void *content, char *cbtype, char *cbcharset, size_t length,
1149 char *encoding, void *cbuserdata)
1155 ma = (struct ma_info *)cbuserdata;
1157 if (ma->is_ma > 0) {
1158 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1159 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1160 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1161 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1168 * Now that we've chosen our preferred part, output it.
1170 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1171 void *content, char *cbtype, char *cbcharset, size_t length,
1172 char *encoding, void *cbuserdata)
1176 int add_newline = 0;
1180 ma = (struct ma_info *)cbuserdata;
1182 /* This is not the MIME part you're looking for... */
1183 if (strcasecmp(partnum, ma->chosen_part)) return;
1185 /* If the content-type of this part is in our preferred formats
1186 * list, we can simply output it verbatim.
1188 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1189 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1190 if (!strcasecmp(buf, cbtype)) {
1191 /* Yeah! Go! W00t!! */
1193 text_content = (char *)content;
1194 if (text_content[length-1] != '\n') {
1198 cprintf("Content-type: %s", cbtype);
1199 if (strlen(cbcharset) > 0) {
1200 cprintf("; charset=%s", cbcharset);
1202 cprintf("\nContent-length: %d\n",
1203 (int)(length + add_newline) );
1204 if (strlen(encoding) > 0) {
1205 cprintf("Content-transfer-encoding: %s\n", encoding);
1208 cprintf("Content-transfer-encoding: 7bit\n");
1211 client_write(content, length);
1212 if (add_newline) cprintf("\n");
1217 /* No translations required or possible: output as text/plain */
1218 cprintf("Content-type: text/plain\n\n");
1219 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1220 length, encoding, cbuserdata);
1225 char desired_section[64];
1232 * Callback function for
1234 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1235 void *content, char *cbtype, char *cbcharset, size_t length,
1236 char *encoding, void *cbuserdata)
1238 struct encapmsg *encap;
1240 encap = (struct encapmsg *)cbuserdata;
1242 /* Only proceed if this is the desired section... */
1243 if (!strcasecmp(encap->desired_section, partnum)) {
1244 encap->msglen = length;
1245 encap->msg = malloc(length + 2);
1246 memcpy(encap->msg, content, length);
1256 * Get a message off disk. (returns om_* values found in msgbase.h)
1259 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1260 int mode, /* how would you like that message? */
1261 int headers_only, /* eschew the message body? */
1262 int do_proto, /* do Citadel protocol responses? */
1263 int crlf, /* Use CRLF newlines instead of LF? */
1264 char *section /* NULL or a message/rfc822 section */
1266 struct CtdlMessage *TheMessage = NULL;
1267 int retcode = om_no_such_msg;
1268 struct encapmsg encap;
1270 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1272 (section ? section : "<>")
1275 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1276 if (do_proto) cprintf("%d Not logged in.\n",
1277 ERROR + NOT_LOGGED_IN);
1278 return(om_not_logged_in);
1281 /* FIXME: check message id against msglist for this room */
1284 * Fetch the message from disk. If we're in any sort of headers
1285 * only mode, request that we don't even bother loading the body
1288 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1289 TheMessage = CtdlFetchMessage(msg_num, 0);
1292 TheMessage = CtdlFetchMessage(msg_num, 1);
1295 if (TheMessage == NULL) {
1296 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1297 ERROR + MESSAGE_NOT_FOUND, msg_num);
1298 return(om_no_such_msg);
1301 /* Here is the weird form of this command, to process only an
1302 * encapsulated message/rfc822 section.
1304 if (section) if (strlen(section)>0) if (strcmp(section, "0")) {
1305 memset(&encap, 0, sizeof encap);
1306 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1307 mime_parser(TheMessage->cm_fields['M'],
1309 *extract_encapsulated_message,
1310 NULL, NULL, (void *)&encap, 0
1312 CtdlFreeMessage(TheMessage);
1316 encap.msg[encap.msglen] = 0;
1317 TheMessage = convert_internet_message(encap.msg);
1318 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1320 /* Now we let it fall through to the bottom of this
1321 * function, because TheMessage now contains the
1322 * encapsulated message instead of the top-level
1323 * message. Isn't that neat?
1328 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1329 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1330 retcode = om_no_such_msg;
1335 /* Ok, output the message now */
1336 retcode = CtdlOutputPreLoadedMsg(
1338 headers_only, do_proto, crlf);
1339 CtdlFreeMessage(TheMessage);
1346 * Get a message off disk. (returns om_* values found in msgbase.h)
1349 int CtdlOutputPreLoadedMsg(
1350 struct CtdlMessage *TheMessage,
1351 int mode, /* how would you like that message? */
1352 int headers_only, /* eschew the message body? */
1353 int do_proto, /* do Citadel protocol responses? */
1354 int crlf /* Use CRLF newlines instead of LF? */
1360 char display_name[256];
1362 char *nl; /* newline string */
1364 int subject_found = 0;
1367 /* Buffers needed for RFC822 translation. These are all filled
1368 * using functions that are bounds-checked, and therefore we can
1369 * make them substantially smaller than SIZ.
1377 char datestamp[100];
1379 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1380 ((TheMessage == NULL) ? "NULL" : "not null"),
1381 mode, headers_only, do_proto, crlf);
1383 strcpy(mid, "unknown");
1384 nl = (crlf ? "\r\n" : "\n");
1386 if (!is_valid_message(TheMessage)) {
1388 "ERROR: invalid preloaded message for output\n");
1389 return(om_no_such_msg);
1392 /* Are we downloading a MIME component? */
1393 if (mode == MT_DOWNLOAD) {
1394 if (TheMessage->cm_format_type != FMT_RFC822) {
1396 cprintf("%d This is not a MIME message.\n",
1397 ERROR + ILLEGAL_VALUE);
1398 } else if (CC->download_fp != NULL) {
1399 if (do_proto) cprintf(
1400 "%d You already have a download open.\n",
1401 ERROR + RESOURCE_BUSY);
1403 /* Parse the message text component */
1404 mptr = TheMessage->cm_fields['M'];
1405 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1406 /* If there's no file open by this time, the requested
1407 * section wasn't found, so print an error
1409 if (CC->download_fp == NULL) {
1410 if (do_proto) cprintf(
1411 "%d Section %s not found.\n",
1412 ERROR + FILE_NOT_FOUND,
1413 CC->download_desired_section);
1416 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1419 /* now for the user-mode message reading loops */
1420 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1422 /* Does the caller want to skip the headers? */
1423 if (headers_only == HEADERS_NONE) goto START_TEXT;
1425 /* Tell the client which format type we're using. */
1426 if ( (mode == MT_CITADEL) && (do_proto) ) {
1427 cprintf("type=%d\n", TheMessage->cm_format_type);
1430 /* nhdr=yes means that we're only displaying headers, no body */
1431 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1432 && (mode == MT_CITADEL)
1435 cprintf("nhdr=yes\n");
1438 /* begin header processing loop for Citadel message format */
1440 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1442 safestrncpy(display_name, "<unknown>", sizeof display_name);
1443 if (TheMessage->cm_fields['A']) {
1444 strcpy(buf, TheMessage->cm_fields['A']);
1445 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1446 safestrncpy(display_name, "****", sizeof display_name);
1448 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1449 safestrncpy(display_name, "anonymous", sizeof display_name);
1452 safestrncpy(display_name, buf, sizeof display_name);
1454 if ((is_room_aide())
1455 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1456 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1457 size_t tmp = strlen(display_name);
1458 snprintf(&display_name[tmp],
1459 sizeof display_name - tmp,
1464 /* Don't show Internet address for users on the
1465 * local Citadel network.
1468 if (TheMessage->cm_fields['N'] != NULL)
1469 if (strlen(TheMessage->cm_fields['N']) > 0)
1470 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1474 /* Now spew the header fields in the order we like them. */
1475 safestrncpy(allkeys, FORDER, sizeof allkeys);
1476 for (i=0; i<strlen(allkeys); ++i) {
1477 k = (int) allkeys[i];
1479 if ( (TheMessage->cm_fields[k] != NULL)
1480 && (msgkeys[k] != NULL) ) {
1482 if (do_proto) cprintf("%s=%s\n",
1486 else if ((k == 'F') && (suppress_f)) {
1489 /* Masquerade display name if needed */
1491 if (do_proto) cprintf("%s=%s\n",
1493 TheMessage->cm_fields[k]
1502 /* begin header processing loop for RFC822 transfer format */
1507 strcpy(snode, NODENAME);
1508 strcpy(lnode, HUMANNODE);
1509 if (mode == MT_RFC822) {
1510 for (i = 0; i < 256; ++i) {
1511 if (TheMessage->cm_fields[i]) {
1512 mptr = TheMessage->cm_fields[i];
1515 safestrncpy(luser, mptr, sizeof luser);
1516 safestrncpy(suser, mptr, sizeof suser);
1518 else if (i == 'Y') {
1519 cprintf("CC: %s%s", mptr, nl);
1521 else if (i == 'U') {
1522 cprintf("Subject: %s%s", mptr, nl);
1526 safestrncpy(mid, mptr, sizeof mid);
1528 safestrncpy(lnode, mptr, sizeof lnode);
1530 safestrncpy(fuser, mptr, sizeof fuser);
1531 /* else if (i == 'O')
1532 cprintf("X-Citadel-Room: %s%s",
1535 safestrncpy(snode, mptr, sizeof snode);
1537 cprintf("To: %s%s", mptr, nl);
1538 else if (i == 'T') {
1539 datestring(datestamp, sizeof datestamp,
1540 atol(mptr), DATESTRING_RFC822);
1541 cprintf("Date: %s%s", datestamp, nl);
1545 if (subject_found == 0) {
1546 cprintf("Subject: (no subject)%s", nl);
1550 for (i=0; i<strlen(suser); ++i) {
1551 suser[i] = tolower(suser[i]);
1552 if (!isalnum(suser[i])) suser[i]='_';
1555 if (mode == MT_RFC822) {
1556 if (!strcasecmp(snode, NODENAME)) {
1557 safestrncpy(snode, FQDN, sizeof snode);
1560 /* Construct a fun message id */
1561 cprintf("Message-ID: <%s", mid);
1562 if (strchr(mid, '@')==NULL) {
1563 cprintf("@%s", snode);
1567 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1568 cprintf("From: \"----\" <x@x.org>%s", nl);
1570 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1571 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1573 else if (strlen(fuser) > 0) {
1574 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1577 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1580 cprintf("Organization: %s%s", lnode, nl);
1582 /* Blank line signifying RFC822 end-of-headers */
1583 if (TheMessage->cm_format_type != FMT_RFC822) {
1588 /* end header processing loop ... at this point, we're in the text */
1590 if (headers_only == HEADERS_FAST) goto DONE;
1591 mptr = TheMessage->cm_fields['M'];
1593 /* Tell the client about the MIME parts in this message */
1594 if (TheMessage->cm_format_type == FMT_RFC822) {
1595 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1596 memset(&ma, 0, sizeof(struct ma_info));
1597 mime_parser(mptr, NULL,
1598 (do_proto ? *list_this_part : NULL),
1599 (do_proto ? *list_this_pref : NULL),
1600 (do_proto ? *list_this_suff : NULL),
1603 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1604 char *start_of_text = NULL;
1605 start_of_text = strstr(mptr, "\n\r\n");
1606 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1607 if (start_of_text == NULL) start_of_text = mptr;
1609 start_of_text = strstr(start_of_text, "\n");
1611 while (ch=*mptr, ch!=0) {
1615 else switch(headers_only) {
1617 if (mptr >= start_of_text) {
1618 if (ch == 10) cprintf("%s", nl);
1619 else cprintf("%c", ch);
1623 if (mptr < start_of_text) {
1624 if (ch == 10) cprintf("%s", nl);
1625 else cprintf("%c", ch);
1629 if (ch == 10) cprintf("%s", nl);
1630 else cprintf("%c", ch);
1639 if (headers_only == HEADERS_ONLY) {
1643 /* signify start of msg text */
1644 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1645 if (do_proto) cprintf("text\n");
1648 /* If the format type on disk is 1 (fixed-format), then we want
1649 * everything to be output completely literally ... regardless of
1650 * what message transfer format is in use.
1652 if (TheMessage->cm_format_type == FMT_FIXED) {
1653 if (mode == MT_MIME) {
1654 cprintf("Content-type: text/plain\n\n");
1657 while (ch = *mptr++, ch > 0) {
1660 if ((ch == 10) || (strlen(buf) > 250)) {
1661 cprintf("%s%s", buf, nl);
1664 buf[strlen(buf) + 1] = 0;
1665 buf[strlen(buf)] = ch;
1668 if (strlen(buf) > 0)
1669 cprintf("%s%s", buf, nl);
1672 /* If the message on disk is format 0 (Citadel vari-format), we
1673 * output using the formatter at 80 columns. This is the final output
1674 * form if the transfer format is RFC822, but if the transfer format
1675 * is Citadel proprietary, it'll still work, because the indentation
1676 * for new paragraphs is correct and the client will reformat the
1677 * message to the reader's screen width.
1679 if (TheMessage->cm_format_type == FMT_CITADEL) {
1680 if (mode == MT_MIME) {
1681 cprintf("Content-type: text/x-citadel-variformat\n\n");
1683 memfmout(mptr, 0, nl);
1686 /* If the message on disk is format 4 (MIME), we've gotta hand it
1687 * off to the MIME parser. The client has already been told that
1688 * this message is format 1 (fixed format), so the callback function
1689 * we use will display those parts as-is.
1691 if (TheMessage->cm_format_type == FMT_RFC822) {
1692 memset(&ma, 0, sizeof(struct ma_info));
1694 if (mode == MT_MIME) {
1695 strcpy(ma.chosen_part, "1");
1696 mime_parser(mptr, NULL,
1697 *choose_preferred, *fixed_output_pre,
1698 *fixed_output_post, (void *)&ma, 0);
1699 mime_parser(mptr, NULL,
1700 *output_preferred, NULL, NULL, (void *)&ma, 0);
1703 mime_parser(mptr, NULL,
1704 *fixed_output, *fixed_output_pre,
1705 *fixed_output_post, (void *)&ma, 0);
1710 DONE: /* now we're done */
1711 if (do_proto) cprintf("000\n");
1718 * display a message (mode 0 - Citadel proprietary)
1720 void cmd_msg0(char *cmdbuf)
1723 int headers_only = HEADERS_ALL;
1725 msgid = extract_long(cmdbuf, 0);
1726 headers_only = extract_int(cmdbuf, 1);
1728 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL);
1734 * display a message (mode 2 - RFC822)
1736 void cmd_msg2(char *cmdbuf)
1739 int headers_only = HEADERS_ALL;
1741 msgid = extract_long(cmdbuf, 0);
1742 headers_only = extract_int(cmdbuf, 1);
1744 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL);
1750 * display a message (mode 3 - IGnet raw format - internal programs only)
1752 void cmd_msg3(char *cmdbuf)
1755 struct CtdlMessage *msg;
1758 if (CC->internal_pgm == 0) {
1759 cprintf("%d This command is for internal programs only.\n",
1760 ERROR + HIGHER_ACCESS_REQUIRED);
1764 msgnum = extract_long(cmdbuf, 0);
1765 msg = CtdlFetchMessage(msgnum, 1);
1767 cprintf("%d Message %ld not found.\n",
1768 ERROR + MESSAGE_NOT_FOUND, msgnum);
1772 serialize_message(&smr, msg);
1773 CtdlFreeMessage(msg);
1776 cprintf("%d Unable to serialize message\n",
1777 ERROR + INTERNAL_ERROR);
1781 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1782 client_write((char *)smr.ser, (int)smr.len);
1789 * Display a message using MIME content types
1791 void cmd_msg4(char *cmdbuf)
1796 msgid = extract_long(cmdbuf, 0);
1797 extract_token(section, cmdbuf, 1, '|', sizeof section);
1798 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) );
1804 * Client tells us its preferred message format(s)
1806 void cmd_msgp(char *cmdbuf)
1808 safestrncpy(CC->preferred_formats, cmdbuf,
1809 sizeof(CC->preferred_formats));
1810 cprintf("%d ok\n", CIT_OK);
1815 * Open a component of a MIME message as a download file
1817 void cmd_opna(char *cmdbuf)
1820 char desired_section[128];
1822 msgid = extract_long(cmdbuf, 0);
1823 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1824 safestrncpy(CC->download_desired_section, desired_section,
1825 sizeof CC->download_desired_section);
1826 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL);
1831 * Save a message pointer into a specified room
1832 * (Returns 0 for success, nonzero for failure)
1833 * roomname may be NULL to use the current room
1835 * Note that the 'supplied_msg' field may be set to NULL, in which case
1836 * the message will be fetched from disk, by number, if we need to perform
1837 * replication checks. This adds an additional database read, so if the
1838 * caller already has the message in memory then it should be supplied.
1840 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check,
1841 struct CtdlMessage *supplied_msg) {
1843 char hold_rm[ROOMNAMELEN];
1844 struct cdbdata *cdbfr;
1847 long highest_msg = 0L;
1848 struct CtdlMessage *msg = NULL;
1850 /*lprintf(CTDL_DEBUG,
1851 "CtdlSaveMsgPointerInRoom(room=%s, msgid=%ld, repl=%d)\n",
1852 roomname, msgid, do_repl_check);*/
1854 strcpy(hold_rm, CC->room.QRname);
1856 /* Now the regular stuff */
1857 if (lgetroom(&CC->room,
1858 ((roomname != NULL) ? roomname : CC->room.QRname) )
1860 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1861 return(ERROR + ROOM_NOT_FOUND);
1864 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1865 if (cdbfr == NULL) {
1869 msglist = (long *) cdbfr->ptr;
1870 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
1871 num_msgs = cdbfr->len / sizeof(long);
1875 /* Make sure the message doesn't already exist in this room. It
1876 * is absolutely taboo to have more than one reference to the same
1877 * message in a room.
1879 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1880 if (msglist[i] == msgid) {
1881 lputroom(&CC->room); /* unlock the room */
1882 getroom(&CC->room, hold_rm);
1884 return(ERROR + ALREADY_EXISTS);
1888 /* Now add the new message */
1890 msglist = realloc(msglist, (num_msgs * sizeof(long)));
1892 if (msglist == NULL) {
1893 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1895 msglist[num_msgs - 1] = msgid;
1897 /* Sort the message list, so all the msgid's are in order */
1898 num_msgs = sort_msglist(msglist, num_msgs);
1900 /* Determine the highest message number */
1901 highest_msg = msglist[num_msgs - 1];
1903 /* Write it back to disk. */
1904 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1905 msglist, (int)(num_msgs * sizeof(long)));
1907 /* Free up the memory we used. */
1910 /* Update the highest-message pointer and unlock the room. */
1911 CC->room.QRhighest = highest_msg;
1912 lputroom(&CC->room);
1914 /* Perform replication checks if necessary */
1915 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
1916 if (supplied_msg != NULL) {
1920 msg = CtdlFetchMessage(msgid, 0);
1924 ReplicationChecks(msg);
1929 /* If the message has an Exclusive ID, index that... */
1931 if (msg->cm_fields['E'] != NULL) {
1932 index_message_by_euid(msg->cm_fields['E'],
1937 /* Free up the memory we may have allocated */
1938 if ( (msg != NULL) && (msg != supplied_msg) ) {
1939 CtdlFreeMessage(msg);
1942 /* Go back to the room we were in before we wandered here... */
1943 getroom(&CC->room, hold_rm);
1945 /* Bump the reference count for this message. */
1946 AdjRefCount(msgid, +1);
1948 /* Return success. */
1955 * Message base operation to save a new message to the message store
1956 * (returns new message number)
1958 * This is the back end for CtdlSubmitMsg() and should not be directly
1959 * called by server-side modules.
1962 long send_message(struct CtdlMessage *msg) {
1970 /* Get a new message number */
1971 newmsgid = get_new_message_number();
1972 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1974 /* Generate an ID if we don't have one already */
1975 if (msg->cm_fields['I']==NULL) {
1976 msg->cm_fields['I'] = strdup(msgidbuf);
1979 /* If the message is big, set its body aside for storage elsewhere */
1980 if (msg->cm_fields['M'] != NULL) {
1981 if (strlen(msg->cm_fields['M']) > BIGMSG) {
1983 holdM = msg->cm_fields['M'];
1984 msg->cm_fields['M'] = NULL;
1988 /* Serialize our data structure for storage in the database */
1989 serialize_message(&smr, msg);
1992 msg->cm_fields['M'] = holdM;
1996 cprintf("%d Unable to serialize message\n",
1997 ERROR + INTERNAL_ERROR);
2001 /* Write our little bundle of joy into the message base */
2002 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2003 smr.ser, smr.len) < 0) {
2004 lprintf(CTDL_ERR, "Can't store message\n");
2008 cdb_store(CDB_BIGMSGS,
2018 /* Free the memory we used for the serialized message */
2021 /* Return the *local* message ID to the caller
2022 * (even if we're storing an incoming network message)
2030 * Serialize a struct CtdlMessage into the format used on disk and network.
2032 * This function loads up a "struct ser_ret" (defined in server.h) which
2033 * contains the length of the serialized message and a pointer to the
2034 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2036 void serialize_message(struct ser_ret *ret, /* return values */
2037 struct CtdlMessage *msg) /* unserialized msg */
2041 static char *forder = FORDER;
2043 if (is_valid_message(msg) == 0) return; /* self check */
2046 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2047 ret->len = ret->len +
2048 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2050 lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
2051 ret->ser = malloc(ret->len);
2052 if (ret->ser == NULL) {
2058 ret->ser[1] = msg->cm_anon_type;
2059 ret->ser[2] = msg->cm_format_type;
2062 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2063 ret->ser[wlen++] = (char)forder[i];
2064 strcpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
2065 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
2067 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2068 (long)ret->len, (long)wlen);
2076 * Check to see if any messages already exist in the current room which
2077 * carry the same Exclusive ID as this one. If any are found, delete them.
2079 void ReplicationChecks(struct CtdlMessage *msg) {
2080 long old_msgnum = (-1L);
2082 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2084 lprintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2087 /* No exclusive id? Don't do anything. */
2088 if (msg == NULL) return;
2089 if (msg->cm_fields['E'] == NULL) return;
2090 if (strlen(msg->cm_fields['E']) == 0) return;
2091 /*lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2092 msg->cm_fields['E'], CC->room.QRname);*/
2094 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2095 if (old_msgnum > 0L) {
2096 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2097 CtdlDeleteMessages(CC->room.QRname, old_msgnum, "", 0);
2104 * Save a message to disk and submit it into the delivery system.
2106 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2107 struct recptypes *recps, /* recipients (if mail) */
2108 char *force /* force a particular room? */
2110 char submit_filename[128];
2111 char generated_timestamp[32];
2112 char hold_rm[ROOMNAMELEN];
2113 char actual_rm[ROOMNAMELEN];
2114 char force_room[ROOMNAMELEN];
2115 char content_type[SIZ]; /* We have to learn this */
2116 char recipient[SIZ];
2119 struct ctdluser userbuf;
2121 struct MetaData smi;
2122 FILE *network_fp = NULL;
2123 static int seqnum = 1;
2124 struct CtdlMessage *imsg = NULL;
2127 char *hold_R, *hold_D;
2128 char *collected_addresses = NULL;
2129 struct addresses_to_be_filed *aptr = NULL;
2130 char *saved_rfc822_version = NULL;
2131 int qualified_for_journaling = 0;
2133 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2134 if (is_valid_message(msg) == 0) return(-1); /* self check */
2136 /* If this message has no timestamp, we take the liberty of
2137 * giving it one, right now.
2139 if (msg->cm_fields['T'] == NULL) {
2140 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2141 msg->cm_fields['T'] = strdup(generated_timestamp);
2144 /* If this message has no path, we generate one.
2146 if (msg->cm_fields['P'] == NULL) {
2147 if (msg->cm_fields['A'] != NULL) {
2148 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2149 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2150 if (isspace(msg->cm_fields['P'][a])) {
2151 msg->cm_fields['P'][a] = ' ';
2156 msg->cm_fields['P'] = strdup("unknown");
2160 if (force == NULL) {
2161 strcpy(force_room, "");
2164 strcpy(force_room, force);
2167 /* Learn about what's inside, because it's what's inside that counts */
2168 if (msg->cm_fields['M'] == NULL) {
2169 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2173 switch (msg->cm_format_type) {
2175 strcpy(content_type, "text/x-citadel-variformat");
2178 strcpy(content_type, "text/plain");
2181 strcpy(content_type, "text/plain");
2182 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type: ");
2184 safestrncpy(content_type, &mptr[14],
2185 sizeof content_type);
2186 for (a = 0; a < strlen(content_type); ++a) {
2187 if ((content_type[a] == ';')
2188 || (content_type[a] == ' ')
2189 || (content_type[a] == 13)
2190 || (content_type[a] == 10)) {
2191 content_type[a] = 0;
2197 /* Goto the correct room */
2198 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
2199 strcpy(hold_rm, CC->room.QRname);
2200 strcpy(actual_rm, CC->room.QRname);
2201 if (recps != NULL) {
2202 strcpy(actual_rm, SENTITEMS);
2205 /* If the user is a twit, move to the twit room for posting */
2207 if (CC->user.axlevel == 2) {
2208 strcpy(hold_rm, actual_rm);
2209 strcpy(actual_rm, config.c_twitroom);
2210 lprintf(CTDL_DEBUG, "Diverting to twit room\n");
2214 /* ...or if this message is destined for Aide> then go there. */
2215 if (strlen(force_room) > 0) {
2216 strcpy(actual_rm, force_room);
2219 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2220 if (strcasecmp(actual_rm, CC->room.QRname)) {
2221 /* getroom(&CC->room, actual_rm); */
2222 usergoto(actual_rm, 0, 1, NULL, NULL);
2226 * If this message has no O (room) field, generate one.
2228 if (msg->cm_fields['O'] == NULL) {
2229 msg->cm_fields['O'] = strdup(CC->room.QRname);
2232 /* Perform "before save" hooks (aborting if any return nonzero) */
2233 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2234 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2237 * If this message has an Exclusive ID, and the room is replication
2238 * checking enabled, then do replication checks.
2240 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2241 ReplicationChecks(msg);
2244 /* Save it to disk */
2245 lprintf(CTDL_DEBUG, "Saving to disk\n");
2246 newmsgid = send_message(msg);
2247 if (newmsgid <= 0L) return(-5);
2249 /* Write a supplemental message info record. This doesn't have to
2250 * be a critical section because nobody else knows about this message
2253 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2254 memset(&smi, 0, sizeof(struct MetaData));
2255 smi.meta_msgnum = newmsgid;
2256 smi.meta_refcount = 0;
2257 safestrncpy(smi.meta_content_type, content_type,
2258 sizeof smi.meta_content_type);
2261 * Measure how big this message will be when rendered as RFC822.
2262 * We do this for two reasons:
2263 * 1. We need the RFC822 length for the new metadata record, so the
2264 * POP and IMAP services don't have to calculate message lengths
2265 * while the user is waiting (multiplied by potentially hundreds
2266 * or thousands of messages).
2267 * 2. If journaling is enabled, we will need an RFC822 version of the
2268 * message to attach to the journalized copy.
2270 if (CC->redirect_buffer != NULL) {
2271 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2274 CC->redirect_buffer = malloc(SIZ);
2275 CC->redirect_len = 0;
2276 CC->redirect_alloc = SIZ;
2277 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2278 smi.meta_rfc822_length = CC->redirect_len;
2279 saved_rfc822_version = CC->redirect_buffer;
2280 CC->redirect_buffer = NULL;
2281 CC->redirect_len = 0;
2282 CC->redirect_alloc = 0;
2286 /* Now figure out where to store the pointers */
2287 lprintf(CTDL_DEBUG, "Storing pointers\n");
2289 /* If this is being done by the networker delivering a private
2290 * message, we want to BYPASS saving the sender's copy (because there
2291 * is no local sender; it would otherwise go to the Trashcan).
2293 if ((!CC->internal_pgm) || (recps == NULL)) {
2294 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2295 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2296 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2300 /* For internet mail, drop a copy in the outbound queue room */
2302 if (recps->num_internet > 0) {
2303 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2306 /* If other rooms are specified, drop them there too. */
2308 if (recps->num_room > 0)
2309 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2310 extract_token(recipient, recps->recp_room, i,
2311 '|', sizeof recipient);
2312 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2313 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2316 /* Bump this user's messages posted counter. */
2317 lprintf(CTDL_DEBUG, "Updating user\n");
2318 lgetuser(&CC->user, CC->curr_user);
2319 CC->user.posted = CC->user.posted + 1;
2320 lputuser(&CC->user);
2322 /* If this is private, local mail, make a copy in the
2323 * recipient's mailbox and bump the reference count.
2326 if (recps->num_local > 0)
2327 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2328 extract_token(recipient, recps->recp_local, i,
2329 '|', sizeof recipient);
2330 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2332 if (getuser(&userbuf, recipient) == 0) {
2333 MailboxName(actual_rm, sizeof actual_rm,
2334 &userbuf, MAILROOM);
2335 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2336 BumpNewMailCounter(userbuf.usernum);
2339 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2340 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2345 /* Perform "after save" hooks */
2346 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2347 PerformMessageHooks(msg, EVT_AFTERSAVE);
2349 /* For IGnet mail, we have to save a new copy into the spooler for
2350 * each recipient, with the R and D fields set to the recipient and
2351 * destination-node. This has two ugly side effects: all other
2352 * recipients end up being unlisted in this recipient's copy of the
2353 * message, and it has to deliver multiple messages to the same
2354 * node. We'll revisit this again in a year or so when everyone has
2355 * a network spool receiver that can handle the new style messages.
2358 if (recps->num_ignet > 0)
2359 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2360 extract_token(recipient, recps->recp_ignet, i,
2361 '|', sizeof recipient);
2363 hold_R = msg->cm_fields['R'];
2364 hold_D = msg->cm_fields['D'];
2365 msg->cm_fields['R'] = malloc(SIZ);
2366 msg->cm_fields['D'] = malloc(128);
2367 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2368 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2370 serialize_message(&smr, msg);
2372 snprintf(submit_filename, sizeof submit_filename,
2373 "%s/netmail.%04lx.%04x.%04x",
2375 (long) getpid(), CC->cs_pid, ++seqnum);
2376 network_fp = fopen(submit_filename, "wb+");
2377 if (network_fp != NULL) {
2378 fwrite(smr.ser, smr.len, 1, network_fp);
2384 free(msg->cm_fields['R']);
2385 free(msg->cm_fields['D']);
2386 msg->cm_fields['R'] = hold_R;
2387 msg->cm_fields['D'] = hold_D;
2390 /* Go back to the room we started from */
2391 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2392 if (strcasecmp(hold_rm, CC->room.QRname))
2393 /* getroom(&CC->room, hold_rm); */
2394 usergoto(hold_rm, 0, 1, NULL, NULL);
2396 /* For internet mail, generate delivery instructions.
2397 * Yes, this is recursive. Deal with it. Infinite recursion does
2398 * not happen because the delivery instructions message does not
2399 * contain a recipient.
2402 if (recps->num_internet > 0) {
2403 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2404 instr = malloc(SIZ * 2);
2405 snprintf(instr, SIZ * 2,
2406 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2408 SPOOLMIME, newmsgid, (long)time(NULL),
2409 msg->cm_fields['A'], msg->cm_fields['N']
2412 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2413 size_t tmp = strlen(instr);
2414 extract_token(recipient, recps->recp_internet,
2415 i, '|', sizeof recipient);
2416 snprintf(&instr[tmp], SIZ * 2 - tmp,
2417 "remote|%s|0||\n", recipient);
2420 imsg = malloc(sizeof(struct CtdlMessage));
2421 memset(imsg, 0, sizeof(struct CtdlMessage));
2422 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2423 imsg->cm_anon_type = MES_NORMAL;
2424 imsg->cm_format_type = FMT_RFC822;
2425 imsg->cm_fields['A'] = strdup("Citadel");
2426 imsg->cm_fields['J'] = strdup("do not journal");
2427 imsg->cm_fields['M'] = instr;
2428 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2429 CtdlFreeMessage(imsg);
2433 * Any addresses to harvest for someone's address book?
2435 if ( (CC->logged_in) && (recps != NULL) ) {
2436 collected_addresses = harvest_collected_addresses(msg);
2439 if (collected_addresses != NULL) {
2440 begin_critical_section(S_ATBF);
2441 aptr = (struct addresses_to_be_filed *)
2442 malloc(sizeof(struct addresses_to_be_filed));
2444 MailboxName(actual_rm, sizeof actual_rm,
2445 &CC->user, USERCONTACTSROOM);
2446 aptr->roomname = strdup(actual_rm);
2447 aptr->collected_addresses = collected_addresses;
2449 end_critical_section(S_ATBF);
2453 * Determine whether this message qualifies for journaling.
2455 if (msg->cm_fields['J'] != NULL) {
2456 qualified_for_journaling = 0;
2459 if (recps == NULL) {
2460 qualified_for_journaling = config.c_journal_pubmsgs;
2462 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2463 qualified_for_journaling = config.c_journal_email;
2466 qualified_for_journaling = config.c_journal_pubmsgs;
2471 * Do we have to perform journaling? If so, hand off the saved
2472 * RFC822 version will be handed off to the journaler for background
2473 * submit. Otherwise, we have to free the memory ourselves.
2475 if (saved_rfc822_version != NULL) {
2476 if (qualified_for_journaling) {
2477 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2480 free(saved_rfc822_version);
2493 * Convenience function for generating small administrative messages.
2495 void quickie_message(char *from, char *to, char *room, char *text,
2496 int format_type, char *subject)
2498 struct CtdlMessage *msg;
2499 struct recptypes *recp = NULL;
2501 msg = malloc(sizeof(struct CtdlMessage));
2502 memset(msg, 0, sizeof(struct CtdlMessage));
2503 msg->cm_magic = CTDLMESSAGE_MAGIC;
2504 msg->cm_anon_type = MES_NORMAL;
2505 msg->cm_format_type = format_type;
2506 msg->cm_fields['A'] = strdup(from);
2507 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2508 msg->cm_fields['N'] = strdup(NODENAME);
2510 msg->cm_fields['R'] = strdup(to);
2511 recp = validate_recipients(to);
2513 if (subject != NULL) {
2514 msg->cm_fields['U'] = strdup(subject);
2516 msg->cm_fields['M'] = strdup(text);
2518 CtdlSubmitMsg(msg, recp, room);
2519 CtdlFreeMessage(msg);
2520 if (recp != NULL) free(recp);
2526 * Back end function used by CtdlMakeMessage() and similar functions
2528 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2529 size_t maxlen, /* maximum message length */
2530 char *exist, /* if non-null, append to it;
2531 exist is ALWAYS freed */
2532 int crlf /* CRLF newlines instead of LF */
2536 size_t message_len = 0;
2537 size_t buffer_len = 0;
2543 if (exist == NULL) {
2550 message_len = strlen(exist);
2551 buffer_len = message_len + 4096;
2552 m = realloc(exist, buffer_len);
2559 /* flush the input if we have nowhere to store it */
2564 /* read in the lines of message text one by one */
2566 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2567 if (!strcmp(buf, terminator)) finished = 1;
2569 strcat(buf, "\r\n");
2575 if ( (!flushing) && (!finished) ) {
2576 /* Measure the line */
2577 linelen = strlen(buf);
2579 /* augment the buffer if we have to */
2580 if ((message_len + linelen) >= buffer_len) {
2581 ptr = realloc(m, (buffer_len * 2) );
2582 if (ptr == NULL) { /* flush if can't allocate */
2585 buffer_len = (buffer_len * 2);
2587 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2591 /* Add the new line to the buffer. NOTE: this loop must avoid
2592 * using functions like strcat() and strlen() because they
2593 * traverse the entire buffer upon every call, and doing that
2594 * for a multi-megabyte message slows it down beyond usability.
2596 strcpy(&m[message_len], buf);
2597 message_len += linelen;
2600 /* if we've hit the max msg length, flush the rest */
2601 if (message_len >= maxlen) flushing = 1;
2603 } while (!finished);
2611 * Build a binary message to be saved on disk.
2612 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2613 * will become part of the message. This means you are no longer
2614 * responsible for managing that memory -- it will be freed along with
2615 * the rest of the fields when CtdlFreeMessage() is called.)
2618 struct CtdlMessage *CtdlMakeMessage(
2619 struct ctdluser *author, /* author's user structure */
2620 char *recipient, /* NULL if it's not mail */
2621 char *recp_cc, /* NULL if it's not mail */
2622 char *room, /* room where it's going */
2623 int type, /* see MES_ types in header file */
2624 int format_type, /* variformat, plain text, MIME... */
2625 char *fake_name, /* who we're masquerading as */
2626 char *subject, /* Subject (optional) */
2627 char *supplied_euid, /* ...or NULL if this is irrelevant */
2628 char *preformatted_text /* ...or NULL to read text from client */
2630 char dest_node[SIZ];
2632 struct CtdlMessage *msg;
2634 msg = malloc(sizeof(struct CtdlMessage));
2635 memset(msg, 0, sizeof(struct CtdlMessage));
2636 msg->cm_magic = CTDLMESSAGE_MAGIC;
2637 msg->cm_anon_type = type;
2638 msg->cm_format_type = format_type;
2640 /* Don't confuse the poor folks if it's not routed mail. */
2641 strcpy(dest_node, "");
2646 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2647 msg->cm_fields['P'] = strdup(buf);
2649 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2650 msg->cm_fields['T'] = strdup(buf);
2652 if (fake_name[0]) /* author */
2653 msg->cm_fields['A'] = strdup(fake_name);
2655 msg->cm_fields['A'] = strdup(author->fullname);
2657 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2658 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2661 msg->cm_fields['O'] = strdup(CC->room.QRname);
2664 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2665 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2667 if (recipient[0] != 0) {
2668 msg->cm_fields['R'] = strdup(recipient);
2670 if (recp_cc[0] != 0) {
2671 msg->cm_fields['Y'] = strdup(recp_cc);
2673 if (dest_node[0] != 0) {
2674 msg->cm_fields['D'] = strdup(dest_node);
2677 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2678 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2681 if (subject != NULL) {
2683 if (strlen(subject) > 0) {
2684 msg->cm_fields['U'] = strdup(subject);
2688 if (supplied_euid != NULL) {
2689 msg->cm_fields['E'] = strdup(supplied_euid);
2692 if (preformatted_text != NULL) {
2693 msg->cm_fields['M'] = preformatted_text;
2696 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2697 config.c_maxmsglen, NULL, 0);
2705 * Check to see whether we have permission to post a message in the current
2706 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2707 * returns 0 on success.
2709 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2711 if (!(CC->logged_in)) {
2712 snprintf(errmsgbuf, n, "Not logged in.");
2713 return (ERROR + NOT_LOGGED_IN);
2716 if ((CC->user.axlevel < 2)
2717 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2718 snprintf(errmsgbuf, n, "Need to be validated to enter "
2719 "(except in %s> to sysop)", MAILROOM);
2720 return (ERROR + HIGHER_ACCESS_REQUIRED);
2723 if ((CC->user.axlevel < 4)
2724 && (CC->room.QRflags & QR_NETWORK)) {
2725 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2726 return (ERROR + HIGHER_ACCESS_REQUIRED);
2729 if ((CC->user.axlevel < 6)
2730 && (CC->room.QRflags & QR_READONLY)) {
2731 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2732 return (ERROR + HIGHER_ACCESS_REQUIRED);
2735 strcpy(errmsgbuf, "Ok");
2741 * Check to see if the specified user has Internet mail permission
2742 * (returns nonzero if permission is granted)
2744 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2746 /* Do not allow twits to send Internet mail */
2747 if (who->axlevel <= 2) return(0);
2749 /* Globally enabled? */
2750 if (config.c_restrict == 0) return(1);
2752 /* User flagged ok? */
2753 if (who->flags & US_INTERNET) return(2);
2755 /* Aide level access? */
2756 if (who->axlevel >= 6) return(3);
2758 /* No mail for you! */
2764 * Validate recipients, count delivery types and errors, and handle aliasing
2765 * FIXME check for dupes!!!!!
2766 * Returns 0 if all addresses are ok, -1 if no addresses were specified,
2767 * or the number of addresses found invalid.
2769 struct recptypes *validate_recipients(char *supplied_recipients) {
2770 struct recptypes *ret;
2771 char recipients[SIZ];
2772 char this_recp[256];
2773 char this_recp_cooked[256];
2779 struct ctdluser tempUS;
2780 struct ctdlroom tempQR;
2784 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2785 if (ret == NULL) return(NULL);
2786 memset(ret, 0, sizeof(struct recptypes));
2789 ret->num_internet = 0;
2794 if (supplied_recipients == NULL) {
2795 strcpy(recipients, "");
2798 safestrncpy(recipients, supplied_recipients, sizeof recipients);
2801 /* Change all valid separator characters to commas */
2802 for (i=0; i<strlen(recipients); ++i) {
2803 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2804 recipients[i] = ',';
2808 /* Now start extracting recipients... */
2810 while (strlen(recipients) > 0) {
2812 for (i=0; i<=strlen(recipients); ++i) {
2813 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
2814 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
2815 safestrncpy(this_recp, recipients, i+1);
2817 if (recipients[i] == ',') {
2818 strcpy(recipients, &recipients[i+1]);
2821 strcpy(recipients, "");
2828 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
2830 mailtype = alias(this_recp);
2831 mailtype = alias(this_recp);
2832 mailtype = alias(this_recp);
2833 for (j=0; j<=strlen(this_recp); ++j) {
2834 if (this_recp[j]=='_') {
2835 this_recp_cooked[j] = ' ';
2838 this_recp_cooked[j] = this_recp[j];
2844 if (!strcasecmp(this_recp, "sysop")) {
2846 strcpy(this_recp, config.c_aideroom);
2847 if (strlen(ret->recp_room) > 0) {
2848 strcat(ret->recp_room, "|");
2850 strcat(ret->recp_room, this_recp);
2852 else if (getuser(&tempUS, this_recp) == 0) {
2854 strcpy(this_recp, tempUS.fullname);
2855 if (strlen(ret->recp_local) > 0) {
2856 strcat(ret->recp_local, "|");
2858 strcat(ret->recp_local, this_recp);
2860 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2862 strcpy(this_recp, tempUS.fullname);
2863 if (strlen(ret->recp_local) > 0) {
2864 strcat(ret->recp_local, "|");
2866 strcat(ret->recp_local, this_recp);
2868 else if ( (!strncasecmp(this_recp, "room_", 5))
2869 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2871 if (strlen(ret->recp_room) > 0) {
2872 strcat(ret->recp_room, "|");
2874 strcat(ret->recp_room, &this_recp_cooked[5]);
2882 /* Yes, you're reading this correctly: if the target
2883 * domain points back to the local system or an attached
2884 * Citadel directory, the address is invalid. That's
2885 * because if the address were valid, we would have
2886 * already translated it to a local address by now.
2888 if (IsDirectory(this_recp)) {
2893 ++ret->num_internet;
2894 if (strlen(ret->recp_internet) > 0) {
2895 strcat(ret->recp_internet, "|");
2897 strcat(ret->recp_internet, this_recp);
2902 if (strlen(ret->recp_ignet) > 0) {
2903 strcat(ret->recp_ignet, "|");
2905 strcat(ret->recp_ignet, this_recp);
2913 if (strlen(ret->errormsg) == 0) {
2914 snprintf(append, sizeof append,
2915 "Invalid recipient: %s",
2919 snprintf(append, sizeof append,
2922 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2923 strcat(ret->errormsg, append);
2927 if (strlen(ret->display_recp) == 0) {
2928 strcpy(append, this_recp);
2931 snprintf(append, sizeof append, ", %s",
2934 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2935 strcat(ret->display_recp, append);
2940 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2941 ret->num_room + ret->num_error) == 0) {
2942 ret->num_error = (-1);
2943 strcpy(ret->errormsg, "No recipients specified.");
2946 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2947 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2948 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2949 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2950 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2951 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2959 * message entry - mode 0 (normal)
2961 void cmd_ent0(char *entargs)
2967 char supplied_euid[128];
2968 char masquerade_as[SIZ];
2970 int format_type = 0;
2971 char newusername[SIZ];
2972 struct CtdlMessage *msg;
2976 struct recptypes *valid = NULL;
2977 struct recptypes *valid_to = NULL;
2978 struct recptypes *valid_cc = NULL;
2979 struct recptypes *valid_bcc = NULL;
2986 post = extract_int(entargs, 0);
2987 extract_token(recp, entargs, 1, '|', sizeof recp);
2988 anon_flag = extract_int(entargs, 2);
2989 format_type = extract_int(entargs, 3);
2990 extract_token(subject, entargs, 4, '|', sizeof subject);
2991 do_confirm = extract_int(entargs, 6);
2992 extract_token(cc, entargs, 7, '|', sizeof cc);
2993 extract_token(bcc, entargs, 8, '|', sizeof bcc);
2994 switch(CC->room.QRdefaultview) {
2997 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3000 supplied_euid[0] = 0;
3004 /* first check to make sure the request is valid. */
3006 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
3008 cprintf("%d %s\n", err, errmsg);
3012 /* Check some other permission type things. */
3015 if (CC->user.axlevel < 6) {
3016 cprintf("%d You don't have permission to masquerade.\n",
3017 ERROR + HIGHER_ACCESS_REQUIRED);
3020 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3021 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
3022 safestrncpy(CC->fake_postname, newusername,
3023 sizeof(CC->fake_postname) );
3024 cprintf("%d ok\n", CIT_OK);
3027 CC->cs_flags |= CS_POSTING;
3029 /* In the Mail> room we have to behave a little differently --
3030 * make sure the user has specified at least one recipient. Then
3031 * validate the recipient(s).
3033 if ( (CC->room.QRflags & QR_MAILBOX)
3034 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
3036 if (CC->user.axlevel < 2) {
3037 strcpy(recp, "sysop");
3042 valid_to = validate_recipients(recp);
3043 if (valid_to->num_error > 0) {
3044 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
3049 valid_cc = validate_recipients(cc);
3050 if (valid_cc->num_error > 0) {
3051 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
3057 valid_bcc = validate_recipients(bcc);
3058 if (valid_bcc->num_error > 0) {
3059 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
3066 /* Recipient required, but none were specified */
3067 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3071 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3075 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3076 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3077 cprintf("%d You do not have permission "
3078 "to send Internet mail.\n",
3079 ERROR + HIGHER_ACCESS_REQUIRED);
3087 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)
3088 && (CC->user.axlevel < 4) ) {
3089 cprintf("%d Higher access required for network mail.\n",
3090 ERROR + HIGHER_ACCESS_REQUIRED);
3097 if ((RESTRICT_INTERNET == 1)
3098 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3099 && ((CC->user.flags & US_INTERNET) == 0)
3100 && (!CC->internal_pgm)) {
3101 cprintf("%d You don't have access to Internet mail.\n",
3102 ERROR + HIGHER_ACCESS_REQUIRED);
3111 /* Is this a room which has anonymous-only or anonymous-option? */
3112 anonymous = MES_NORMAL;
3113 if (CC->room.QRflags & QR_ANONONLY) {
3114 anonymous = MES_ANONONLY;
3116 if (CC->room.QRflags & QR_ANONOPT) {
3117 if (anon_flag == 1) { /* only if the user requested it */
3118 anonymous = MES_ANONOPT;
3122 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3126 /* If we're only checking the validity of the request, return
3127 * success without creating the message.
3130 cprintf("%d %s\n", CIT_OK,
3131 ((valid_to != NULL) ? valid_to->display_recp : "") );
3138 /* We don't need these anymore because we'll do it differently below */
3143 /* Handle author masquerading */
3144 if (CC->fake_postname[0]) {
3145 strcpy(masquerade_as, CC->fake_postname);
3147 else if (CC->fake_username[0]) {
3148 strcpy(masquerade_as, CC->fake_username);
3151 strcpy(masquerade_as, "");
3154 /* Read in the message from the client. */
3156 cprintf("%d send message\n", START_CHAT_MODE);
3158 cprintf("%d send message\n", SEND_LISTING);
3161 msg = CtdlMakeMessage(&CC->user, recp, cc,
3162 CC->room.QRname, anonymous, format_type,
3163 masquerade_as, subject,
3164 ((strlen(supplied_euid) > 0) ? supplied_euid : NULL),
3167 /* Put together one big recipients struct containing to/cc/bcc all in
3168 * one. This is for the envelope.
3170 char *all_recps = malloc(SIZ * 3);
3171 strcpy(all_recps, recp);
3172 if (strlen(cc) > 0) {
3173 if (strlen(all_recps) > 0) {
3174 strcat(all_recps, ",");
3176 strcat(all_recps, cc);
3178 if (strlen(bcc) > 0) {
3179 if (strlen(all_recps) > 0) {
3180 strcat(all_recps, ",");
3182 strcat(all_recps, bcc);
3184 if (strlen(all_recps) > 0) {
3185 valid = validate_recipients(all_recps);
3193 msgnum = CtdlSubmitMsg(msg, valid, "");
3196 cprintf("%ld\n", msgnum);
3198 cprintf("Message accepted.\n");
3201 cprintf("Internal error.\n");
3203 if (msg->cm_fields['E'] != NULL) {
3204 cprintf("%s\n", msg->cm_fields['E']);
3211 CtdlFreeMessage(msg);
3213 CC->fake_postname[0] = '\0';
3214 if (valid != NULL) {
3223 * API function to delete messages which match a set of criteria
3224 * (returns the actual number of messages deleted)
3226 int CtdlDeleteMessages(char *room_name, /* which room */
3227 long dmsgnum, /* or "0" for any */
3228 char *content_type, /* or "" for any */
3229 int deferred /* let TDAP sweep it later */
3233 struct ctdlroom qrbuf;
3234 struct cdbdata *cdbfr;
3235 long *msglist = NULL;
3236 long *dellist = NULL;
3239 int num_deleted = 0;
3241 struct MetaData smi;
3243 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s, %d)\n",
3244 room_name, dmsgnum, content_type, deferred);
3246 /* get room record, obtaining a lock... */
3247 if (lgetroom(&qrbuf, room_name) != 0) {
3248 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3250 return (0); /* room not found */
3252 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3254 if (cdbfr != NULL) {
3255 dellist = malloc(cdbfr->len);
3256 msglist = (long *) cdbfr->ptr;
3257 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3258 num_msgs = cdbfr->len / sizeof(long);
3262 for (i = 0; i < num_msgs; ++i) {
3265 /* Set/clear a bit for each criterion */
3267 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
3268 delete_this |= 0x01;
3270 if (strlen(content_type) == 0) {
3271 delete_this |= 0x02;
3273 GetMetaData(&smi, msglist[i]);
3274 if (!strcasecmp(smi.meta_content_type,
3276 delete_this |= 0x02;
3280 /* Delete message only if all bits are set */
3281 if (delete_this == 0x03) {
3282 dellist[num_deleted++] = msglist[i];
3287 num_msgs = sort_msglist(msglist, num_msgs);
3288 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3289 msglist, (int)(num_msgs * sizeof(long)));
3291 qrbuf.QRhighest = msglist[num_msgs - 1];
3296 * If the delete operation is "deferred" (and technically, any delete
3297 * operation not performed by THE DREADED AUTO-PURGER ought to be
3298 * a deferred delete) then we save a pointer to the message in the
3299 * DELETED_MSGS_ROOM. This will cause the reference count to remain
3300 * at least 1, which will save the user from having to synchronously
3301 * wait for various disk-intensive operations to complete.
3303 if ( (deferred) && (num_deleted) ) {
3304 for (i=0; i<num_deleted; ++i) {
3305 CtdlCopyMsgToRoom(dellist[i], DELETED_MSGS_ROOM);
3309 /* Go through the messages we pulled out of the index, and decrement
3310 * their reference counts by 1. If this is the only room the message
3311 * was in, the reference count will reach zero and the message will
3312 * automatically be deleted from the database. We do this in a
3313 * separate pass because there might be plug-in hooks getting called,
3314 * and we don't want that happening during an S_ROOMS critical
3317 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3318 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3319 AdjRefCount(dellist[i], -1);
3322 /* Now free the memory we used, and go away. */
3323 if (msglist != NULL) free(msglist);
3324 if (dellist != NULL) free(dellist);
3325 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3326 return (num_deleted);
3332 * Check whether the current user has permission to delete messages from
3333 * the current room (returns 1 for yes, 0 for no)
3335 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3336 getuser(&CC->user, CC->curr_user);
3337 if ((CC->user.axlevel < 6)
3338 && (CC->user.usernum != CC->room.QRroomaide)
3339 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3340 && (!(CC->internal_pgm))) {
3349 * Delete message from current room
3351 void cmd_dele(char *delstr)
3356 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3357 cprintf("%d Higher access required.\n",
3358 ERROR + HIGHER_ACCESS_REQUIRED);
3361 delnum = extract_long(delstr, 0);
3363 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "", 1);
3366 cprintf("%d %d message%s deleted.\n", CIT_OK,
3367 num_deleted, ((num_deleted != 1) ? "s" : ""));
3369 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3375 * Back end API function for moves and deletes
3377 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3380 err = CtdlSaveMsgPointerInRoom(dest, msgnum, 1, NULL);
3381 if (err != 0) return(err);
3389 * move or copy a message to another room
3391 void cmd_move(char *args)
3394 char targ[ROOMNAMELEN];
3395 struct ctdlroom qtemp;
3401 num = extract_long(args, 0);
3402 extract_token(targ, args, 1, '|', sizeof targ);
3403 convert_room_name_macros(targ, sizeof targ);
3404 targ[ROOMNAMELEN - 1] = 0;
3405 is_copy = extract_int(args, 2);
3407 if (getroom(&qtemp, targ) != 0) {
3408 cprintf("%d '%s' does not exist.\n",
3409 ERROR + ROOM_NOT_FOUND, targ);
3413 getuser(&CC->user, CC->curr_user);
3414 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3416 /* Check for permission to perform this operation.
3417 * Remember: "CC->room" is source, "qtemp" is target.
3421 /* Aides can move/copy */
3422 if (CC->user.axlevel >= 6) permit = 1;
3424 /* Room aides can move/copy */
3425 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3427 /* Permit move/copy from personal rooms */
3428 if ((CC->room.QRflags & QR_MAILBOX)
3429 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3431 /* Permit only copy from public to personal room */
3433 && (!(CC->room.QRflags & QR_MAILBOX))
3434 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3436 /* User must have access to target room */
3437 if (!(ra & UA_KNOWN)) permit = 0;
3440 cprintf("%d Higher access required.\n",
3441 ERROR + HIGHER_ACCESS_REQUIRED);
3445 err = CtdlCopyMsgToRoom(num, targ);
3447 cprintf("%d Cannot store message in %s: error %d\n",
3452 /* Now delete the message from the source room,
3453 * if this is a 'move' rather than a 'copy' operation.
3456 CtdlDeleteMessages(CC->room.QRname, num, "", 0);
3459 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3465 * GetMetaData() - Get the supplementary record for a message
3467 void GetMetaData(struct MetaData *smibuf, long msgnum)
3470 struct cdbdata *cdbsmi;
3473 memset(smibuf, 0, sizeof(struct MetaData));
3474 smibuf->meta_msgnum = msgnum;
3475 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3477 /* Use the negative of the message number for its supp record index */
3478 TheIndex = (0L - msgnum);
3480 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3481 if (cdbsmi == NULL) {
3482 return; /* record not found; go with defaults */
3484 memcpy(smibuf, cdbsmi->ptr,
3485 ((cdbsmi->len > sizeof(struct MetaData)) ?
3486 sizeof(struct MetaData) : cdbsmi->len));
3493 * PutMetaData() - (re)write supplementary record for a message
3495 void PutMetaData(struct MetaData *smibuf)
3499 /* Use the negative of the message number for the metadata db index */
3500 TheIndex = (0L - smibuf->meta_msgnum);
3502 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3503 smibuf->meta_msgnum, smibuf->meta_refcount);
3505 cdb_store(CDB_MSGMAIN,
3506 &TheIndex, (int)sizeof(long),
3507 smibuf, (int)sizeof(struct MetaData));
3512 * AdjRefCount - change the reference count for a message;
3513 * delete the message if it reaches zero
3515 void AdjRefCount(long msgnum, int incr)
3518 struct MetaData smi;
3521 /* This is a *tight* critical section; please keep it that way, as
3522 * it may get called while nested in other critical sections.
3523 * Complicating this any further will surely cause deadlock!
3525 begin_critical_section(S_SUPPMSGMAIN);
3526 GetMetaData(&smi, msgnum);
3527 smi.meta_refcount += incr;
3529 end_critical_section(S_SUPPMSGMAIN);
3530 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3531 msgnum, incr, smi.meta_refcount);
3533 /* If the reference count is now zero, delete the message
3534 * (and its supplementary record as well).
3536 if (smi.meta_refcount == 0) {
3537 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3539 /* Remove from fulltext index */
3540 if (config.c_enable_fulltext) {
3541 ft_index_message(msgnum, 0);
3544 /* Remove from message base */
3546 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3547 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3549 /* Remove metadata record */
3550 delnum = (0L - msgnum);
3551 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3556 * Write a generic object to this room
3558 * Note: this could be much more efficient. Right now we use two temporary
3559 * files, and still pull the message into memory as with all others.
3561 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3562 char *content_type, /* MIME type of this object */
3563 char *tempfilename, /* Where to fetch it from */
3564 struct ctdluser *is_mailbox, /* Mailbox room? */
3565 int is_binary, /* Is encoding necessary? */
3566 int is_unique, /* Del others of this type? */
3567 unsigned int flags /* Internal save flags */
3572 struct ctdlroom qrbuf;
3573 char roomname[ROOMNAMELEN];
3574 struct CtdlMessage *msg;
3576 char *raw_message = NULL;
3577 char *encoded_message = NULL;
3578 off_t raw_length = 0;
3580 if (is_mailbox != NULL) {
3581 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3584 safestrncpy(roomname, req_room, sizeof(roomname));
3587 fp = fopen(tempfilename, "rb");
3589 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3590 tempfilename, strerror(errno));
3593 fseek(fp, 0L, SEEK_END);
3594 raw_length = ftell(fp);
3596 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3598 raw_message = malloc((size_t)raw_length + 2);
3599 fread(raw_message, (size_t)raw_length, 1, fp);
3603 encoded_message = malloc((size_t)
3604 (((raw_length * 134) / 100) + 4096 ) );
3607 encoded_message = malloc((size_t)(raw_length + 4096));
3610 sprintf(encoded_message, "Content-type: %s\n", content_type);
3613 sprintf(&encoded_message[strlen(encoded_message)],
3614 "Content-transfer-encoding: base64\n\n"
3618 sprintf(&encoded_message[strlen(encoded_message)],
3619 "Content-transfer-encoding: 7bit\n\n"
3625 &encoded_message[strlen(encoded_message)],
3631 raw_message[raw_length] = 0;
3633 &encoded_message[strlen(encoded_message)],
3641 lprintf(CTDL_DEBUG, "Allocating\n");
3642 msg = malloc(sizeof(struct CtdlMessage));
3643 memset(msg, 0, sizeof(struct CtdlMessage));
3644 msg->cm_magic = CTDLMESSAGE_MAGIC;
3645 msg->cm_anon_type = MES_NORMAL;
3646 msg->cm_format_type = 4;
3647 msg->cm_fields['A'] = strdup(CC->user.fullname);
3648 msg->cm_fields['O'] = strdup(req_room);
3649 msg->cm_fields['N'] = strdup(config.c_nodename);
3650 msg->cm_fields['H'] = strdup(config.c_humannode);
3651 msg->cm_flags = flags;
3653 msg->cm_fields['M'] = encoded_message;
3655 /* Create the requested room if we have to. */
3656 if (getroom(&qrbuf, roomname) != 0) {
3657 create_room(roomname,
3658 ( (is_mailbox != NULL) ? 5 : 3 ),
3659 "", 0, 1, 0, VIEW_BBS);
3661 /* If the caller specified this object as unique, delete all
3662 * other objects of this type that are currently in the room.
3665 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3666 CtdlDeleteMessages(roomname, 0L, content_type, 0)
3669 /* Now write the data */
3670 CtdlSubmitMsg(msg, NULL, roomname);
3671 CtdlFreeMessage(msg);
3679 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3680 config_msgnum = msgnum;
3684 char *CtdlGetSysConfig(char *sysconfname) {
3685 char hold_rm[ROOMNAMELEN];
3688 struct CtdlMessage *msg;
3691 strcpy(hold_rm, CC->room.QRname);
3692 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3693 getroom(&CC->room, hold_rm);
3698 /* We want the last (and probably only) config in this room */
3699 begin_critical_section(S_CONFIG);
3700 config_msgnum = (-1L);
3701 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3702 CtdlGetSysConfigBackend, NULL);
3703 msgnum = config_msgnum;
3704 end_critical_section(S_CONFIG);
3710 msg = CtdlFetchMessage(msgnum, 1);
3712 conf = strdup(msg->cm_fields['M']);
3713 CtdlFreeMessage(msg);
3720 getroom(&CC->room, hold_rm);
3722 if (conf != NULL) do {
3723 extract_token(buf, conf, 0, '\n', sizeof buf);
3724 strcpy(conf, &conf[strlen(buf)+1]);
3725 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3730 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3731 char temp[PATH_MAX];
3734 CtdlMakeTempFileName(temp, sizeof temp);
3736 fp = fopen(temp, "w");
3737 if (fp == NULL) return;
3738 fprintf(fp, "%s", sysconfdata);
3741 /* this handy API function does all the work for us */
3742 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3748 * Determine whether a given Internet address belongs to the current user
3750 int CtdlIsMe(char *addr, int addr_buf_len)
3752 struct recptypes *recp;
3755 recp = validate_recipients(addr);
3756 if (recp == NULL) return(0);
3758 if (recp->num_local == 0) {
3763 for (i=0; i<recp->num_local; ++i) {
3764 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3765 if (!strcasecmp(addr, CC->user.fullname)) {
3777 * Citadel protocol command to do the same
3779 void cmd_isme(char *argbuf) {
3782 if (CtdlAccessCheck(ac_logged_in)) return;
3783 extract_token(addr, argbuf, 0, '|', sizeof addr);
3785 if (CtdlIsMe(addr, sizeof addr)) {
3786 cprintf("%d %s\n", CIT_OK, addr);
3789 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);