4 * Implements the message store.
18 #if TIME_WITH_SYS_TIME
19 # include <sys/time.h>
23 # include <sys/time.h>
38 #include "serv_extensions.h"
42 #include "sysdep_decls.h"
43 #include "citserver.h"
50 #include "mime_parser.h"
53 #include "internet_addressing.h"
54 #include "serv_fulltext.h"
56 #include "euidindex.h"
59 struct addresses_to_be_filed *atbf = NULL;
62 * This really belongs in serv_network.c, but I don't know how to export
63 * symbols between modules.
65 struct FilterList *filterlist = NULL;
69 * These are the four-character field headers we use when outputting
70 * messages in Citadel format (as opposed to RFC822 format).
73 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
74 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
75 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
76 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
77 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
78 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
79 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
80 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
107 * This function is self explanatory.
108 * (What can I say, I'm in a weird mood today...)
110 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
114 for (i = 0; i < strlen(name); ++i) {
115 if (name[i] == '@') {
116 while (isspace(name[i - 1]) && i > 0) {
117 strcpy(&name[i - 1], &name[i]);
120 while (isspace(name[i + 1])) {
121 strcpy(&name[i + 1], &name[i + 2]);
129 * Aliasing for network mail.
130 * (Error messages have been commented out, because this is a server.)
132 int alias(char *name)
133 { /* process alias and routing info for mail */
136 char aaa[SIZ], bbb[SIZ];
137 char *ignetcfg = NULL;
138 char *ignetmap = NULL;
145 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
153 "mail.aliases", "r");
155 fp = fopen("/dev/null", "r");
162 while (fgets(aaa, sizeof aaa, fp) != NULL) {
163 while (isspace(name[0]))
164 strcpy(name, &name[1]);
165 aaa[strlen(aaa) - 1] = 0;
167 for (a = 0; a < strlen(aaa); ++a) {
169 strcpy(bbb, &aaa[a + 1]);
173 if (!strcasecmp(name, aaa))
178 /* Hit the Global Address Book */
179 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
183 lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name);
185 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
186 for (a=0; a<strlen(name); ++a) {
187 if (name[a] == '@') {
188 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
190 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
195 /* determine local or remote type, see citadel.h */
196 at = haschar(name, '@');
197 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
198 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
199 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
201 /* figure out the delivery mode */
202 extract_token(node, name, 1, '@', sizeof node);
204 /* If there are one or more dots in the nodename, we assume that it
205 * is an FQDN and will attempt SMTP delivery to the Internet.
207 if (haschar(node, '.') > 0) {
208 return(MES_INTERNET);
211 /* Otherwise we look in the IGnet maps for a valid Citadel node.
212 * Try directly-connected nodes first...
214 ignetcfg = CtdlGetSysConfig(IGNETCFG);
215 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
216 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
217 extract_token(testnode, buf, 0, '|', sizeof testnode);
218 if (!strcasecmp(node, testnode)) {
226 * Then try nodes that are two or more hops away.
228 ignetmap = CtdlGetSysConfig(IGNETMAP);
229 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
230 extract_token(buf, ignetmap, i, '\n', sizeof buf);
231 extract_token(testnode, buf, 0, '|', sizeof testnode);
232 if (!strcasecmp(node, testnode)) {
239 /* If we get to this point it's an invalid node name */
254 "/citadel.control", "r");
256 lprintf(CTDL_CRIT, "Cannot open citadel.control: %s\n",
260 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
266 * Back end for the MSGS command: output message number only.
268 void simple_listing(long msgnum, void *userdata)
270 cprintf("%ld\n", msgnum);
276 * Back end for the MSGS command: output header summary.
278 void headers_listing(long msgnum, void *userdata)
280 struct CtdlMessage *msg;
282 msg = CtdlFetchMessage(msgnum, 0);
284 cprintf("%ld|0|||||\n", msgnum);
288 cprintf("%ld|%s|%s|%s|%s|%s|\n",
290 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
291 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
292 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
293 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
294 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
296 CtdlFreeMessage(msg);
301 /* Determine if a given message matches the fields in a message template.
302 * Return 0 for a successful match.
304 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
307 /* If there aren't any fields in the template, all messages will
310 if (template == NULL) return(0);
312 /* Null messages are bogus. */
313 if (msg == NULL) return(1);
315 for (i='A'; i<='Z'; ++i) {
316 if (template->cm_fields[i] != NULL) {
317 if (msg->cm_fields[i] == NULL) {
320 if (strcasecmp(msg->cm_fields[i],
321 template->cm_fields[i])) return 1;
325 /* All compares succeeded: we have a match! */
332 * Retrieve the "seen" message list for the current room.
334 void CtdlGetSeen(char *buf, int which_set) {
337 /* Learn about the user and room in question */
338 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
340 if (which_set == ctdlsetseen_seen)
341 safestrncpy(buf, vbuf.v_seen, SIZ);
342 if (which_set == ctdlsetseen_answered)
343 safestrncpy(buf, vbuf.v_answered, SIZ);
349 * Manipulate the "seen msgs" string (or other message set strings)
351 void CtdlSetSeen(long target_msgnum, 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(%ld, %d, %d)\n",
372 target_msgnum, target_setting, which_set);
374 /* Learn about the user and room in question */
375 CtdlGetRelationship(&vbuf,
376 ((which_user != NULL) ? which_user : &CC->user),
377 ((which_room != NULL) ? which_room : &CC->room)
380 /* Load the message list */
381 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
383 msglist = malloc(cdbfr->len);
384 memcpy(msglist, cdbfr->ptr, cdbfr->len);
385 num_msgs = cdbfr->len / sizeof(long);
388 return; /* No messages at all? No further action. */
391 is_set = malloc(num_msgs * sizeof(char));
392 memset(is_set, 0, (num_msgs * sizeof(char)) );
394 /* Decide which message set we're manipulating */
396 case ctdlsetseen_seen:
397 safestrncpy(vset, vbuf.v_seen, sizeof vset);
399 case ctdlsetseen_answered:
400 safestrncpy(vset, vbuf.v_answered, sizeof vset);
404 /* lprintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
406 /* Translate the existing sequence set into an array of booleans */
407 num_sets = num_tokens(vset, ',');
408 for (s=0; s<num_sets; ++s) {
409 extract_token(setstr, vset, s, ',', sizeof setstr);
411 extract_token(lostr, setstr, 0, ':', sizeof lostr);
412 if (num_tokens(setstr, ':') >= 2) {
413 extract_token(histr, setstr, 1, ':', sizeof histr);
414 if (!strcmp(histr, "*")) {
415 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
419 strcpy(histr, lostr);
424 for (i = 0; i < num_msgs; ++i) {
425 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
431 /* Now translate the array of booleans back into a sequence set */
436 for (i=0; i<num_msgs; ++i) {
439 if (msglist[i] == target_msgnum) {
440 is_seen = target_setting;
447 if (lo < 0L) lo = msglist[i];
451 if ( ((is_seen == 0) && (was_seen == 1))
452 || ((is_seen == 1) && (i == num_msgs-1)) ) {
454 /* begin trim-o-matic code */
457 while ( (strlen(vset) + 20) > sizeof vset) {
458 remove_token(vset, 0, ',');
460 if (j--) break; /* loop no more than 9 times */
462 if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
466 snprintf(lostr, sizeof lostr,
467 "1:%ld,%s", t, vset);
468 safestrncpy(vset, lostr, sizeof vset);
470 /* end trim-o-matic code */
478 snprintf(&vset[tmp], (sizeof vset) - tmp,
482 snprintf(&vset[tmp], (sizeof vset) - tmp,
491 /* Decide which message set we're manipulating */
493 case ctdlsetseen_seen:
494 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
496 case ctdlsetseen_answered:
497 safestrncpy(vbuf.v_answered, vset,
498 sizeof vbuf.v_answered);
503 /* lprintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
505 CtdlSetRelationship(&vbuf,
506 ((which_user != NULL) ? which_user : &CC->user),
507 ((which_room != NULL) ? which_room : &CC->room)
513 * API function to perform an operation for each qualifying message in the
514 * current room. (Returns the number of messages processed.)
516 int CtdlForEachMessage(int mode, long ref,
518 struct CtdlMessage *compare,
519 void (*CallBack) (long, void *),
525 struct cdbdata *cdbfr;
526 long *msglist = NULL;
528 int num_processed = 0;
531 struct CtdlMessage *msg;
534 int printed_lastold = 0;
536 /* Learn about the user and room in question */
538 getuser(&CC->user, CC->curr_user);
539 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
541 /* Load the message list */
542 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
544 msglist = malloc(cdbfr->len);
545 memcpy(msglist, cdbfr->ptr, cdbfr->len);
546 num_msgs = cdbfr->len / sizeof(long);
549 return 0; /* No messages at all? No further action. */
554 * Now begin the traversal.
556 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
558 /* If the caller is looking for a specific MIME type, filter
559 * out all messages which are not of the type requested.
561 if (content_type != NULL) if (strlen(content_type) > 0) {
563 /* This call to GetMetaData() sits inside this loop
564 * so that we only do the extra database read per msg
565 * if we need to. Doing the extra read all the time
566 * really kills the server. If we ever need to use
567 * metadata for another search criterion, we need to
568 * move the read somewhere else -- but still be smart
569 * enough to only do the read if the caller has
570 * specified something that will need it.
572 GetMetaData(&smi, msglist[a]);
574 if (strcasecmp(smi.meta_content_type, content_type)) {
580 num_msgs = sort_msglist(msglist, num_msgs);
582 /* If a template was supplied, filter out the messages which
583 * don't match. (This could induce some delays!)
586 if (compare != NULL) {
587 for (a = 0; a < num_msgs; ++a) {
588 msg = CtdlFetchMessage(msglist[a], 1);
590 if (CtdlMsgCmp(msg, compare)) {
593 CtdlFreeMessage(msg);
601 * Now iterate through the message list, according to the
602 * criteria supplied by the caller.
605 for (a = 0; a < num_msgs; ++a) {
606 thismsg = msglist[a];
607 if (mode == MSGS_ALL) {
611 is_seen = is_msg_in_sequence_set(
612 vbuf.v_seen, thismsg);
613 if (is_seen) lastold = thismsg;
619 || ((mode == MSGS_OLD) && (is_seen))
620 || ((mode == MSGS_NEW) && (!is_seen))
621 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
622 || ((mode == MSGS_FIRST) && (a < ref))
623 || ((mode == MSGS_GT) && (thismsg > ref))
624 || ((mode == MSGS_EQ) && (thismsg == ref))
627 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
629 CallBack(lastold, userdata);
633 if (CallBack) CallBack(thismsg, userdata);
637 free(msglist); /* Clean up */
638 return num_processed;
644 * cmd_msgs() - get list of message #'s in this room
645 * implements the MSGS server command using CtdlForEachMessage()
647 void cmd_msgs(char *cmdbuf)
656 int with_template = 0;
657 struct CtdlMessage *template = NULL;
658 int with_headers = 0;
660 extract_token(which, cmdbuf, 0, '|', sizeof which);
661 cm_ref = extract_int(cmdbuf, 1);
662 with_template = extract_int(cmdbuf, 2);
663 with_headers = extract_int(cmdbuf, 3);
667 if (!strncasecmp(which, "OLD", 3))
669 else if (!strncasecmp(which, "NEW", 3))
671 else if (!strncasecmp(which, "FIRST", 5))
673 else if (!strncasecmp(which, "LAST", 4))
675 else if (!strncasecmp(which, "GT", 2))
678 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
679 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
685 cprintf("%d Send template then receive message list\n",
687 template = (struct CtdlMessage *)
688 malloc(sizeof(struct CtdlMessage));
689 memset(template, 0, sizeof(struct CtdlMessage));
690 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
691 extract_token(tfield, buf, 0, '|', sizeof tfield);
692 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
693 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
694 if (!strcasecmp(tfield, msgkeys[i])) {
695 template->cm_fields[i] =
703 cprintf("%d \n", LISTING_FOLLOWS);
706 CtdlForEachMessage(mode,
710 (with_headers ? headers_listing : simple_listing),
713 if (template != NULL) CtdlFreeMessage(template);
721 * help_subst() - support routine for help file viewer
723 void help_subst(char *strbuf, char *source, char *dest)
728 while (p = pattern2(strbuf, source), (p >= 0)) {
729 strcpy(workbuf, &strbuf[p + strlen(source)]);
730 strcpy(&strbuf[p], dest);
731 strcat(strbuf, workbuf);
736 void do_help_subst(char *buffer)
740 help_subst(buffer, "^nodename", config.c_nodename);
741 help_subst(buffer, "^humannode", config.c_humannode);
742 help_subst(buffer, "^fqdn", config.c_fqdn);
743 help_subst(buffer, "^username", CC->user.fullname);
744 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
745 help_subst(buffer, "^usernum", buf2);
746 help_subst(buffer, "^sysadm", config.c_sysadm);
747 help_subst(buffer, "^variantname", CITADEL);
748 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
749 help_subst(buffer, "^maxsessions", buf2);
750 help_subst(buffer, "^bbsdir", CTDLDIR);
756 * memfmout() - Citadel text formatter and paginator.
757 * Although the original purpose of this routine was to format
758 * text to the reader's screen width, all we're really using it
759 * for here is to format text out to 80 columns before sending it
760 * to the client. The client software may reformat it again.
763 int width, /* screen width to use */
764 char *mptr, /* where are we going to get our text from? */
765 char subst, /* nonzero if we should do substitutions */
766 char *nl) /* string to terminate lines with */
778 c = 1; /* c is the current pos */
782 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
784 buffer[strlen(buffer) + 1] = 0;
785 buffer[strlen(buffer)] = ch;
788 if (buffer[0] == '^')
789 do_help_subst(buffer);
791 buffer[strlen(buffer) + 1] = 0;
793 strcpy(buffer, &buffer[1]);
801 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
803 if (((old == 13) || (old == 10)) && (isspace(real))) {
811 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
812 cprintf("%s%s", nl, aaa);
821 if ((strlen(aaa) + c) > (width - 5)) {
830 if ((ch == 13) || (ch == 10)) {
831 cprintf("%s%s", aaa, nl);
838 cprintf("%s%s", aaa, nl);
844 * Callback function for mime parser that simply lists the part
846 void list_this_part(char *name, char *filename, char *partnum, char *disp,
847 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
852 ma = (struct ma_info *)cbuserdata;
853 if (ma->is_ma == 0) {
854 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
855 name, filename, partnum, disp, cbtype, (long)length);
860 * Callback function for multipart prefix
862 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
863 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
868 ma = (struct ma_info *)cbuserdata;
869 if (!strcasecmp(cbtype, "multipart/alternative")) {
873 if (ma->is_ma == 0) {
874 cprintf("pref=%s|%s\n", partnum, cbtype);
879 * Callback function for multipart sufffix
881 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
882 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
887 ma = (struct ma_info *)cbuserdata;
888 if (ma->is_ma == 0) {
889 cprintf("suff=%s|%s\n", partnum, cbtype);
891 if (!strcasecmp(cbtype, "multipart/alternative")) {
898 * Callback function for mime parser that opens a section for downloading
900 void mime_download(char *name, char *filename, char *partnum, char *disp,
901 void *content, char *cbtype, char *cbcharset, size_t length,
902 char *encoding, void *cbuserdata)
905 /* Silently go away if there's already a download open... */
906 if (CC->download_fp != NULL)
909 /* ...or if this is not the desired section */
910 if (strcasecmp(CC->download_desired_section, partnum))
913 CC->download_fp = tmpfile();
914 if (CC->download_fp == NULL)
917 fwrite(content, length, 1, CC->download_fp);
918 fflush(CC->download_fp);
919 rewind(CC->download_fp);
921 OpenCmdResult(filename, cbtype);
927 * Load a message from disk into memory.
928 * This is used by CtdlOutputMsg() and other fetch functions.
930 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
931 * using the CtdlMessageFree() function.
933 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
935 struct cdbdata *dmsgtext;
936 struct CtdlMessage *ret = NULL;
940 cit_uint8_t field_header;
942 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
944 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
945 if (dmsgtext == NULL) {
948 mptr = dmsgtext->ptr;
949 upper_bound = mptr + dmsgtext->len;
951 /* Parse the three bytes that begin EVERY message on disk.
952 * The first is always 0xFF, the on-disk magic number.
953 * The second is the anonymous/public type byte.
954 * The third is the format type byte (vari, fixed, or MIME).
959 "Message %ld appears to be corrupted.\n",
964 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
965 memset(ret, 0, sizeof(struct CtdlMessage));
967 ret->cm_magic = CTDLMESSAGE_MAGIC;
968 ret->cm_anon_type = *mptr++; /* Anon type byte */
969 ret->cm_format_type = *mptr++; /* Format type byte */
972 * The rest is zero or more arbitrary fields. Load them in.
973 * We're done when we encounter either a zero-length field or
974 * have just processed the 'M' (message text) field.
977 if (mptr >= upper_bound) {
980 field_header = *mptr++;
981 ret->cm_fields[field_header] = strdup(mptr);
983 while (*mptr++ != 0); /* advance to next field */
985 } while ((mptr < upper_bound) && (field_header != 'M'));
989 /* Always make sure there's something in the msg text field. If
990 * it's NULL, the message text is most likely stored separately,
991 * so go ahead and fetch that. Failing that, just set a dummy
992 * body so other code doesn't barf.
994 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
995 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
996 if (dmsgtext != NULL) {
997 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1001 if (ret->cm_fields['M'] == NULL) {
1002 ret->cm_fields['M'] = strdup("<no text>\n");
1005 /* Perform "before read" hooks (aborting if any return nonzero) */
1006 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1007 CtdlFreeMessage(ret);
1016 * Returns 1 if the supplied pointer points to a valid Citadel message.
1017 * If the pointer is NULL or the magic number check fails, returns 0.
1019 int is_valid_message(struct CtdlMessage *msg) {
1022 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1023 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1031 * 'Destructor' for struct CtdlMessage
1033 void CtdlFreeMessage(struct CtdlMessage *msg)
1037 if (is_valid_message(msg) == 0) return;
1039 for (i = 0; i < 256; ++i)
1040 if (msg->cm_fields[i] != NULL) {
1041 free(msg->cm_fields[i]);
1044 msg->cm_magic = 0; /* just in case */
1050 * Pre callback function for multipart/alternative
1052 * NOTE: this differs from the standard behavior for a reason. Normally when
1053 * displaying multipart/alternative you want to show the _last_ usable
1054 * format in the message. Here we show the _first_ one, because it's
1055 * usually text/plain. Since this set of functions is designed for text
1056 * output to non-MIME-aware clients, this is the desired behavior.
1059 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1060 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1065 ma = (struct ma_info *)cbuserdata;
1066 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1067 if (!strcasecmp(cbtype, "multipart/alternative")) {
1075 * Post callback function for multipart/alternative
1077 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1078 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1083 ma = (struct ma_info *)cbuserdata;
1084 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1085 if (!strcasecmp(cbtype, "multipart/alternative")) {
1093 * Inline callback function for mime parser that wants to display text
1095 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1096 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1104 ma = (struct ma_info *)cbuserdata;
1106 lprintf(CTDL_DEBUG, "fixed_output() type=<%s>\n", cbtype);
1109 * If we're in the middle of a multipart/alternative scope and
1110 * we've already printed another section, skip this one.
1112 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
1113 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1118 if ( (!strcasecmp(cbtype, "text/plain"))
1119 || (strlen(cbtype)==0) ) {
1122 client_write(wptr, length);
1123 if (wptr[length-1] != '\n') {
1128 else if (!strcasecmp(cbtype, "text/html")) {
1129 ptr = html_to_ascii(content, 80, 0);
1131 client_write(ptr, wlen);
1132 if (ptr[wlen-1] != '\n') {
1137 else if (strncasecmp(cbtype, "multipart/", 10)) {
1138 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1139 partnum, filename, cbtype, (long)length);
1144 * The client is elegant and sophisticated and wants to be choosy about
1145 * MIME content types, so figure out which multipart/alternative part
1146 * we're going to send.
1148 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1149 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1156 ma = (struct ma_info *)cbuserdata;
1158 if (ma->is_ma > 0) {
1159 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1160 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1161 if (!strcasecmp(buf, cbtype)) {
1162 strcpy(ma->chosen_part, partnum);
1169 * Now that we've chosen our preferred part, output it.
1171 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1172 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1177 int add_newline = 0;
1181 ma = (struct ma_info *)cbuserdata;
1183 /* This is not the MIME part you're looking for... */
1184 if (strcasecmp(partnum, ma->chosen_part)) return;
1186 /* If the content-type of this part is in our preferred formats
1187 * list, we can simply output it verbatim.
1189 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1190 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1191 if (!strcasecmp(buf, cbtype)) {
1192 /* Yeah! Go! W00t!! */
1194 text_content = (char *)content;
1195 if (text_content[length-1] != '\n') {
1199 cprintf("Content-type: %s", cbtype);
1200 if (strlen(cbcharset) > 0) {
1201 cprintf("; charset=%s", cbcharset);
1203 cprintf("\nContent-length: %d\n",
1204 (int)(length + add_newline) );
1205 if (strlen(encoding) > 0) {
1206 cprintf("Content-transfer-encoding: %s\n", encoding);
1209 cprintf("Content-transfer-encoding: 7bit\n");
1212 client_write(content, length);
1213 if (add_newline) cprintf("\n");
1218 /* No translations required or possible: output as text/plain */
1219 cprintf("Content-type: text/plain\n\n");
1220 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1221 length, encoding, cbuserdata);
1226 * Get a message off disk. (returns om_* values found in msgbase.h)
1229 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1230 int mode, /* how would you like that message? */
1231 int headers_only, /* eschew the message body? */
1232 int do_proto, /* do Citadel protocol responses? */
1233 int crlf /* Use CRLF newlines instead of LF? */
1235 struct CtdlMessage *TheMessage = NULL;
1236 int retcode = om_no_such_msg;
1238 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
1241 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1242 if (do_proto) cprintf("%d Not logged in.\n",
1243 ERROR + NOT_LOGGED_IN);
1244 return(om_not_logged_in);
1247 /* FIXME: check message id against msglist for this room */
1250 * Fetch the message from disk. If we're in any sort of headers
1251 * only mode, request that we don't even bother loading the body
1254 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1255 TheMessage = CtdlFetchMessage(msg_num, 0);
1258 TheMessage = CtdlFetchMessage(msg_num, 1);
1261 if (TheMessage == NULL) {
1262 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1263 ERROR + MESSAGE_NOT_FOUND, msg_num);
1264 return(om_no_such_msg);
1267 retcode = CtdlOutputPreLoadedMsg(
1269 headers_only, do_proto, crlf);
1271 CtdlFreeMessage(TheMessage);
1278 * Get a message off disk. (returns om_* values found in msgbase.h)
1281 int CtdlOutputPreLoadedMsg(
1282 struct CtdlMessage *TheMessage,
1283 int mode, /* how would you like that message? */
1284 int headers_only, /* eschew the message body? */
1285 int do_proto, /* do Citadel protocol responses? */
1286 int crlf /* Use CRLF newlines instead of LF? */
1292 char display_name[256];
1294 char *nl; /* newline string */
1296 int subject_found = 0;
1299 /* Buffers needed for RFC822 translation. These are all filled
1300 * using functions that are bounds-checked, and therefore we can
1301 * make them substantially smaller than SIZ.
1309 char datestamp[100];
1311 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1312 ((TheMessage == NULL) ? "NULL" : "not null"),
1313 mode, headers_only, do_proto, crlf);
1315 strcpy(mid, "unknown");
1316 nl = (crlf ? "\r\n" : "\n");
1318 if (!is_valid_message(TheMessage)) {
1320 "ERROR: invalid preloaded message for output\n");
1321 return(om_no_such_msg);
1324 /* Are we downloading a MIME component? */
1325 if (mode == MT_DOWNLOAD) {
1326 if (TheMessage->cm_format_type != FMT_RFC822) {
1328 cprintf("%d This is not a MIME message.\n",
1329 ERROR + ILLEGAL_VALUE);
1330 } else if (CC->download_fp != NULL) {
1331 if (do_proto) cprintf(
1332 "%d You already have a download open.\n",
1333 ERROR + RESOURCE_BUSY);
1335 /* Parse the message text component */
1336 mptr = TheMessage->cm_fields['M'];
1337 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1338 /* If there's no file open by this time, the requested
1339 * section wasn't found, so print an error
1341 if (CC->download_fp == NULL) {
1342 if (do_proto) cprintf(
1343 "%d Section %s not found.\n",
1344 ERROR + FILE_NOT_FOUND,
1345 CC->download_desired_section);
1348 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1351 /* now for the user-mode message reading loops */
1352 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1354 /* Does the caller want to skip the headers? */
1355 if (headers_only == HEADERS_NONE) goto START_TEXT;
1357 /* Tell the client which format type we're using. */
1358 if ( (mode == MT_CITADEL) && (do_proto) ) {
1359 cprintf("type=%d\n", TheMessage->cm_format_type);
1362 /* nhdr=yes means that we're only displaying headers, no body */
1363 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1364 && (mode == MT_CITADEL)
1367 cprintf("nhdr=yes\n");
1370 /* begin header processing loop for Citadel message format */
1372 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1374 safestrncpy(display_name, "<unknown>", sizeof display_name);
1375 if (TheMessage->cm_fields['A']) {
1376 strcpy(buf, TheMessage->cm_fields['A']);
1377 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1378 safestrncpy(display_name, "****", sizeof display_name);
1380 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1381 safestrncpy(display_name, "anonymous", sizeof display_name);
1384 safestrncpy(display_name, buf, sizeof display_name);
1386 if ((is_room_aide())
1387 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1388 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1389 size_t tmp = strlen(display_name);
1390 snprintf(&display_name[tmp],
1391 sizeof display_name - tmp,
1396 /* Don't show Internet address for users on the
1397 * local Citadel network.
1400 if (TheMessage->cm_fields['N'] != NULL)
1401 if (strlen(TheMessage->cm_fields['N']) > 0)
1402 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1406 /* Now spew the header fields in the order we like them. */
1407 safestrncpy(allkeys, FORDER, sizeof allkeys);
1408 for (i=0; i<strlen(allkeys); ++i) {
1409 k = (int) allkeys[i];
1411 if ( (TheMessage->cm_fields[k] != NULL)
1412 && (msgkeys[k] != NULL) ) {
1414 if (do_proto) cprintf("%s=%s\n",
1418 else if ((k == 'F') && (suppress_f)) {
1421 /* Masquerade display name if needed */
1423 if (do_proto) cprintf("%s=%s\n",
1425 TheMessage->cm_fields[k]
1434 /* begin header processing loop for RFC822 transfer format */
1439 strcpy(snode, NODENAME);
1440 strcpy(lnode, HUMANNODE);
1441 if (mode == MT_RFC822) {
1442 for (i = 0; i < 256; ++i) {
1443 if (TheMessage->cm_fields[i]) {
1444 mptr = TheMessage->cm_fields[i];
1447 safestrncpy(luser, mptr, sizeof luser);
1448 safestrncpy(suser, mptr, sizeof suser);
1450 else if (i == 'Y') {
1451 cprintf("CC: %s%s", mptr, nl);
1453 else if (i == 'U') {
1454 cprintf("Subject: %s%s", mptr, nl);
1458 safestrncpy(mid, mptr, sizeof mid);
1460 safestrncpy(lnode, mptr, sizeof lnode);
1462 safestrncpy(fuser, mptr, sizeof fuser);
1463 /* else if (i == 'O')
1464 cprintf("X-Citadel-Room: %s%s",
1467 safestrncpy(snode, mptr, sizeof snode);
1469 cprintf("To: %s%s", mptr, nl);
1470 else if (i == 'T') {
1471 datestring(datestamp, sizeof datestamp,
1472 atol(mptr), DATESTRING_RFC822);
1473 cprintf("Date: %s%s", datestamp, nl);
1477 if (subject_found == 0) {
1478 cprintf("Subject: (no subject)%s", nl);
1482 for (i=0; i<strlen(suser); ++i) {
1483 suser[i] = tolower(suser[i]);
1484 if (!isalnum(suser[i])) suser[i]='_';
1487 if (mode == MT_RFC822) {
1488 if (!strcasecmp(snode, NODENAME)) {
1489 safestrncpy(snode, FQDN, sizeof snode);
1492 /* Construct a fun message id */
1493 cprintf("Message-ID: <%s", mid);
1494 if (strchr(mid, '@')==NULL) {
1495 cprintf("@%s", snode);
1499 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1500 cprintf("From: \"----\" <x@x.org>%s", nl);
1502 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1503 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1505 else if (strlen(fuser) > 0) {
1506 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1509 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1512 cprintf("Organization: %s%s", lnode, nl);
1514 /* Blank line signifying RFC822 end-of-headers */
1515 if (TheMessage->cm_format_type != FMT_RFC822) {
1520 /* end header processing loop ... at this point, we're in the text */
1522 if (headers_only == HEADERS_FAST) goto DONE;
1523 mptr = TheMessage->cm_fields['M'];
1525 /* Tell the client about the MIME parts in this message */
1526 if (TheMessage->cm_format_type == FMT_RFC822) {
1527 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1528 memset(&ma, 0, sizeof(struct ma_info));
1529 mime_parser(mptr, NULL,
1530 (do_proto ? *list_this_part : NULL),
1531 (do_proto ? *list_this_pref : NULL),
1532 (do_proto ? *list_this_suff : NULL),
1535 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1536 char *start_of_text = NULL;
1537 start_of_text = strstr(mptr, "\n\r\n");
1538 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1539 if (start_of_text == NULL) start_of_text = mptr;
1541 start_of_text = strstr(start_of_text, "\n");
1543 while (ch=*mptr, ch!=0) {
1547 else switch(headers_only) {
1549 if (mptr >= start_of_text) {
1550 if (ch == 10) cprintf("%s", nl);
1551 else cprintf("%c", ch);
1555 if (mptr < start_of_text) {
1556 if (ch == 10) cprintf("%s", nl);
1557 else cprintf("%c", ch);
1561 if (ch == 10) cprintf("%s", nl);
1562 else cprintf("%c", ch);
1571 if (headers_only == HEADERS_ONLY) {
1575 /* signify start of msg text */
1576 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1577 if (do_proto) cprintf("text\n");
1580 /* If the format type on disk is 1 (fixed-format), then we want
1581 * everything to be output completely literally ... regardless of
1582 * what message transfer format is in use.
1584 if (TheMessage->cm_format_type == FMT_FIXED) {
1585 if (mode == MT_MIME) {
1586 cprintf("Content-type: text/plain\n\n");
1589 while (ch = *mptr++, ch > 0) {
1592 if ((ch == 10) || (strlen(buf) > 250)) {
1593 cprintf("%s%s", buf, nl);
1596 buf[strlen(buf) + 1] = 0;
1597 buf[strlen(buf)] = ch;
1600 if (strlen(buf) > 0)
1601 cprintf("%s%s", buf, nl);
1604 /* If the message on disk is format 0 (Citadel vari-format), we
1605 * output using the formatter at 80 columns. This is the final output
1606 * form if the transfer format is RFC822, but if the transfer format
1607 * is Citadel proprietary, it'll still work, because the indentation
1608 * for new paragraphs is correct and the client will reformat the
1609 * message to the reader's screen width.
1611 if (TheMessage->cm_format_type == FMT_CITADEL) {
1612 if (mode == MT_MIME) {
1613 cprintf("Content-type: text/x-citadel-variformat\n\n");
1615 memfmout(80, mptr, 0, nl);
1618 /* If the message on disk is format 4 (MIME), we've gotta hand it
1619 * off to the MIME parser. The client has already been told that
1620 * this message is format 1 (fixed format), so the callback function
1621 * we use will display those parts as-is.
1623 if (TheMessage->cm_format_type == FMT_RFC822) {
1624 memset(&ma, 0, sizeof(struct ma_info));
1626 if (mode == MT_MIME) {
1627 strcpy(ma.chosen_part, "1");
1628 mime_parser(mptr, NULL,
1629 *choose_preferred, *fixed_output_pre,
1630 *fixed_output_post, (void *)&ma, 0);
1631 mime_parser(mptr, NULL,
1632 *output_preferred, NULL, NULL, (void *)&ma, 0);
1635 mime_parser(mptr, NULL,
1636 *fixed_output, *fixed_output_pre,
1637 *fixed_output_post, (void *)&ma, 0);
1642 DONE: /* now we're done */
1643 if (do_proto) cprintf("000\n");
1650 * display a message (mode 0 - Citadel proprietary)
1652 void cmd_msg0(char *cmdbuf)
1655 int headers_only = HEADERS_ALL;
1657 msgid = extract_long(cmdbuf, 0);
1658 headers_only = extract_int(cmdbuf, 1);
1660 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1666 * display a message (mode 2 - RFC822)
1668 void cmd_msg2(char *cmdbuf)
1671 int headers_only = HEADERS_ALL;
1673 msgid = extract_long(cmdbuf, 0);
1674 headers_only = extract_int(cmdbuf, 1);
1676 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1682 * display a message (mode 3 - IGnet raw format - internal programs only)
1684 void cmd_msg3(char *cmdbuf)
1687 struct CtdlMessage *msg;
1690 if (CC->internal_pgm == 0) {
1691 cprintf("%d This command is for internal programs only.\n",
1692 ERROR + HIGHER_ACCESS_REQUIRED);
1696 msgnum = extract_long(cmdbuf, 0);
1697 msg = CtdlFetchMessage(msgnum, 1);
1699 cprintf("%d Message %ld not found.\n",
1700 ERROR + MESSAGE_NOT_FOUND, msgnum);
1704 serialize_message(&smr, msg);
1705 CtdlFreeMessage(msg);
1708 cprintf("%d Unable to serialize message\n",
1709 ERROR + INTERNAL_ERROR);
1713 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1714 client_write((char *)smr.ser, (int)smr.len);
1721 * Display a message using MIME content types
1723 void cmd_msg4(char *cmdbuf)
1727 msgid = extract_long(cmdbuf, 0);
1728 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1734 * Client tells us its preferred message format(s)
1736 void cmd_msgp(char *cmdbuf)
1738 safestrncpy(CC->preferred_formats, cmdbuf,
1739 sizeof(CC->preferred_formats));
1740 cprintf("%d ok\n", CIT_OK);
1745 * Open a component of a MIME message as a download file
1747 void cmd_opna(char *cmdbuf)
1750 char desired_section[128];
1752 msgid = extract_long(cmdbuf, 0);
1753 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1754 safestrncpy(CC->download_desired_section, desired_section, sizeof CC->download_desired_section);
1755 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1760 * Save a message pointer into a specified room
1761 * (Returns 0 for success, nonzero for failure)
1762 * roomname may be NULL to use the current room
1764 * Note that the 'supplied_msg' field may be set to NULL, in which case
1765 * the message will be fetched from disk, by number, if we need to perform
1766 * replication checks. This adds an additional database read, so if the
1767 * caller already has the message in memory then it should be supplied.
1769 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check,
1770 struct CtdlMessage *supplied_msg) {
1772 char hold_rm[ROOMNAMELEN];
1773 struct cdbdata *cdbfr;
1776 long highest_msg = 0L;
1777 struct CtdlMessage *msg = NULL;
1779 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom(roomname=%s, msgid=%ld, do_repl_check=%d)\n",
1780 roomname, msgid, do_repl_check);
1782 strcpy(hold_rm, CC->room.QRname);
1784 /* We may need to check to see if this message is real */
1785 if (do_repl_check) {
1786 if (supplied_msg != NULL) {
1790 msg = CtdlFetchMessage(msgid, 0);
1792 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1795 /* Perform replication checks if necessary */
1796 if ( (do_repl_check) && (msg != NULL) ) {
1798 if (getroom(&CC->room,
1799 ((roomname != NULL) ? roomname : CC->room.QRname) )
1801 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1802 if ( (msg != NULL) && (msg != supplied_msg) ) CtdlFreeMessage(msg);
1803 return(ERROR + ROOM_NOT_FOUND);
1806 ReplicationChecks(msg);
1809 /* Now the regular stuff */
1810 if (lgetroom(&CC->room,
1811 ((roomname != NULL) ? roomname : CC->room.QRname) )
1813 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1814 if ( (msg != NULL) && (msg != supplied_msg) ) CtdlFreeMessage(msg);
1815 return(ERROR + ROOM_NOT_FOUND);
1818 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1819 if (cdbfr == NULL) {
1823 msglist = malloc(cdbfr->len);
1824 if (msglist == NULL)
1825 lprintf(CTDL_ALERT, "ERROR malloc msglist!\n");
1826 num_msgs = cdbfr->len / sizeof(long);
1827 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1832 /* Make sure the message doesn't already exist in this room. It
1833 * is absolutely taboo to have more than one reference to the same
1834 * message in a room.
1836 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1837 if (msglist[i] == msgid) {
1838 lputroom(&CC->room); /* unlock the room */
1839 getroom(&CC->room, hold_rm);
1840 if ( (msg != NULL) && (msg != supplied_msg) ) CtdlFreeMessage(msg);
1842 return(ERROR + ALREADY_EXISTS);
1846 /* Now add the new message */
1848 msglist = realloc(msglist, (num_msgs * sizeof(long)));
1850 if (msglist == NULL) {
1851 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1853 msglist[num_msgs - 1] = msgid;
1855 /* Sort the message list, so all the msgid's are in order */
1856 num_msgs = sort_msglist(msglist, num_msgs);
1858 /* Determine the highest message number */
1859 highest_msg = msglist[num_msgs - 1];
1861 /* Write it back to disk. */
1862 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1863 msglist, (int)(num_msgs * sizeof(long)));
1865 /* Free up the memory we used. */
1868 /* If the message has an Exclusive ID, index that... */
1870 if (msg->cm_fields['E'] != NULL) {
1871 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
1875 /* Update the highest-message pointer and unlock the room. */
1876 CC->room.QRhighest = highest_msg;
1877 lputroom(&CC->room);
1878 getroom(&CC->room, hold_rm);
1880 /* Bump the reference count for this message. */
1881 AdjRefCount(msgid, +1);
1883 /* Return success. */
1884 if ( (msg != NULL) && (msg != supplied_msg) ) CtdlFreeMessage(msg);
1891 * Message base operation to save a new message to the message store
1892 * (returns new message number)
1894 * This is the back end for CtdlSubmitMsg() and should not be directly
1895 * called by server-side modules.
1898 long send_message(struct CtdlMessage *msg) {
1906 /* Get a new message number */
1907 newmsgid = get_new_message_number();
1908 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1910 /* Generate an ID if we don't have one already */
1911 if (msg->cm_fields['I']==NULL) {
1912 msg->cm_fields['I'] = strdup(msgidbuf);
1915 /* If the message is big, set its body aside for storage elsewhere */
1916 if (msg->cm_fields['M'] != NULL) {
1917 if (strlen(msg->cm_fields['M']) > BIGMSG) {
1919 holdM = msg->cm_fields['M'];
1920 msg->cm_fields['M'] = NULL;
1924 /* Serialize our data structure for storage in the database */
1925 serialize_message(&smr, msg);
1928 msg->cm_fields['M'] = holdM;
1932 cprintf("%d Unable to serialize message\n",
1933 ERROR + INTERNAL_ERROR);
1937 /* Write our little bundle of joy into the message base */
1938 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
1939 smr.ser, smr.len) < 0) {
1940 lprintf(CTDL_ERR, "Can't store message\n");
1944 cdb_store(CDB_BIGMSGS,
1954 /* Free the memory we used for the serialized message */
1957 /* Return the *local* message ID to the caller
1958 * (even if we're storing an incoming network message)
1966 * Serialize a struct CtdlMessage into the format used on disk and network.
1968 * This function loads up a "struct ser_ret" (defined in server.h) which
1969 * contains the length of the serialized message and a pointer to the
1970 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1972 void serialize_message(struct ser_ret *ret, /* return values */
1973 struct CtdlMessage *msg) /* unserialized msg */
1977 static char *forder = FORDER;
1979 if (is_valid_message(msg) == 0) return; /* self check */
1982 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1983 ret->len = ret->len +
1984 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1986 lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1987 ret->ser = malloc(ret->len);
1988 if (ret->ser == NULL) {
1994 ret->ser[1] = msg->cm_anon_type;
1995 ret->ser[2] = msg->cm_format_type;
1998 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1999 ret->ser[wlen++] = (char)forder[i];
2000 strcpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
2001 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
2003 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2004 (long)ret->len, (long)wlen);
2012 * Check to see if any messages already exist in the current room which
2013 * carry the same Exclusive ID as this one. If any are found, delete them.
2015 void ReplicationChecks(struct CtdlMessage *msg) {
2016 long old_msgnum = (-1L);
2018 /* No exclusive id? Don't do anything. */
2019 if (msg == NULL) return;
2020 if (msg->cm_fields['E'] == NULL) return;
2021 if (strlen(msg->cm_fields['E']) == 0) return;
2022 lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2023 msg->cm_fields['E'], CC->room.QRname);
2025 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2026 if (old_msgnum > 0L) {
2027 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2028 CtdlDeleteMessages(CC->room.QRname, old_msgnum, "", 0);
2035 * Save a message to disk and submit it into the delivery system.
2037 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2038 struct recptypes *recps, /* recipients (if mail) */
2039 char *force /* force a particular room? */
2041 char submit_filename[128];
2042 char generated_timestamp[32];
2043 char hold_rm[ROOMNAMELEN];
2044 char actual_rm[ROOMNAMELEN];
2045 char force_room[ROOMNAMELEN];
2046 char content_type[SIZ]; /* We have to learn this */
2047 char recipient[SIZ];
2050 struct ctdluser userbuf;
2052 struct MetaData smi;
2053 FILE *network_fp = NULL;
2054 static int seqnum = 1;
2055 struct CtdlMessage *imsg = NULL;
2058 char *hold_R, *hold_D;
2059 char *collected_addresses = NULL;
2060 struct addresses_to_be_filed *aptr = NULL;
2062 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2063 if (is_valid_message(msg) == 0) return(-1); /* self check */
2065 /* If this message has no timestamp, we take the liberty of
2066 * giving it one, right now.
2068 if (msg->cm_fields['T'] == NULL) {
2069 lprintf(CTDL_DEBUG, "Generating timestamp\n");
2070 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2071 msg->cm_fields['T'] = strdup(generated_timestamp);
2074 /* If this message has no path, we generate one.
2076 if (msg->cm_fields['P'] == NULL) {
2077 lprintf(CTDL_DEBUG, "Generating path\n");
2078 if (msg->cm_fields['A'] != NULL) {
2079 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2080 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2081 if (isspace(msg->cm_fields['P'][a])) {
2082 msg->cm_fields['P'][a] = ' ';
2087 msg->cm_fields['P'] = strdup("unknown");
2091 if (force == NULL) {
2092 strcpy(force_room, "");
2095 strcpy(force_room, force);
2098 /* Learn about what's inside, because it's what's inside that counts */
2099 lprintf(CTDL_DEBUG, "Learning what's inside\n");
2100 if (msg->cm_fields['M'] == NULL) {
2101 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2105 switch (msg->cm_format_type) {
2107 strcpy(content_type, "text/x-citadel-variformat");
2110 strcpy(content_type, "text/plain");
2113 strcpy(content_type, "text/plain");
2114 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type: ");
2116 safestrncpy(content_type, &mptr[14],
2117 sizeof content_type);
2118 for (a = 0; a < strlen(content_type); ++a) {
2119 if ((content_type[a] == ';')
2120 || (content_type[a] == ' ')
2121 || (content_type[a] == 13)
2122 || (content_type[a] == 10)) {
2123 content_type[a] = 0;
2129 /* Goto the correct room */
2130 lprintf(CTDL_DEBUG, "Selected room %s\n",
2131 (recps) ? CC->room.QRname : SENTITEMS);
2132 strcpy(hold_rm, CC->room.QRname);
2133 strcpy(actual_rm, CC->room.QRname);
2134 if (recps != NULL) {
2135 strcpy(actual_rm, SENTITEMS);
2138 /* If the user is a twit, move to the twit room for posting */
2139 lprintf(CTDL_DEBUG, "Handling twit stuff: %s\n",
2140 (CC->user.axlevel == 2) ? config.c_twitroom : "OK");
2142 if (CC->user.axlevel == 2) {
2143 strcpy(hold_rm, actual_rm);
2144 strcpy(actual_rm, config.c_twitroom);
2148 /* ...or if this message is destined for Aide> then go there. */
2149 if (strlen(force_room) > 0) {
2150 strcpy(actual_rm, force_room);
2153 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2154 if (strcasecmp(actual_rm, CC->room.QRname)) {
2155 /* getroom(&CC->room, actual_rm); */
2156 usergoto(actual_rm, 0, 1, NULL, NULL);
2160 * If this message has no O (room) field, generate one.
2162 if (msg->cm_fields['O'] == NULL) {
2163 msg->cm_fields['O'] = strdup(CC->room.QRname);
2166 /* Perform "before save" hooks (aborting if any return nonzero) */
2167 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2168 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2170 /* If this message has an Exclusive ID, perform replication checks */
2171 lprintf(CTDL_DEBUG, "Performing replication checks\n");
2172 ReplicationChecks(msg);
2174 /* Save it to disk */
2175 lprintf(CTDL_DEBUG, "Saving to disk\n");
2176 newmsgid = send_message(msg);
2177 if (newmsgid <= 0L) return(-5);
2179 /* Write a supplemental message info record. This doesn't have to
2180 * be a critical section because nobody else knows about this message
2183 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2184 memset(&smi, 0, sizeof(struct MetaData));
2185 smi.meta_msgnum = newmsgid;
2186 smi.meta_refcount = 0;
2187 safestrncpy(smi.meta_content_type, content_type,
2188 sizeof smi.meta_content_type);
2190 /* As part of the new metadata record, measure how
2191 * big this message will be when displayed as RFC822.
2192 * Both POP and IMAP use this, and it's best to just take the hit now
2193 * instead of having to potentially measure thousands of messages when
2194 * a mailbox is opened later.
2197 if (CC->redirect_buffer != NULL) {
2198 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2201 CC->redirect_buffer = malloc(SIZ);
2202 CC->redirect_len = 0;
2203 CC->redirect_alloc = SIZ;
2204 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2205 smi.meta_rfc822_length = CC->redirect_len;
2206 free(CC->redirect_buffer);
2207 CC->redirect_buffer = NULL;
2208 CC->redirect_len = 0;
2209 CC->redirect_alloc = 0;
2213 /* Now figure out where to store the pointers */
2214 lprintf(CTDL_DEBUG, "Storing pointers\n");
2216 /* If this is being done by the networker delivering a private
2217 * message, we want to BYPASS saving the sender's copy (because there
2218 * is no local sender; it would otherwise go to the Trashcan).
2220 if ((!CC->internal_pgm) || (recps == NULL)) {
2221 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2222 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2223 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2227 /* For internet mail, drop a copy in the outbound queue room */
2229 if (recps->num_internet > 0) {
2230 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2233 /* If other rooms are specified, drop them there too. */
2235 if (recps->num_room > 0)
2236 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2237 extract_token(recipient, recps->recp_room, i,
2238 '|', sizeof recipient);
2239 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2240 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2243 /* Bump this user's messages posted counter. */
2244 lprintf(CTDL_DEBUG, "Updating user\n");
2245 lgetuser(&CC->user, CC->curr_user);
2246 CC->user.posted = CC->user.posted + 1;
2247 lputuser(&CC->user);
2249 /* If this is private, local mail, make a copy in the
2250 * recipient's mailbox and bump the reference count.
2253 if (recps->num_local > 0)
2254 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2255 extract_token(recipient, recps->recp_local, i,
2256 '|', sizeof recipient);
2257 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2259 if (getuser(&userbuf, recipient) == 0) {
2260 MailboxName(actual_rm, sizeof actual_rm,
2261 &userbuf, MAILROOM);
2262 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2263 BumpNewMailCounter(userbuf.usernum);
2266 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2267 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2271 /* Perform "after save" hooks */
2272 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2273 PerformMessageHooks(msg, EVT_AFTERSAVE);
2275 /* For IGnet mail, we have to save a new copy into the spooler for
2276 * each recipient, with the R and D fields set to the recipient and
2277 * destination-node. This has two ugly side effects: all other
2278 * recipients end up being unlisted in this recipient's copy of the
2279 * message, and it has to deliver multiple messages to the same
2280 * node. We'll revisit this again in a year or so when everyone has
2281 * a network spool receiver that can handle the new style messages.
2284 if (recps->num_ignet > 0)
2285 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2286 extract_token(recipient, recps->recp_ignet, i,
2287 '|', sizeof recipient);
2289 hold_R = msg->cm_fields['R'];
2290 hold_D = msg->cm_fields['D'];
2291 msg->cm_fields['R'] = malloc(SIZ);
2292 msg->cm_fields['D'] = malloc(128);
2293 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2294 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2296 serialize_message(&smr, msg);
2298 snprintf(submit_filename, sizeof submit_filename,
2299 #ifndef HAVE_SPOOL_DIR
2304 "/network/spoolin/netmail.%04lx.%04x.%04x",
2305 (long) getpid(), CC->cs_pid, ++seqnum);
2306 network_fp = fopen(submit_filename, "wb+");
2307 if (network_fp != NULL) {
2308 fwrite(smr.ser, smr.len, 1, network_fp);
2314 free(msg->cm_fields['R']);
2315 free(msg->cm_fields['D']);
2316 msg->cm_fields['R'] = hold_R;
2317 msg->cm_fields['D'] = hold_D;
2320 /* Go back to the room we started from */
2321 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2322 if (strcasecmp(hold_rm, CC->room.QRname))
2323 /* getroom(&CC->room, hold_rm); */
2324 usergoto(hold_rm, 0, 1, NULL, NULL);
2326 /* For internet mail, generate delivery instructions.
2327 * Yes, this is recursive. Deal with it. Infinite recursion does
2328 * not happen because the delivery instructions message does not
2329 * contain a recipient.
2332 if (recps->num_internet > 0) {
2333 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2334 instr = malloc(SIZ * 2);
2335 snprintf(instr, SIZ * 2,
2336 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2338 SPOOLMIME, newmsgid, (long)time(NULL),
2339 msg->cm_fields['A'], msg->cm_fields['N']
2342 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2343 size_t tmp = strlen(instr);
2344 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2345 snprintf(&instr[tmp], SIZ * 2 - tmp,
2346 "remote|%s|0||\n", recipient);
2349 imsg = malloc(sizeof(struct CtdlMessage));
2350 memset(imsg, 0, sizeof(struct CtdlMessage));
2351 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2352 imsg->cm_anon_type = MES_NORMAL;
2353 imsg->cm_format_type = FMT_RFC822;
2354 imsg->cm_fields['A'] = strdup("Citadel");
2355 imsg->cm_fields['M'] = instr;
2356 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2357 CtdlFreeMessage(imsg);
2361 * Any addresses to harvest for someone's address book?
2363 if ( (CC->logged_in) && (recps != NULL) ) {
2364 collected_addresses = harvest_collected_addresses(msg);
2367 if (collected_addresses != NULL) {
2368 begin_critical_section(S_ATBF);
2369 aptr = (struct addresses_to_be_filed *) malloc(sizeof(struct addresses_to_be_filed));
2371 MailboxName(actual_rm, sizeof actual_rm, &CC->user, USERCONTACTSROOM);
2372 aptr->roomname = strdup(actual_rm);
2373 aptr->collected_addresses = collected_addresses;
2375 end_critical_section(S_ATBF);
2388 * Convenience function for generating small administrative messages.
2390 void quickie_message(char *from, char *to, char *room, char *text,
2391 int format_type, char *subject)
2393 struct CtdlMessage *msg;
2394 struct recptypes *recp = NULL;
2396 msg = malloc(sizeof(struct CtdlMessage));
2397 memset(msg, 0, sizeof(struct CtdlMessage));
2398 msg->cm_magic = CTDLMESSAGE_MAGIC;
2399 msg->cm_anon_type = MES_NORMAL;
2400 msg->cm_format_type = format_type;
2401 msg->cm_fields['A'] = strdup(from);
2402 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2403 msg->cm_fields['N'] = strdup(NODENAME);
2405 msg->cm_fields['R'] = strdup(to);
2406 recp = validate_recipients(to);
2408 if (subject != NULL) {
2409 msg->cm_fields['U'] = strdup(subject);
2411 msg->cm_fields['M'] = strdup(text);
2413 CtdlSubmitMsg(msg, recp, room);
2414 CtdlFreeMessage(msg);
2415 if (recp != NULL) free(recp);
2421 * Back end function used by CtdlMakeMessage() and similar functions
2423 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2424 size_t maxlen, /* maximum message length */
2425 char *exist, /* if non-null, append to it;
2426 exist is ALWAYS freed */
2427 int crlf /* CRLF newlines instead of LF */
2431 size_t message_len = 0;
2432 size_t buffer_len = 0;
2438 if (exist == NULL) {
2445 message_len = strlen(exist);
2446 buffer_len = message_len + 4096;
2447 m = realloc(exist, buffer_len);
2454 /* flush the input if we have nowhere to store it */
2459 /* read in the lines of message text one by one */
2461 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2462 if (!strcmp(buf, terminator)) finished = 1;
2464 strcat(buf, "\r\n");
2470 if ( (!flushing) && (!finished) ) {
2471 /* Measure the line */
2472 linelen = strlen(buf);
2474 /* augment the buffer if we have to */
2475 if ((message_len + linelen) >= buffer_len) {
2476 ptr = realloc(m, (buffer_len * 2) );
2477 if (ptr == NULL) { /* flush if can't allocate */
2480 buffer_len = (buffer_len * 2);
2482 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2486 /* Add the new line to the buffer. NOTE: this loop must avoid
2487 * using functions like strcat() and strlen() because they
2488 * traverse the entire buffer upon every call, and doing that
2489 * for a multi-megabyte message slows it down beyond usability.
2491 strcpy(&m[message_len], buf);
2492 message_len += linelen;
2495 /* if we've hit the max msg length, flush the rest */
2496 if (message_len >= maxlen) flushing = 1;
2498 } while (!finished);
2506 * Build a binary message to be saved on disk.
2507 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2508 * will become part of the message. This means you are no longer
2509 * responsible for managing that memory -- it will be freed along with
2510 * the rest of the fields when CtdlFreeMessage() is called.)
2513 struct CtdlMessage *CtdlMakeMessage(
2514 struct ctdluser *author, /* author's user structure */
2515 char *recipient, /* NULL if it's not mail */
2516 char *recp_cc, /* NULL if it's not mail */
2517 char *room, /* room where it's going */
2518 int type, /* see MES_ types in header file */
2519 int format_type, /* variformat, plain text, MIME... */
2520 char *fake_name, /* who we're masquerading as */
2521 char *subject, /* Subject (optional) */
2522 char *preformatted_text /* ...or NULL to read text from client */
2524 char dest_node[SIZ];
2526 struct CtdlMessage *msg;
2528 msg = malloc(sizeof(struct CtdlMessage));
2529 memset(msg, 0, sizeof(struct CtdlMessage));
2530 msg->cm_magic = CTDLMESSAGE_MAGIC;
2531 msg->cm_anon_type = type;
2532 msg->cm_format_type = format_type;
2534 /* Don't confuse the poor folks if it's not routed mail. */
2535 strcpy(dest_node, "");
2540 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2541 msg->cm_fields['P'] = strdup(buf);
2543 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2544 msg->cm_fields['T'] = strdup(buf);
2546 if (fake_name[0]) /* author */
2547 msg->cm_fields['A'] = strdup(fake_name);
2549 msg->cm_fields['A'] = strdup(author->fullname);
2551 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2552 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2555 msg->cm_fields['O'] = strdup(CC->room.QRname);
2558 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2559 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2561 if (recipient[0] != 0) {
2562 msg->cm_fields['R'] = strdup(recipient);
2564 if (recp_cc[0] != 0) {
2565 msg->cm_fields['Y'] = strdup(recp_cc);
2567 if (dest_node[0] != 0) {
2568 msg->cm_fields['D'] = strdup(dest_node);
2571 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2572 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2575 if (subject != NULL) {
2577 if (strlen(subject) > 0) {
2578 msg->cm_fields['U'] = strdup(subject);
2582 if (preformatted_text != NULL) {
2583 msg->cm_fields['M'] = preformatted_text;
2586 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2587 config.c_maxmsglen, NULL, 0);
2595 * Check to see whether we have permission to post a message in the current
2596 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2597 * returns 0 on success.
2599 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2601 if (!(CC->logged_in)) {
2602 snprintf(errmsgbuf, n, "Not logged in.");
2603 return (ERROR + NOT_LOGGED_IN);
2606 if ((CC->user.axlevel < 2)
2607 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2608 snprintf(errmsgbuf, n, "Need to be validated to enter "
2609 "(except in %s> to sysop)", MAILROOM);
2610 return (ERROR + HIGHER_ACCESS_REQUIRED);
2613 if ((CC->user.axlevel < 4)
2614 && (CC->room.QRflags & QR_NETWORK)) {
2615 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2616 return (ERROR + HIGHER_ACCESS_REQUIRED);
2619 if ((CC->user.axlevel < 6)
2620 && (CC->room.QRflags & QR_READONLY)) {
2621 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2622 return (ERROR + HIGHER_ACCESS_REQUIRED);
2625 strcpy(errmsgbuf, "Ok");
2631 * Check to see if the specified user has Internet mail permission
2632 * (returns nonzero if permission is granted)
2634 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2636 /* Do not allow twits to send Internet mail */
2637 if (who->axlevel <= 2) return(0);
2639 /* Globally enabled? */
2640 if (config.c_restrict == 0) return(1);
2642 /* User flagged ok? */
2643 if (who->flags & US_INTERNET) return(2);
2645 /* Aide level access? */
2646 if (who->axlevel >= 6) return(3);
2648 /* No mail for you! */
2654 * Validate recipients, count delivery types and errors, and handle aliasing
2655 * FIXME check for dupes!!!!!
2656 * Returns 0 if all addresses are ok, -1 if no addresses were specified,
2657 * or the number of addresses found invalid.
2659 struct recptypes *validate_recipients(char *recipients) {
2660 struct recptypes *ret;
2661 char this_recp[SIZ];
2662 char this_recp_cooked[SIZ];
2668 struct ctdluser tempUS;
2669 struct ctdlroom tempQR;
2672 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2673 if (ret == NULL) return(NULL);
2674 memset(ret, 0, sizeof(struct recptypes));
2677 ret->num_internet = 0;
2682 if (recipients == NULL) {
2685 else if (strlen(recipients) == 0) {
2689 /* Change all valid separator characters to commas */
2690 for (i=0; i<strlen(recipients); ++i) {
2691 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2692 recipients[i] = ',';
2697 num_recps = num_tokens(recipients, ',');
2700 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2701 extract_token(this_recp, recipients, i, ',', sizeof this_recp);
2703 lprintf(CTDL_DEBUG, "Evaluating recipient #%d <%s>\n", i, this_recp);
2704 mailtype = alias(this_recp);
2705 mailtype = alias(this_recp);
2706 mailtype = alias(this_recp);
2707 for (j=0; j<=strlen(this_recp); ++j) {
2708 if (this_recp[j]=='_') {
2709 this_recp_cooked[j] = ' ';
2712 this_recp_cooked[j] = this_recp[j];
2718 if (!strcasecmp(this_recp, "sysop")) {
2720 strcpy(this_recp, config.c_aideroom);
2721 if (strlen(ret->recp_room) > 0) {
2722 strcat(ret->recp_room, "|");
2724 strcat(ret->recp_room, this_recp);
2726 else if (getuser(&tempUS, this_recp) == 0) {
2728 strcpy(this_recp, tempUS.fullname);
2729 if (strlen(ret->recp_local) > 0) {
2730 strcat(ret->recp_local, "|");
2732 strcat(ret->recp_local, this_recp);
2734 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2736 strcpy(this_recp, tempUS.fullname);
2737 if (strlen(ret->recp_local) > 0) {
2738 strcat(ret->recp_local, "|");
2740 strcat(ret->recp_local, this_recp);
2742 else if ( (!strncasecmp(this_recp, "room_", 5))
2743 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2745 if (strlen(ret->recp_room) > 0) {
2746 strcat(ret->recp_room, "|");
2748 strcat(ret->recp_room, &this_recp_cooked[5]);
2756 /* Yes, you're reading this correctly: if the target
2757 * domain points back to the local system or an attached
2758 * Citadel directory, the address is invalid. That's
2759 * because if the address were valid, we would have
2760 * already translated it to a local address by now.
2762 if (IsDirectory(this_recp)) {
2767 ++ret->num_internet;
2768 if (strlen(ret->recp_internet) > 0) {
2769 strcat(ret->recp_internet, "|");
2771 strcat(ret->recp_internet, this_recp);
2776 if (strlen(ret->recp_ignet) > 0) {
2777 strcat(ret->recp_ignet, "|");
2779 strcat(ret->recp_ignet, this_recp);
2787 if (strlen(ret->errormsg) == 0) {
2788 snprintf(append, sizeof append,
2789 "Invalid recipient: %s",
2793 snprintf(append, sizeof append,
2796 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2797 strcat(ret->errormsg, append);
2801 if (strlen(ret->display_recp) == 0) {
2802 strcpy(append, this_recp);
2805 snprintf(append, sizeof append, ", %s",
2808 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2809 strcat(ret->display_recp, append);
2814 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2815 ret->num_room + ret->num_error) == 0) {
2816 ret->num_error = (-1);
2817 strcpy(ret->errormsg, "No recipients specified.");
2820 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2821 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2822 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2823 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2824 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2825 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2833 * message entry - mode 0 (normal)
2835 void cmd_ent0(char *entargs)
2841 char masquerade_as[SIZ];
2843 int format_type = 0;
2844 char newusername[SIZ];
2845 struct CtdlMessage *msg;
2849 struct recptypes *valid = NULL;
2850 struct recptypes *valid_to = NULL;
2851 struct recptypes *valid_cc = NULL;
2852 struct recptypes *valid_bcc = NULL;
2859 post = extract_int(entargs, 0);
2860 extract_token(recp, entargs, 1, '|', sizeof recp);
2861 anon_flag = extract_int(entargs, 2);
2862 format_type = extract_int(entargs, 3);
2863 extract_token(subject, entargs, 4, '|', sizeof subject);
2864 do_confirm = extract_int(entargs, 6);
2865 extract_token(cc, entargs, 7, '|', sizeof cc);
2866 extract_token(bcc, entargs, 8, '|', sizeof bcc);
2868 /* first check to make sure the request is valid. */
2870 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2872 cprintf("%d %s\n", err, errmsg);
2876 /* Check some other permission type things. */
2879 if (CC->user.axlevel < 6) {
2880 cprintf("%d You don't have permission to masquerade.\n",
2881 ERROR + HIGHER_ACCESS_REQUIRED);
2884 extract_token(newusername, entargs, 5, '|', sizeof newusername);
2885 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2886 safestrncpy(CC->fake_postname, newusername,
2887 sizeof(CC->fake_postname) );
2888 cprintf("%d ok\n", CIT_OK);
2891 CC->cs_flags |= CS_POSTING;
2893 /* In the Mail> room we have to behave a little differently --
2894 * make sure the user has specified at least one recipient. Then
2895 * validate the recipient(s).
2897 if ( (CC->room.QRflags & QR_MAILBOX)
2898 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2900 if (CC->user.axlevel < 2) {
2901 strcpy(recp, "sysop");
2906 valid_to = validate_recipients(recp);
2907 if (valid_to->num_error > 0) {
2908 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
2913 valid_cc = validate_recipients(cc);
2914 if (valid_cc->num_error > 0) {
2915 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
2921 valid_bcc = validate_recipients(bcc);
2922 if (valid_bcc->num_error > 0) {
2923 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
2930 /* Recipient required, but none were specified */
2931 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
2935 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
2939 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
2940 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2941 cprintf("%d You do not have permission "
2942 "to send Internet mail.\n",
2943 ERROR + HIGHER_ACCESS_REQUIRED);
2951 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)
2952 && (CC->user.axlevel < 4) ) {
2953 cprintf("%d Higher access required for network mail.\n",
2954 ERROR + HIGHER_ACCESS_REQUIRED);
2961 if ((RESTRICT_INTERNET == 1)
2962 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
2963 && ((CC->user.flags & US_INTERNET) == 0)
2964 && (!CC->internal_pgm)) {
2965 cprintf("%d You don't have access to Internet mail.\n",
2966 ERROR + HIGHER_ACCESS_REQUIRED);
2975 /* Is this a room which has anonymous-only or anonymous-option? */
2976 anonymous = MES_NORMAL;
2977 if (CC->room.QRflags & QR_ANONONLY) {
2978 anonymous = MES_ANONONLY;
2980 if (CC->room.QRflags & QR_ANONOPT) {
2981 if (anon_flag == 1) { /* only if the user requested it */
2982 anonymous = MES_ANONOPT;
2986 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
2990 /* If we're only checking the validity of the request, return
2991 * success without creating the message.
2994 cprintf("%d %s\n", CIT_OK,
2995 ((valid_to != NULL) ? valid_to->display_recp : "") );
3002 /* We don't need these anymore because we'll do it differently below */
3007 /* Handle author masquerading */
3008 if (CC->fake_postname[0]) {
3009 strcpy(masquerade_as, CC->fake_postname);
3011 else if (CC->fake_username[0]) {
3012 strcpy(masquerade_as, CC->fake_username);
3015 strcpy(masquerade_as, "");
3018 /* Read in the message from the client. */
3020 cprintf("%d send message\n", START_CHAT_MODE);
3022 cprintf("%d send message\n", SEND_LISTING);
3025 msg = CtdlMakeMessage(&CC->user, recp, cc,
3026 CC->room.QRname, anonymous, format_type,
3027 masquerade_as, subject, NULL);
3029 /* Put together one big recipients struct containing to/cc/bcc all in
3030 * one. This is for the envelope.
3032 char *all_recps = malloc(SIZ * 3);
3033 strcpy(all_recps, recp);
3034 if (strlen(cc) > 0) {
3035 if (strlen(all_recps) > 0) {
3036 strcat(all_recps, ",");
3038 strcat(all_recps, cc);
3040 if (strlen(bcc) > 0) {
3041 if (strlen(all_recps) > 0) {
3042 strcat(all_recps, ",");
3044 strcat(all_recps, bcc);
3046 if (strlen(all_recps) > 0) {
3047 valid = validate_recipients(all_recps);
3055 msgnum = CtdlSubmitMsg(msg, valid, "");
3058 cprintf("%ld\n", msgnum);
3060 cprintf("Message accepted.\n");
3063 cprintf("Internal error.\n");
3065 if (msg->cm_fields['E'] != NULL) {
3066 cprintf("%s\n", msg->cm_fields['E']);
3073 CtdlFreeMessage(msg);
3075 CC->fake_postname[0] = '\0';
3076 if (valid != NULL) {
3085 * API function to delete messages which match a set of criteria
3086 * (returns the actual number of messages deleted)
3088 int CtdlDeleteMessages(char *room_name, /* which room */
3089 long dmsgnum, /* or "0" for any */
3090 char *content_type, /* or "" for any */
3091 int deferred /* let TDAP sweep it later */
3095 struct ctdlroom qrbuf;
3096 struct cdbdata *cdbfr;
3097 long *msglist = NULL;
3098 long *dellist = NULL;
3101 int num_deleted = 0;
3103 struct MetaData smi;
3105 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s, %d)\n",
3106 room_name, dmsgnum, content_type, deferred);
3108 /* get room record, obtaining a lock... */
3109 if (lgetroom(&qrbuf, room_name) != 0) {
3110 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3112 return (0); /* room not found */
3114 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3116 if (cdbfr != NULL) {
3117 msglist = malloc(cdbfr->len);
3118 dellist = malloc(cdbfr->len);
3119 memcpy(msglist, cdbfr->ptr, cdbfr->len);
3120 num_msgs = cdbfr->len / sizeof(long);
3124 for (i = 0; i < num_msgs; ++i) {
3127 /* Set/clear a bit for each criterion */
3129 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
3130 delete_this |= 0x01;
3132 if (strlen(content_type) == 0) {
3133 delete_this |= 0x02;
3135 GetMetaData(&smi, msglist[i]);
3136 if (!strcasecmp(smi.meta_content_type,
3138 delete_this |= 0x02;
3142 /* Delete message only if all bits are set */
3143 if (delete_this == 0x03) {
3144 dellist[num_deleted++] = msglist[i];
3149 num_msgs = sort_msglist(msglist, num_msgs);
3150 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3151 msglist, (int)(num_msgs * sizeof(long)));
3153 qrbuf.QRhighest = msglist[num_msgs - 1];
3158 * If the delete operation is "deferred" (and technically, any delete
3159 * operation not performed by THE DREADED AUTO-PURGER ought to be
3160 * a deferred delete) then we save a pointer to the message in the
3161 * DELETED_MSGS_ROOM. This will cause the reference count to remain
3162 * at least 1, which will save the user from having to synchronously
3163 * wait for various disk-intensive operations to complete.
3165 if ( (deferred) && (num_deleted) ) {
3166 for (i=0; i<num_deleted; ++i) {
3167 CtdlCopyMsgToRoom(dellist[i], DELETED_MSGS_ROOM);
3171 /* Go through the messages we pulled out of the index, and decrement
3172 * their reference counts by 1. If this is the only room the message
3173 * was in, the reference count will reach zero and the message will
3174 * automatically be deleted from the database. We do this in a
3175 * separate pass because there might be plug-in hooks getting called,
3176 * and we don't want that happening during an S_ROOMS critical
3179 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3180 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3181 AdjRefCount(dellist[i], -1);
3184 /* Now free the memory we used, and go away. */
3185 if (msglist != NULL) free(msglist);
3186 if (dellist != NULL) free(dellist);
3187 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3188 return (num_deleted);
3194 * Check whether the current user has permission to delete messages from
3195 * the current room (returns 1 for yes, 0 for no)
3197 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3198 getuser(&CC->user, CC->curr_user);
3199 if ((CC->user.axlevel < 6)
3200 && (CC->user.usernum != CC->room.QRroomaide)
3201 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3202 && (!(CC->internal_pgm))) {
3211 * Delete message from current room
3213 void cmd_dele(char *delstr)
3218 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3219 cprintf("%d Higher access required.\n",
3220 ERROR + HIGHER_ACCESS_REQUIRED);
3223 delnum = extract_long(delstr, 0);
3225 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "", 1);
3228 cprintf("%d %d message%s deleted.\n", CIT_OK,
3229 num_deleted, ((num_deleted != 1) ? "s" : ""));
3231 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3237 * Back end API function for moves and deletes
3239 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3242 err = CtdlSaveMsgPointerInRoom(dest, msgnum, 1, NULL);
3243 if (err != 0) return(err);
3251 * move or copy a message to another room
3253 void cmd_move(char *args)
3256 char targ[ROOMNAMELEN];
3257 struct ctdlroom qtemp;
3263 num = extract_long(args, 0);
3264 extract_token(targ, args, 1, '|', sizeof targ);
3265 targ[ROOMNAMELEN - 1] = 0;
3266 is_copy = extract_int(args, 2);
3268 if (getroom(&qtemp, targ) != 0) {
3269 cprintf("%d '%s' does not exist.\n",
3270 ERROR + ROOM_NOT_FOUND, targ);
3274 getuser(&CC->user, CC->curr_user);
3275 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3277 /* Check for permission to perform this operation.
3278 * Remember: "CC->room" is source, "qtemp" is target.
3282 /* Aides can move/copy */
3283 if (CC->user.axlevel >= 6) permit = 1;
3285 /* Room aides can move/copy */
3286 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3288 /* Permit move/copy from personal rooms */
3289 if ((CC->room.QRflags & QR_MAILBOX)
3290 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3292 /* Permit only copy from public to personal room */
3294 && (!(CC->room.QRflags & QR_MAILBOX))
3295 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3297 /* User must have access to target room */
3298 if (!(ra & UA_KNOWN)) permit = 0;
3301 cprintf("%d Higher access required.\n",
3302 ERROR + HIGHER_ACCESS_REQUIRED);
3306 err = CtdlCopyMsgToRoom(num, targ);
3308 cprintf("%d Cannot store message in %s: error %d\n",
3313 /* Now delete the message from the source room,
3314 * if this is a 'move' rather than a 'copy' operation.
3317 CtdlDeleteMessages(CC->room.QRname, num, "", 0);
3320 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3326 * GetMetaData() - Get the supplementary record for a message
3328 void GetMetaData(struct MetaData *smibuf, long msgnum)
3331 struct cdbdata *cdbsmi;
3334 memset(smibuf, 0, sizeof(struct MetaData));
3335 smibuf->meta_msgnum = msgnum;
3336 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3338 /* Use the negative of the message number for its supp record index */
3339 TheIndex = (0L - msgnum);
3341 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3342 if (cdbsmi == NULL) {
3343 return; /* record not found; go with defaults */
3345 memcpy(smibuf, cdbsmi->ptr,
3346 ((cdbsmi->len > sizeof(struct MetaData)) ?
3347 sizeof(struct MetaData) : cdbsmi->len));
3354 * PutMetaData() - (re)write supplementary record for a message
3356 void PutMetaData(struct MetaData *smibuf)
3360 /* Use the negative of the message number for the metadata db index */
3361 TheIndex = (0L - smibuf->meta_msgnum);
3363 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3364 smibuf->meta_msgnum, smibuf->meta_refcount);
3366 cdb_store(CDB_MSGMAIN,
3367 &TheIndex, (int)sizeof(long),
3368 smibuf, (int)sizeof(struct MetaData));
3373 * AdjRefCount - change the reference count for a message;
3374 * delete the message if it reaches zero
3376 void AdjRefCount(long msgnum, int incr)
3379 struct MetaData smi;
3382 /* This is a *tight* critical section; please keep it that way, as
3383 * it may get called while nested in other critical sections.
3384 * Complicating this any further will surely cause deadlock!
3386 begin_critical_section(S_SUPPMSGMAIN);
3387 GetMetaData(&smi, msgnum);
3388 smi.meta_refcount += incr;
3390 end_critical_section(S_SUPPMSGMAIN);
3391 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3392 msgnum, incr, smi.meta_refcount);
3394 /* If the reference count is now zero, delete the message
3395 * (and its supplementary record as well).
3397 if (smi.meta_refcount == 0) {
3398 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3400 /* Remove from fulltext index */
3401 if (config.c_enable_fulltext) {
3402 ft_index_message(msgnum, 0);
3405 /* Remove from message base */
3407 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3408 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3410 /* Remove metadata record */
3411 delnum = (0L - msgnum);
3412 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3417 * Write a generic object to this room
3419 * Note: this could be much more efficient. Right now we use two temporary
3420 * files, and still pull the message into memory as with all others.
3422 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3423 char *content_type, /* MIME type of this object */
3424 char *tempfilename, /* Where to fetch it from */
3425 struct ctdluser *is_mailbox, /* Mailbox room? */
3426 int is_binary, /* Is encoding necessary? */
3427 int is_unique, /* Del others of this type? */
3428 unsigned int flags /* Internal save flags */
3433 struct ctdlroom qrbuf;
3434 char roomname[ROOMNAMELEN];
3435 struct CtdlMessage *msg;
3437 char *raw_message = NULL;
3438 char *encoded_message = NULL;
3439 off_t raw_length = 0;
3441 if (is_mailbox != NULL)
3442 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3444 safestrncpy(roomname, req_room, sizeof(roomname));
3445 lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3448 fp = fopen(tempfilename, "rb");
3450 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3451 tempfilename, strerror(errno));
3454 fseek(fp, 0L, SEEK_END);
3455 raw_length = ftell(fp);
3457 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3459 raw_message = malloc((size_t)raw_length + 2);
3460 fread(raw_message, (size_t)raw_length, 1, fp);
3464 encoded_message = malloc((size_t)
3465 (((raw_length * 134) / 100) + 4096 ) );
3468 encoded_message = malloc((size_t)(raw_length + 4096));
3471 sprintf(encoded_message, "Content-type: %s\n", content_type);
3474 sprintf(&encoded_message[strlen(encoded_message)],
3475 "Content-transfer-encoding: base64\n\n"
3479 sprintf(&encoded_message[strlen(encoded_message)],
3480 "Content-transfer-encoding: 7bit\n\n"
3486 &encoded_message[strlen(encoded_message)],
3492 raw_message[raw_length] = 0;
3494 &encoded_message[strlen(encoded_message)],
3502 lprintf(CTDL_DEBUG, "Allocating\n");
3503 msg = malloc(sizeof(struct CtdlMessage));
3504 memset(msg, 0, sizeof(struct CtdlMessage));
3505 msg->cm_magic = CTDLMESSAGE_MAGIC;
3506 msg->cm_anon_type = MES_NORMAL;
3507 msg->cm_format_type = 4;
3508 msg->cm_fields['A'] = strdup(CC->user.fullname);
3509 msg->cm_fields['O'] = strdup(req_room);
3510 msg->cm_fields['N'] = strdup(config.c_nodename);
3511 msg->cm_fields['H'] = strdup(config.c_humannode);
3512 msg->cm_flags = flags;
3514 msg->cm_fields['M'] = encoded_message;
3516 /* Create the requested room if we have to. */
3517 if (getroom(&qrbuf, roomname) != 0) {
3518 create_room(roomname,
3519 ( (is_mailbox != NULL) ? 5 : 3 ),
3520 "", 0, 1, 0, VIEW_BBS);
3522 /* If the caller specified this object as unique, delete all
3523 * other objects of this type that are currently in the room.
3526 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3527 CtdlDeleteMessages(roomname, 0L, content_type, 0)
3530 /* Now write the data */
3531 CtdlSubmitMsg(msg, NULL, roomname);
3532 CtdlFreeMessage(msg);
3540 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3541 config_msgnum = msgnum;
3545 char *CtdlGetSysConfig(char *sysconfname) {
3546 char hold_rm[ROOMNAMELEN];
3549 struct CtdlMessage *msg;
3552 strcpy(hold_rm, CC->room.QRname);
3553 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3554 getroom(&CC->room, hold_rm);
3559 /* We want the last (and probably only) config in this room */
3560 begin_critical_section(S_CONFIG);
3561 config_msgnum = (-1L);
3562 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3563 CtdlGetSysConfigBackend, NULL);
3564 msgnum = config_msgnum;
3565 end_critical_section(S_CONFIG);
3571 msg = CtdlFetchMessage(msgnum, 1);
3573 conf = strdup(msg->cm_fields['M']);
3574 CtdlFreeMessage(msg);
3581 getroom(&CC->room, hold_rm);
3583 if (conf != NULL) do {
3584 extract_token(buf, conf, 0, '\n', sizeof buf);
3585 strcpy(conf, &conf[strlen(buf)+1]);
3586 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3591 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3592 char temp[PATH_MAX];
3595 strcpy(temp, tmpnam(NULL));
3597 fp = fopen(temp, "w");
3598 if (fp == NULL) return;
3599 fprintf(fp, "%s", sysconfdata);
3602 /* this handy API function does all the work for us */
3603 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3609 * Determine whether a given Internet address belongs to the current user
3611 int CtdlIsMe(char *addr, int addr_buf_len)
3613 struct recptypes *recp;
3616 recp = validate_recipients(addr);
3617 if (recp == NULL) return(0);
3619 if (recp->num_local == 0) {
3624 for (i=0; i<recp->num_local; ++i) {
3625 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3626 if (!strcasecmp(addr, CC->user.fullname)) {
3638 * Citadel protocol command to do the same
3640 void cmd_isme(char *argbuf) {
3643 if (CtdlAccessCheck(ac_logged_in)) return;
3644 extract_token(addr, argbuf, 0, '|', sizeof addr);
3646 if (CtdlIsMe(addr, sizeof addr)) {
3647 cprintf("%d %s\n", CIT_OK, addr);
3650 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);