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 qualified_for_journaling = 1; /* FIXME */
2479 * Do we have to perform journaling? If so, hand off the saved
2480 * RFC822 version will be handed off to the journaler for background
2481 * submit. Otherwise, we have to free the memory ourselves.
2483 if (saved_rfc822_version != NULL) {
2484 if (qualified_for_journaling) {
2485 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2488 free(saved_rfc822_version);
2501 * Convenience function for generating small administrative messages.
2503 void quickie_message(char *from, char *to, char *room, char *text,
2504 int format_type, char *subject)
2506 struct CtdlMessage *msg;
2507 struct recptypes *recp = NULL;
2509 msg = malloc(sizeof(struct CtdlMessage));
2510 memset(msg, 0, sizeof(struct CtdlMessage));
2511 msg->cm_magic = CTDLMESSAGE_MAGIC;
2512 msg->cm_anon_type = MES_NORMAL;
2513 msg->cm_format_type = format_type;
2514 msg->cm_fields['A'] = strdup(from);
2515 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2516 msg->cm_fields['N'] = strdup(NODENAME);
2518 msg->cm_fields['R'] = strdup(to);
2519 recp = validate_recipients(to);
2521 if (subject != NULL) {
2522 msg->cm_fields['U'] = strdup(subject);
2524 msg->cm_fields['M'] = strdup(text);
2526 CtdlSubmitMsg(msg, recp, room);
2527 CtdlFreeMessage(msg);
2528 if (recp != NULL) free(recp);
2534 * Back end function used by CtdlMakeMessage() and similar functions
2536 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2537 size_t maxlen, /* maximum message length */
2538 char *exist, /* if non-null, append to it;
2539 exist is ALWAYS freed */
2540 int crlf /* CRLF newlines instead of LF */
2544 size_t message_len = 0;
2545 size_t buffer_len = 0;
2551 if (exist == NULL) {
2558 message_len = strlen(exist);
2559 buffer_len = message_len + 4096;
2560 m = realloc(exist, buffer_len);
2567 /* flush the input if we have nowhere to store it */
2572 /* read in the lines of message text one by one */
2574 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2575 if (!strcmp(buf, terminator)) finished = 1;
2577 strcat(buf, "\r\n");
2583 if ( (!flushing) && (!finished) ) {
2584 /* Measure the line */
2585 linelen = strlen(buf);
2587 /* augment the buffer if we have to */
2588 if ((message_len + linelen) >= buffer_len) {
2589 ptr = realloc(m, (buffer_len * 2) );
2590 if (ptr == NULL) { /* flush if can't allocate */
2593 buffer_len = (buffer_len * 2);
2595 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2599 /* Add the new line to the buffer. NOTE: this loop must avoid
2600 * using functions like strcat() and strlen() because they
2601 * traverse the entire buffer upon every call, and doing that
2602 * for a multi-megabyte message slows it down beyond usability.
2604 strcpy(&m[message_len], buf);
2605 message_len += linelen;
2608 /* if we've hit the max msg length, flush the rest */
2609 if (message_len >= maxlen) flushing = 1;
2611 } while (!finished);
2619 * Build a binary message to be saved on disk.
2620 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2621 * will become part of the message. This means you are no longer
2622 * responsible for managing that memory -- it will be freed along with
2623 * the rest of the fields when CtdlFreeMessage() is called.)
2626 struct CtdlMessage *CtdlMakeMessage(
2627 struct ctdluser *author, /* author's user structure */
2628 char *recipient, /* NULL if it's not mail */
2629 char *recp_cc, /* NULL if it's not mail */
2630 char *room, /* room where it's going */
2631 int type, /* see MES_ types in header file */
2632 int format_type, /* variformat, plain text, MIME... */
2633 char *fake_name, /* who we're masquerading as */
2634 char *subject, /* Subject (optional) */
2635 char *preformatted_text /* ...or NULL to read text from client */
2637 char dest_node[SIZ];
2639 struct CtdlMessage *msg;
2641 msg = malloc(sizeof(struct CtdlMessage));
2642 memset(msg, 0, sizeof(struct CtdlMessage));
2643 msg->cm_magic = CTDLMESSAGE_MAGIC;
2644 msg->cm_anon_type = type;
2645 msg->cm_format_type = format_type;
2647 /* Don't confuse the poor folks if it's not routed mail. */
2648 strcpy(dest_node, "");
2653 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2654 msg->cm_fields['P'] = strdup(buf);
2656 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2657 msg->cm_fields['T'] = strdup(buf);
2659 if (fake_name[0]) /* author */
2660 msg->cm_fields['A'] = strdup(fake_name);
2662 msg->cm_fields['A'] = strdup(author->fullname);
2664 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2665 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2668 msg->cm_fields['O'] = strdup(CC->room.QRname);
2671 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2672 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2674 if (recipient[0] != 0) {
2675 msg->cm_fields['R'] = strdup(recipient);
2677 if (recp_cc[0] != 0) {
2678 msg->cm_fields['Y'] = strdup(recp_cc);
2680 if (dest_node[0] != 0) {
2681 msg->cm_fields['D'] = strdup(dest_node);
2684 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2685 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2688 if (subject != NULL) {
2690 if (strlen(subject) > 0) {
2691 msg->cm_fields['U'] = strdup(subject);
2695 if (preformatted_text != NULL) {
2696 msg->cm_fields['M'] = preformatted_text;
2699 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2700 config.c_maxmsglen, NULL, 0);
2708 * Check to see whether we have permission to post a message in the current
2709 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2710 * returns 0 on success.
2712 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2714 if (!(CC->logged_in)) {
2715 snprintf(errmsgbuf, n, "Not logged in.");
2716 return (ERROR + NOT_LOGGED_IN);
2719 if ((CC->user.axlevel < 2)
2720 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2721 snprintf(errmsgbuf, n, "Need to be validated to enter "
2722 "(except in %s> to sysop)", MAILROOM);
2723 return (ERROR + HIGHER_ACCESS_REQUIRED);
2726 if ((CC->user.axlevel < 4)
2727 && (CC->room.QRflags & QR_NETWORK)) {
2728 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2729 return (ERROR + HIGHER_ACCESS_REQUIRED);
2732 if ((CC->user.axlevel < 6)
2733 && (CC->room.QRflags & QR_READONLY)) {
2734 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2735 return (ERROR + HIGHER_ACCESS_REQUIRED);
2738 strcpy(errmsgbuf, "Ok");
2744 * Check to see if the specified user has Internet mail permission
2745 * (returns nonzero if permission is granted)
2747 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2749 /* Do not allow twits to send Internet mail */
2750 if (who->axlevel <= 2) return(0);
2752 /* Globally enabled? */
2753 if (config.c_restrict == 0) return(1);
2755 /* User flagged ok? */
2756 if (who->flags & US_INTERNET) return(2);
2758 /* Aide level access? */
2759 if (who->axlevel >= 6) return(3);
2761 /* No mail for you! */
2767 * Validate recipients, count delivery types and errors, and handle aliasing
2768 * FIXME check for dupes!!!!!
2769 * Returns 0 if all addresses are ok, -1 if no addresses were specified,
2770 * or the number of addresses found invalid.
2772 struct recptypes *validate_recipients(char *supplied_recipients) {
2773 struct recptypes *ret;
2774 char recipients[SIZ];
2775 char this_recp[256];
2776 char this_recp_cooked[256];
2782 struct ctdluser tempUS;
2783 struct ctdlroom tempQR;
2787 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2788 if (ret == NULL) return(NULL);
2789 memset(ret, 0, sizeof(struct recptypes));
2792 ret->num_internet = 0;
2797 if (supplied_recipients == NULL) {
2798 strcpy(recipients, "");
2801 safestrncpy(recipients, supplied_recipients, sizeof recipients);
2804 /* Change all valid separator characters to commas */
2805 for (i=0; i<strlen(recipients); ++i) {
2806 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2807 recipients[i] = ',';
2811 /* Now start extracting recipients... */
2813 while (strlen(recipients) > 0) {
2815 for (i=0; i<=strlen(recipients); ++i) {
2816 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
2817 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
2818 safestrncpy(this_recp, recipients, i+1);
2820 if (recipients[i] == ',') {
2821 strcpy(recipients, &recipients[i+1]);
2824 strcpy(recipients, "");
2831 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
2833 mailtype = alias(this_recp);
2834 mailtype = alias(this_recp);
2835 mailtype = alias(this_recp);
2836 for (j=0; j<=strlen(this_recp); ++j) {
2837 if (this_recp[j]=='_') {
2838 this_recp_cooked[j] = ' ';
2841 this_recp_cooked[j] = this_recp[j];
2847 if (!strcasecmp(this_recp, "sysop")) {
2849 strcpy(this_recp, config.c_aideroom);
2850 if (strlen(ret->recp_room) > 0) {
2851 strcat(ret->recp_room, "|");
2853 strcat(ret->recp_room, this_recp);
2855 else if (getuser(&tempUS, this_recp) == 0) {
2857 strcpy(this_recp, tempUS.fullname);
2858 if (strlen(ret->recp_local) > 0) {
2859 strcat(ret->recp_local, "|");
2861 strcat(ret->recp_local, this_recp);
2863 else if (getuser(&tempUS, this_recp_cooked) == 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 ( (!strncasecmp(this_recp, "room_", 5))
2872 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2874 if (strlen(ret->recp_room) > 0) {
2875 strcat(ret->recp_room, "|");
2877 strcat(ret->recp_room, &this_recp_cooked[5]);
2885 /* Yes, you're reading this correctly: if the target
2886 * domain points back to the local system or an attached
2887 * Citadel directory, the address is invalid. That's
2888 * because if the address were valid, we would have
2889 * already translated it to a local address by now.
2891 if (IsDirectory(this_recp)) {
2896 ++ret->num_internet;
2897 if (strlen(ret->recp_internet) > 0) {
2898 strcat(ret->recp_internet, "|");
2900 strcat(ret->recp_internet, this_recp);
2905 if (strlen(ret->recp_ignet) > 0) {
2906 strcat(ret->recp_ignet, "|");
2908 strcat(ret->recp_ignet, this_recp);
2916 if (strlen(ret->errormsg) == 0) {
2917 snprintf(append, sizeof append,
2918 "Invalid recipient: %s",
2922 snprintf(append, sizeof append,
2925 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2926 strcat(ret->errormsg, append);
2930 if (strlen(ret->display_recp) == 0) {
2931 strcpy(append, this_recp);
2934 snprintf(append, sizeof append, ", %s",
2937 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2938 strcat(ret->display_recp, append);
2943 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2944 ret->num_room + ret->num_error) == 0) {
2945 ret->num_error = (-1);
2946 strcpy(ret->errormsg, "No recipients specified.");
2949 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2950 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2951 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2952 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2953 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2954 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2962 * message entry - mode 0 (normal)
2964 void cmd_ent0(char *entargs)
2970 char masquerade_as[SIZ];
2972 int format_type = 0;
2973 char newusername[SIZ];
2974 struct CtdlMessage *msg;
2978 struct recptypes *valid = NULL;
2979 struct recptypes *valid_to = NULL;
2980 struct recptypes *valid_cc = NULL;
2981 struct recptypes *valid_bcc = NULL;
2988 post = extract_int(entargs, 0);
2989 extract_token(recp, entargs, 1, '|', sizeof recp);
2990 anon_flag = extract_int(entargs, 2);
2991 format_type = extract_int(entargs, 3);
2992 extract_token(subject, entargs, 4, '|', sizeof subject);
2993 do_confirm = extract_int(entargs, 6);
2994 extract_token(cc, entargs, 7, '|', sizeof cc);
2995 extract_token(bcc, entargs, 8, '|', sizeof bcc);
2997 /* first check to make sure the request is valid. */
2999 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
3001 cprintf("%d %s\n", err, errmsg);
3005 /* Check some other permission type things. */
3008 if (CC->user.axlevel < 6) {
3009 cprintf("%d You don't have permission to masquerade.\n",
3010 ERROR + HIGHER_ACCESS_REQUIRED);
3013 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3014 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
3015 safestrncpy(CC->fake_postname, newusername,
3016 sizeof(CC->fake_postname) );
3017 cprintf("%d ok\n", CIT_OK);
3020 CC->cs_flags |= CS_POSTING;
3022 /* In the Mail> room we have to behave a little differently --
3023 * make sure the user has specified at least one recipient. Then
3024 * validate the recipient(s).
3026 if ( (CC->room.QRflags & QR_MAILBOX)
3027 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
3029 if (CC->user.axlevel < 2) {
3030 strcpy(recp, "sysop");
3035 valid_to = validate_recipients(recp);
3036 if (valid_to->num_error > 0) {
3037 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
3042 valid_cc = validate_recipients(cc);
3043 if (valid_cc->num_error > 0) {
3044 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
3050 valid_bcc = validate_recipients(bcc);
3051 if (valid_bcc->num_error > 0) {
3052 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
3059 /* Recipient required, but none were specified */
3060 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3064 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3068 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3069 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3070 cprintf("%d You do not have permission "
3071 "to send Internet mail.\n",
3072 ERROR + HIGHER_ACCESS_REQUIRED);
3080 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)
3081 && (CC->user.axlevel < 4) ) {
3082 cprintf("%d Higher access required for network mail.\n",
3083 ERROR + HIGHER_ACCESS_REQUIRED);
3090 if ((RESTRICT_INTERNET == 1)
3091 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3092 && ((CC->user.flags & US_INTERNET) == 0)
3093 && (!CC->internal_pgm)) {
3094 cprintf("%d You don't have access to Internet mail.\n",
3095 ERROR + HIGHER_ACCESS_REQUIRED);
3104 /* Is this a room which has anonymous-only or anonymous-option? */
3105 anonymous = MES_NORMAL;
3106 if (CC->room.QRflags & QR_ANONONLY) {
3107 anonymous = MES_ANONONLY;
3109 if (CC->room.QRflags & QR_ANONOPT) {
3110 if (anon_flag == 1) { /* only if the user requested it */
3111 anonymous = MES_ANONOPT;
3115 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3119 /* If we're only checking the validity of the request, return
3120 * success without creating the message.
3123 cprintf("%d %s\n", CIT_OK,
3124 ((valid_to != NULL) ? valid_to->display_recp : "") );
3131 /* We don't need these anymore because we'll do it differently below */
3136 /* Handle author masquerading */
3137 if (CC->fake_postname[0]) {
3138 strcpy(masquerade_as, CC->fake_postname);
3140 else if (CC->fake_username[0]) {
3141 strcpy(masquerade_as, CC->fake_username);
3144 strcpy(masquerade_as, "");
3147 /* Read in the message from the client. */
3149 cprintf("%d send message\n", START_CHAT_MODE);
3151 cprintf("%d send message\n", SEND_LISTING);
3154 msg = CtdlMakeMessage(&CC->user, recp, cc,
3155 CC->room.QRname, anonymous, format_type,
3156 masquerade_as, subject, NULL);
3158 /* Put together one big recipients struct containing to/cc/bcc all in
3159 * one. This is for the envelope.
3161 char *all_recps = malloc(SIZ * 3);
3162 strcpy(all_recps, recp);
3163 if (strlen(cc) > 0) {
3164 if (strlen(all_recps) > 0) {
3165 strcat(all_recps, ",");
3167 strcat(all_recps, cc);
3169 if (strlen(bcc) > 0) {
3170 if (strlen(all_recps) > 0) {
3171 strcat(all_recps, ",");
3173 strcat(all_recps, bcc);
3175 if (strlen(all_recps) > 0) {
3176 valid = validate_recipients(all_recps);
3184 msgnum = CtdlSubmitMsg(msg, valid, "");
3187 cprintf("%ld\n", msgnum);
3189 cprintf("Message accepted.\n");
3192 cprintf("Internal error.\n");
3194 if (msg->cm_fields['E'] != NULL) {
3195 cprintf("%s\n", msg->cm_fields['E']);
3202 CtdlFreeMessage(msg);
3204 CC->fake_postname[0] = '\0';
3205 if (valid != NULL) {
3214 * API function to delete messages which match a set of criteria
3215 * (returns the actual number of messages deleted)
3217 int CtdlDeleteMessages(char *room_name, /* which room */
3218 long dmsgnum, /* or "0" for any */
3219 char *content_type, /* or "" for any */
3220 int deferred /* let TDAP sweep it later */
3224 struct ctdlroom qrbuf;
3225 struct cdbdata *cdbfr;
3226 long *msglist = NULL;
3227 long *dellist = NULL;
3230 int num_deleted = 0;
3232 struct MetaData smi;
3234 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s, %d)\n",
3235 room_name, dmsgnum, content_type, deferred);
3237 /* get room record, obtaining a lock... */
3238 if (lgetroom(&qrbuf, room_name) != 0) {
3239 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3241 return (0); /* room not found */
3243 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3245 if (cdbfr != NULL) {
3246 dellist = malloc(cdbfr->len);
3247 msglist = (long *) cdbfr->ptr;
3248 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3249 num_msgs = cdbfr->len / sizeof(long);
3253 for (i = 0; i < num_msgs; ++i) {
3256 /* Set/clear a bit for each criterion */
3258 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
3259 delete_this |= 0x01;
3261 if (strlen(content_type) == 0) {
3262 delete_this |= 0x02;
3264 GetMetaData(&smi, msglist[i]);
3265 if (!strcasecmp(smi.meta_content_type,
3267 delete_this |= 0x02;
3271 /* Delete message only if all bits are set */
3272 if (delete_this == 0x03) {
3273 dellist[num_deleted++] = msglist[i];
3278 num_msgs = sort_msglist(msglist, num_msgs);
3279 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3280 msglist, (int)(num_msgs * sizeof(long)));
3282 qrbuf.QRhighest = msglist[num_msgs - 1];
3287 * If the delete operation is "deferred" (and technically, any delete
3288 * operation not performed by THE DREADED AUTO-PURGER ought to be
3289 * a deferred delete) then we save a pointer to the message in the
3290 * DELETED_MSGS_ROOM. This will cause the reference count to remain
3291 * at least 1, which will save the user from having to synchronously
3292 * wait for various disk-intensive operations to complete.
3294 if ( (deferred) && (num_deleted) ) {
3295 for (i=0; i<num_deleted; ++i) {
3296 CtdlCopyMsgToRoom(dellist[i], DELETED_MSGS_ROOM);
3300 /* Go through the messages we pulled out of the index, and decrement
3301 * their reference counts by 1. If this is the only room the message
3302 * was in, the reference count will reach zero and the message will
3303 * automatically be deleted from the database. We do this in a
3304 * separate pass because there might be plug-in hooks getting called,
3305 * and we don't want that happening during an S_ROOMS critical
3308 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3309 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3310 AdjRefCount(dellist[i], -1);
3313 /* Now free the memory we used, and go away. */
3314 if (msglist != NULL) free(msglist);
3315 if (dellist != NULL) free(dellist);
3316 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3317 return (num_deleted);
3323 * Check whether the current user has permission to delete messages from
3324 * the current room (returns 1 for yes, 0 for no)
3326 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3327 getuser(&CC->user, CC->curr_user);
3328 if ((CC->user.axlevel < 6)
3329 && (CC->user.usernum != CC->room.QRroomaide)
3330 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3331 && (!(CC->internal_pgm))) {
3340 * Delete message from current room
3342 void cmd_dele(char *delstr)
3347 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3348 cprintf("%d Higher access required.\n",
3349 ERROR + HIGHER_ACCESS_REQUIRED);
3352 delnum = extract_long(delstr, 0);
3354 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "", 1);
3357 cprintf("%d %d message%s deleted.\n", CIT_OK,
3358 num_deleted, ((num_deleted != 1) ? "s" : ""));
3360 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3366 * Back end API function for moves and deletes
3368 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3371 err = CtdlSaveMsgPointerInRoom(dest, msgnum, 1, NULL);
3372 if (err != 0) return(err);
3380 * move or copy a message to another room
3382 void cmd_move(char *args)
3385 char targ[ROOMNAMELEN];
3386 struct ctdlroom qtemp;
3392 num = extract_long(args, 0);
3393 extract_token(targ, args, 1, '|', sizeof targ);
3394 convert_room_name_macros(targ, sizeof targ);
3395 targ[ROOMNAMELEN - 1] = 0;
3396 is_copy = extract_int(args, 2);
3398 if (getroom(&qtemp, targ) != 0) {
3399 cprintf("%d '%s' does not exist.\n",
3400 ERROR + ROOM_NOT_FOUND, targ);
3404 getuser(&CC->user, CC->curr_user);
3405 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3407 /* Check for permission to perform this operation.
3408 * Remember: "CC->room" is source, "qtemp" is target.
3412 /* Aides can move/copy */
3413 if (CC->user.axlevel >= 6) permit = 1;
3415 /* Room aides can move/copy */
3416 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3418 /* Permit move/copy from personal rooms */
3419 if ((CC->room.QRflags & QR_MAILBOX)
3420 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3422 /* Permit only copy from public to personal room */
3424 && (!(CC->room.QRflags & QR_MAILBOX))
3425 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3427 /* User must have access to target room */
3428 if (!(ra & UA_KNOWN)) permit = 0;
3431 cprintf("%d Higher access required.\n",
3432 ERROR + HIGHER_ACCESS_REQUIRED);
3436 err = CtdlCopyMsgToRoom(num, targ);
3438 cprintf("%d Cannot store message in %s: error %d\n",
3443 /* Now delete the message from the source room,
3444 * if this is a 'move' rather than a 'copy' operation.
3447 CtdlDeleteMessages(CC->room.QRname, num, "", 0);
3450 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3456 * GetMetaData() - Get the supplementary record for a message
3458 void GetMetaData(struct MetaData *smibuf, long msgnum)
3461 struct cdbdata *cdbsmi;
3464 memset(smibuf, 0, sizeof(struct MetaData));
3465 smibuf->meta_msgnum = msgnum;
3466 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3468 /* Use the negative of the message number for its supp record index */
3469 TheIndex = (0L - msgnum);
3471 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3472 if (cdbsmi == NULL) {
3473 return; /* record not found; go with defaults */
3475 memcpy(smibuf, cdbsmi->ptr,
3476 ((cdbsmi->len > sizeof(struct MetaData)) ?
3477 sizeof(struct MetaData) : cdbsmi->len));
3484 * PutMetaData() - (re)write supplementary record for a message
3486 void PutMetaData(struct MetaData *smibuf)
3490 /* Use the negative of the message number for the metadata db index */
3491 TheIndex = (0L - smibuf->meta_msgnum);
3493 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3494 smibuf->meta_msgnum, smibuf->meta_refcount);
3496 cdb_store(CDB_MSGMAIN,
3497 &TheIndex, (int)sizeof(long),
3498 smibuf, (int)sizeof(struct MetaData));
3503 * AdjRefCount - change the reference count for a message;
3504 * delete the message if it reaches zero
3506 void AdjRefCount(long msgnum, int incr)
3509 struct MetaData smi;
3512 /* This is a *tight* critical section; please keep it that way, as
3513 * it may get called while nested in other critical sections.
3514 * Complicating this any further will surely cause deadlock!
3516 begin_critical_section(S_SUPPMSGMAIN);
3517 GetMetaData(&smi, msgnum);
3518 smi.meta_refcount += incr;
3520 end_critical_section(S_SUPPMSGMAIN);
3521 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3522 msgnum, incr, smi.meta_refcount);
3524 /* If the reference count is now zero, delete the message
3525 * (and its supplementary record as well).
3527 if (smi.meta_refcount == 0) {
3528 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3530 /* Remove from fulltext index */
3531 if (config.c_enable_fulltext) {
3532 ft_index_message(msgnum, 0);
3535 /* Remove from message base */
3537 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3538 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3540 /* Remove metadata record */
3541 delnum = (0L - msgnum);
3542 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3547 * Write a generic object to this room
3549 * Note: this could be much more efficient. Right now we use two temporary
3550 * files, and still pull the message into memory as with all others.
3552 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3553 char *content_type, /* MIME type of this object */
3554 char *tempfilename, /* Where to fetch it from */
3555 struct ctdluser *is_mailbox, /* Mailbox room? */
3556 int is_binary, /* Is encoding necessary? */
3557 int is_unique, /* Del others of this type? */
3558 unsigned int flags /* Internal save flags */
3563 struct ctdlroom qrbuf;
3564 char roomname[ROOMNAMELEN];
3565 struct CtdlMessage *msg;
3567 char *raw_message = NULL;
3568 char *encoded_message = NULL;
3569 off_t raw_length = 0;
3571 if (is_mailbox != NULL) {
3572 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3575 safestrncpy(roomname, req_room, sizeof(roomname));
3578 fp = fopen(tempfilename, "rb");
3580 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3581 tempfilename, strerror(errno));
3584 fseek(fp, 0L, SEEK_END);
3585 raw_length = ftell(fp);
3587 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3589 raw_message = malloc((size_t)raw_length + 2);
3590 fread(raw_message, (size_t)raw_length, 1, fp);
3594 encoded_message = malloc((size_t)
3595 (((raw_length * 134) / 100) + 4096 ) );
3598 encoded_message = malloc((size_t)(raw_length + 4096));
3601 sprintf(encoded_message, "Content-type: %s\n", content_type);
3604 sprintf(&encoded_message[strlen(encoded_message)],
3605 "Content-transfer-encoding: base64\n\n"
3609 sprintf(&encoded_message[strlen(encoded_message)],
3610 "Content-transfer-encoding: 7bit\n\n"
3616 &encoded_message[strlen(encoded_message)],
3622 raw_message[raw_length] = 0;
3624 &encoded_message[strlen(encoded_message)],
3632 lprintf(CTDL_DEBUG, "Allocating\n");
3633 msg = malloc(sizeof(struct CtdlMessage));
3634 memset(msg, 0, sizeof(struct CtdlMessage));
3635 msg->cm_magic = CTDLMESSAGE_MAGIC;
3636 msg->cm_anon_type = MES_NORMAL;
3637 msg->cm_format_type = 4;
3638 msg->cm_fields['A'] = strdup(CC->user.fullname);
3639 msg->cm_fields['O'] = strdup(req_room);
3640 msg->cm_fields['N'] = strdup(config.c_nodename);
3641 msg->cm_fields['H'] = strdup(config.c_humannode);
3642 msg->cm_flags = flags;
3644 msg->cm_fields['M'] = encoded_message;
3646 /* Create the requested room if we have to. */
3647 if (getroom(&qrbuf, roomname) != 0) {
3648 create_room(roomname,
3649 ( (is_mailbox != NULL) ? 5 : 3 ),
3650 "", 0, 1, 0, VIEW_BBS);
3652 /* If the caller specified this object as unique, delete all
3653 * other objects of this type that are currently in the room.
3656 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3657 CtdlDeleteMessages(roomname, 0L, content_type, 0)
3660 /* Now write the data */
3661 CtdlSubmitMsg(msg, NULL, roomname);
3662 CtdlFreeMessage(msg);
3670 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3671 config_msgnum = msgnum;
3675 char *CtdlGetSysConfig(char *sysconfname) {
3676 char hold_rm[ROOMNAMELEN];
3679 struct CtdlMessage *msg;
3682 strcpy(hold_rm, CC->room.QRname);
3683 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3684 getroom(&CC->room, hold_rm);
3689 /* We want the last (and probably only) config in this room */
3690 begin_critical_section(S_CONFIG);
3691 config_msgnum = (-1L);
3692 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3693 CtdlGetSysConfigBackend, NULL);
3694 msgnum = config_msgnum;
3695 end_critical_section(S_CONFIG);
3701 msg = CtdlFetchMessage(msgnum, 1);
3703 conf = strdup(msg->cm_fields['M']);
3704 CtdlFreeMessage(msg);
3711 getroom(&CC->room, hold_rm);
3713 if (conf != NULL) do {
3714 extract_token(buf, conf, 0, '\n', sizeof buf);
3715 strcpy(conf, &conf[strlen(buf)+1]);
3716 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3721 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3722 char temp[PATH_MAX];
3725 CtdlMakeTempFileName(temp, sizeof temp);
3727 fp = fopen(temp, "w");
3728 if (fp == NULL) return;
3729 fprintf(fp, "%s", sysconfdata);
3732 /* this handy API function does all the work for us */
3733 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3739 * Determine whether a given Internet address belongs to the current user
3741 int CtdlIsMe(char *addr, int addr_buf_len)
3743 struct recptypes *recp;
3746 recp = validate_recipients(addr);
3747 if (recp == NULL) return(0);
3749 if (recp->num_local == 0) {
3754 for (i=0; i<recp->num_local; ++i) {
3755 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3756 if (!strcasecmp(addr, CC->user.fullname)) {
3768 * Citadel protocol command to do the same
3770 void cmd_isme(char *argbuf) {
3773 if (CtdlAccessCheck(ac_logged_in)) return;
3774 extract_token(addr, argbuf, 0, '|', sizeof addr);
3776 if (CtdlIsMe(addr, sizeof addr)) {
3777 cprintf("%d %s\n", CIT_OK, addr);
3780 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);