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;
143 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
144 stripallbut(name, '<', '>');
152 "mail.aliases", "r");
154 fp = fopen("/dev/null", "r");
161 while (fgets(aaa, sizeof aaa, fp) != NULL) {
162 while (isspace(name[0]))
163 strcpy(name, &name[1]);
164 aaa[strlen(aaa) - 1] = 0;
166 for (a = 0; a < strlen(aaa); ++a) {
168 strcpy(bbb, &aaa[a + 1]);
172 if (!strcasecmp(name, aaa))
177 /* Hit the Global Address Book */
178 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
182 lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name);
184 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
185 for (a=0; a<strlen(name); ++a) {
186 if (name[a] == '@') {
187 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
189 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
194 /* determine local or remote type, see citadel.h */
195 at = haschar(name, '@');
196 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
197 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
198 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
200 /* figure out the delivery mode */
201 extract_token(node, name, 1, '@', sizeof node);
203 /* If there are one or more dots in the nodename, we assume that it
204 * is an FQDN and will attempt SMTP delivery to the Internet.
206 if (haschar(node, '.') > 0) {
207 return(MES_INTERNET);
210 /* Otherwise we look in the IGnet maps for a valid Citadel node.
211 * Try directly-connected nodes first...
213 ignetcfg = CtdlGetSysConfig(IGNETCFG);
214 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
215 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
216 extract_token(testnode, buf, 0, '|', sizeof testnode);
217 if (!strcasecmp(node, testnode)) {
225 * Then try nodes that are two or more hops away.
227 ignetmap = CtdlGetSysConfig(IGNETMAP);
228 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
229 extract_token(buf, ignetmap, i, '\n', sizeof buf);
230 extract_token(testnode, buf, 0, '|', sizeof testnode);
231 if (!strcasecmp(node, testnode)) {
238 /* If we get to this point it's an invalid node name */
248 #ifndef HAVE_DATA_DIR
253 "/citadel.control", "r");
255 lprintf(CTDL_CRIT, "Cannot open citadel.control: %s\n",
259 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
265 * Back end for the MSGS command: output message number only.
267 void simple_listing(long msgnum, void *userdata)
269 cprintf("%ld\n", msgnum);
275 * Back end for the MSGS command: output header summary.
277 void headers_listing(long msgnum, void *userdata)
279 struct CtdlMessage *msg;
281 msg = CtdlFetchMessage(msgnum, 0);
283 cprintf("%ld|0|||||\n", msgnum);
287 cprintf("%ld|%s|%s|%s|%s|%s|\n",
289 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
290 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
291 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
292 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
293 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
295 CtdlFreeMessage(msg);
300 /* Determine if a given message matches the fields in a message template.
301 * Return 0 for a successful match.
303 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
306 /* If there aren't any fields in the template, all messages will
309 if (template == NULL) return(0);
311 /* Null messages are bogus. */
312 if (msg == NULL) return(1);
314 for (i='A'; i<='Z'; ++i) {
315 if (template->cm_fields[i] != NULL) {
316 if (msg->cm_fields[i] == NULL) {
319 if (strcasecmp(msg->cm_fields[i],
320 template->cm_fields[i])) return 1;
324 /* All compares succeeded: we have a match! */
331 * Retrieve the "seen" message list for the current room.
333 void CtdlGetSeen(char *buf, int which_set) {
336 /* Learn about the user and room in question */
337 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
339 if (which_set == ctdlsetseen_seen)
340 safestrncpy(buf, vbuf.v_seen, SIZ);
341 if (which_set == ctdlsetseen_answered)
342 safestrncpy(buf, vbuf.v_answered, SIZ);
348 * Manipulate the "seen msgs" string (or other message set strings)
350 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
351 int target_setting, int which_set,
352 struct ctdluser *which_user, struct ctdlroom *which_room) {
353 struct cdbdata *cdbfr;
365 char *is_set; /* actually an array of booleans */
368 char setstr[SIZ], lostr[SIZ], histr[SIZ];
371 lprintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %d, %d)\n",
372 num_target_msgnums, target_msgnums[0],
373 target_setting, which_set);
375 /* Learn about the user and room in question */
376 CtdlGetRelationship(&vbuf,
377 ((which_user != NULL) ? which_user : &CC->user),
378 ((which_room != NULL) ? which_room : &CC->room)
381 /* Load the message list */
382 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
384 msglist = (long *) cdbfr->ptr;
385 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
386 num_msgs = cdbfr->len / sizeof(long);
389 return; /* No messages at all? No further action. */
392 is_set = malloc(num_msgs * sizeof(char));
393 memset(is_set, 0, (num_msgs * sizeof(char)) );
395 /* Decide which message set we're manipulating */
397 case ctdlsetseen_seen:
398 safestrncpy(vset, vbuf.v_seen, sizeof vset);
400 case ctdlsetseen_answered:
401 safestrncpy(vset, vbuf.v_answered, sizeof vset);
405 /* lprintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
407 /* Translate the existing sequence set into an array of booleans */
408 num_sets = num_tokens(vset, ',');
409 for (s=0; s<num_sets; ++s) {
410 extract_token(setstr, vset, s, ',', sizeof setstr);
412 extract_token(lostr, setstr, 0, ':', sizeof lostr);
413 if (num_tokens(setstr, ':') >= 2) {
414 extract_token(histr, setstr, 1, ':', sizeof histr);
415 if (!strcmp(histr, "*")) {
416 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
420 strcpy(histr, lostr);
425 for (i = 0; i < num_msgs; ++i) {
426 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
432 /* Now translate the array of booleans back into a sequence set */
437 for (i=0; i<num_msgs; ++i) {
439 is_seen = is_set[i]; /* Default to existing setting */
441 for (k=0; k<num_target_msgnums; ++k) {
442 if (msglist[i] == target_msgnums[k]) {
443 is_seen = target_setting;
448 if (lo < 0L) lo = msglist[i];
452 if ( ((is_seen == 0) && (was_seen == 1))
453 || ((is_seen == 1) && (i == num_msgs-1)) ) {
455 /* begin trim-o-matic code */
458 while ( (strlen(vset) + 20) > sizeof vset) {
459 remove_token(vset, 0, ',');
461 if (j--) break; /* loop no more than 9 times */
463 if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
467 snprintf(lostr, sizeof lostr,
468 "1:%ld,%s", t, vset);
469 safestrncpy(vset, lostr, sizeof vset);
471 /* end trim-o-matic code */
479 snprintf(&vset[tmp], (sizeof vset) - tmp,
483 snprintf(&vset[tmp], (sizeof vset) - tmp,
492 /* Decide which message set we're manipulating */
494 case ctdlsetseen_seen:
495 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
497 case ctdlsetseen_answered:
498 safestrncpy(vbuf.v_answered, vset,
499 sizeof vbuf.v_answered);
504 /* lprintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
506 CtdlSetRelationship(&vbuf,
507 ((which_user != NULL) ? which_user : &CC->user),
508 ((which_room != NULL) ? which_room : &CC->room)
514 * API function to perform an operation for each qualifying message in the
515 * current room. (Returns the number of messages processed.)
517 int CtdlForEachMessage(int mode, long ref,
519 struct CtdlMessage *compare,
520 void (*CallBack) (long, void *),
526 struct cdbdata *cdbfr;
527 long *msglist = NULL;
529 int num_processed = 0;
532 struct CtdlMessage *msg;
535 int printed_lastold = 0;
537 /* Learn about the user and room in question */
539 getuser(&CC->user, CC->curr_user);
540 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
542 /* Load the message list */
543 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
545 msglist = (long *) cdbfr->ptr;
546 cdbfr->ptr = NULL; /* CtdlForEachMessage() now owns this memory */
547 num_msgs = cdbfr->len / sizeof(long);
550 return 0; /* No messages at all? No further action. */
555 * Now begin the traversal.
557 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
559 /* If the caller is looking for a specific MIME type, filter
560 * out all messages which are not of the type requested.
562 if (content_type != NULL) if (strlen(content_type) > 0) {
564 /* This call to GetMetaData() sits inside this loop
565 * so that we only do the extra database read per msg
566 * if we need to. Doing the extra read all the time
567 * really kills the server. If we ever need to use
568 * metadata for another search criterion, we need to
569 * move the read somewhere else -- but still be smart
570 * enough to only do the read if the caller has
571 * specified something that will need it.
573 GetMetaData(&smi, msglist[a]);
575 if (strcasecmp(smi.meta_content_type, content_type)) {
581 num_msgs = sort_msglist(msglist, num_msgs);
583 /* If a template was supplied, filter out the messages which
584 * don't match. (This could induce some delays!)
587 if (compare != NULL) {
588 for (a = 0; a < num_msgs; ++a) {
589 msg = CtdlFetchMessage(msglist[a], 1);
591 if (CtdlMsgCmp(msg, compare)) {
594 CtdlFreeMessage(msg);
602 * Now iterate through the message list, according to the
603 * criteria supplied by the caller.
606 for (a = 0; a < num_msgs; ++a) {
607 thismsg = msglist[a];
608 if (mode == MSGS_ALL) {
612 is_seen = is_msg_in_sequence_set(
613 vbuf.v_seen, thismsg);
614 if (is_seen) lastold = thismsg;
620 || ((mode == MSGS_OLD) && (is_seen))
621 || ((mode == MSGS_NEW) && (!is_seen))
622 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
623 || ((mode == MSGS_FIRST) && (a < ref))
624 || ((mode == MSGS_GT) && (thismsg > ref))
625 || ((mode == MSGS_EQ) && (thismsg == ref))
628 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
630 CallBack(lastold, userdata);
634 if (CallBack) CallBack(thismsg, userdata);
638 free(msglist); /* Clean up */
639 return num_processed;
645 * cmd_msgs() - get list of message #'s in this room
646 * implements the MSGS server command using CtdlForEachMessage()
648 void cmd_msgs(char *cmdbuf)
657 int with_template = 0;
658 struct CtdlMessage *template = NULL;
659 int with_headers = 0;
661 extract_token(which, cmdbuf, 0, '|', sizeof which);
662 cm_ref = extract_int(cmdbuf, 1);
663 with_template = extract_int(cmdbuf, 2);
664 with_headers = extract_int(cmdbuf, 3);
668 if (!strncasecmp(which, "OLD", 3))
670 else if (!strncasecmp(which, "NEW", 3))
672 else if (!strncasecmp(which, "FIRST", 5))
674 else if (!strncasecmp(which, "LAST", 4))
676 else if (!strncasecmp(which, "GT", 2))
679 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
680 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
686 cprintf("%d Send template then receive message list\n",
688 template = (struct CtdlMessage *)
689 malloc(sizeof(struct CtdlMessage));
690 memset(template, 0, sizeof(struct CtdlMessage));
691 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
692 extract_token(tfield, buf, 0, '|', sizeof tfield);
693 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
694 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
695 if (!strcasecmp(tfield, msgkeys[i])) {
696 template->cm_fields[i] =
704 cprintf("%d \n", LISTING_FOLLOWS);
707 CtdlForEachMessage(mode,
711 (with_headers ? headers_listing : simple_listing),
714 if (template != NULL) CtdlFreeMessage(template);
722 * help_subst() - support routine for help file viewer
724 void help_subst(char *strbuf, char *source, char *dest)
729 while (p = pattern2(strbuf, source), (p >= 0)) {
730 strcpy(workbuf, &strbuf[p + strlen(source)]);
731 strcpy(&strbuf[p], dest);
732 strcat(strbuf, workbuf);
737 void do_help_subst(char *buffer)
741 help_subst(buffer, "^nodename", config.c_nodename);
742 help_subst(buffer, "^humannode", config.c_humannode);
743 help_subst(buffer, "^fqdn", config.c_fqdn);
744 help_subst(buffer, "^username", CC->user.fullname);
745 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
746 help_subst(buffer, "^usernum", buf2);
747 help_subst(buffer, "^sysadm", config.c_sysadm);
748 help_subst(buffer, "^variantname", CITADEL);
749 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
750 help_subst(buffer, "^maxsessions", buf2);
751 help_subst(buffer, "^bbsdir", CTDLDIR);
757 * memfmout() - Citadel text formatter and paginator.
758 * Although the original purpose of this routine was to format
759 * text to the reader's screen width, all we're really using it
760 * for here is to format text out to 80 columns before sending it
761 * to the client. The client software may reformat it again.
764 int width, /* screen width to use */
765 char *mptr, /* where are we going to get our text from? */
766 char subst, /* nonzero if we should do substitutions */
767 char *nl) /* string to terminate lines with */
779 c = 1; /* c is the current pos */
783 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
785 buffer[strlen(buffer) + 1] = 0;
786 buffer[strlen(buffer)] = ch;
789 if (buffer[0] == '^')
790 do_help_subst(buffer);
792 buffer[strlen(buffer) + 1] = 0;
794 strcpy(buffer, &buffer[1]);
802 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
804 if (((old == 13) || (old == 10)) && (isspace(real))) {
812 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
813 cprintf("%s%s", nl, aaa);
822 if ((strlen(aaa) + c) > (width - 5)) {
831 if ((ch == 13) || (ch == 10)) {
832 cprintf("%s%s", aaa, nl);
839 cprintf("%s%s", aaa, nl);
845 * Callback function for mime parser that simply lists the part
847 void list_this_part(char *name, char *filename, char *partnum, char *disp,
848 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
853 ma = (struct ma_info *)cbuserdata;
854 if (ma->is_ma == 0) {
855 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
856 name, filename, partnum, disp, cbtype, (long)length);
861 * Callback function for multipart prefix
863 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
864 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
869 ma = (struct ma_info *)cbuserdata;
870 if (!strcasecmp(cbtype, "multipart/alternative")) {
874 if (ma->is_ma == 0) {
875 cprintf("pref=%s|%s\n", partnum, cbtype);
880 * Callback function for multipart sufffix
882 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
883 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
888 ma = (struct ma_info *)cbuserdata;
889 if (ma->is_ma == 0) {
890 cprintf("suff=%s|%s\n", partnum, cbtype);
892 if (!strcasecmp(cbtype, "multipart/alternative")) {
899 * Callback function for mime parser that opens a section for downloading
901 void mime_download(char *name, char *filename, char *partnum, char *disp,
902 void *content, char *cbtype, char *cbcharset, size_t length,
903 char *encoding, void *cbuserdata)
906 /* Silently go away if there's already a download open... */
907 if (CC->download_fp != NULL)
910 /* ...or if this is not the desired section */
911 if (strcasecmp(CC->download_desired_section, partnum))
914 CC->download_fp = tmpfile();
915 if (CC->download_fp == NULL)
918 fwrite(content, length, 1, CC->download_fp);
919 fflush(CC->download_fp);
920 rewind(CC->download_fp);
922 OpenCmdResult(filename, cbtype);
928 * Load a message from disk into memory.
929 * This is used by CtdlOutputMsg() and other fetch functions.
931 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
932 * using the CtdlMessageFree() function.
934 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
936 struct cdbdata *dmsgtext;
937 struct CtdlMessage *ret = NULL;
941 cit_uint8_t field_header;
943 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
945 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
946 if (dmsgtext == NULL) {
949 mptr = dmsgtext->ptr;
950 upper_bound = mptr + dmsgtext->len;
952 /* Parse the three bytes that begin EVERY message on disk.
953 * The first is always 0xFF, the on-disk magic number.
954 * The second is the anonymous/public type byte.
955 * The third is the format type byte (vari, fixed, or MIME).
960 "Message %ld appears to be corrupted.\n",
965 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
966 memset(ret, 0, sizeof(struct CtdlMessage));
968 ret->cm_magic = CTDLMESSAGE_MAGIC;
969 ret->cm_anon_type = *mptr++; /* Anon type byte */
970 ret->cm_format_type = *mptr++; /* Format type byte */
973 * The rest is zero or more arbitrary fields. Load them in.
974 * We're done when we encounter either a zero-length field or
975 * have just processed the 'M' (message text) field.
978 if (mptr >= upper_bound) {
981 field_header = *mptr++;
982 ret->cm_fields[field_header] = strdup(mptr);
984 while (*mptr++ != 0); /* advance to next field */
986 } while ((mptr < upper_bound) && (field_header != 'M'));
990 /* Always make sure there's something in the msg text field. If
991 * it's NULL, the message text is most likely stored separately,
992 * so go ahead and fetch that. Failing that, just set a dummy
993 * body so other code doesn't barf.
995 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
996 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
997 if (dmsgtext != NULL) {
998 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1002 if (ret->cm_fields['M'] == NULL) {
1003 ret->cm_fields['M'] = strdup("<no text>\n");
1006 /* Perform "before read" hooks (aborting if any return nonzero) */
1007 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1008 CtdlFreeMessage(ret);
1017 * Returns 1 if the supplied pointer points to a valid Citadel message.
1018 * If the pointer is NULL or the magic number check fails, returns 0.
1020 int is_valid_message(struct CtdlMessage *msg) {
1023 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1024 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1032 * 'Destructor' for struct CtdlMessage
1034 void CtdlFreeMessage(struct CtdlMessage *msg)
1038 if (is_valid_message(msg) == 0) return;
1040 for (i = 0; i < 256; ++i)
1041 if (msg->cm_fields[i] != NULL) {
1042 free(msg->cm_fields[i]);
1045 msg->cm_magic = 0; /* just in case */
1051 * Pre callback function for multipart/alternative
1053 * NOTE: this differs from the standard behavior for a reason. Normally when
1054 * displaying multipart/alternative you want to show the _last_ usable
1055 * format in the message. Here we show the _first_ one, because it's
1056 * usually text/plain. Since this set of functions is designed for text
1057 * output to non-MIME-aware clients, this is the desired behavior.
1060 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1061 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1066 ma = (struct ma_info *)cbuserdata;
1067 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1068 if (!strcasecmp(cbtype, "multipart/alternative")) {
1072 if (!strcasecmp(cbtype, "message/rfc822")) {
1078 * Post callback function for multipart/alternative
1080 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1081 void *content, char *cbtype, char *cbcharset, size_t length,
1082 char *encoding, void *cbuserdata)
1086 ma = (struct ma_info *)cbuserdata;
1087 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1088 if (!strcasecmp(cbtype, "multipart/alternative")) {
1092 if (!strcasecmp(cbtype, "message/rfc822")) {
1098 * Inline callback function for mime parser that wants to display text
1100 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1101 void *content, char *cbtype, char *cbcharset, size_t length,
1102 char *encoding, void *cbuserdata)
1109 ma = (struct ma_info *)cbuserdata;
1112 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1113 partnum, filename, cbtype, (long)length);
1116 * If we're in the middle of a multipart/alternative scope and
1117 * we've already printed another section, skip this one.
1119 if ( (ma->is_ma) && (ma->did_print) ) {
1120 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n",
1126 if ( (!strcasecmp(cbtype, "text/plain"))
1127 || (strlen(cbtype)==0) ) {
1130 client_write(wptr, length);
1131 if (wptr[length-1] != '\n') {
1136 else if (!strcasecmp(cbtype, "text/html")) {
1137 ptr = html_to_ascii(content, length, 80, 0);
1139 client_write(ptr, wlen);
1140 if (ptr[wlen-1] != '\n') {
1145 else if (PerformFixedOutputHooks(cbtype, content, length)) {
1146 /* above function returns nonzero if it handled the part */
1148 else if (strncasecmp(cbtype, "multipart/", 10)) {
1149 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1150 partnum, filename, cbtype, (long)length);
1155 * The client is elegant and sophisticated and wants to be choosy about
1156 * MIME content types, so figure out which multipart/alternative part
1157 * we're going to send.
1159 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1160 void *content, char *cbtype, char *cbcharset, size_t length,
1161 char *encoding, void *cbuserdata)
1167 ma = (struct ma_info *)cbuserdata;
1169 if (ma->is_ma > 0) {
1170 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1171 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1172 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1173 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1180 * Now that we've chosen our preferred part, output it.
1182 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1183 void *content, char *cbtype, char *cbcharset, size_t length,
1184 char *encoding, void *cbuserdata)
1188 int add_newline = 0;
1192 ma = (struct ma_info *)cbuserdata;
1194 /* This is not the MIME part you're looking for... */
1195 if (strcasecmp(partnum, ma->chosen_part)) return;
1197 /* If the content-type of this part is in our preferred formats
1198 * list, we can simply output it verbatim.
1200 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1201 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1202 if (!strcasecmp(buf, cbtype)) {
1203 /* Yeah! Go! W00t!! */
1205 text_content = (char *)content;
1206 if (text_content[length-1] != '\n') {
1210 cprintf("Content-type: %s", cbtype);
1211 if (strlen(cbcharset) > 0) {
1212 cprintf("; charset=%s", cbcharset);
1214 cprintf("\nContent-length: %d\n",
1215 (int)(length + add_newline) );
1216 if (strlen(encoding) > 0) {
1217 cprintf("Content-transfer-encoding: %s\n", encoding);
1220 cprintf("Content-transfer-encoding: 7bit\n");
1223 client_write(content, length);
1224 if (add_newline) cprintf("\n");
1229 /* No translations required or possible: output as text/plain */
1230 cprintf("Content-type: text/plain\n\n");
1231 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1232 length, encoding, cbuserdata);
1237 char desired_section[64];
1244 * Callback function for
1246 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1247 void *content, char *cbtype, char *cbcharset, size_t length,
1248 char *encoding, void *cbuserdata)
1250 struct encapmsg *encap;
1252 encap = (struct encapmsg *)cbuserdata;
1254 /* Only proceed if this is the desired section... */
1255 if (!strcasecmp(encap->desired_section, partnum)) {
1256 encap->msglen = length;
1257 encap->msg = malloc(length + 2);
1258 memcpy(encap->msg, content, length);
1268 * Get a message off disk. (returns om_* values found in msgbase.h)
1271 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1272 int mode, /* how would you like that message? */
1273 int headers_only, /* eschew the message body? */
1274 int do_proto, /* do Citadel protocol responses? */
1275 int crlf, /* Use CRLF newlines instead of LF? */
1276 char *section /* NULL or a message/rfc822 section */
1278 struct CtdlMessage *TheMessage = NULL;
1279 int retcode = om_no_such_msg;
1280 struct encapmsg encap;
1282 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1284 (section ? section : "<>")
1287 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1288 if (do_proto) cprintf("%d Not logged in.\n",
1289 ERROR + NOT_LOGGED_IN);
1290 return(om_not_logged_in);
1293 /* FIXME: check message id against msglist for this room */
1296 * Fetch the message from disk. If we're in any sort of headers
1297 * only mode, request that we don't even bother loading the body
1300 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1301 TheMessage = CtdlFetchMessage(msg_num, 0);
1304 TheMessage = CtdlFetchMessage(msg_num, 1);
1307 if (TheMessage == NULL) {
1308 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1309 ERROR + MESSAGE_NOT_FOUND, msg_num);
1310 return(om_no_such_msg);
1313 /* Here is the weird form of this command, to process only an
1314 * encapsulated message/rfc822 section.
1316 if (section) if (strlen(section)>0) if (strcmp(section, "0")) {
1317 memset(&encap, 0, sizeof encap);
1318 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1319 mime_parser(TheMessage->cm_fields['M'],
1321 *extract_encapsulated_message,
1322 NULL, NULL, (void *)&encap, 0
1324 CtdlFreeMessage(TheMessage);
1328 encap.msg[encap.msglen] = 0;
1329 TheMessage = convert_internet_message(encap.msg);
1330 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1332 /* Now we let it fall through to the bottom of this
1333 * function, because TheMessage now contains the
1334 * encapsulated message instead of the top-level
1335 * message. Isn't that neat?
1340 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1341 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1342 retcode = om_no_such_msg;
1347 /* Ok, output the message now */
1348 retcode = CtdlOutputPreLoadedMsg(
1350 headers_only, do_proto, crlf);
1351 CtdlFreeMessage(TheMessage);
1358 * Get a message off disk. (returns om_* values found in msgbase.h)
1361 int CtdlOutputPreLoadedMsg(
1362 struct CtdlMessage *TheMessage,
1363 int mode, /* how would you like that message? */
1364 int headers_only, /* eschew the message body? */
1365 int do_proto, /* do Citadel protocol responses? */
1366 int crlf /* Use CRLF newlines instead of LF? */
1372 char display_name[256];
1374 char *nl; /* newline string */
1376 int subject_found = 0;
1379 /* Buffers needed for RFC822 translation. These are all filled
1380 * using functions that are bounds-checked, and therefore we can
1381 * make them substantially smaller than SIZ.
1389 char datestamp[100];
1391 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1392 ((TheMessage == NULL) ? "NULL" : "not null"),
1393 mode, headers_only, do_proto, crlf);
1395 strcpy(mid, "unknown");
1396 nl = (crlf ? "\r\n" : "\n");
1398 if (!is_valid_message(TheMessage)) {
1400 "ERROR: invalid preloaded message for output\n");
1401 return(om_no_such_msg);
1404 /* Are we downloading a MIME component? */
1405 if (mode == MT_DOWNLOAD) {
1406 if (TheMessage->cm_format_type != FMT_RFC822) {
1408 cprintf("%d This is not a MIME message.\n",
1409 ERROR + ILLEGAL_VALUE);
1410 } else if (CC->download_fp != NULL) {
1411 if (do_proto) cprintf(
1412 "%d You already have a download open.\n",
1413 ERROR + RESOURCE_BUSY);
1415 /* Parse the message text component */
1416 mptr = TheMessage->cm_fields['M'];
1417 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1418 /* If there's no file open by this time, the requested
1419 * section wasn't found, so print an error
1421 if (CC->download_fp == NULL) {
1422 if (do_proto) cprintf(
1423 "%d Section %s not found.\n",
1424 ERROR + FILE_NOT_FOUND,
1425 CC->download_desired_section);
1428 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1431 /* now for the user-mode message reading loops */
1432 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1434 /* Does the caller want to skip the headers? */
1435 if (headers_only == HEADERS_NONE) goto START_TEXT;
1437 /* Tell the client which format type we're using. */
1438 if ( (mode == MT_CITADEL) && (do_proto) ) {
1439 cprintf("type=%d\n", TheMessage->cm_format_type);
1442 /* nhdr=yes means that we're only displaying headers, no body */
1443 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1444 && (mode == MT_CITADEL)
1447 cprintf("nhdr=yes\n");
1450 /* begin header processing loop for Citadel message format */
1452 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1454 safestrncpy(display_name, "<unknown>", sizeof display_name);
1455 if (TheMessage->cm_fields['A']) {
1456 strcpy(buf, TheMessage->cm_fields['A']);
1457 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1458 safestrncpy(display_name, "****", sizeof display_name);
1460 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1461 safestrncpy(display_name, "anonymous", sizeof display_name);
1464 safestrncpy(display_name, buf, sizeof display_name);
1466 if ((is_room_aide())
1467 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1468 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1469 size_t tmp = strlen(display_name);
1470 snprintf(&display_name[tmp],
1471 sizeof display_name - tmp,
1476 /* Don't show Internet address for users on the
1477 * local Citadel network.
1480 if (TheMessage->cm_fields['N'] != NULL)
1481 if (strlen(TheMessage->cm_fields['N']) > 0)
1482 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1486 /* Now spew the header fields in the order we like them. */
1487 safestrncpy(allkeys, FORDER, sizeof allkeys);
1488 for (i=0; i<strlen(allkeys); ++i) {
1489 k = (int) allkeys[i];
1491 if ( (TheMessage->cm_fields[k] != NULL)
1492 && (msgkeys[k] != NULL) ) {
1494 if (do_proto) cprintf("%s=%s\n",
1498 else if ((k == 'F') && (suppress_f)) {
1501 /* Masquerade display name if needed */
1503 if (do_proto) cprintf("%s=%s\n",
1505 TheMessage->cm_fields[k]
1514 /* begin header processing loop for RFC822 transfer format */
1519 strcpy(snode, NODENAME);
1520 strcpy(lnode, HUMANNODE);
1521 if (mode == MT_RFC822) {
1522 for (i = 0; i < 256; ++i) {
1523 if (TheMessage->cm_fields[i]) {
1524 mptr = TheMessage->cm_fields[i];
1527 safestrncpy(luser, mptr, sizeof luser);
1528 safestrncpy(suser, mptr, sizeof suser);
1530 else if (i == 'Y') {
1531 cprintf("CC: %s%s", mptr, nl);
1533 else if (i == 'U') {
1534 cprintf("Subject: %s%s", mptr, nl);
1538 safestrncpy(mid, mptr, sizeof mid);
1540 safestrncpy(lnode, mptr, sizeof lnode);
1542 safestrncpy(fuser, mptr, sizeof fuser);
1543 /* else if (i == 'O')
1544 cprintf("X-Citadel-Room: %s%s",
1547 safestrncpy(snode, mptr, sizeof snode);
1549 cprintf("To: %s%s", mptr, nl);
1550 else if (i == 'T') {
1551 datestring(datestamp, sizeof datestamp,
1552 atol(mptr), DATESTRING_RFC822);
1553 cprintf("Date: %s%s", datestamp, nl);
1557 if (subject_found == 0) {
1558 cprintf("Subject: (no subject)%s", nl);
1562 for (i=0; i<strlen(suser); ++i) {
1563 suser[i] = tolower(suser[i]);
1564 if (!isalnum(suser[i])) suser[i]='_';
1567 if (mode == MT_RFC822) {
1568 if (!strcasecmp(snode, NODENAME)) {
1569 safestrncpy(snode, FQDN, sizeof snode);
1572 /* Construct a fun message id */
1573 cprintf("Message-ID: <%s", mid);
1574 if (strchr(mid, '@')==NULL) {
1575 cprintf("@%s", snode);
1579 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1580 cprintf("From: \"----\" <x@x.org>%s", nl);
1582 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1583 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1585 else if (strlen(fuser) > 0) {
1586 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1589 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1592 cprintf("Organization: %s%s", lnode, nl);
1594 /* Blank line signifying RFC822 end-of-headers */
1595 if (TheMessage->cm_format_type != FMT_RFC822) {
1600 /* end header processing loop ... at this point, we're in the text */
1602 if (headers_only == HEADERS_FAST) goto DONE;
1603 mptr = TheMessage->cm_fields['M'];
1605 /* Tell the client about the MIME parts in this message */
1606 if (TheMessage->cm_format_type == FMT_RFC822) {
1607 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1608 memset(&ma, 0, sizeof(struct ma_info));
1609 mime_parser(mptr, NULL,
1610 (do_proto ? *list_this_part : NULL),
1611 (do_proto ? *list_this_pref : NULL),
1612 (do_proto ? *list_this_suff : NULL),
1615 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1616 char *start_of_text = NULL;
1617 start_of_text = strstr(mptr, "\n\r\n");
1618 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1619 if (start_of_text == NULL) start_of_text = mptr;
1621 start_of_text = strstr(start_of_text, "\n");
1623 while (ch=*mptr, ch!=0) {
1627 else switch(headers_only) {
1629 if (mptr >= start_of_text) {
1630 if (ch == 10) cprintf("%s", nl);
1631 else cprintf("%c", ch);
1635 if (mptr < start_of_text) {
1636 if (ch == 10) cprintf("%s", nl);
1637 else cprintf("%c", ch);
1641 if (ch == 10) cprintf("%s", nl);
1642 else cprintf("%c", ch);
1651 if (headers_only == HEADERS_ONLY) {
1655 /* signify start of msg text */
1656 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1657 if (do_proto) cprintf("text\n");
1660 /* If the format type on disk is 1 (fixed-format), then we want
1661 * everything to be output completely literally ... regardless of
1662 * what message transfer format is in use.
1664 if (TheMessage->cm_format_type == FMT_FIXED) {
1665 if (mode == MT_MIME) {
1666 cprintf("Content-type: text/plain\n\n");
1669 while (ch = *mptr++, ch > 0) {
1672 if ((ch == 10) || (strlen(buf) > 250)) {
1673 cprintf("%s%s", buf, nl);
1676 buf[strlen(buf) + 1] = 0;
1677 buf[strlen(buf)] = ch;
1680 if (strlen(buf) > 0)
1681 cprintf("%s%s", buf, nl);
1684 /* If the message on disk is format 0 (Citadel vari-format), we
1685 * output using the formatter at 80 columns. This is the final output
1686 * form if the transfer format is RFC822, but if the transfer format
1687 * is Citadel proprietary, it'll still work, because the indentation
1688 * for new paragraphs is correct and the client will reformat the
1689 * message to the reader's screen width.
1691 if (TheMessage->cm_format_type == FMT_CITADEL) {
1692 if (mode == MT_MIME) {
1693 cprintf("Content-type: text/x-citadel-variformat\n\n");
1695 memfmout(80, mptr, 0, nl);
1698 /* If the message on disk is format 4 (MIME), we've gotta hand it
1699 * off to the MIME parser. The client has already been told that
1700 * this message is format 1 (fixed format), so the callback function
1701 * we use will display those parts as-is.
1703 if (TheMessage->cm_format_type == FMT_RFC822) {
1704 memset(&ma, 0, sizeof(struct ma_info));
1706 if (mode == MT_MIME) {
1707 strcpy(ma.chosen_part, "1");
1708 mime_parser(mptr, NULL,
1709 *choose_preferred, *fixed_output_pre,
1710 *fixed_output_post, (void *)&ma, 0);
1711 mime_parser(mptr, NULL,
1712 *output_preferred, NULL, NULL, (void *)&ma, 0);
1715 mime_parser(mptr, NULL,
1716 *fixed_output, *fixed_output_pre,
1717 *fixed_output_post, (void *)&ma, 0);
1722 DONE: /* now we're done */
1723 if (do_proto) cprintf("000\n");
1730 * display a message (mode 0 - Citadel proprietary)
1732 void cmd_msg0(char *cmdbuf)
1735 int headers_only = HEADERS_ALL;
1737 msgid = extract_long(cmdbuf, 0);
1738 headers_only = extract_int(cmdbuf, 1);
1740 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL);
1746 * display a message (mode 2 - RFC822)
1748 void cmd_msg2(char *cmdbuf)
1751 int headers_only = HEADERS_ALL;
1753 msgid = extract_long(cmdbuf, 0);
1754 headers_only = extract_int(cmdbuf, 1);
1756 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL);
1762 * display a message (mode 3 - IGnet raw format - internal programs only)
1764 void cmd_msg3(char *cmdbuf)
1767 struct CtdlMessage *msg;
1770 if (CC->internal_pgm == 0) {
1771 cprintf("%d This command is for internal programs only.\n",
1772 ERROR + HIGHER_ACCESS_REQUIRED);
1776 msgnum = extract_long(cmdbuf, 0);
1777 msg = CtdlFetchMessage(msgnum, 1);
1779 cprintf("%d Message %ld not found.\n",
1780 ERROR + MESSAGE_NOT_FOUND, msgnum);
1784 serialize_message(&smr, msg);
1785 CtdlFreeMessage(msg);
1788 cprintf("%d Unable to serialize message\n",
1789 ERROR + INTERNAL_ERROR);
1793 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1794 client_write((char *)smr.ser, (int)smr.len);
1801 * Display a message using MIME content types
1803 void cmd_msg4(char *cmdbuf)
1808 msgid = extract_long(cmdbuf, 0);
1809 extract_token(section, cmdbuf, 1, '|', sizeof section);
1810 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) );
1816 * Client tells us its preferred message format(s)
1818 void cmd_msgp(char *cmdbuf)
1820 safestrncpy(CC->preferred_formats, cmdbuf,
1821 sizeof(CC->preferred_formats));
1822 cprintf("%d ok\n", CIT_OK);
1827 * Open a component of a MIME message as a download file
1829 void cmd_opna(char *cmdbuf)
1832 char desired_section[128];
1834 msgid = extract_long(cmdbuf, 0);
1835 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1836 safestrncpy(CC->download_desired_section, desired_section,
1837 sizeof CC->download_desired_section);
1838 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL);
1843 * Save a message pointer into a specified room
1844 * (Returns 0 for success, nonzero for failure)
1845 * roomname may be NULL to use the current room
1847 * Note that the 'supplied_msg' field may be set to NULL, in which case
1848 * the message will be fetched from disk, by number, if we need to perform
1849 * replication checks. This adds an additional database read, so if the
1850 * caller already has the message in memory then it should be supplied.
1852 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check,
1853 struct CtdlMessage *supplied_msg) {
1855 char hold_rm[ROOMNAMELEN];
1856 struct cdbdata *cdbfr;
1859 long highest_msg = 0L;
1860 struct CtdlMessage *msg = NULL;
1862 /*lprintf(CTDL_DEBUG,
1863 "CtdlSaveMsgPointerInRoom(room=%s, msgid=%ld, repl=%d)\n",
1864 roomname, msgid, do_repl_check);*/
1866 strcpy(hold_rm, CC->room.QRname);
1868 /* Now the regular stuff */
1869 if (lgetroom(&CC->room,
1870 ((roomname != NULL) ? roomname : CC->room.QRname) )
1872 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1873 return(ERROR + ROOM_NOT_FOUND);
1876 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1877 if (cdbfr == NULL) {
1881 msglist = (long *) cdbfr->ptr;
1882 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
1883 num_msgs = cdbfr->len / sizeof(long);
1887 /* Make sure the message doesn't already exist in this room. It
1888 * is absolutely taboo to have more than one reference to the same
1889 * message in a room.
1891 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1892 if (msglist[i] == msgid) {
1893 lputroom(&CC->room); /* unlock the room */
1894 getroom(&CC->room, hold_rm);
1896 return(ERROR + ALREADY_EXISTS);
1900 /* Now add the new message */
1902 msglist = realloc(msglist, (num_msgs * sizeof(long)));
1904 if (msglist == NULL) {
1905 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1907 msglist[num_msgs - 1] = msgid;
1909 /* Sort the message list, so all the msgid's are in order */
1910 num_msgs = sort_msglist(msglist, num_msgs);
1912 /* Determine the highest message number */
1913 highest_msg = msglist[num_msgs - 1];
1915 /* Write it back to disk. */
1916 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1917 msglist, (int)(num_msgs * sizeof(long)));
1919 /* Free up the memory we used. */
1922 /* Update the highest-message pointer and unlock the room. */
1923 CC->room.QRhighest = highest_msg;
1924 lputroom(&CC->room);
1926 /* Perform replication checks if necessary */
1927 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
1928 if (supplied_msg != NULL) {
1932 msg = CtdlFetchMessage(msgid, 0);
1936 ReplicationChecks(msg);
1941 /* If the message has an Exclusive ID, index that... */
1943 if (msg->cm_fields['E'] != NULL) {
1944 index_message_by_euid(msg->cm_fields['E'],
1949 /* Free up the memory we may have allocated */
1950 if ( (msg != NULL) && (msg != supplied_msg) ) {
1951 CtdlFreeMessage(msg);
1954 /* Go back to the room we were in before we wandered here... */
1955 getroom(&CC->room, hold_rm);
1957 /* Bump the reference count for this message. */
1958 AdjRefCount(msgid, +1);
1960 /* Return success. */
1967 * Message base operation to save a new message to the message store
1968 * (returns new message number)
1970 * This is the back end for CtdlSubmitMsg() and should not be directly
1971 * called by server-side modules.
1974 long send_message(struct CtdlMessage *msg) {
1982 /* Get a new message number */
1983 newmsgid = get_new_message_number();
1984 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1986 /* Generate an ID if we don't have one already */
1987 if (msg->cm_fields['I']==NULL) {
1988 msg->cm_fields['I'] = strdup(msgidbuf);
1991 /* If the message is big, set its body aside for storage elsewhere */
1992 if (msg->cm_fields['M'] != NULL) {
1993 if (strlen(msg->cm_fields['M']) > BIGMSG) {
1995 holdM = msg->cm_fields['M'];
1996 msg->cm_fields['M'] = NULL;
2000 /* Serialize our data structure for storage in the database */
2001 serialize_message(&smr, msg);
2004 msg->cm_fields['M'] = holdM;
2008 cprintf("%d Unable to serialize message\n",
2009 ERROR + INTERNAL_ERROR);
2013 /* Write our little bundle of joy into the message base */
2014 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2015 smr.ser, smr.len) < 0) {
2016 lprintf(CTDL_ERR, "Can't store message\n");
2020 cdb_store(CDB_BIGMSGS,
2030 /* Free the memory we used for the serialized message */
2033 /* Return the *local* message ID to the caller
2034 * (even if we're storing an incoming network message)
2042 * Serialize a struct CtdlMessage into the format used on disk and network.
2044 * This function loads up a "struct ser_ret" (defined in server.h) which
2045 * contains the length of the serialized message and a pointer to the
2046 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2048 void serialize_message(struct ser_ret *ret, /* return values */
2049 struct CtdlMessage *msg) /* unserialized msg */
2053 static char *forder = FORDER;
2055 if (is_valid_message(msg) == 0) return; /* self check */
2058 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2059 ret->len = ret->len +
2060 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2062 lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
2063 ret->ser = malloc(ret->len);
2064 if (ret->ser == NULL) {
2070 ret->ser[1] = msg->cm_anon_type;
2071 ret->ser[2] = msg->cm_format_type;
2074 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2075 ret->ser[wlen++] = (char)forder[i];
2076 strcpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
2077 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
2079 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2080 (long)ret->len, (long)wlen);
2088 * Check to see if any messages already exist in the current room which
2089 * carry the same Exclusive ID as this one. If any are found, delete them.
2091 void ReplicationChecks(struct CtdlMessage *msg) {
2092 long old_msgnum = (-1L);
2094 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2096 lprintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2099 /* No exclusive id? Don't do anything. */
2100 if (msg == NULL) return;
2101 if (msg->cm_fields['E'] == NULL) return;
2102 if (strlen(msg->cm_fields['E']) == 0) return;
2103 /*lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2104 msg->cm_fields['E'], CC->room.QRname);*/
2106 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2107 if (old_msgnum > 0L) {
2108 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2109 CtdlDeleteMessages(CC->room.QRname, old_msgnum, "", 0);
2116 * Save a message to disk and submit it into the delivery system.
2118 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2119 struct recptypes *recps, /* recipients (if mail) */
2120 char *force /* force a particular room? */
2122 char submit_filename[128];
2123 char generated_timestamp[32];
2124 char hold_rm[ROOMNAMELEN];
2125 char actual_rm[ROOMNAMELEN];
2126 char force_room[ROOMNAMELEN];
2127 char content_type[SIZ]; /* We have to learn this */
2128 char recipient[SIZ];
2131 struct ctdluser userbuf;
2133 struct MetaData smi;
2134 FILE *network_fp = NULL;
2135 static int seqnum = 1;
2136 struct CtdlMessage *imsg = NULL;
2139 char *hold_R, *hold_D;
2140 char *collected_addresses = NULL;
2141 struct addresses_to_be_filed *aptr = NULL;
2142 char *saved_rfc822_version = NULL;
2143 int qualified_for_journaling = 0;
2145 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2146 if (is_valid_message(msg) == 0) return(-1); /* self check */
2148 /* If this message has no timestamp, we take the liberty of
2149 * giving it one, right now.
2151 if (msg->cm_fields['T'] == NULL) {
2152 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2153 msg->cm_fields['T'] = strdup(generated_timestamp);
2156 /* If this message has no path, we generate one.
2158 if (msg->cm_fields['P'] == NULL) {
2159 if (msg->cm_fields['A'] != NULL) {
2160 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2161 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2162 if (isspace(msg->cm_fields['P'][a])) {
2163 msg->cm_fields['P'][a] = ' ';
2168 msg->cm_fields['P'] = strdup("unknown");
2172 if (force == NULL) {
2173 strcpy(force_room, "");
2176 strcpy(force_room, force);
2179 /* Learn about what's inside, because it's what's inside that counts */
2180 if (msg->cm_fields['M'] == NULL) {
2181 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2185 switch (msg->cm_format_type) {
2187 strcpy(content_type, "text/x-citadel-variformat");
2190 strcpy(content_type, "text/plain");
2193 strcpy(content_type, "text/plain");
2194 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type: ");
2196 safestrncpy(content_type, &mptr[14],
2197 sizeof content_type);
2198 for (a = 0; a < strlen(content_type); ++a) {
2199 if ((content_type[a] == ';')
2200 || (content_type[a] == ' ')
2201 || (content_type[a] == 13)
2202 || (content_type[a] == 10)) {
2203 content_type[a] = 0;
2209 /* Goto the correct room */
2210 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
2211 strcpy(hold_rm, CC->room.QRname);
2212 strcpy(actual_rm, CC->room.QRname);
2213 if (recps != NULL) {
2214 strcpy(actual_rm, SENTITEMS);
2217 /* If the user is a twit, move to the twit room for posting */
2219 if (CC->user.axlevel == 2) {
2220 strcpy(hold_rm, actual_rm);
2221 strcpy(actual_rm, config.c_twitroom);
2222 lprintf(CTDL_DEBUG, "Diverting to twit room\n");
2226 /* ...or if this message is destined for Aide> then go there. */
2227 if (strlen(force_room) > 0) {
2228 strcpy(actual_rm, force_room);
2231 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2232 if (strcasecmp(actual_rm, CC->room.QRname)) {
2233 /* getroom(&CC->room, actual_rm); */
2234 usergoto(actual_rm, 0, 1, NULL, NULL);
2238 * If this message has no O (room) field, generate one.
2240 if (msg->cm_fields['O'] == NULL) {
2241 msg->cm_fields['O'] = strdup(CC->room.QRname);
2244 /* Perform "before save" hooks (aborting if any return nonzero) */
2245 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2246 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2249 * If this message has an Exclusive ID, and the room is replication
2250 * checking enabled, then do replication checks.
2252 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2253 ReplicationChecks(msg);
2256 /* Save it to disk */
2257 lprintf(CTDL_DEBUG, "Saving to disk\n");
2258 newmsgid = send_message(msg);
2259 if (newmsgid <= 0L) return(-5);
2261 /* Write a supplemental message info record. This doesn't have to
2262 * be a critical section because nobody else knows about this message
2265 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2266 memset(&smi, 0, sizeof(struct MetaData));
2267 smi.meta_msgnum = newmsgid;
2268 smi.meta_refcount = 0;
2269 safestrncpy(smi.meta_content_type, content_type,
2270 sizeof smi.meta_content_type);
2273 * Measure how big this message will be when rendered as RFC822.
2274 * We do this for two reasons:
2275 * 1. We need the RFC822 length for the new metadata record, so the
2276 * POP and IMAP services don't have to calculate message lengths
2277 * while the user is waiting (multiplied by potentially hundreds
2278 * or thousands of messages).
2279 * 2. If journaling is enabled, we will need an RFC822 version of the
2280 * message to attach to the journalized copy.
2282 if (CC->redirect_buffer != NULL) {
2283 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2286 CC->redirect_buffer = malloc(SIZ);
2287 CC->redirect_len = 0;
2288 CC->redirect_alloc = SIZ;
2289 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2290 smi.meta_rfc822_length = CC->redirect_len;
2291 saved_rfc822_version = CC->redirect_buffer;
2292 CC->redirect_buffer = NULL;
2293 CC->redirect_len = 0;
2294 CC->redirect_alloc = 0;
2298 /* Now figure out where to store the pointers */
2299 lprintf(CTDL_DEBUG, "Storing pointers\n");
2301 /* If this is being done by the networker delivering a private
2302 * message, we want to BYPASS saving the sender's copy (because there
2303 * is no local sender; it would otherwise go to the Trashcan).
2305 if ((!CC->internal_pgm) || (recps == NULL)) {
2306 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2307 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2308 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2312 /* For internet mail, drop a copy in the outbound queue room */
2314 if (recps->num_internet > 0) {
2315 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2318 /* If other rooms are specified, drop them there too. */
2320 if (recps->num_room > 0)
2321 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2322 extract_token(recipient, recps->recp_room, i,
2323 '|', sizeof recipient);
2324 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2325 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2328 /* Bump this user's messages posted counter. */
2329 lprintf(CTDL_DEBUG, "Updating user\n");
2330 lgetuser(&CC->user, CC->curr_user);
2331 CC->user.posted = CC->user.posted + 1;
2332 lputuser(&CC->user);
2334 /* If this is private, local mail, make a copy in the
2335 * recipient's mailbox and bump the reference count.
2338 if (recps->num_local > 0)
2339 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2340 extract_token(recipient, recps->recp_local, i,
2341 '|', sizeof recipient);
2342 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2344 if (getuser(&userbuf, recipient) == 0) {
2345 MailboxName(actual_rm, sizeof actual_rm,
2346 &userbuf, MAILROOM);
2347 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2348 BumpNewMailCounter(userbuf.usernum);
2351 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2352 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2357 /* Perform "after save" hooks */
2358 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2359 PerformMessageHooks(msg, EVT_AFTERSAVE);
2361 /* For IGnet mail, we have to save a new copy into the spooler for
2362 * each recipient, with the R and D fields set to the recipient and
2363 * destination-node. This has two ugly side effects: all other
2364 * recipients end up being unlisted in this recipient's copy of the
2365 * message, and it has to deliver multiple messages to the same
2366 * node. We'll revisit this again in a year or so when everyone has
2367 * a network spool receiver that can handle the new style messages.
2370 if (recps->num_ignet > 0)
2371 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2372 extract_token(recipient, recps->recp_ignet, i,
2373 '|', sizeof recipient);
2375 hold_R = msg->cm_fields['R'];
2376 hold_D = msg->cm_fields['D'];
2377 msg->cm_fields['R'] = malloc(SIZ);
2378 msg->cm_fields['D'] = malloc(128);
2379 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2380 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2382 serialize_message(&smr, msg);
2384 snprintf(submit_filename, sizeof submit_filename,
2385 #ifndef HAVE_SPOOL_DIR
2390 "/network/spoolin/netmail.%04lx.%04x.%04x",
2391 (long) getpid(), CC->cs_pid, ++seqnum);
2392 network_fp = fopen(submit_filename, "wb+");
2393 if (network_fp != NULL) {
2394 fwrite(smr.ser, smr.len, 1, network_fp);
2400 free(msg->cm_fields['R']);
2401 free(msg->cm_fields['D']);
2402 msg->cm_fields['R'] = hold_R;
2403 msg->cm_fields['D'] = hold_D;
2406 /* Go back to the room we started from */
2407 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2408 if (strcasecmp(hold_rm, CC->room.QRname))
2409 /* getroom(&CC->room, hold_rm); */
2410 usergoto(hold_rm, 0, 1, NULL, NULL);
2412 /* For internet mail, generate delivery instructions.
2413 * Yes, this is recursive. Deal with it. Infinite recursion does
2414 * not happen because the delivery instructions message does not
2415 * contain a recipient.
2418 if (recps->num_internet > 0) {
2419 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2420 instr = malloc(SIZ * 2);
2421 snprintf(instr, SIZ * 2,
2422 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2424 SPOOLMIME, newmsgid, (long)time(NULL),
2425 msg->cm_fields['A'], msg->cm_fields['N']
2428 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2429 size_t tmp = strlen(instr);
2430 extract_token(recipient, recps->recp_internet,
2431 i, '|', sizeof recipient);
2432 snprintf(&instr[tmp], SIZ * 2 - tmp,
2433 "remote|%s|0||\n", recipient);
2436 imsg = malloc(sizeof(struct CtdlMessage));
2437 memset(imsg, 0, sizeof(struct CtdlMessage));
2438 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2439 imsg->cm_anon_type = MES_NORMAL;
2440 imsg->cm_format_type = FMT_RFC822;
2441 imsg->cm_fields['A'] = strdup("Citadel");
2442 imsg->cm_fields['J'] = strdup("do not journal");
2443 imsg->cm_fields['M'] = instr;
2444 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2445 CtdlFreeMessage(imsg);
2449 * Any addresses to harvest for someone's address book?
2451 if ( (CC->logged_in) && (recps != NULL) ) {
2452 collected_addresses = harvest_collected_addresses(msg);
2455 if (collected_addresses != NULL) {
2456 begin_critical_section(S_ATBF);
2457 aptr = (struct addresses_to_be_filed *)
2458 malloc(sizeof(struct addresses_to_be_filed));
2460 MailboxName(actual_rm, sizeof actual_rm,
2461 &CC->user, USERCONTACTSROOM);
2462 aptr->roomname = strdup(actual_rm);
2463 aptr->collected_addresses = collected_addresses;
2465 end_critical_section(S_ATBF);
2469 * Determine whether this message qualifies for journaling.
2471 if (msg->cm_fields['J'] != NULL) {
2472 qualified_for_journaling = 0;
2475 if (recps == NULL) {
2476 qualified_for_journaling = config.c_journal_pubmsgs;
2478 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2479 qualified_for_journaling = config.c_journal_email;
2482 qualified_for_journaling = config.c_journal_pubmsgs;
2487 * Do we have to perform journaling? If so, hand off the saved
2488 * RFC822 version will be handed off to the journaler for background
2489 * submit. Otherwise, we have to free the memory ourselves.
2491 if (saved_rfc822_version != NULL) {
2492 if (qualified_for_journaling) {
2493 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2496 free(saved_rfc822_version);
2509 * Convenience function for generating small administrative messages.
2511 void quickie_message(char *from, char *to, char *room, char *text,
2512 int format_type, char *subject)
2514 struct CtdlMessage *msg;
2515 struct recptypes *recp = NULL;
2517 msg = malloc(sizeof(struct CtdlMessage));
2518 memset(msg, 0, sizeof(struct CtdlMessage));
2519 msg->cm_magic = CTDLMESSAGE_MAGIC;
2520 msg->cm_anon_type = MES_NORMAL;
2521 msg->cm_format_type = format_type;
2522 msg->cm_fields['A'] = strdup(from);
2523 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2524 msg->cm_fields['N'] = strdup(NODENAME);
2526 msg->cm_fields['R'] = strdup(to);
2527 recp = validate_recipients(to);
2529 if (subject != NULL) {
2530 msg->cm_fields['U'] = strdup(subject);
2532 msg->cm_fields['M'] = strdup(text);
2534 CtdlSubmitMsg(msg, recp, room);
2535 CtdlFreeMessage(msg);
2536 if (recp != NULL) free(recp);
2542 * Back end function used by CtdlMakeMessage() and similar functions
2544 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2545 size_t maxlen, /* maximum message length */
2546 char *exist, /* if non-null, append to it;
2547 exist is ALWAYS freed */
2548 int crlf /* CRLF newlines instead of LF */
2552 size_t message_len = 0;
2553 size_t buffer_len = 0;
2559 if (exist == NULL) {
2566 message_len = strlen(exist);
2567 buffer_len = message_len + 4096;
2568 m = realloc(exist, buffer_len);
2575 /* flush the input if we have nowhere to store it */
2580 /* read in the lines of message text one by one */
2582 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2583 if (!strcmp(buf, terminator)) finished = 1;
2585 strcat(buf, "\r\n");
2591 if ( (!flushing) && (!finished) ) {
2592 /* Measure the line */
2593 linelen = strlen(buf);
2595 /* augment the buffer if we have to */
2596 if ((message_len + linelen) >= buffer_len) {
2597 ptr = realloc(m, (buffer_len * 2) );
2598 if (ptr == NULL) { /* flush if can't allocate */
2601 buffer_len = (buffer_len * 2);
2603 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2607 /* Add the new line to the buffer. NOTE: this loop must avoid
2608 * using functions like strcat() and strlen() because they
2609 * traverse the entire buffer upon every call, and doing that
2610 * for a multi-megabyte message slows it down beyond usability.
2612 strcpy(&m[message_len], buf);
2613 message_len += linelen;
2616 /* if we've hit the max msg length, flush the rest */
2617 if (message_len >= maxlen) flushing = 1;
2619 } while (!finished);
2627 * Build a binary message to be saved on disk.
2628 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2629 * will become part of the message. This means you are no longer
2630 * responsible for managing that memory -- it will be freed along with
2631 * the rest of the fields when CtdlFreeMessage() is called.)
2634 struct CtdlMessage *CtdlMakeMessage(
2635 struct ctdluser *author, /* author's user structure */
2636 char *recipient, /* NULL if it's not mail */
2637 char *recp_cc, /* NULL if it's not mail */
2638 char *room, /* room where it's going */
2639 int type, /* see MES_ types in header file */
2640 int format_type, /* variformat, plain text, MIME... */
2641 char *fake_name, /* who we're masquerading as */
2642 char *subject, /* Subject (optional) */
2643 char *preformatted_text /* ...or NULL to read text from client */
2645 char dest_node[SIZ];
2647 struct CtdlMessage *msg;
2649 msg = malloc(sizeof(struct CtdlMessage));
2650 memset(msg, 0, sizeof(struct CtdlMessage));
2651 msg->cm_magic = CTDLMESSAGE_MAGIC;
2652 msg->cm_anon_type = type;
2653 msg->cm_format_type = format_type;
2655 /* Don't confuse the poor folks if it's not routed mail. */
2656 strcpy(dest_node, "");
2661 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2662 msg->cm_fields['P'] = strdup(buf);
2664 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2665 msg->cm_fields['T'] = strdup(buf);
2667 if (fake_name[0]) /* author */
2668 msg->cm_fields['A'] = strdup(fake_name);
2670 msg->cm_fields['A'] = strdup(author->fullname);
2672 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2673 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2676 msg->cm_fields['O'] = strdup(CC->room.QRname);
2679 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2680 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2682 if (recipient[0] != 0) {
2683 msg->cm_fields['R'] = strdup(recipient);
2685 if (recp_cc[0] != 0) {
2686 msg->cm_fields['Y'] = strdup(recp_cc);
2688 if (dest_node[0] != 0) {
2689 msg->cm_fields['D'] = strdup(dest_node);
2692 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2693 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2696 if (subject != NULL) {
2698 if (strlen(subject) > 0) {
2699 msg->cm_fields['U'] = strdup(subject);
2703 if (preformatted_text != NULL) {
2704 msg->cm_fields['M'] = preformatted_text;
2707 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2708 config.c_maxmsglen, NULL, 0);
2716 * Check to see whether we have permission to post a message in the current
2717 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2718 * returns 0 on success.
2720 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2722 if (!(CC->logged_in)) {
2723 snprintf(errmsgbuf, n, "Not logged in.");
2724 return (ERROR + NOT_LOGGED_IN);
2727 if ((CC->user.axlevel < 2)
2728 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2729 snprintf(errmsgbuf, n, "Need to be validated to enter "
2730 "(except in %s> to sysop)", MAILROOM);
2731 return (ERROR + HIGHER_ACCESS_REQUIRED);
2734 if ((CC->user.axlevel < 4)
2735 && (CC->room.QRflags & QR_NETWORK)) {
2736 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2737 return (ERROR + HIGHER_ACCESS_REQUIRED);
2740 if ((CC->user.axlevel < 6)
2741 && (CC->room.QRflags & QR_READONLY)) {
2742 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2743 return (ERROR + HIGHER_ACCESS_REQUIRED);
2746 strcpy(errmsgbuf, "Ok");
2752 * Check to see if the specified user has Internet mail permission
2753 * (returns nonzero if permission is granted)
2755 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2757 /* Do not allow twits to send Internet mail */
2758 if (who->axlevel <= 2) return(0);
2760 /* Globally enabled? */
2761 if (config.c_restrict == 0) return(1);
2763 /* User flagged ok? */
2764 if (who->flags & US_INTERNET) return(2);
2766 /* Aide level access? */
2767 if (who->axlevel >= 6) return(3);
2769 /* No mail for you! */
2775 * Validate recipients, count delivery types and errors, and handle aliasing
2776 * FIXME check for dupes!!!!!
2777 * Returns 0 if all addresses are ok, -1 if no addresses were specified,
2778 * or the number of addresses found invalid.
2780 struct recptypes *validate_recipients(char *supplied_recipients) {
2781 struct recptypes *ret;
2782 char recipients[SIZ];
2783 char this_recp[256];
2784 char this_recp_cooked[256];
2790 struct ctdluser tempUS;
2791 struct ctdlroom tempQR;
2795 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2796 if (ret == NULL) return(NULL);
2797 memset(ret, 0, sizeof(struct recptypes));
2800 ret->num_internet = 0;
2805 if (supplied_recipients == NULL) {
2806 strcpy(recipients, "");
2809 safestrncpy(recipients, supplied_recipients, sizeof recipients);
2812 /* Change all valid separator characters to commas */
2813 for (i=0; i<strlen(recipients); ++i) {
2814 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2815 recipients[i] = ',';
2819 /* Now start extracting recipients... */
2821 while (strlen(recipients) > 0) {
2823 for (i=0; i<=strlen(recipients); ++i) {
2824 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
2825 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
2826 safestrncpy(this_recp, recipients, i+1);
2828 if (recipients[i] == ',') {
2829 strcpy(recipients, &recipients[i+1]);
2832 strcpy(recipients, "");
2839 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
2841 mailtype = alias(this_recp);
2842 mailtype = alias(this_recp);
2843 mailtype = alias(this_recp);
2844 for (j=0; j<=strlen(this_recp); ++j) {
2845 if (this_recp[j]=='_') {
2846 this_recp_cooked[j] = ' ';
2849 this_recp_cooked[j] = this_recp[j];
2855 if (!strcasecmp(this_recp, "sysop")) {
2857 strcpy(this_recp, config.c_aideroom);
2858 if (strlen(ret->recp_room) > 0) {
2859 strcat(ret->recp_room, "|");
2861 strcat(ret->recp_room, this_recp);
2863 else if (getuser(&tempUS, this_recp) == 0) {
2865 strcpy(this_recp, tempUS.fullname);
2866 if (strlen(ret->recp_local) > 0) {
2867 strcat(ret->recp_local, "|");
2869 strcat(ret->recp_local, this_recp);
2871 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2873 strcpy(this_recp, tempUS.fullname);
2874 if (strlen(ret->recp_local) > 0) {
2875 strcat(ret->recp_local, "|");
2877 strcat(ret->recp_local, this_recp);
2879 else if ( (!strncasecmp(this_recp, "room_", 5))
2880 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2882 if (strlen(ret->recp_room) > 0) {
2883 strcat(ret->recp_room, "|");
2885 strcat(ret->recp_room, &this_recp_cooked[5]);
2893 /* Yes, you're reading this correctly: if the target
2894 * domain points back to the local system or an attached
2895 * Citadel directory, the address is invalid. That's
2896 * because if the address were valid, we would have
2897 * already translated it to a local address by now.
2899 if (IsDirectory(this_recp)) {
2904 ++ret->num_internet;
2905 if (strlen(ret->recp_internet) > 0) {
2906 strcat(ret->recp_internet, "|");
2908 strcat(ret->recp_internet, this_recp);
2913 if (strlen(ret->recp_ignet) > 0) {
2914 strcat(ret->recp_ignet, "|");
2916 strcat(ret->recp_ignet, this_recp);
2924 if (strlen(ret->errormsg) == 0) {
2925 snprintf(append, sizeof append,
2926 "Invalid recipient: %s",
2930 snprintf(append, sizeof append,
2933 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2934 strcat(ret->errormsg, append);
2938 if (strlen(ret->display_recp) == 0) {
2939 strcpy(append, this_recp);
2942 snprintf(append, sizeof append, ", %s",
2945 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2946 strcat(ret->display_recp, append);
2951 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2952 ret->num_room + ret->num_error) == 0) {
2953 ret->num_error = (-1);
2954 strcpy(ret->errormsg, "No recipients specified.");
2957 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2958 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2959 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2960 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2961 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2962 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2970 * message entry - mode 0 (normal)
2972 void cmd_ent0(char *entargs)
2978 char masquerade_as[SIZ];
2980 int format_type = 0;
2981 char newusername[SIZ];
2982 struct CtdlMessage *msg;
2986 struct recptypes *valid = NULL;
2987 struct recptypes *valid_to = NULL;
2988 struct recptypes *valid_cc = NULL;
2989 struct recptypes *valid_bcc = NULL;
2996 post = extract_int(entargs, 0);
2997 extract_token(recp, entargs, 1, '|', sizeof recp);
2998 anon_flag = extract_int(entargs, 2);
2999 format_type = extract_int(entargs, 3);
3000 extract_token(subject, entargs, 4, '|', sizeof subject);
3001 do_confirm = extract_int(entargs, 6);
3002 extract_token(cc, entargs, 7, '|', sizeof cc);
3003 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3005 /* first check to make sure the request is valid. */
3007 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
3009 cprintf("%d %s\n", err, errmsg);
3013 /* Check some other permission type things. */
3016 if (CC->user.axlevel < 6) {
3017 cprintf("%d You don't have permission to masquerade.\n",
3018 ERROR + HIGHER_ACCESS_REQUIRED);
3021 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3022 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
3023 safestrncpy(CC->fake_postname, newusername,
3024 sizeof(CC->fake_postname) );
3025 cprintf("%d ok\n", CIT_OK);
3028 CC->cs_flags |= CS_POSTING;
3030 /* In the Mail> room we have to behave a little differently --
3031 * make sure the user has specified at least one recipient. Then
3032 * validate the recipient(s).
3034 if ( (CC->room.QRflags & QR_MAILBOX)
3035 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
3037 if (CC->user.axlevel < 2) {
3038 strcpy(recp, "sysop");
3043 valid_to = validate_recipients(recp);
3044 if (valid_to->num_error > 0) {
3045 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
3050 valid_cc = validate_recipients(cc);
3051 if (valid_cc->num_error > 0) {
3052 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
3058 valid_bcc = validate_recipients(bcc);
3059 if (valid_bcc->num_error > 0) {
3060 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
3067 /* Recipient required, but none were specified */
3068 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3072 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3076 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3077 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3078 cprintf("%d You do not have permission "
3079 "to send Internet mail.\n",
3080 ERROR + HIGHER_ACCESS_REQUIRED);
3088 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)
3089 && (CC->user.axlevel < 4) ) {
3090 cprintf("%d Higher access required for network mail.\n",
3091 ERROR + HIGHER_ACCESS_REQUIRED);
3098 if ((RESTRICT_INTERNET == 1)
3099 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3100 && ((CC->user.flags & US_INTERNET) == 0)
3101 && (!CC->internal_pgm)) {
3102 cprintf("%d You don't have access to Internet mail.\n",
3103 ERROR + HIGHER_ACCESS_REQUIRED);
3112 /* Is this a room which has anonymous-only or anonymous-option? */
3113 anonymous = MES_NORMAL;
3114 if (CC->room.QRflags & QR_ANONONLY) {
3115 anonymous = MES_ANONONLY;
3117 if (CC->room.QRflags & QR_ANONOPT) {
3118 if (anon_flag == 1) { /* only if the user requested it */
3119 anonymous = MES_ANONOPT;
3123 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3127 /* If we're only checking the validity of the request, return
3128 * success without creating the message.
3131 cprintf("%d %s\n", CIT_OK,
3132 ((valid_to != NULL) ? valid_to->display_recp : "") );
3139 /* We don't need these anymore because we'll do it differently below */
3144 /* Handle author masquerading */
3145 if (CC->fake_postname[0]) {
3146 strcpy(masquerade_as, CC->fake_postname);
3148 else if (CC->fake_username[0]) {
3149 strcpy(masquerade_as, CC->fake_username);
3152 strcpy(masquerade_as, "");
3155 /* Read in the message from the client. */
3157 cprintf("%d send message\n", START_CHAT_MODE);
3159 cprintf("%d send message\n", SEND_LISTING);
3162 msg = CtdlMakeMessage(&CC->user, recp, cc,
3163 CC->room.QRname, anonymous, format_type,
3164 masquerade_as, subject, NULL);
3166 /* Put together one big recipients struct containing to/cc/bcc all in
3167 * one. This is for the envelope.
3169 char *all_recps = malloc(SIZ * 3);
3170 strcpy(all_recps, recp);
3171 if (strlen(cc) > 0) {
3172 if (strlen(all_recps) > 0) {
3173 strcat(all_recps, ",");
3175 strcat(all_recps, cc);
3177 if (strlen(bcc) > 0) {
3178 if (strlen(all_recps) > 0) {
3179 strcat(all_recps, ",");
3181 strcat(all_recps, bcc);
3183 if (strlen(all_recps) > 0) {
3184 valid = validate_recipients(all_recps);
3192 msgnum = CtdlSubmitMsg(msg, valid, "");
3195 cprintf("%ld\n", msgnum);
3197 cprintf("Message accepted.\n");
3200 cprintf("Internal error.\n");
3202 if (msg->cm_fields['E'] != NULL) {
3203 cprintf("%s\n", msg->cm_fields['E']);
3210 CtdlFreeMessage(msg);
3212 CC->fake_postname[0] = '\0';
3213 if (valid != NULL) {
3222 * API function to delete messages which match a set of criteria
3223 * (returns the actual number of messages deleted)
3225 int CtdlDeleteMessages(char *room_name, /* which room */
3226 long dmsgnum, /* or "0" for any */
3227 char *content_type, /* or "" for any */
3228 int deferred /* let TDAP sweep it later */
3232 struct ctdlroom qrbuf;
3233 struct cdbdata *cdbfr;
3234 long *msglist = NULL;
3235 long *dellist = NULL;
3238 int num_deleted = 0;
3240 struct MetaData smi;
3242 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s, %d)\n",
3243 room_name, dmsgnum, content_type, deferred);
3245 /* get room record, obtaining a lock... */
3246 if (lgetroom(&qrbuf, room_name) != 0) {
3247 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3249 return (0); /* room not found */
3251 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3253 if (cdbfr != NULL) {
3254 dellist = malloc(cdbfr->len);
3255 msglist = (long *) cdbfr->ptr;
3256 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3257 num_msgs = cdbfr->len / sizeof(long);
3261 for (i = 0; i < num_msgs; ++i) {
3264 /* Set/clear a bit for each criterion */
3266 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
3267 delete_this |= 0x01;
3269 if (strlen(content_type) == 0) {
3270 delete_this |= 0x02;
3272 GetMetaData(&smi, msglist[i]);
3273 if (!strcasecmp(smi.meta_content_type,
3275 delete_this |= 0x02;
3279 /* Delete message only if all bits are set */
3280 if (delete_this == 0x03) {
3281 dellist[num_deleted++] = msglist[i];
3286 num_msgs = sort_msglist(msglist, num_msgs);
3287 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3288 msglist, (int)(num_msgs * sizeof(long)));
3290 qrbuf.QRhighest = msglist[num_msgs - 1];
3295 * If the delete operation is "deferred" (and technically, any delete
3296 * operation not performed by THE DREADED AUTO-PURGER ought to be
3297 * a deferred delete) then we save a pointer to the message in the
3298 * DELETED_MSGS_ROOM. This will cause the reference count to remain
3299 * at least 1, which will save the user from having to synchronously
3300 * wait for various disk-intensive operations to complete.
3302 if ( (deferred) && (num_deleted) ) {
3303 for (i=0; i<num_deleted; ++i) {
3304 CtdlCopyMsgToRoom(dellist[i], DELETED_MSGS_ROOM);
3308 /* Go through the messages we pulled out of the index, and decrement
3309 * their reference counts by 1. If this is the only room the message
3310 * was in, the reference count will reach zero and the message will
3311 * automatically be deleted from the database. We do this in a
3312 * separate pass because there might be plug-in hooks getting called,
3313 * and we don't want that happening during an S_ROOMS critical
3316 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3317 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3318 AdjRefCount(dellist[i], -1);
3321 /* Now free the memory we used, and go away. */
3322 if (msglist != NULL) free(msglist);
3323 if (dellist != NULL) free(dellist);
3324 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3325 return (num_deleted);
3331 * Check whether the current user has permission to delete messages from
3332 * the current room (returns 1 for yes, 0 for no)
3334 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3335 getuser(&CC->user, CC->curr_user);
3336 if ((CC->user.axlevel < 6)
3337 && (CC->user.usernum != CC->room.QRroomaide)
3338 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3339 && (!(CC->internal_pgm))) {
3348 * Delete message from current room
3350 void cmd_dele(char *delstr)
3355 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3356 cprintf("%d Higher access required.\n",
3357 ERROR + HIGHER_ACCESS_REQUIRED);
3360 delnum = extract_long(delstr, 0);
3362 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "", 1);
3365 cprintf("%d %d message%s deleted.\n", CIT_OK,
3366 num_deleted, ((num_deleted != 1) ? "s" : ""));
3368 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3374 * Back end API function for moves and deletes
3376 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3379 err = CtdlSaveMsgPointerInRoom(dest, msgnum, 1, NULL);
3380 if (err != 0) return(err);
3388 * move or copy a message to another room
3390 void cmd_move(char *args)
3393 char targ[ROOMNAMELEN];
3394 struct ctdlroom qtemp;
3400 num = extract_long(args, 0);
3401 extract_token(targ, args, 1, '|', sizeof targ);
3402 convert_room_name_macros(targ, sizeof targ);
3403 targ[ROOMNAMELEN - 1] = 0;
3404 is_copy = extract_int(args, 2);
3406 if (getroom(&qtemp, targ) != 0) {
3407 cprintf("%d '%s' does not exist.\n",
3408 ERROR + ROOM_NOT_FOUND, targ);
3412 getuser(&CC->user, CC->curr_user);
3413 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3415 /* Check for permission to perform this operation.
3416 * Remember: "CC->room" is source, "qtemp" is target.
3420 /* Aides can move/copy */
3421 if (CC->user.axlevel >= 6) permit = 1;
3423 /* Room aides can move/copy */
3424 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3426 /* Permit move/copy from personal rooms */
3427 if ((CC->room.QRflags & QR_MAILBOX)
3428 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3430 /* Permit only copy from public to personal room */
3432 && (!(CC->room.QRflags & QR_MAILBOX))
3433 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3435 /* User must have access to target room */
3436 if (!(ra & UA_KNOWN)) permit = 0;
3439 cprintf("%d Higher access required.\n",
3440 ERROR + HIGHER_ACCESS_REQUIRED);
3444 err = CtdlCopyMsgToRoom(num, targ);
3446 cprintf("%d Cannot store message in %s: error %d\n",
3451 /* Now delete the message from the source room,
3452 * if this is a 'move' rather than a 'copy' operation.
3455 CtdlDeleteMessages(CC->room.QRname, num, "", 0);
3458 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3464 * GetMetaData() - Get the supplementary record for a message
3466 void GetMetaData(struct MetaData *smibuf, long msgnum)
3469 struct cdbdata *cdbsmi;
3472 memset(smibuf, 0, sizeof(struct MetaData));
3473 smibuf->meta_msgnum = msgnum;
3474 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3476 /* Use the negative of the message number for its supp record index */
3477 TheIndex = (0L - msgnum);
3479 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3480 if (cdbsmi == NULL) {
3481 return; /* record not found; go with defaults */
3483 memcpy(smibuf, cdbsmi->ptr,
3484 ((cdbsmi->len > sizeof(struct MetaData)) ?
3485 sizeof(struct MetaData) : cdbsmi->len));
3492 * PutMetaData() - (re)write supplementary record for a message
3494 void PutMetaData(struct MetaData *smibuf)
3498 /* Use the negative of the message number for the metadata db index */
3499 TheIndex = (0L - smibuf->meta_msgnum);
3501 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3502 smibuf->meta_msgnum, smibuf->meta_refcount);
3504 cdb_store(CDB_MSGMAIN,
3505 &TheIndex, (int)sizeof(long),
3506 smibuf, (int)sizeof(struct MetaData));
3511 * AdjRefCount - change the reference count for a message;
3512 * delete the message if it reaches zero
3514 void AdjRefCount(long msgnum, int incr)
3517 struct MetaData smi;
3520 /* This is a *tight* critical section; please keep it that way, as
3521 * it may get called while nested in other critical sections.
3522 * Complicating this any further will surely cause deadlock!
3524 begin_critical_section(S_SUPPMSGMAIN);
3525 GetMetaData(&smi, msgnum);
3526 smi.meta_refcount += incr;
3528 end_critical_section(S_SUPPMSGMAIN);
3529 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3530 msgnum, incr, smi.meta_refcount);
3532 /* If the reference count is now zero, delete the message
3533 * (and its supplementary record as well).
3535 if (smi.meta_refcount == 0) {
3536 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3538 /* Remove from fulltext index */
3539 if (config.c_enable_fulltext) {
3540 ft_index_message(msgnum, 0);
3543 /* Remove from message base */
3545 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3546 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3548 /* Remove metadata record */
3549 delnum = (0L - msgnum);
3550 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3555 * Write a generic object to this room
3557 * Note: this could be much more efficient. Right now we use two temporary
3558 * files, and still pull the message into memory as with all others.
3560 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3561 char *content_type, /* MIME type of this object */
3562 char *tempfilename, /* Where to fetch it from */
3563 struct ctdluser *is_mailbox, /* Mailbox room? */
3564 int is_binary, /* Is encoding necessary? */
3565 int is_unique, /* Del others of this type? */
3566 unsigned int flags /* Internal save flags */
3571 struct ctdlroom qrbuf;
3572 char roomname[ROOMNAMELEN];
3573 struct CtdlMessage *msg;
3575 char *raw_message = NULL;
3576 char *encoded_message = NULL;
3577 off_t raw_length = 0;
3579 if (is_mailbox != NULL) {
3580 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3583 safestrncpy(roomname, req_room, sizeof(roomname));
3586 fp = fopen(tempfilename, "rb");
3588 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3589 tempfilename, strerror(errno));
3592 fseek(fp, 0L, SEEK_END);
3593 raw_length = ftell(fp);
3595 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3597 raw_message = malloc((size_t)raw_length + 2);
3598 fread(raw_message, (size_t)raw_length, 1, fp);
3602 encoded_message = malloc((size_t)
3603 (((raw_length * 134) / 100) + 4096 ) );
3606 encoded_message = malloc((size_t)(raw_length + 4096));
3609 sprintf(encoded_message, "Content-type: %s\n", content_type);
3612 sprintf(&encoded_message[strlen(encoded_message)],
3613 "Content-transfer-encoding: base64\n\n"
3617 sprintf(&encoded_message[strlen(encoded_message)],
3618 "Content-transfer-encoding: 7bit\n\n"
3624 &encoded_message[strlen(encoded_message)],
3630 raw_message[raw_length] = 0;
3632 &encoded_message[strlen(encoded_message)],
3640 lprintf(CTDL_DEBUG, "Allocating\n");
3641 msg = malloc(sizeof(struct CtdlMessage));
3642 memset(msg, 0, sizeof(struct CtdlMessage));
3643 msg->cm_magic = CTDLMESSAGE_MAGIC;
3644 msg->cm_anon_type = MES_NORMAL;
3645 msg->cm_format_type = 4;
3646 msg->cm_fields['A'] = strdup(CC->user.fullname);
3647 msg->cm_fields['O'] = strdup(req_room);
3648 msg->cm_fields['N'] = strdup(config.c_nodename);
3649 msg->cm_fields['H'] = strdup(config.c_humannode);
3650 msg->cm_flags = flags;
3652 msg->cm_fields['M'] = encoded_message;
3654 /* Create the requested room if we have to. */
3655 if (getroom(&qrbuf, roomname) != 0) {
3656 create_room(roomname,
3657 ( (is_mailbox != NULL) ? 5 : 3 ),
3658 "", 0, 1, 0, VIEW_BBS);
3660 /* If the caller specified this object as unique, delete all
3661 * other objects of this type that are currently in the room.
3664 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3665 CtdlDeleteMessages(roomname, 0L, content_type, 0)
3668 /* Now write the data */
3669 CtdlSubmitMsg(msg, NULL, roomname);
3670 CtdlFreeMessage(msg);
3678 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3679 config_msgnum = msgnum;
3683 char *CtdlGetSysConfig(char *sysconfname) {
3684 char hold_rm[ROOMNAMELEN];
3687 struct CtdlMessage *msg;
3690 strcpy(hold_rm, CC->room.QRname);
3691 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3692 getroom(&CC->room, hold_rm);
3697 /* We want the last (and probably only) config in this room */
3698 begin_critical_section(S_CONFIG);
3699 config_msgnum = (-1L);
3700 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3701 CtdlGetSysConfigBackend, NULL);
3702 msgnum = config_msgnum;
3703 end_critical_section(S_CONFIG);
3709 msg = CtdlFetchMessage(msgnum, 1);
3711 conf = strdup(msg->cm_fields['M']);
3712 CtdlFreeMessage(msg);
3719 getroom(&CC->room, hold_rm);
3721 if (conf != NULL) do {
3722 extract_token(buf, conf, 0, '\n', sizeof buf);
3723 strcpy(conf, &conf[strlen(buf)+1]);
3724 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3729 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3730 char temp[PATH_MAX];
3733 CtdlMakeTempFileName(temp, sizeof temp);
3735 fp = fopen(temp, "w");
3736 if (fp == NULL) return;
3737 fprintf(fp, "%s", sysconfdata);
3740 /* this handy API function does all the work for us */
3741 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3747 * Determine whether a given Internet address belongs to the current user
3749 int CtdlIsMe(char *addr, int addr_buf_len)
3751 struct recptypes *recp;
3754 recp = validate_recipients(addr);
3755 if (recp == NULL) return(0);
3757 if (recp->num_local == 0) {
3762 for (i=0; i<recp->num_local; ++i) {
3763 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3764 if (!strcasecmp(addr, CC->user.fullname)) {
3776 * Citadel protocol command to do the same
3778 void cmd_isme(char *argbuf) {
3781 if (CtdlAccessCheck(ac_logged_in)) return;
3782 extract_token(addr, argbuf, 0, '|', sizeof addr);
3784 if (CtdlIsMe(addr, sizeof addr)) {
3785 cprintf("%d %s\n", CIT_OK, addr);
3788 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);