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"
56 struct addresses_to_be_filed *atbf = NULL;
59 * This really belongs in serv_network.c, but I don't know how to export
60 * symbols between modules.
62 struct FilterList *filterlist = NULL;
66 * These are the four-character field headers we use when outputting
67 * messages in Citadel format (as opposed to RFC822 format).
70 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
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,
105 * This function is self explanatory.
106 * (What can I say, I'm in a weird mood today...)
108 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
112 for (i = 0; i < strlen(name); ++i) {
113 if (name[i] == '@') {
114 while (isspace(name[i - 1]) && i > 0) {
115 strcpy(&name[i - 1], &name[i]);
118 while (isspace(name[i + 1])) {
119 strcpy(&name[i + 1], &name[i + 2]);
127 * Aliasing for network mail.
128 * (Error messages have been commented out, because this is a server.)
130 int alias(char *name)
131 { /* process alias and routing info for mail */
134 char aaa[SIZ], bbb[SIZ];
135 char *ignetcfg = NULL;
136 char *ignetmap = NULL;
144 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
145 stripallbut(name, '<', '>');
148 * DIRTY HACK FOLLOWS! due to configs in the network dir in the
149 * legacy installations, we need to calculate ifdeffed here.
160 fp = fopen(filename, "r");
162 fp = fopen("/dev/null", "r");
169 while (fgets(aaa, sizeof aaa, fp) != NULL) {
170 while (isspace(name[0]))
171 strcpy(name, &name[1]);
172 aaa[strlen(aaa) - 1] = 0;
174 for (a = 0; a < strlen(aaa); ++a) {
176 strcpy(bbb, &aaa[a + 1]);
180 if (!strcasecmp(name, aaa))
185 /* Hit the Global Address Book */
186 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
190 lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name);
192 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
193 for (a=0; a<strlen(name); ++a) {
194 if (name[a] == '@') {
195 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
197 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
202 /* determine local or remote type, see citadel.h */
203 at = haschar(name, '@');
204 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
205 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
206 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
208 /* figure out the delivery mode */
209 extract_token(node, name, 1, '@', sizeof node);
211 /* If there are one or more dots in the nodename, we assume that it
212 * is an FQDN and will attempt SMTP delivery to the Internet.
214 if (haschar(node, '.') > 0) {
215 return(MES_INTERNET);
218 /* Otherwise we look in the IGnet maps for a valid Citadel node.
219 * Try directly-connected nodes first...
221 ignetcfg = CtdlGetSysConfig(IGNETCFG);
222 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
223 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
224 extract_token(testnode, buf, 0, '|', sizeof testnode);
225 if (!strcasecmp(node, testnode)) {
233 * Then try nodes that are two or more hops away.
235 ignetmap = CtdlGetSysConfig(IGNETMAP);
236 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
237 extract_token(buf, ignetmap, i, '\n', sizeof buf);
238 extract_token(testnode, buf, 0, '|', sizeof testnode);
239 if (!strcasecmp(node, testnode)) {
246 /* If we get to this point it's an invalid node name */
255 fp = fopen(file_citadel_control, "r");
257 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
258 file_citadel_control,
262 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
268 * Back end for the MSGS command: output message number only.
270 void simple_listing(long msgnum, void *userdata)
272 cprintf("%ld\n", msgnum);
278 * Back end for the MSGS command: output header summary.
280 void headers_listing(long msgnum, void *userdata)
282 struct CtdlMessage *msg;
284 msg = CtdlFetchMessage(msgnum, 0);
286 cprintf("%ld|0|||||\n", msgnum);
290 cprintf("%ld|%s|%s|%s|%s|%s|\n",
292 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
293 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
294 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
295 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
296 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
298 CtdlFreeMessage(msg);
303 /* Determine if a given message matches the fields in a message template.
304 * Return 0 for a successful match.
306 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
309 /* If there aren't any fields in the template, all messages will
312 if (template == NULL) return(0);
314 /* Null messages are bogus. */
315 if (msg == NULL) return(1);
317 for (i='A'; i<='Z'; ++i) {
318 if (template->cm_fields[i] != NULL) {
319 if (msg->cm_fields[i] == NULL) {
322 if (strcasecmp(msg->cm_fields[i],
323 template->cm_fields[i])) return 1;
327 /* All compares succeeded: we have a match! */
334 * Retrieve the "seen" message list for the current room.
336 void CtdlGetSeen(char *buf, int which_set) {
339 /* Learn about the user and room in question */
340 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
342 if (which_set == ctdlsetseen_seen)
343 safestrncpy(buf, vbuf.v_seen, SIZ);
344 if (which_set == ctdlsetseen_answered)
345 safestrncpy(buf, vbuf.v_answered, SIZ);
351 * Manipulate the "seen msgs" string (or other message set strings)
353 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
354 int target_setting, int which_set,
355 struct ctdluser *which_user, struct ctdlroom *which_room) {
356 struct cdbdata *cdbfr;
368 char *is_set; /* actually an array of booleans */
371 char setstr[SIZ], lostr[SIZ], histr[SIZ];
374 lprintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %d, %d)\n",
375 num_target_msgnums, target_msgnums[0],
376 target_setting, which_set);
378 /* Learn about the user and room in question */
379 CtdlGetRelationship(&vbuf,
380 ((which_user != NULL) ? which_user : &CC->user),
381 ((which_room != NULL) ? which_room : &CC->room)
384 /* Load the message list */
385 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
387 msglist = (long *) cdbfr->ptr;
388 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
389 num_msgs = cdbfr->len / sizeof(long);
392 return; /* No messages at all? No further action. */
395 is_set = malloc(num_msgs * sizeof(char));
396 memset(is_set, 0, (num_msgs * sizeof(char)) );
398 /* Decide which message set we're manipulating */
400 case ctdlsetseen_seen:
401 safestrncpy(vset, vbuf.v_seen, sizeof vset);
403 case ctdlsetseen_answered:
404 safestrncpy(vset, vbuf.v_answered, sizeof vset);
408 /* lprintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
410 /* Translate the existing sequence set into an array of booleans */
411 num_sets = num_tokens(vset, ',');
412 for (s=0; s<num_sets; ++s) {
413 extract_token(setstr, vset, s, ',', sizeof setstr);
415 extract_token(lostr, setstr, 0, ':', sizeof lostr);
416 if (num_tokens(setstr, ':') >= 2) {
417 extract_token(histr, setstr, 1, ':', sizeof histr);
418 if (!strcmp(histr, "*")) {
419 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
423 strcpy(histr, lostr);
428 for (i = 0; i < num_msgs; ++i) {
429 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
435 /* Now translate the array of booleans back into a sequence set */
440 for (i=0; i<num_msgs; ++i) {
442 is_seen = is_set[i]; /* Default to existing setting */
444 for (k=0; k<num_target_msgnums; ++k) {
445 if (msglist[i] == target_msgnums[k]) {
446 is_seen = target_setting;
451 if (lo < 0L) lo = msglist[i];
455 if ( ((is_seen == 0) && (was_seen == 1))
456 || ((is_seen == 1) && (i == num_msgs-1)) ) {
458 /* begin trim-o-matic code */
461 while ( (strlen(vset) + 20) > sizeof vset) {
462 remove_token(vset, 0, ',');
464 if (j--) break; /* loop no more than 9 times */
466 if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
470 snprintf(lostr, sizeof lostr,
471 "1:%ld,%s", t, vset);
472 safestrncpy(vset, lostr, sizeof vset);
474 /* end trim-o-matic code */
482 snprintf(&vset[tmp], (sizeof vset) - tmp,
486 snprintf(&vset[tmp], (sizeof vset) - tmp,
495 /* Decide which message set we're manipulating */
497 case ctdlsetseen_seen:
498 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
500 case ctdlsetseen_answered:
501 safestrncpy(vbuf.v_answered, vset,
502 sizeof vbuf.v_answered);
507 /* lprintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
509 CtdlSetRelationship(&vbuf,
510 ((which_user != NULL) ? which_user : &CC->user),
511 ((which_room != NULL) ? which_room : &CC->room)
517 * API function to perform an operation for each qualifying message in the
518 * current room. (Returns the number of messages processed.)
520 int CtdlForEachMessage(int mode, long ref,
522 struct CtdlMessage *compare,
523 void (*CallBack) (long, void *),
529 struct cdbdata *cdbfr;
530 long *msglist = NULL;
532 int num_processed = 0;
535 struct CtdlMessage *msg;
538 int printed_lastold = 0;
540 /* Learn about the user and room in question */
542 getuser(&CC->user, CC->curr_user);
543 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
545 /* Load the message list */
546 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
548 msglist = (long *) cdbfr->ptr;
549 cdbfr->ptr = NULL; /* CtdlForEachMessage() now owns this memory */
550 num_msgs = cdbfr->len / sizeof(long);
553 return 0; /* No messages at all? No further action. */
558 * Now begin the traversal.
560 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
562 /* If the caller is looking for a specific MIME type, filter
563 * out all messages which are not of the type requested.
565 if (content_type != NULL) if (strlen(content_type) > 0) {
567 /* This call to GetMetaData() sits inside this loop
568 * so that we only do the extra database read per msg
569 * if we need to. Doing the extra read all the time
570 * really kills the server. If we ever need to use
571 * metadata for another search criterion, we need to
572 * move the read somewhere else -- but still be smart
573 * enough to only do the read if the caller has
574 * specified something that will need it.
576 GetMetaData(&smi, msglist[a]);
578 if (strcasecmp(smi.meta_content_type, content_type)) {
584 num_msgs = sort_msglist(msglist, num_msgs);
586 /* If a template was supplied, filter out the messages which
587 * don't match. (This could induce some delays!)
590 if (compare != NULL) {
591 for (a = 0; a < num_msgs; ++a) {
592 msg = CtdlFetchMessage(msglist[a], 1);
594 if (CtdlMsgCmp(msg, compare)) {
597 CtdlFreeMessage(msg);
605 * Now iterate through the message list, according to the
606 * criteria supplied by the caller.
609 for (a = 0; a < num_msgs; ++a) {
610 thismsg = msglist[a];
611 if (mode == MSGS_ALL) {
615 is_seen = is_msg_in_sequence_set(
616 vbuf.v_seen, thismsg);
617 if (is_seen) lastold = thismsg;
623 || ((mode == MSGS_OLD) && (is_seen))
624 || ((mode == MSGS_NEW) && (!is_seen))
625 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
626 || ((mode == MSGS_FIRST) && (a < ref))
627 || ((mode == MSGS_GT) && (thismsg > ref))
628 || ((mode == MSGS_EQ) && (thismsg == ref))
631 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
633 CallBack(lastold, userdata);
637 if (CallBack) CallBack(thismsg, userdata);
641 free(msglist); /* Clean up */
642 return num_processed;
648 * cmd_msgs() - get list of message #'s in this room
649 * implements the MSGS server command using CtdlForEachMessage()
651 void cmd_msgs(char *cmdbuf)
660 int with_template = 0;
661 struct CtdlMessage *template = NULL;
662 int with_headers = 0;
664 extract_token(which, cmdbuf, 0, '|', sizeof which);
665 cm_ref = extract_int(cmdbuf, 1);
666 with_template = extract_int(cmdbuf, 2);
667 with_headers = extract_int(cmdbuf, 3);
671 if (!strncasecmp(which, "OLD", 3))
673 else if (!strncasecmp(which, "NEW", 3))
675 else if (!strncasecmp(which, "FIRST", 5))
677 else if (!strncasecmp(which, "LAST", 4))
679 else if (!strncasecmp(which, "GT", 2))
682 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
683 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
689 cprintf("%d Send template then receive message list\n",
691 template = (struct CtdlMessage *)
692 malloc(sizeof(struct CtdlMessage));
693 memset(template, 0, sizeof(struct CtdlMessage));
694 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
695 extract_token(tfield, buf, 0, '|', sizeof tfield);
696 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
697 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
698 if (!strcasecmp(tfield, msgkeys[i])) {
699 template->cm_fields[i] =
707 cprintf("%d \n", LISTING_FOLLOWS);
710 CtdlForEachMessage(mode,
714 (with_headers ? headers_listing : simple_listing),
717 if (template != NULL) CtdlFreeMessage(template);
725 * help_subst() - support routine for help file viewer
727 void help_subst(char *strbuf, char *source, char *dest)
732 while (p = pattern2(strbuf, source), (p >= 0)) {
733 strcpy(workbuf, &strbuf[p + strlen(source)]);
734 strcpy(&strbuf[p], dest);
735 strcat(strbuf, workbuf);
740 void do_help_subst(char *buffer)
744 help_subst(buffer, "^nodename", config.c_nodename);
745 help_subst(buffer, "^humannode", config.c_humannode);
746 help_subst(buffer, "^fqdn", config.c_fqdn);
747 help_subst(buffer, "^username", CC->user.fullname);
748 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
749 help_subst(buffer, "^usernum", buf2);
750 help_subst(buffer, "^sysadm", config.c_sysadm);
751 help_subst(buffer, "^variantname", CITADEL);
752 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
753 help_subst(buffer, "^maxsessions", buf2);
754 help_subst(buffer, "^bbsdir", CTDLDIR);
760 * memfmout() - Citadel text formatter and paginator.
761 * Although the original purpose of this routine was to format
762 * text to the reader's screen width, all we're really using it
763 * for here is to format text out to 80 columns before sending it
764 * to the client. The client software may reformat it again.
767 int width, /* screen width to use */
768 char *mptr, /* where are we going to get our text from? */
769 char subst, /* nonzero if we should do substitutions */
770 char *nl) /* string to terminate lines with */
782 c = 1; /* c is the current pos */
786 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
788 buffer[strlen(buffer) + 1] = 0;
789 buffer[strlen(buffer)] = ch;
792 if (buffer[0] == '^')
793 do_help_subst(buffer);
795 buffer[strlen(buffer) + 1] = 0;
797 strcpy(buffer, &buffer[1]);
805 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
807 if (((old == 13) || (old == 10)) && (isspace(real))) {
815 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
816 cprintf("%s%s", nl, aaa);
825 if ((strlen(aaa) + c) > (width - 5)) {
834 if ((ch == 13) || (ch == 10)) {
835 cprintf("%s%s", aaa, nl);
842 cprintf("%s%s", aaa, nl);
848 * Callback function for mime parser that simply lists the part
850 void list_this_part(char *name, char *filename, char *partnum, char *disp,
851 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
856 ma = (struct ma_info *)cbuserdata;
857 if (ma->is_ma == 0) {
858 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
859 name, filename, partnum, disp, cbtype, (long)length);
864 * Callback function for multipart prefix
866 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
867 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
872 ma = (struct ma_info *)cbuserdata;
873 if (!strcasecmp(cbtype, "multipart/alternative")) {
877 if (ma->is_ma == 0) {
878 cprintf("pref=%s|%s\n", partnum, cbtype);
883 * Callback function for multipart sufffix
885 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
886 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
891 ma = (struct ma_info *)cbuserdata;
892 if (ma->is_ma == 0) {
893 cprintf("suff=%s|%s\n", partnum, cbtype);
895 if (!strcasecmp(cbtype, "multipart/alternative")) {
902 * Callback function for mime parser that opens a section for downloading
904 void mime_download(char *name, char *filename, char *partnum, char *disp,
905 void *content, char *cbtype, char *cbcharset, size_t length,
906 char *encoding, void *cbuserdata)
909 /* Silently go away if there's already a download open... */
910 if (CC->download_fp != NULL)
913 /* ...or if this is not the desired section */
914 if (strcasecmp(CC->download_desired_section, partnum))
917 CC->download_fp = tmpfile();
918 if (CC->download_fp == NULL)
921 fwrite(content, length, 1, CC->download_fp);
922 fflush(CC->download_fp);
923 rewind(CC->download_fp);
925 OpenCmdResult(filename, cbtype);
931 * Load a message from disk into memory.
932 * This is used by CtdlOutputMsg() and other fetch functions.
934 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
935 * using the CtdlMessageFree() function.
937 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
939 struct cdbdata *dmsgtext;
940 struct CtdlMessage *ret = NULL;
944 cit_uint8_t field_header;
946 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
948 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
949 if (dmsgtext == NULL) {
952 mptr = dmsgtext->ptr;
953 upper_bound = mptr + dmsgtext->len;
955 /* Parse the three bytes that begin EVERY message on disk.
956 * The first is always 0xFF, the on-disk magic number.
957 * The second is the anonymous/public type byte.
958 * The third is the format type byte (vari, fixed, or MIME).
963 "Message %ld appears to be corrupted.\n",
968 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
969 memset(ret, 0, sizeof(struct CtdlMessage));
971 ret->cm_magic = CTDLMESSAGE_MAGIC;
972 ret->cm_anon_type = *mptr++; /* Anon type byte */
973 ret->cm_format_type = *mptr++; /* Format type byte */
976 * The rest is zero or more arbitrary fields. Load them in.
977 * We're done when we encounter either a zero-length field or
978 * have just processed the 'M' (message text) field.
981 if (mptr >= upper_bound) {
984 field_header = *mptr++;
985 ret->cm_fields[field_header] = strdup(mptr);
987 while (*mptr++ != 0); /* advance to next field */
989 } while ((mptr < upper_bound) && (field_header != 'M'));
993 /* Always make sure there's something in the msg text field. If
994 * it's NULL, the message text is most likely stored separately,
995 * so go ahead and fetch that. Failing that, just set a dummy
996 * body so other code doesn't barf.
998 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
999 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1000 if (dmsgtext != NULL) {
1001 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1005 if (ret->cm_fields['M'] == NULL) {
1006 ret->cm_fields['M'] = strdup("<no text>\n");
1009 /* Perform "before read" hooks (aborting if any return nonzero) */
1010 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1011 CtdlFreeMessage(ret);
1020 * Returns 1 if the supplied pointer points to a valid Citadel message.
1021 * If the pointer is NULL or the magic number check fails, returns 0.
1023 int is_valid_message(struct CtdlMessage *msg) {
1026 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1027 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1035 * 'Destructor' for struct CtdlMessage
1037 void CtdlFreeMessage(struct CtdlMessage *msg)
1041 if (is_valid_message(msg) == 0) return;
1043 for (i = 0; i < 256; ++i)
1044 if (msg->cm_fields[i] != NULL) {
1045 free(msg->cm_fields[i]);
1048 msg->cm_magic = 0; /* just in case */
1054 * Pre callback function for multipart/alternative
1056 * NOTE: this differs from the standard behavior for a reason. Normally when
1057 * displaying multipart/alternative you want to show the _last_ usable
1058 * format in the message. Here we show the _first_ one, because it's
1059 * usually text/plain. Since this set of functions is designed for text
1060 * output to non-MIME-aware clients, this is the desired behavior.
1063 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1064 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1069 ma = (struct ma_info *)cbuserdata;
1070 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1071 if (!strcasecmp(cbtype, "multipart/alternative")) {
1075 if (!strcasecmp(cbtype, "message/rfc822")) {
1081 * Post callback function for multipart/alternative
1083 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1084 void *content, char *cbtype, char *cbcharset, size_t length,
1085 char *encoding, void *cbuserdata)
1089 ma = (struct ma_info *)cbuserdata;
1090 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1091 if (!strcasecmp(cbtype, "multipart/alternative")) {
1095 if (!strcasecmp(cbtype, "message/rfc822")) {
1101 * Inline callback function for mime parser that wants to display text
1103 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1104 void *content, char *cbtype, char *cbcharset, size_t length,
1105 char *encoding, void *cbuserdata)
1112 ma = (struct ma_info *)cbuserdata;
1115 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1116 partnum, filename, cbtype, (long)length);
1119 * If we're in the middle of a multipart/alternative scope and
1120 * we've already printed another section, skip this one.
1122 if ( (ma->is_ma) && (ma->did_print) ) {
1123 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n",
1129 if ( (!strcasecmp(cbtype, "text/plain"))
1130 || (strlen(cbtype)==0) ) {
1133 client_write(wptr, length);
1134 if (wptr[length-1] != '\n') {
1139 else if (!strcasecmp(cbtype, "text/html")) {
1140 ptr = html_to_ascii(content, length, 80, 0);
1142 client_write(ptr, wlen);
1143 if (ptr[wlen-1] != '\n') {
1148 else if (PerformFixedOutputHooks(cbtype, content, length)) {
1149 /* above function returns nonzero if it handled the part */
1151 else if (strncasecmp(cbtype, "multipart/", 10)) {
1152 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1153 partnum, filename, cbtype, (long)length);
1158 * The client is elegant and sophisticated and wants to be choosy about
1159 * MIME content types, so figure out which multipart/alternative part
1160 * we're going to send.
1162 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1163 void *content, char *cbtype, char *cbcharset, size_t length,
1164 char *encoding, void *cbuserdata)
1170 ma = (struct ma_info *)cbuserdata;
1172 if (ma->is_ma > 0) {
1173 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1174 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1175 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1176 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1183 * Now that we've chosen our preferred part, output it.
1185 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1186 void *content, char *cbtype, char *cbcharset, size_t length,
1187 char *encoding, void *cbuserdata)
1191 int add_newline = 0;
1195 ma = (struct ma_info *)cbuserdata;
1197 /* This is not the MIME part you're looking for... */
1198 if (strcasecmp(partnum, ma->chosen_part)) return;
1200 /* If the content-type of this part is in our preferred formats
1201 * list, we can simply output it verbatim.
1203 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1204 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1205 if (!strcasecmp(buf, cbtype)) {
1206 /* Yeah! Go! W00t!! */
1208 text_content = (char *)content;
1209 if (text_content[length-1] != '\n') {
1213 cprintf("Content-type: %s", cbtype);
1214 if (strlen(cbcharset) > 0) {
1215 cprintf("; charset=%s", cbcharset);
1217 cprintf("\nContent-length: %d\n",
1218 (int)(length + add_newline) );
1219 if (strlen(encoding) > 0) {
1220 cprintf("Content-transfer-encoding: %s\n", encoding);
1223 cprintf("Content-transfer-encoding: 7bit\n");
1226 client_write(content, length);
1227 if (add_newline) cprintf("\n");
1232 /* No translations required or possible: output as text/plain */
1233 cprintf("Content-type: text/plain\n\n");
1234 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1235 length, encoding, cbuserdata);
1240 char desired_section[64];
1247 * Callback function for
1249 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1250 void *content, char *cbtype, char *cbcharset, size_t length,
1251 char *encoding, void *cbuserdata)
1253 struct encapmsg *encap;
1255 encap = (struct encapmsg *)cbuserdata;
1257 /* Only proceed if this is the desired section... */
1258 if (!strcasecmp(encap->desired_section, partnum)) {
1259 encap->msglen = length;
1260 encap->msg = malloc(length + 2);
1261 memcpy(encap->msg, content, length);
1271 * Get a message off disk. (returns om_* values found in msgbase.h)
1274 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1275 int mode, /* how would you like that message? */
1276 int headers_only, /* eschew the message body? */
1277 int do_proto, /* do Citadel protocol responses? */
1278 int crlf, /* Use CRLF newlines instead of LF? */
1279 char *section /* NULL or a message/rfc822 section */
1281 struct CtdlMessage *TheMessage = NULL;
1282 int retcode = om_no_such_msg;
1283 struct encapmsg encap;
1285 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1287 (section ? section : "<>")
1290 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1291 if (do_proto) cprintf("%d Not logged in.\n",
1292 ERROR + NOT_LOGGED_IN);
1293 return(om_not_logged_in);
1296 /* FIXME: check message id against msglist for this room */
1299 * Fetch the message from disk. If we're in any sort of headers
1300 * only mode, request that we don't even bother loading the body
1303 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1304 TheMessage = CtdlFetchMessage(msg_num, 0);
1307 TheMessage = CtdlFetchMessage(msg_num, 1);
1310 if (TheMessage == NULL) {
1311 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1312 ERROR + MESSAGE_NOT_FOUND, msg_num);
1313 return(om_no_such_msg);
1316 /* Here is the weird form of this command, to process only an
1317 * encapsulated message/rfc822 section.
1319 if (section) if (strlen(section)>0) if (strcmp(section, "0")) {
1320 memset(&encap, 0, sizeof encap);
1321 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1322 mime_parser(TheMessage->cm_fields['M'],
1324 *extract_encapsulated_message,
1325 NULL, NULL, (void *)&encap, 0
1327 CtdlFreeMessage(TheMessage);
1331 encap.msg[encap.msglen] = 0;
1332 TheMessage = convert_internet_message(encap.msg);
1333 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1335 /* Now we let it fall through to the bottom of this
1336 * function, because TheMessage now contains the
1337 * encapsulated message instead of the top-level
1338 * message. Isn't that neat?
1343 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1344 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1345 retcode = om_no_such_msg;
1350 /* Ok, output the message now */
1351 retcode = CtdlOutputPreLoadedMsg(
1353 headers_only, do_proto, crlf);
1354 CtdlFreeMessage(TheMessage);
1361 * Get a message off disk. (returns om_* values found in msgbase.h)
1364 int CtdlOutputPreLoadedMsg(
1365 struct CtdlMessage *TheMessage,
1366 int mode, /* how would you like that message? */
1367 int headers_only, /* eschew the message body? */
1368 int do_proto, /* do Citadel protocol responses? */
1369 int crlf /* Use CRLF newlines instead of LF? */
1375 char display_name[256];
1377 char *nl; /* newline string */
1379 int subject_found = 0;
1382 /* Buffers needed for RFC822 translation. These are all filled
1383 * using functions that are bounds-checked, and therefore we can
1384 * make them substantially smaller than SIZ.
1392 char datestamp[100];
1394 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1395 ((TheMessage == NULL) ? "NULL" : "not null"),
1396 mode, headers_only, do_proto, crlf);
1398 strcpy(mid, "unknown");
1399 nl = (crlf ? "\r\n" : "\n");
1401 if (!is_valid_message(TheMessage)) {
1403 "ERROR: invalid preloaded message for output\n");
1404 return(om_no_such_msg);
1407 /* Are we downloading a MIME component? */
1408 if (mode == MT_DOWNLOAD) {
1409 if (TheMessage->cm_format_type != FMT_RFC822) {
1411 cprintf("%d This is not a MIME message.\n",
1412 ERROR + ILLEGAL_VALUE);
1413 } else if (CC->download_fp != NULL) {
1414 if (do_proto) cprintf(
1415 "%d You already have a download open.\n",
1416 ERROR + RESOURCE_BUSY);
1418 /* Parse the message text component */
1419 mptr = TheMessage->cm_fields['M'];
1420 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1421 /* If there's no file open by this time, the requested
1422 * section wasn't found, so print an error
1424 if (CC->download_fp == NULL) {
1425 if (do_proto) cprintf(
1426 "%d Section %s not found.\n",
1427 ERROR + FILE_NOT_FOUND,
1428 CC->download_desired_section);
1431 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1434 /* now for the user-mode message reading loops */
1435 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1437 /* Does the caller want to skip the headers? */
1438 if (headers_only == HEADERS_NONE) goto START_TEXT;
1440 /* Tell the client which format type we're using. */
1441 if ( (mode == MT_CITADEL) && (do_proto) ) {
1442 cprintf("type=%d\n", TheMessage->cm_format_type);
1445 /* nhdr=yes means that we're only displaying headers, no body */
1446 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1447 && (mode == MT_CITADEL)
1450 cprintf("nhdr=yes\n");
1453 /* begin header processing loop for Citadel message format */
1455 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1457 safestrncpy(display_name, "<unknown>", sizeof display_name);
1458 if (TheMessage->cm_fields['A']) {
1459 strcpy(buf, TheMessage->cm_fields['A']);
1460 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1461 safestrncpy(display_name, "****", sizeof display_name);
1463 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1464 safestrncpy(display_name, "anonymous", sizeof display_name);
1467 safestrncpy(display_name, buf, sizeof display_name);
1469 if ((is_room_aide())
1470 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1471 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1472 size_t tmp = strlen(display_name);
1473 snprintf(&display_name[tmp],
1474 sizeof display_name - tmp,
1479 /* Don't show Internet address for users on the
1480 * local Citadel network.
1483 if (TheMessage->cm_fields['N'] != NULL)
1484 if (strlen(TheMessage->cm_fields['N']) > 0)
1485 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1489 /* Now spew the header fields in the order we like them. */
1490 safestrncpy(allkeys, FORDER, sizeof allkeys);
1491 for (i=0; i<strlen(allkeys); ++i) {
1492 k = (int) allkeys[i];
1494 if ( (TheMessage->cm_fields[k] != NULL)
1495 && (msgkeys[k] != NULL) ) {
1497 if (do_proto) cprintf("%s=%s\n",
1501 else if ((k == 'F') && (suppress_f)) {
1504 /* Masquerade display name if needed */
1506 if (do_proto) cprintf("%s=%s\n",
1508 TheMessage->cm_fields[k]
1517 /* begin header processing loop for RFC822 transfer format */
1522 strcpy(snode, NODENAME);
1523 strcpy(lnode, HUMANNODE);
1524 if (mode == MT_RFC822) {
1525 for (i = 0; i < 256; ++i) {
1526 if (TheMessage->cm_fields[i]) {
1527 mptr = TheMessage->cm_fields[i];
1530 safestrncpy(luser, mptr, sizeof luser);
1531 safestrncpy(suser, mptr, sizeof suser);
1533 else if (i == 'Y') {
1534 cprintf("CC: %s%s", mptr, nl);
1536 else if (i == 'U') {
1537 cprintf("Subject: %s%s", mptr, nl);
1541 safestrncpy(mid, mptr, sizeof mid);
1543 safestrncpy(lnode, mptr, sizeof lnode);
1545 safestrncpy(fuser, mptr, sizeof fuser);
1546 /* else if (i == 'O')
1547 cprintf("X-Citadel-Room: %s%s",
1550 safestrncpy(snode, mptr, sizeof snode);
1552 cprintf("To: %s%s", mptr, nl);
1553 else if (i == 'T') {
1554 datestring(datestamp, sizeof datestamp,
1555 atol(mptr), DATESTRING_RFC822);
1556 cprintf("Date: %s%s", datestamp, nl);
1560 if (subject_found == 0) {
1561 cprintf("Subject: (no subject)%s", nl);
1565 for (i=0; i<strlen(suser); ++i) {
1566 suser[i] = tolower(suser[i]);
1567 if (!isalnum(suser[i])) suser[i]='_';
1570 if (mode == MT_RFC822) {
1571 if (!strcasecmp(snode, NODENAME)) {
1572 safestrncpy(snode, FQDN, sizeof snode);
1575 /* Construct a fun message id */
1576 cprintf("Message-ID: <%s", mid);
1577 if (strchr(mid, '@')==NULL) {
1578 cprintf("@%s", snode);
1582 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1583 cprintf("From: \"----\" <x@x.org>%s", nl);
1585 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1586 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1588 else if (strlen(fuser) > 0) {
1589 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1592 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1595 cprintf("Organization: %s%s", lnode, nl);
1597 /* Blank line signifying RFC822 end-of-headers */
1598 if (TheMessage->cm_format_type != FMT_RFC822) {
1603 /* end header processing loop ... at this point, we're in the text */
1605 if (headers_only == HEADERS_FAST) goto DONE;
1606 mptr = TheMessage->cm_fields['M'];
1608 /* Tell the client about the MIME parts in this message */
1609 if (TheMessage->cm_format_type == FMT_RFC822) {
1610 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1611 memset(&ma, 0, sizeof(struct ma_info));
1612 mime_parser(mptr, NULL,
1613 (do_proto ? *list_this_part : NULL),
1614 (do_proto ? *list_this_pref : NULL),
1615 (do_proto ? *list_this_suff : NULL),
1618 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1619 char *start_of_text = NULL;
1620 start_of_text = strstr(mptr, "\n\r\n");
1621 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1622 if (start_of_text == NULL) start_of_text = mptr;
1624 start_of_text = strstr(start_of_text, "\n");
1626 while (ch=*mptr, ch!=0) {
1630 else switch(headers_only) {
1632 if (mptr >= start_of_text) {
1633 if (ch == 10) cprintf("%s", nl);
1634 else cprintf("%c", ch);
1638 if (mptr < start_of_text) {
1639 if (ch == 10) cprintf("%s", nl);
1640 else cprintf("%c", ch);
1644 if (ch == 10) cprintf("%s", nl);
1645 else cprintf("%c", ch);
1654 if (headers_only == HEADERS_ONLY) {
1658 /* signify start of msg text */
1659 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1660 if (do_proto) cprintf("text\n");
1663 /* If the format type on disk is 1 (fixed-format), then we want
1664 * everything to be output completely literally ... regardless of
1665 * what message transfer format is in use.
1667 if (TheMessage->cm_format_type == FMT_FIXED) {
1668 if (mode == MT_MIME) {
1669 cprintf("Content-type: text/plain\n\n");
1672 while (ch = *mptr++, ch > 0) {
1675 if ((ch == 10) || (strlen(buf) > 250)) {
1676 cprintf("%s%s", buf, nl);
1679 buf[strlen(buf) + 1] = 0;
1680 buf[strlen(buf)] = ch;
1683 if (strlen(buf) > 0)
1684 cprintf("%s%s", buf, nl);
1687 /* If the message on disk is format 0 (Citadel vari-format), we
1688 * output using the formatter at 80 columns. This is the final output
1689 * form if the transfer format is RFC822, but if the transfer format
1690 * is Citadel proprietary, it'll still work, because the indentation
1691 * for new paragraphs is correct and the client will reformat the
1692 * message to the reader's screen width.
1694 if (TheMessage->cm_format_type == FMT_CITADEL) {
1695 if (mode == MT_MIME) {
1696 cprintf("Content-type: text/x-citadel-variformat\n\n");
1698 memfmout(80, mptr, 0, nl);
1701 /* If the message on disk is format 4 (MIME), we've gotta hand it
1702 * off to the MIME parser. The client has already been told that
1703 * this message is format 1 (fixed format), so the callback function
1704 * we use will display those parts as-is.
1706 if (TheMessage->cm_format_type == FMT_RFC822) {
1707 memset(&ma, 0, sizeof(struct ma_info));
1709 if (mode == MT_MIME) {
1710 strcpy(ma.chosen_part, "1");
1711 mime_parser(mptr, NULL,
1712 *choose_preferred, *fixed_output_pre,
1713 *fixed_output_post, (void *)&ma, 0);
1714 mime_parser(mptr, NULL,
1715 *output_preferred, NULL, NULL, (void *)&ma, 0);
1718 mime_parser(mptr, NULL,
1719 *fixed_output, *fixed_output_pre,
1720 *fixed_output_post, (void *)&ma, 0);
1725 DONE: /* now we're done */
1726 if (do_proto) cprintf("000\n");
1733 * display a message (mode 0 - Citadel proprietary)
1735 void cmd_msg0(char *cmdbuf)
1738 int headers_only = HEADERS_ALL;
1740 msgid = extract_long(cmdbuf, 0);
1741 headers_only = extract_int(cmdbuf, 1);
1743 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL);
1749 * display a message (mode 2 - RFC822)
1751 void cmd_msg2(char *cmdbuf)
1754 int headers_only = HEADERS_ALL;
1756 msgid = extract_long(cmdbuf, 0);
1757 headers_only = extract_int(cmdbuf, 1);
1759 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL);
1765 * display a message (mode 3 - IGnet raw format - internal programs only)
1767 void cmd_msg3(char *cmdbuf)
1770 struct CtdlMessage *msg;
1773 if (CC->internal_pgm == 0) {
1774 cprintf("%d This command is for internal programs only.\n",
1775 ERROR + HIGHER_ACCESS_REQUIRED);
1779 msgnum = extract_long(cmdbuf, 0);
1780 msg = CtdlFetchMessage(msgnum, 1);
1782 cprintf("%d Message %ld not found.\n",
1783 ERROR + MESSAGE_NOT_FOUND, msgnum);
1787 serialize_message(&smr, msg);
1788 CtdlFreeMessage(msg);
1791 cprintf("%d Unable to serialize message\n",
1792 ERROR + INTERNAL_ERROR);
1796 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1797 client_write((char *)smr.ser, (int)smr.len);
1804 * Display a message using MIME content types
1806 void cmd_msg4(char *cmdbuf)
1811 msgid = extract_long(cmdbuf, 0);
1812 extract_token(section, cmdbuf, 1, '|', sizeof section);
1813 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) );
1819 * Client tells us its preferred message format(s)
1821 void cmd_msgp(char *cmdbuf)
1823 safestrncpy(CC->preferred_formats, cmdbuf,
1824 sizeof(CC->preferred_formats));
1825 cprintf("%d ok\n", CIT_OK);
1830 * Open a component of a MIME message as a download file
1832 void cmd_opna(char *cmdbuf)
1835 char desired_section[128];
1837 msgid = extract_long(cmdbuf, 0);
1838 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1839 safestrncpy(CC->download_desired_section, desired_section,
1840 sizeof CC->download_desired_section);
1841 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL);
1846 * Save a message pointer into a specified room
1847 * (Returns 0 for success, nonzero for failure)
1848 * roomname may be NULL to use the current room
1850 * Note that the 'supplied_msg' field may be set to NULL, in which case
1851 * the message will be fetched from disk, by number, if we need to perform
1852 * replication checks. This adds an additional database read, so if the
1853 * caller already has the message in memory then it should be supplied.
1855 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check,
1856 struct CtdlMessage *supplied_msg) {
1858 char hold_rm[ROOMNAMELEN];
1859 struct cdbdata *cdbfr;
1862 long highest_msg = 0L;
1863 struct CtdlMessage *msg = NULL;
1865 /*lprintf(CTDL_DEBUG,
1866 "CtdlSaveMsgPointerInRoom(room=%s, msgid=%ld, repl=%d)\n",
1867 roomname, msgid, do_repl_check);*/
1869 strcpy(hold_rm, CC->room.QRname);
1871 /* Now the regular stuff */
1872 if (lgetroom(&CC->room,
1873 ((roomname != NULL) ? roomname : CC->room.QRname) )
1875 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1876 return(ERROR + ROOM_NOT_FOUND);
1879 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1880 if (cdbfr == NULL) {
1884 msglist = (long *) cdbfr->ptr;
1885 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
1886 num_msgs = cdbfr->len / sizeof(long);
1890 /* Make sure the message doesn't already exist in this room. It
1891 * is absolutely taboo to have more than one reference to the same
1892 * message in a room.
1894 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1895 if (msglist[i] == msgid) {
1896 lputroom(&CC->room); /* unlock the room */
1897 getroom(&CC->room, hold_rm);
1899 return(ERROR + ALREADY_EXISTS);
1903 /* Now add the new message */
1905 msglist = realloc(msglist, (num_msgs * sizeof(long)));
1907 if (msglist == NULL) {
1908 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1910 msglist[num_msgs - 1] = msgid;
1912 /* Sort the message list, so all the msgid's are in order */
1913 num_msgs = sort_msglist(msglist, num_msgs);
1915 /* Determine the highest message number */
1916 highest_msg = msglist[num_msgs - 1];
1918 /* Write it back to disk. */
1919 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1920 msglist, (int)(num_msgs * sizeof(long)));
1922 /* Free up the memory we used. */
1925 /* Update the highest-message pointer and unlock the room. */
1926 CC->room.QRhighest = highest_msg;
1927 lputroom(&CC->room);
1929 /* Perform replication checks if necessary */
1930 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
1931 if (supplied_msg != NULL) {
1935 msg = CtdlFetchMessage(msgid, 0);
1939 ReplicationChecks(msg);
1944 /* If the message has an Exclusive ID, index that... */
1946 if (msg->cm_fields['E'] != NULL) {
1947 index_message_by_euid(msg->cm_fields['E'],
1952 /* Free up the memory we may have allocated */
1953 if ( (msg != NULL) && (msg != supplied_msg) ) {
1954 CtdlFreeMessage(msg);
1957 /* Go back to the room we were in before we wandered here... */
1958 getroom(&CC->room, hold_rm);
1960 /* Bump the reference count for this message. */
1961 AdjRefCount(msgid, +1);
1963 /* Return success. */
1970 * Message base operation to save a new message to the message store
1971 * (returns new message number)
1973 * This is the back end for CtdlSubmitMsg() and should not be directly
1974 * called by server-side modules.
1977 long send_message(struct CtdlMessage *msg) {
1985 /* Get a new message number */
1986 newmsgid = get_new_message_number();
1987 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1989 /* Generate an ID if we don't have one already */
1990 if (msg->cm_fields['I']==NULL) {
1991 msg->cm_fields['I'] = strdup(msgidbuf);
1994 /* If the message is big, set its body aside for storage elsewhere */
1995 if (msg->cm_fields['M'] != NULL) {
1996 if (strlen(msg->cm_fields['M']) > BIGMSG) {
1998 holdM = msg->cm_fields['M'];
1999 msg->cm_fields['M'] = NULL;
2003 /* Serialize our data structure for storage in the database */
2004 serialize_message(&smr, msg);
2007 msg->cm_fields['M'] = holdM;
2011 cprintf("%d Unable to serialize message\n",
2012 ERROR + INTERNAL_ERROR);
2016 /* Write our little bundle of joy into the message base */
2017 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2018 smr.ser, smr.len) < 0) {
2019 lprintf(CTDL_ERR, "Can't store message\n");
2023 cdb_store(CDB_BIGMSGS,
2033 /* Free the memory we used for the serialized message */
2036 /* Return the *local* message ID to the caller
2037 * (even if we're storing an incoming network message)
2045 * Serialize a struct CtdlMessage into the format used on disk and network.
2047 * This function loads up a "struct ser_ret" (defined in server.h) which
2048 * contains the length of the serialized message and a pointer to the
2049 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2051 void serialize_message(struct ser_ret *ret, /* return values */
2052 struct CtdlMessage *msg) /* unserialized msg */
2056 static char *forder = FORDER;
2058 if (is_valid_message(msg) == 0) return; /* self check */
2061 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2062 ret->len = ret->len +
2063 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2065 lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
2066 ret->ser = malloc(ret->len);
2067 if (ret->ser == NULL) {
2073 ret->ser[1] = msg->cm_anon_type;
2074 ret->ser[2] = msg->cm_format_type;
2077 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2078 ret->ser[wlen++] = (char)forder[i];
2079 strcpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
2080 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
2082 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2083 (long)ret->len, (long)wlen);
2091 * Check to see if any messages already exist in the current room which
2092 * carry the same Exclusive ID as this one. If any are found, delete them.
2094 void ReplicationChecks(struct CtdlMessage *msg) {
2095 long old_msgnum = (-1L);
2097 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2099 lprintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2102 /* No exclusive id? Don't do anything. */
2103 if (msg == NULL) return;
2104 if (msg->cm_fields['E'] == NULL) return;
2105 if (strlen(msg->cm_fields['E']) == 0) return;
2106 /*lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2107 msg->cm_fields['E'], CC->room.QRname);*/
2109 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2110 if (old_msgnum > 0L) {
2111 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2112 CtdlDeleteMessages(CC->room.QRname, old_msgnum, "", 0);
2119 * Save a message to disk and submit it into the delivery system.
2121 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2122 struct recptypes *recps, /* recipients (if mail) */
2123 char *force /* force a particular room? */
2125 char submit_filename[128];
2126 char generated_timestamp[32];
2127 char hold_rm[ROOMNAMELEN];
2128 char actual_rm[ROOMNAMELEN];
2129 char force_room[ROOMNAMELEN];
2130 char content_type[SIZ]; /* We have to learn this */
2131 char recipient[SIZ];
2134 struct ctdluser userbuf;
2136 struct MetaData smi;
2137 FILE *network_fp = NULL;
2138 static int seqnum = 1;
2139 struct CtdlMessage *imsg = NULL;
2142 char *hold_R, *hold_D;
2143 char *collected_addresses = NULL;
2144 struct addresses_to_be_filed *aptr = NULL;
2145 char *saved_rfc822_version = NULL;
2146 int qualified_for_journaling = 0;
2148 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2149 if (is_valid_message(msg) == 0) return(-1); /* self check */
2151 /* If this message has no timestamp, we take the liberty of
2152 * giving it one, right now.
2154 if (msg->cm_fields['T'] == NULL) {
2155 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2156 msg->cm_fields['T'] = strdup(generated_timestamp);
2159 /* If this message has no path, we generate one.
2161 if (msg->cm_fields['P'] == NULL) {
2162 if (msg->cm_fields['A'] != NULL) {
2163 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2164 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2165 if (isspace(msg->cm_fields['P'][a])) {
2166 msg->cm_fields['P'][a] = ' ';
2171 msg->cm_fields['P'] = strdup("unknown");
2175 if (force == NULL) {
2176 strcpy(force_room, "");
2179 strcpy(force_room, force);
2182 /* Learn about what's inside, because it's what's inside that counts */
2183 if (msg->cm_fields['M'] == NULL) {
2184 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2188 switch (msg->cm_format_type) {
2190 strcpy(content_type, "text/x-citadel-variformat");
2193 strcpy(content_type, "text/plain");
2196 strcpy(content_type, "text/plain");
2197 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type: ");
2199 safestrncpy(content_type, &mptr[14],
2200 sizeof content_type);
2201 for (a = 0; a < strlen(content_type); ++a) {
2202 if ((content_type[a] == ';')
2203 || (content_type[a] == ' ')
2204 || (content_type[a] == 13)
2205 || (content_type[a] == 10)) {
2206 content_type[a] = 0;
2212 /* Goto the correct room */
2213 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
2214 strcpy(hold_rm, CC->room.QRname);
2215 strcpy(actual_rm, CC->room.QRname);
2216 if (recps != NULL) {
2217 strcpy(actual_rm, SENTITEMS);
2220 /* If the user is a twit, move to the twit room for posting */
2222 if (CC->user.axlevel == 2) {
2223 strcpy(hold_rm, actual_rm);
2224 strcpy(actual_rm, config.c_twitroom);
2225 lprintf(CTDL_DEBUG, "Diverting to twit room\n");
2229 /* ...or if this message is destined for Aide> then go there. */
2230 if (strlen(force_room) > 0) {
2231 strcpy(actual_rm, force_room);
2234 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2235 if (strcasecmp(actual_rm, CC->room.QRname)) {
2236 /* getroom(&CC->room, actual_rm); */
2237 usergoto(actual_rm, 0, 1, NULL, NULL);
2241 * If this message has no O (room) field, generate one.
2243 if (msg->cm_fields['O'] == NULL) {
2244 msg->cm_fields['O'] = strdup(CC->room.QRname);
2247 /* Perform "before save" hooks (aborting if any return nonzero) */
2248 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2249 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2252 * If this message has an Exclusive ID, and the room is replication
2253 * checking enabled, then do replication checks.
2255 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2256 ReplicationChecks(msg);
2259 /* Save it to disk */
2260 lprintf(CTDL_DEBUG, "Saving to disk\n");
2261 newmsgid = send_message(msg);
2262 if (newmsgid <= 0L) return(-5);
2264 /* Write a supplemental message info record. This doesn't have to
2265 * be a critical section because nobody else knows about this message
2268 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2269 memset(&smi, 0, sizeof(struct MetaData));
2270 smi.meta_msgnum = newmsgid;
2271 smi.meta_refcount = 0;
2272 safestrncpy(smi.meta_content_type, content_type,
2273 sizeof smi.meta_content_type);
2276 * Measure how big this message will be when rendered as RFC822.
2277 * We do this for two reasons:
2278 * 1. We need the RFC822 length for the new metadata record, so the
2279 * POP and IMAP services don't have to calculate message lengths
2280 * while the user is waiting (multiplied by potentially hundreds
2281 * or thousands of messages).
2282 * 2. If journaling is enabled, we will need an RFC822 version of the
2283 * message to attach to the journalized copy.
2285 if (CC->redirect_buffer != NULL) {
2286 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2289 CC->redirect_buffer = malloc(SIZ);
2290 CC->redirect_len = 0;
2291 CC->redirect_alloc = SIZ;
2292 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2293 smi.meta_rfc822_length = CC->redirect_len;
2294 saved_rfc822_version = CC->redirect_buffer;
2295 CC->redirect_buffer = NULL;
2296 CC->redirect_len = 0;
2297 CC->redirect_alloc = 0;
2301 /* Now figure out where to store the pointers */
2302 lprintf(CTDL_DEBUG, "Storing pointers\n");
2304 /* If this is being done by the networker delivering a private
2305 * message, we want to BYPASS saving the sender's copy (because there
2306 * is no local sender; it would otherwise go to the Trashcan).
2308 if ((!CC->internal_pgm) || (recps == NULL)) {
2309 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2310 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2311 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2315 /* For internet mail, drop a copy in the outbound queue room */
2317 if (recps->num_internet > 0) {
2318 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2321 /* If other rooms are specified, drop them there too. */
2323 if (recps->num_room > 0)
2324 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2325 extract_token(recipient, recps->recp_room, i,
2326 '|', sizeof recipient);
2327 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2328 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2331 /* Bump this user's messages posted counter. */
2332 lprintf(CTDL_DEBUG, "Updating user\n");
2333 lgetuser(&CC->user, CC->curr_user);
2334 CC->user.posted = CC->user.posted + 1;
2335 lputuser(&CC->user);
2337 /* If this is private, local mail, make a copy in the
2338 * recipient's mailbox and bump the reference count.
2341 if (recps->num_local > 0)
2342 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2343 extract_token(recipient, recps->recp_local, i,
2344 '|', sizeof recipient);
2345 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2347 if (getuser(&userbuf, recipient) == 0) {
2348 MailboxName(actual_rm, sizeof actual_rm,
2349 &userbuf, MAILROOM);
2350 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2351 BumpNewMailCounter(userbuf.usernum);
2354 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2355 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2360 /* Perform "after save" hooks */
2361 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2362 PerformMessageHooks(msg, EVT_AFTERSAVE);
2364 /* For IGnet mail, we have to save a new copy into the spooler for
2365 * each recipient, with the R and D fields set to the recipient and
2366 * destination-node. This has two ugly side effects: all other
2367 * recipients end up being unlisted in this recipient's copy of the
2368 * message, and it has to deliver multiple messages to the same
2369 * node. We'll revisit this again in a year or so when everyone has
2370 * a network spool receiver that can handle the new style messages.
2373 if (recps->num_ignet > 0)
2374 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2375 extract_token(recipient, recps->recp_ignet, i,
2376 '|', sizeof recipient);
2378 hold_R = msg->cm_fields['R'];
2379 hold_D = msg->cm_fields['D'];
2380 msg->cm_fields['R'] = malloc(SIZ);
2381 msg->cm_fields['D'] = malloc(128);
2382 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2383 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2385 serialize_message(&smr, msg);
2387 snprintf(submit_filename, sizeof submit_filename,
2388 "%s/netmail.%04lx.%04x.%04x",
2390 (long) getpid(), CC->cs_pid, ++seqnum);
2391 network_fp = fopen(submit_filename, "wb+");
2392 if (network_fp != NULL) {
2393 fwrite(smr.ser, smr.len, 1, network_fp);
2399 free(msg->cm_fields['R']);
2400 free(msg->cm_fields['D']);
2401 msg->cm_fields['R'] = hold_R;
2402 msg->cm_fields['D'] = hold_D;
2405 /* Go back to the room we started from */
2406 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2407 if (strcasecmp(hold_rm, CC->room.QRname))
2408 /* getroom(&CC->room, hold_rm); */
2409 usergoto(hold_rm, 0, 1, NULL, NULL);
2411 /* For internet mail, generate delivery instructions.
2412 * Yes, this is recursive. Deal with it. Infinite recursion does
2413 * not happen because the delivery instructions message does not
2414 * contain a recipient.
2417 if (recps->num_internet > 0) {
2418 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2419 instr = malloc(SIZ * 2);
2420 snprintf(instr, SIZ * 2,
2421 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2423 SPOOLMIME, newmsgid, (long)time(NULL),
2424 msg->cm_fields['A'], msg->cm_fields['N']
2427 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2428 size_t tmp = strlen(instr);
2429 extract_token(recipient, recps->recp_internet,
2430 i, '|', sizeof recipient);
2431 snprintf(&instr[tmp], SIZ * 2 - tmp,
2432 "remote|%s|0||\n", recipient);
2435 imsg = malloc(sizeof(struct CtdlMessage));
2436 memset(imsg, 0, sizeof(struct CtdlMessage));
2437 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2438 imsg->cm_anon_type = MES_NORMAL;
2439 imsg->cm_format_type = FMT_RFC822;
2440 imsg->cm_fields['A'] = strdup("Citadel");
2441 imsg->cm_fields['J'] = strdup("do not journal");
2442 imsg->cm_fields['M'] = instr;
2443 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2444 CtdlFreeMessage(imsg);
2448 * Any addresses to harvest for someone's address book?
2450 if ( (CC->logged_in) && (recps != NULL) ) {
2451 collected_addresses = harvest_collected_addresses(msg);
2454 if (collected_addresses != NULL) {
2455 begin_critical_section(S_ATBF);
2456 aptr = (struct addresses_to_be_filed *)
2457 malloc(sizeof(struct addresses_to_be_filed));
2459 MailboxName(actual_rm, sizeof actual_rm,
2460 &CC->user, USERCONTACTSROOM);
2461 aptr->roomname = strdup(actual_rm);
2462 aptr->collected_addresses = collected_addresses;
2464 end_critical_section(S_ATBF);
2468 * Determine whether this message qualifies for journaling.
2470 if (msg->cm_fields['J'] != NULL) {
2471 qualified_for_journaling = 0;
2474 if (recps == NULL) {
2475 qualified_for_journaling = config.c_journal_pubmsgs;
2477 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2478 qualified_for_journaling = config.c_journal_email;
2481 qualified_for_journaling = config.c_journal_pubmsgs;
2486 * Do we have to perform journaling? If so, hand off the saved
2487 * RFC822 version will be handed off to the journaler for background
2488 * submit. Otherwise, we have to free the memory ourselves.
2490 if (saved_rfc822_version != NULL) {
2491 if (qualified_for_journaling) {
2492 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2495 free(saved_rfc822_version);
2508 * Convenience function for generating small administrative messages.
2510 void quickie_message(char *from, char *to, char *room, char *text,
2511 int format_type, char *subject)
2513 struct CtdlMessage *msg;
2514 struct recptypes *recp = NULL;
2516 msg = malloc(sizeof(struct CtdlMessage));
2517 memset(msg, 0, sizeof(struct CtdlMessage));
2518 msg->cm_magic = CTDLMESSAGE_MAGIC;
2519 msg->cm_anon_type = MES_NORMAL;
2520 msg->cm_format_type = format_type;
2521 msg->cm_fields['A'] = strdup(from);
2522 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2523 msg->cm_fields['N'] = strdup(NODENAME);
2525 msg->cm_fields['R'] = strdup(to);
2526 recp = validate_recipients(to);
2528 if (subject != NULL) {
2529 msg->cm_fields['U'] = strdup(subject);
2531 msg->cm_fields['M'] = strdup(text);
2533 CtdlSubmitMsg(msg, recp, room);
2534 CtdlFreeMessage(msg);
2535 if (recp != NULL) free(recp);
2541 * Back end function used by CtdlMakeMessage() and similar functions
2543 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2544 size_t maxlen, /* maximum message length */
2545 char *exist, /* if non-null, append to it;
2546 exist is ALWAYS freed */
2547 int crlf /* CRLF newlines instead of LF */
2551 size_t message_len = 0;
2552 size_t buffer_len = 0;
2558 if (exist == NULL) {
2565 message_len = strlen(exist);
2566 buffer_len = message_len + 4096;
2567 m = realloc(exist, buffer_len);
2574 /* flush the input if we have nowhere to store it */
2579 /* read in the lines of message text one by one */
2581 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2582 if (!strcmp(buf, terminator)) finished = 1;
2584 strcat(buf, "\r\n");
2590 if ( (!flushing) && (!finished) ) {
2591 /* Measure the line */
2592 linelen = strlen(buf);
2594 /* augment the buffer if we have to */
2595 if ((message_len + linelen) >= buffer_len) {
2596 ptr = realloc(m, (buffer_len * 2) );
2597 if (ptr == NULL) { /* flush if can't allocate */
2600 buffer_len = (buffer_len * 2);
2602 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2606 /* Add the new line to the buffer. NOTE: this loop must avoid
2607 * using functions like strcat() and strlen() because they
2608 * traverse the entire buffer upon every call, and doing that
2609 * for a multi-megabyte message slows it down beyond usability.
2611 strcpy(&m[message_len], buf);
2612 message_len += linelen;
2615 /* if we've hit the max msg length, flush the rest */
2616 if (message_len >= maxlen) flushing = 1;
2618 } while (!finished);
2626 * Build a binary message to be saved on disk.
2627 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2628 * will become part of the message. This means you are no longer
2629 * responsible for managing that memory -- it will be freed along with
2630 * the rest of the fields when CtdlFreeMessage() is called.)
2633 struct CtdlMessage *CtdlMakeMessage(
2634 struct ctdluser *author, /* author's user structure */
2635 char *recipient, /* NULL if it's not mail */
2636 char *recp_cc, /* NULL if it's not mail */
2637 char *room, /* room where it's going */
2638 int type, /* see MES_ types in header file */
2639 int format_type, /* variformat, plain text, MIME... */
2640 char *fake_name, /* who we're masquerading as */
2641 char *subject, /* Subject (optional) */
2642 char *preformatted_text /* ...or NULL to read text from client */
2644 char dest_node[SIZ];
2646 struct CtdlMessage *msg;
2648 msg = malloc(sizeof(struct CtdlMessage));
2649 memset(msg, 0, sizeof(struct CtdlMessage));
2650 msg->cm_magic = CTDLMESSAGE_MAGIC;
2651 msg->cm_anon_type = type;
2652 msg->cm_format_type = format_type;
2654 /* Don't confuse the poor folks if it's not routed mail. */
2655 strcpy(dest_node, "");
2660 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2661 msg->cm_fields['P'] = strdup(buf);
2663 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2664 msg->cm_fields['T'] = strdup(buf);
2666 if (fake_name[0]) /* author */
2667 msg->cm_fields['A'] = strdup(fake_name);
2669 msg->cm_fields['A'] = strdup(author->fullname);
2671 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2672 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2675 msg->cm_fields['O'] = strdup(CC->room.QRname);
2678 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2679 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2681 if (recipient[0] != 0) {
2682 msg->cm_fields['R'] = strdup(recipient);
2684 if (recp_cc[0] != 0) {
2685 msg->cm_fields['Y'] = strdup(recp_cc);
2687 if (dest_node[0] != 0) {
2688 msg->cm_fields['D'] = strdup(dest_node);
2691 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2692 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2695 if (subject != NULL) {
2697 if (strlen(subject) > 0) {
2698 msg->cm_fields['U'] = strdup(subject);
2702 if (preformatted_text != NULL) {
2703 msg->cm_fields['M'] = preformatted_text;
2706 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2707 config.c_maxmsglen, NULL, 0);
2715 * Check to see whether we have permission to post a message in the current
2716 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2717 * returns 0 on success.
2719 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2721 if (!(CC->logged_in)) {
2722 snprintf(errmsgbuf, n, "Not logged in.");
2723 return (ERROR + NOT_LOGGED_IN);
2726 if ((CC->user.axlevel < 2)
2727 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2728 snprintf(errmsgbuf, n, "Need to be validated to enter "
2729 "(except in %s> to sysop)", MAILROOM);
2730 return (ERROR + HIGHER_ACCESS_REQUIRED);
2733 if ((CC->user.axlevel < 4)
2734 && (CC->room.QRflags & QR_NETWORK)) {
2735 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2736 return (ERROR + HIGHER_ACCESS_REQUIRED);
2739 if ((CC->user.axlevel < 6)
2740 && (CC->room.QRflags & QR_READONLY)) {
2741 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2742 return (ERROR + HIGHER_ACCESS_REQUIRED);
2745 strcpy(errmsgbuf, "Ok");
2751 * Check to see if the specified user has Internet mail permission
2752 * (returns nonzero if permission is granted)
2754 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2756 /* Do not allow twits to send Internet mail */
2757 if (who->axlevel <= 2) return(0);
2759 /* Globally enabled? */
2760 if (config.c_restrict == 0) return(1);
2762 /* User flagged ok? */
2763 if (who->flags & US_INTERNET) return(2);
2765 /* Aide level access? */
2766 if (who->axlevel >= 6) return(3);
2768 /* No mail for you! */
2774 * Validate recipients, count delivery types and errors, and handle aliasing
2775 * FIXME check for dupes!!!!!
2776 * Returns 0 if all addresses are ok, -1 if no addresses were specified,
2777 * or the number of addresses found invalid.
2779 struct recptypes *validate_recipients(char *supplied_recipients) {
2780 struct recptypes *ret;
2781 char recipients[SIZ];
2782 char this_recp[256];
2783 char this_recp_cooked[256];
2789 struct ctdluser tempUS;
2790 struct ctdlroom tempQR;
2794 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2795 if (ret == NULL) return(NULL);
2796 memset(ret, 0, sizeof(struct recptypes));
2799 ret->num_internet = 0;
2804 if (supplied_recipients == NULL) {
2805 strcpy(recipients, "");
2808 safestrncpy(recipients, supplied_recipients, sizeof recipients);
2811 /* Change all valid separator characters to commas */
2812 for (i=0; i<strlen(recipients); ++i) {
2813 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2814 recipients[i] = ',';
2818 /* Now start extracting recipients... */
2820 while (strlen(recipients) > 0) {
2822 for (i=0; i<=strlen(recipients); ++i) {
2823 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
2824 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
2825 safestrncpy(this_recp, recipients, i+1);
2827 if (recipients[i] == ',') {
2828 strcpy(recipients, &recipients[i+1]);
2831 strcpy(recipients, "");
2838 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
2840 mailtype = alias(this_recp);
2841 mailtype = alias(this_recp);
2842 mailtype = alias(this_recp);
2843 for (j=0; j<=strlen(this_recp); ++j) {
2844 if (this_recp[j]=='_') {
2845 this_recp_cooked[j] = ' ';
2848 this_recp_cooked[j] = this_recp[j];
2854 if (!strcasecmp(this_recp, "sysop")) {
2856 strcpy(this_recp, config.c_aideroom);
2857 if (strlen(ret->recp_room) > 0) {
2858 strcat(ret->recp_room, "|");
2860 strcat(ret->recp_room, this_recp);
2862 else if (getuser(&tempUS, this_recp) == 0) {
2864 strcpy(this_recp, tempUS.fullname);
2865 if (strlen(ret->recp_local) > 0) {
2866 strcat(ret->recp_local, "|");
2868 strcat(ret->recp_local, this_recp);
2870 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2872 strcpy(this_recp, tempUS.fullname);
2873 if (strlen(ret->recp_local) > 0) {
2874 strcat(ret->recp_local, "|");
2876 strcat(ret->recp_local, this_recp);
2878 else if ( (!strncasecmp(this_recp, "room_", 5))
2879 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2881 if (strlen(ret->recp_room) > 0) {
2882 strcat(ret->recp_room, "|");
2884 strcat(ret->recp_room, &this_recp_cooked[5]);
2892 /* Yes, you're reading this correctly: if the target
2893 * domain points back to the local system or an attached
2894 * Citadel directory, the address is invalid. That's
2895 * because if the address were valid, we would have
2896 * already translated it to a local address by now.
2898 if (IsDirectory(this_recp)) {
2903 ++ret->num_internet;
2904 if (strlen(ret->recp_internet) > 0) {
2905 strcat(ret->recp_internet, "|");
2907 strcat(ret->recp_internet, this_recp);
2912 if (strlen(ret->recp_ignet) > 0) {
2913 strcat(ret->recp_ignet, "|");
2915 strcat(ret->recp_ignet, this_recp);
2923 if (strlen(ret->errormsg) == 0) {
2924 snprintf(append, sizeof append,
2925 "Invalid recipient: %s",
2929 snprintf(append, sizeof append,
2932 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2933 strcat(ret->errormsg, append);
2937 if (strlen(ret->display_recp) == 0) {
2938 strcpy(append, this_recp);
2941 snprintf(append, sizeof append, ", %s",
2944 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2945 strcat(ret->display_recp, append);
2950 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2951 ret->num_room + ret->num_error) == 0) {
2952 ret->num_error = (-1);
2953 strcpy(ret->errormsg, "No recipients specified.");
2956 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2957 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2958 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2959 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2960 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2961 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2969 * message entry - mode 0 (normal)
2971 void cmd_ent0(char *entargs)
2977 char masquerade_as[SIZ];
2979 int format_type = 0;
2980 char newusername[SIZ];
2981 struct CtdlMessage *msg;
2985 struct recptypes *valid = NULL;
2986 struct recptypes *valid_to = NULL;
2987 struct recptypes *valid_cc = NULL;
2988 struct recptypes *valid_bcc = NULL;
2995 post = extract_int(entargs, 0);
2996 extract_token(recp, entargs, 1, '|', sizeof recp);
2997 anon_flag = extract_int(entargs, 2);
2998 format_type = extract_int(entargs, 3);
2999 extract_token(subject, entargs, 4, '|', sizeof subject);
3000 do_confirm = extract_int(entargs, 6);
3001 extract_token(cc, entargs, 7, '|', sizeof cc);
3002 extract_token(bcc, entargs, 8, '|', sizeof bcc);
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, NULL);
3165 /* Put together one big recipients struct containing to/cc/bcc all in
3166 * one. This is for the envelope.
3168 char *all_recps = malloc(SIZ * 3);
3169 strcpy(all_recps, recp);
3170 if (strlen(cc) > 0) {
3171 if (strlen(all_recps) > 0) {
3172 strcat(all_recps, ",");
3174 strcat(all_recps, cc);
3176 if (strlen(bcc) > 0) {
3177 if (strlen(all_recps) > 0) {
3178 strcat(all_recps, ",");
3180 strcat(all_recps, bcc);
3182 if (strlen(all_recps) > 0) {
3183 valid = validate_recipients(all_recps);
3191 msgnum = CtdlSubmitMsg(msg, valid, "");
3194 cprintf("%ld\n", msgnum);
3196 cprintf("Message accepted.\n");
3199 cprintf("Internal error.\n");
3201 if (msg->cm_fields['E'] != NULL) {
3202 cprintf("%s\n", msg->cm_fields['E']);
3209 CtdlFreeMessage(msg);
3211 CC->fake_postname[0] = '\0';
3212 if (valid != NULL) {
3221 * API function to delete messages which match a set of criteria
3222 * (returns the actual number of messages deleted)
3224 int CtdlDeleteMessages(char *room_name, /* which room */
3225 long dmsgnum, /* or "0" for any */
3226 char *content_type, /* or "" for any */
3227 int deferred /* let TDAP sweep it later */
3231 struct ctdlroom qrbuf;
3232 struct cdbdata *cdbfr;
3233 long *msglist = NULL;
3234 long *dellist = NULL;
3237 int num_deleted = 0;
3239 struct MetaData smi;
3241 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s, %d)\n",
3242 room_name, dmsgnum, content_type, deferred);
3244 /* get room record, obtaining a lock... */
3245 if (lgetroom(&qrbuf, room_name) != 0) {
3246 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3248 return (0); /* room not found */
3250 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3252 if (cdbfr != NULL) {
3253 dellist = malloc(cdbfr->len);
3254 msglist = (long *) cdbfr->ptr;
3255 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3256 num_msgs = cdbfr->len / sizeof(long);
3260 for (i = 0; i < num_msgs; ++i) {
3263 /* Set/clear a bit for each criterion */
3265 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
3266 delete_this |= 0x01;
3268 if (strlen(content_type) == 0) {
3269 delete_this |= 0x02;
3271 GetMetaData(&smi, msglist[i]);
3272 if (!strcasecmp(smi.meta_content_type,
3274 delete_this |= 0x02;
3278 /* Delete message only if all bits are set */
3279 if (delete_this == 0x03) {
3280 dellist[num_deleted++] = msglist[i];
3285 num_msgs = sort_msglist(msglist, num_msgs);
3286 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3287 msglist, (int)(num_msgs * sizeof(long)));
3289 qrbuf.QRhighest = msglist[num_msgs - 1];
3294 * If the delete operation is "deferred" (and technically, any delete
3295 * operation not performed by THE DREADED AUTO-PURGER ought to be
3296 * a deferred delete) then we save a pointer to the message in the
3297 * DELETED_MSGS_ROOM. This will cause the reference count to remain
3298 * at least 1, which will save the user from having to synchronously
3299 * wait for various disk-intensive operations to complete.
3301 if ( (deferred) && (num_deleted) ) {
3302 for (i=0; i<num_deleted; ++i) {
3303 CtdlCopyMsgToRoom(dellist[i], DELETED_MSGS_ROOM);
3307 /* Go through the messages we pulled out of the index, and decrement
3308 * their reference counts by 1. If this is the only room the message
3309 * was in, the reference count will reach zero and the message will
3310 * automatically be deleted from the database. We do this in a
3311 * separate pass because there might be plug-in hooks getting called,
3312 * and we don't want that happening during an S_ROOMS critical
3315 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3316 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3317 AdjRefCount(dellist[i], -1);
3320 /* Now free the memory we used, and go away. */
3321 if (msglist != NULL) free(msglist);
3322 if (dellist != NULL) free(dellist);
3323 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3324 return (num_deleted);
3330 * Check whether the current user has permission to delete messages from
3331 * the current room (returns 1 for yes, 0 for no)
3333 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3334 getuser(&CC->user, CC->curr_user);
3335 if ((CC->user.axlevel < 6)
3336 && (CC->user.usernum != CC->room.QRroomaide)
3337 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3338 && (!(CC->internal_pgm))) {
3347 * Delete message from current room
3349 void cmd_dele(char *delstr)
3354 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3355 cprintf("%d Higher access required.\n",
3356 ERROR + HIGHER_ACCESS_REQUIRED);
3359 delnum = extract_long(delstr, 0);
3361 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "", 1);
3364 cprintf("%d %d message%s deleted.\n", CIT_OK,
3365 num_deleted, ((num_deleted != 1) ? "s" : ""));
3367 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3373 * Back end API function for moves and deletes
3375 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3378 err = CtdlSaveMsgPointerInRoom(dest, msgnum, 1, NULL);
3379 if (err != 0) return(err);
3387 * move or copy a message to another room
3389 void cmd_move(char *args)
3392 char targ[ROOMNAMELEN];
3393 struct ctdlroom qtemp;
3399 num = extract_long(args, 0);
3400 extract_token(targ, args, 1, '|', sizeof targ);
3401 convert_room_name_macros(targ, sizeof targ);
3402 targ[ROOMNAMELEN - 1] = 0;
3403 is_copy = extract_int(args, 2);
3405 if (getroom(&qtemp, targ) != 0) {
3406 cprintf("%d '%s' does not exist.\n",
3407 ERROR + ROOM_NOT_FOUND, targ);
3411 getuser(&CC->user, CC->curr_user);
3412 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3414 /* Check for permission to perform this operation.
3415 * Remember: "CC->room" is source, "qtemp" is target.
3419 /* Aides can move/copy */
3420 if (CC->user.axlevel >= 6) permit = 1;
3422 /* Room aides can move/copy */
3423 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3425 /* Permit move/copy from personal rooms */
3426 if ((CC->room.QRflags & QR_MAILBOX)
3427 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3429 /* Permit only copy from public to personal room */
3431 && (!(CC->room.QRflags & QR_MAILBOX))
3432 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3434 /* User must have access to target room */
3435 if (!(ra & UA_KNOWN)) permit = 0;
3438 cprintf("%d Higher access required.\n",
3439 ERROR + HIGHER_ACCESS_REQUIRED);
3443 err = CtdlCopyMsgToRoom(num, targ);
3445 cprintf("%d Cannot store message in %s: error %d\n",
3450 /* Now delete the message from the source room,
3451 * if this is a 'move' rather than a 'copy' operation.
3454 CtdlDeleteMessages(CC->room.QRname, num, "", 0);
3457 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3463 * GetMetaData() - Get the supplementary record for a message
3465 void GetMetaData(struct MetaData *smibuf, long msgnum)
3468 struct cdbdata *cdbsmi;
3471 memset(smibuf, 0, sizeof(struct MetaData));
3472 smibuf->meta_msgnum = msgnum;
3473 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3475 /* Use the negative of the message number for its supp record index */
3476 TheIndex = (0L - msgnum);
3478 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3479 if (cdbsmi == NULL) {
3480 return; /* record not found; go with defaults */
3482 memcpy(smibuf, cdbsmi->ptr,
3483 ((cdbsmi->len > sizeof(struct MetaData)) ?
3484 sizeof(struct MetaData) : cdbsmi->len));
3491 * PutMetaData() - (re)write supplementary record for a message
3493 void PutMetaData(struct MetaData *smibuf)
3497 /* Use the negative of the message number for the metadata db index */
3498 TheIndex = (0L - smibuf->meta_msgnum);
3500 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3501 smibuf->meta_msgnum, smibuf->meta_refcount);
3503 cdb_store(CDB_MSGMAIN,
3504 &TheIndex, (int)sizeof(long),
3505 smibuf, (int)sizeof(struct MetaData));
3510 * AdjRefCount - change the reference count for a message;
3511 * delete the message if it reaches zero
3513 void AdjRefCount(long msgnum, int incr)
3516 struct MetaData smi;
3519 /* This is a *tight* critical section; please keep it that way, as
3520 * it may get called while nested in other critical sections.
3521 * Complicating this any further will surely cause deadlock!
3523 begin_critical_section(S_SUPPMSGMAIN);
3524 GetMetaData(&smi, msgnum);
3525 smi.meta_refcount += incr;
3527 end_critical_section(S_SUPPMSGMAIN);
3528 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3529 msgnum, incr, smi.meta_refcount);
3531 /* If the reference count is now zero, delete the message
3532 * (and its supplementary record as well).
3534 if (smi.meta_refcount == 0) {
3535 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3537 /* Remove from fulltext index */
3538 if (config.c_enable_fulltext) {
3539 ft_index_message(msgnum, 0);
3542 /* Remove from message base */
3544 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3545 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3547 /* Remove metadata record */
3548 delnum = (0L - msgnum);
3549 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3554 * Write a generic object to this room
3556 * Note: this could be much more efficient. Right now we use two temporary
3557 * files, and still pull the message into memory as with all others.
3559 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3560 char *content_type, /* MIME type of this object */
3561 char *tempfilename, /* Where to fetch it from */
3562 struct ctdluser *is_mailbox, /* Mailbox room? */
3563 int is_binary, /* Is encoding necessary? */
3564 int is_unique, /* Del others of this type? */
3565 unsigned int flags /* Internal save flags */
3570 struct ctdlroom qrbuf;
3571 char roomname[ROOMNAMELEN];
3572 struct CtdlMessage *msg;
3574 char *raw_message = NULL;
3575 char *encoded_message = NULL;
3576 off_t raw_length = 0;
3578 if (is_mailbox != NULL) {
3579 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3582 safestrncpy(roomname, req_room, sizeof(roomname));
3585 fp = fopen(tempfilename, "rb");
3587 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3588 tempfilename, strerror(errno));
3591 fseek(fp, 0L, SEEK_END);
3592 raw_length = ftell(fp);
3594 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3596 raw_message = malloc((size_t)raw_length + 2);
3597 fread(raw_message, (size_t)raw_length, 1, fp);
3601 encoded_message = malloc((size_t)
3602 (((raw_length * 134) / 100) + 4096 ) );
3605 encoded_message = malloc((size_t)(raw_length + 4096));
3608 sprintf(encoded_message, "Content-type: %s\n", content_type);
3611 sprintf(&encoded_message[strlen(encoded_message)],
3612 "Content-transfer-encoding: base64\n\n"
3616 sprintf(&encoded_message[strlen(encoded_message)],
3617 "Content-transfer-encoding: 7bit\n\n"
3623 &encoded_message[strlen(encoded_message)],
3629 raw_message[raw_length] = 0;
3631 &encoded_message[strlen(encoded_message)],
3639 lprintf(CTDL_DEBUG, "Allocating\n");
3640 msg = malloc(sizeof(struct CtdlMessage));
3641 memset(msg, 0, sizeof(struct CtdlMessage));
3642 msg->cm_magic = CTDLMESSAGE_MAGIC;
3643 msg->cm_anon_type = MES_NORMAL;
3644 msg->cm_format_type = 4;
3645 msg->cm_fields['A'] = strdup(CC->user.fullname);
3646 msg->cm_fields['O'] = strdup(req_room);
3647 msg->cm_fields['N'] = strdup(config.c_nodename);
3648 msg->cm_fields['H'] = strdup(config.c_humannode);
3649 msg->cm_flags = flags;
3651 msg->cm_fields['M'] = encoded_message;
3653 /* Create the requested room if we have to. */
3654 if (getroom(&qrbuf, roomname) != 0) {
3655 create_room(roomname,
3656 ( (is_mailbox != NULL) ? 5 : 3 ),
3657 "", 0, 1, 0, VIEW_BBS);
3659 /* If the caller specified this object as unique, delete all
3660 * other objects of this type that are currently in the room.
3663 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3664 CtdlDeleteMessages(roomname, 0L, content_type, 0)
3667 /* Now write the data */
3668 CtdlSubmitMsg(msg, NULL, roomname);
3669 CtdlFreeMessage(msg);
3677 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3678 config_msgnum = msgnum;
3682 char *CtdlGetSysConfig(char *sysconfname) {
3683 char hold_rm[ROOMNAMELEN];
3686 struct CtdlMessage *msg;
3689 strcpy(hold_rm, CC->room.QRname);
3690 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3691 getroom(&CC->room, hold_rm);
3696 /* We want the last (and probably only) config in this room */
3697 begin_critical_section(S_CONFIG);
3698 config_msgnum = (-1L);
3699 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3700 CtdlGetSysConfigBackend, NULL);
3701 msgnum = config_msgnum;
3702 end_critical_section(S_CONFIG);
3708 msg = CtdlFetchMessage(msgnum, 1);
3710 conf = strdup(msg->cm_fields['M']);
3711 CtdlFreeMessage(msg);
3718 getroom(&CC->room, hold_rm);
3720 if (conf != NULL) do {
3721 extract_token(buf, conf, 0, '\n', sizeof buf);
3722 strcpy(conf, &conf[strlen(buf)+1]);
3723 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3728 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3729 char temp[PATH_MAX];
3732 CtdlMakeTempFileName(temp, sizeof temp);
3734 fp = fopen(temp, "w");
3735 if (fp == NULL) return;
3736 fprintf(fp, "%s", sysconfdata);
3739 /* this handy API function does all the work for us */
3740 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3746 * Determine whether a given Internet address belongs to the current user
3748 int CtdlIsMe(char *addr, int addr_buf_len)
3750 struct recptypes *recp;
3753 recp = validate_recipients(addr);
3754 if (recp == NULL) return(0);
3756 if (recp->num_local == 0) {
3761 for (i=0; i<recp->num_local; ++i) {
3762 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3763 if (!strcasecmp(addr, CC->user.fullname)) {
3775 * Citadel protocol command to do the same
3777 void cmd_isme(char *argbuf) {
3780 if (CtdlAccessCheck(ac_logged_in)) return;
3781 extract_token(addr, argbuf, 0, '|', sizeof addr);
3783 if (CtdlIsMe(addr, sizeof addr)) {
3784 cprintf("%d %s\n", CIT_OK, addr);
3787 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);