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(
1268 TheMessage, msg_num, mode,
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,
1284 int mode, /* how would you like that message? */
1285 int headers_only, /* eschew the message body? */
1286 int do_proto, /* do Citadel protocol responses? */
1287 int crlf /* Use CRLF newlines instead of LF? */
1293 char display_name[256];
1295 char *nl; /* newline string */
1297 int subject_found = 0;
1300 /* Buffers needed for RFC822 translation. These are all filled
1301 * using functions that are bounds-checked, and therefore we can
1302 * make them substantially smaller than SIZ.
1310 char datestamp[100];
1312 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %ld, %d, %d, %d, %d\n",
1313 ((TheMessage == NULL) ? "NULL" : "not null"),
1315 mode, headers_only, do_proto, crlf);
1317 snprintf(mid, sizeof mid, "%ld", msg_num);
1318 nl = (crlf ? "\r\n" : "\n");
1320 if (!is_valid_message(TheMessage)) {
1322 "ERROR: invalid preloaded message for output\n");
1323 return(om_no_such_msg);
1326 /* Are we downloading a MIME component? */
1327 if (mode == MT_DOWNLOAD) {
1328 if (TheMessage->cm_format_type != FMT_RFC822) {
1330 cprintf("%d This is not a MIME message.\n",
1331 ERROR + ILLEGAL_VALUE);
1332 } else if (CC->download_fp != NULL) {
1333 if (do_proto) cprintf(
1334 "%d You already have a download open.\n",
1335 ERROR + RESOURCE_BUSY);
1337 /* Parse the message text component */
1338 mptr = TheMessage->cm_fields['M'];
1339 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1340 /* If there's no file open by this time, the requested
1341 * section wasn't found, so print an error
1343 if (CC->download_fp == NULL) {
1344 if (do_proto) cprintf(
1345 "%d Section %s not found.\n",
1346 ERROR + FILE_NOT_FOUND,
1347 CC->download_desired_section);
1350 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1353 /* now for the user-mode message reading loops */
1354 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1356 /* Does the caller want to skip the headers? */
1357 if (headers_only == HEADERS_NONE) goto START_TEXT;
1359 /* Tell the client which format type we're using. */
1360 if ( (mode == MT_CITADEL) && (do_proto) ) {
1361 cprintf("type=%d\n", TheMessage->cm_format_type);
1364 /* nhdr=yes means that we're only displaying headers, no body */
1365 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1366 && (mode == MT_CITADEL)
1369 cprintf("nhdr=yes\n");
1372 /* begin header processing loop for Citadel message format */
1374 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1376 safestrncpy(display_name, "<unknown>", sizeof display_name);
1377 if (TheMessage->cm_fields['A']) {
1378 strcpy(buf, TheMessage->cm_fields['A']);
1379 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1380 safestrncpy(display_name, "****", sizeof display_name);
1382 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1383 safestrncpy(display_name, "anonymous", sizeof display_name);
1386 safestrncpy(display_name, buf, sizeof display_name);
1388 if ((is_room_aide())
1389 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1390 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1391 size_t tmp = strlen(display_name);
1392 snprintf(&display_name[tmp],
1393 sizeof display_name - tmp,
1398 /* Don't show Internet address for users on the
1399 * local Citadel network.
1402 if (TheMessage->cm_fields['N'] != NULL)
1403 if (strlen(TheMessage->cm_fields['N']) > 0)
1404 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1408 /* Now spew the header fields in the order we like them. */
1409 safestrncpy(allkeys, FORDER, sizeof allkeys);
1410 for (i=0; i<strlen(allkeys); ++i) {
1411 k = (int) allkeys[i];
1413 if ( (TheMessage->cm_fields[k] != NULL)
1414 && (msgkeys[k] != NULL) ) {
1416 if (do_proto) cprintf("%s=%s\n",
1420 else if ((k == 'F') && (suppress_f)) {
1423 /* Masquerade display name if needed */
1425 if (do_proto) cprintf("%s=%s\n",
1427 TheMessage->cm_fields[k]
1436 /* begin header processing loop for RFC822 transfer format */
1441 strcpy(snode, NODENAME);
1442 strcpy(lnode, HUMANNODE);
1443 if (mode == MT_RFC822) {
1444 for (i = 0; i < 256; ++i) {
1445 if (TheMessage->cm_fields[i]) {
1446 mptr = TheMessage->cm_fields[i];
1449 safestrncpy(luser, mptr, sizeof luser);
1450 safestrncpy(suser, mptr, sizeof suser);
1452 else if (i == 'Y') {
1453 cprintf("CC: %s%s", mptr, nl);
1455 else if (i == 'U') {
1456 cprintf("Subject: %s%s", mptr, nl);
1460 safestrncpy(mid, mptr, sizeof mid);
1462 safestrncpy(lnode, mptr, sizeof lnode);
1464 safestrncpy(fuser, mptr, sizeof fuser);
1465 /* else if (i == 'O')
1466 cprintf("X-Citadel-Room: %s%s",
1469 safestrncpy(snode, mptr, sizeof snode);
1471 cprintf("To: %s%s", mptr, nl);
1472 else if (i == 'T') {
1473 datestring(datestamp, sizeof datestamp,
1474 atol(mptr), DATESTRING_RFC822);
1475 cprintf("Date: %s%s", datestamp, nl);
1479 if (subject_found == 0) {
1480 cprintf("Subject: (no subject)%s", nl);
1484 for (i=0; i<strlen(suser); ++i) {
1485 suser[i] = tolower(suser[i]);
1486 if (!isalnum(suser[i])) suser[i]='_';
1489 if (mode == MT_RFC822) {
1490 if (!strcasecmp(snode, NODENAME)) {
1491 safestrncpy(snode, FQDN, sizeof snode);
1494 /* Construct a fun message id */
1495 cprintf("Message-ID: <%s", mid);
1496 if (strchr(mid, '@')==NULL) {
1497 cprintf("@%s", snode);
1501 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1502 cprintf("From: \"----\" <x@x.org>%s", nl);
1504 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1505 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1507 else if (strlen(fuser) > 0) {
1508 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1511 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1514 cprintf("Organization: %s%s", lnode, nl);
1516 /* Blank line signifying RFC822 end-of-headers */
1517 if (TheMessage->cm_format_type != FMT_RFC822) {
1522 /* end header processing loop ... at this point, we're in the text */
1524 if (headers_only == HEADERS_FAST) goto DONE;
1525 mptr = TheMessage->cm_fields['M'];
1527 /* Tell the client about the MIME parts in this message */
1528 if (TheMessage->cm_format_type == FMT_RFC822) {
1529 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1530 memset(&ma, 0, sizeof(struct ma_info));
1531 mime_parser(mptr, NULL,
1532 (do_proto ? *list_this_part : NULL),
1533 (do_proto ? *list_this_pref : NULL),
1534 (do_proto ? *list_this_suff : NULL),
1537 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1538 /* FIXME ... we have to put some code in here to avoid
1539 * printing duplicate header information when both
1540 * Citadel and RFC822 headers exist. Preference should
1541 * probably be given to the RFC822 headers.
1543 int done_rfc822_hdrs = 0;
1544 while (ch=*(mptr++), ch!=0) {
1549 if (!done_rfc822_hdrs) {
1550 if (headers_only != HEADERS_NONE) {
1555 if (headers_only != HEADERS_ONLY) {
1559 if ((*(mptr) == 13) || (*(mptr) == 10)) {
1560 done_rfc822_hdrs = 1;
1564 if (done_rfc822_hdrs) {
1565 if (headers_only != HEADERS_NONE) {
1570 if (headers_only != HEADERS_ONLY) {
1574 if ((*mptr == 13) || (*mptr == 10)) {
1575 done_rfc822_hdrs = 1;
1583 if (headers_only == HEADERS_ONLY) {
1587 /* signify start of msg text */
1588 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1589 if (do_proto) cprintf("text\n");
1592 /* If the format type on disk is 1 (fixed-format), then we want
1593 * everything to be output completely literally ... regardless of
1594 * what message transfer format is in use.
1596 if (TheMessage->cm_format_type == FMT_FIXED) {
1597 if (mode == MT_MIME) {
1598 cprintf("Content-type: text/plain\n\n");
1601 while (ch = *mptr++, ch > 0) {
1604 if ((ch == 10) || (strlen(buf) > 250)) {
1605 cprintf("%s%s", buf, nl);
1608 buf[strlen(buf) + 1] = 0;
1609 buf[strlen(buf)] = ch;
1612 if (strlen(buf) > 0)
1613 cprintf("%s%s", buf, nl);
1616 /* If the message on disk is format 0 (Citadel vari-format), we
1617 * output using the formatter at 80 columns. This is the final output
1618 * form if the transfer format is RFC822, but if the transfer format
1619 * is Citadel proprietary, it'll still work, because the indentation
1620 * for new paragraphs is correct and the client will reformat the
1621 * message to the reader's screen width.
1623 if (TheMessage->cm_format_type == FMT_CITADEL) {
1624 if (mode == MT_MIME) {
1625 cprintf("Content-type: text/x-citadel-variformat\n\n");
1627 memfmout(80, mptr, 0, nl);
1630 /* If the message on disk is format 4 (MIME), we've gotta hand it
1631 * off to the MIME parser. The client has already been told that
1632 * this message is format 1 (fixed format), so the callback function
1633 * we use will display those parts as-is.
1635 if (TheMessage->cm_format_type == FMT_RFC822) {
1636 memset(&ma, 0, sizeof(struct ma_info));
1638 if (mode == MT_MIME) {
1639 strcpy(ma.chosen_part, "1");
1640 mime_parser(mptr, NULL,
1641 *choose_preferred, *fixed_output_pre,
1642 *fixed_output_post, (void *)&ma, 0);
1643 mime_parser(mptr, NULL,
1644 *output_preferred, NULL, NULL, (void *)&ma, 0);
1647 mime_parser(mptr, NULL,
1648 *fixed_output, *fixed_output_pre,
1649 *fixed_output_post, (void *)&ma, 0);
1654 DONE: /* now we're done */
1655 if (do_proto) cprintf("000\n");
1662 * display a message (mode 0 - Citadel proprietary)
1664 void cmd_msg0(char *cmdbuf)
1667 int headers_only = HEADERS_ALL;
1669 msgid = extract_long(cmdbuf, 0);
1670 headers_only = extract_int(cmdbuf, 1);
1672 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1678 * display a message (mode 2 - RFC822)
1680 void cmd_msg2(char *cmdbuf)
1683 int headers_only = HEADERS_ALL;
1685 msgid = extract_long(cmdbuf, 0);
1686 headers_only = extract_int(cmdbuf, 1);
1688 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1694 * display a message (mode 3 - IGnet raw format - internal programs only)
1696 void cmd_msg3(char *cmdbuf)
1699 struct CtdlMessage *msg;
1702 if (CC->internal_pgm == 0) {
1703 cprintf("%d This command is for internal programs only.\n",
1704 ERROR + HIGHER_ACCESS_REQUIRED);
1708 msgnum = extract_long(cmdbuf, 0);
1709 msg = CtdlFetchMessage(msgnum, 1);
1711 cprintf("%d Message %ld not found.\n",
1712 ERROR + MESSAGE_NOT_FOUND, msgnum);
1716 serialize_message(&smr, msg);
1717 CtdlFreeMessage(msg);
1720 cprintf("%d Unable to serialize message\n",
1721 ERROR + INTERNAL_ERROR);
1725 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1726 client_write((char *)smr.ser, (int)smr.len);
1733 * Display a message using MIME content types
1735 void cmd_msg4(char *cmdbuf)
1739 msgid = extract_long(cmdbuf, 0);
1740 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1746 * Client tells us its preferred message format(s)
1748 void cmd_msgp(char *cmdbuf)
1750 safestrncpy(CC->preferred_formats, cmdbuf,
1751 sizeof(CC->preferred_formats));
1752 cprintf("%d ok\n", CIT_OK);
1757 * Open a component of a MIME message as a download file
1759 void cmd_opna(char *cmdbuf)
1762 char desired_section[128];
1764 msgid = extract_long(cmdbuf, 0);
1765 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1766 safestrncpy(CC->download_desired_section, desired_section, sizeof CC->download_desired_section);
1767 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1772 * Save a message pointer into a specified room
1773 * (Returns 0 for success, nonzero for failure)
1774 * roomname may be NULL to use the current room
1776 * Note that the 'supplied_msg' field may be set to NULL, in which case
1777 * the message will be fetched from disk, by number, if we need to perform
1778 * replication checks. This adds an additional database read, so if the
1779 * caller already has the message in memory then it should be supplied.
1781 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check,
1782 struct CtdlMessage *supplied_msg) {
1784 char hold_rm[ROOMNAMELEN];
1785 struct cdbdata *cdbfr;
1788 long highest_msg = 0L;
1789 struct CtdlMessage *msg = NULL;
1791 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom(roomname=%s, msgid=%ld, do_repl_check=%d)\n",
1792 roomname, msgid, do_repl_check);
1794 strcpy(hold_rm, CC->room.QRname);
1796 /* We may need to check to see if this message is real */
1797 if (do_repl_check) {
1798 if (supplied_msg != NULL) {
1802 msg = CtdlFetchMessage(msgid, 0);
1804 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1807 /* Perform replication checks if necessary */
1808 if ( (do_repl_check) && (msg != NULL) ) {
1810 if (getroom(&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 ReplicationChecks(msg);
1821 /* Now the regular stuff */
1822 if (lgetroom(&CC->room,
1823 ((roomname != NULL) ? roomname : CC->room.QRname) )
1825 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1826 if ( (msg != NULL) && (msg != supplied_msg) ) CtdlFreeMessage(msg);
1827 return(ERROR + ROOM_NOT_FOUND);
1830 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1831 if (cdbfr == NULL) {
1835 msglist = malloc(cdbfr->len);
1836 if (msglist == NULL)
1837 lprintf(CTDL_ALERT, "ERROR malloc msglist!\n");
1838 num_msgs = cdbfr->len / sizeof(long);
1839 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1844 /* Make sure the message doesn't already exist in this room. It
1845 * is absolutely taboo to have more than one reference to the same
1846 * message in a room.
1848 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1849 if (msglist[i] == msgid) {
1850 lputroom(&CC->room); /* unlock the room */
1851 getroom(&CC->room, hold_rm);
1852 if ( (msg != NULL) && (msg != supplied_msg) ) CtdlFreeMessage(msg);
1854 return(ERROR + ALREADY_EXISTS);
1858 /* Now add the new message */
1860 msglist = realloc(msglist, (num_msgs * sizeof(long)));
1862 if (msglist == NULL) {
1863 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1865 msglist[num_msgs - 1] = msgid;
1867 /* Sort the message list, so all the msgid's are in order */
1868 num_msgs = sort_msglist(msglist, num_msgs);
1870 /* Determine the highest message number */
1871 highest_msg = msglist[num_msgs - 1];
1873 /* Write it back to disk. */
1874 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1875 msglist, (int)(num_msgs * sizeof(long)));
1877 /* Free up the memory we used. */
1880 /* Update the highest-message pointer and unlock the room. */
1881 CC->room.QRhighest = highest_msg;
1882 lputroom(&CC->room);
1883 getroom(&CC->room, hold_rm);
1885 /* Bump the reference count for this message. */
1886 AdjRefCount(msgid, +1);
1888 /* If the message has an Exclusive ID, index that... */
1890 if (msg->cm_fields['E'] != NULL) {
1891 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
1895 /* Return success. */
1896 if ( (msg != NULL) && (msg != supplied_msg) ) CtdlFreeMessage(msg);
1903 * Message base operation to save a new message to the message store
1904 * (returns new message number)
1906 * This is the back end for CtdlSubmitMsg() and should not be directly
1907 * called by server-side modules.
1910 long send_message(struct CtdlMessage *msg) {
1918 /* Get a new message number */
1919 newmsgid = get_new_message_number();
1920 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1922 /* Generate an ID if we don't have one already */
1923 if (msg->cm_fields['I']==NULL) {
1924 msg->cm_fields['I'] = strdup(msgidbuf);
1927 /* If the message is big, set its body aside for storage elsewhere */
1928 if (msg->cm_fields['M'] != NULL) {
1929 if (strlen(msg->cm_fields['M']) > BIGMSG) {
1931 holdM = msg->cm_fields['M'];
1932 msg->cm_fields['M'] = NULL;
1936 /* Serialize our data structure for storage in the database */
1937 serialize_message(&smr, msg);
1940 msg->cm_fields['M'] = holdM;
1944 cprintf("%d Unable to serialize message\n",
1945 ERROR + INTERNAL_ERROR);
1949 /* Write our little bundle of joy into the message base */
1950 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
1951 smr.ser, smr.len) < 0) {
1952 lprintf(CTDL_ERR, "Can't store message\n");
1956 cdb_store(CDB_BIGMSGS,
1966 /* Free the memory we used for the serialized message */
1969 /* Return the *local* message ID to the caller
1970 * (even if we're storing an incoming network message)
1978 * Serialize a struct CtdlMessage into the format used on disk and network.
1980 * This function loads up a "struct ser_ret" (defined in server.h) which
1981 * contains the length of the serialized message and a pointer to the
1982 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1984 void serialize_message(struct ser_ret *ret, /* return values */
1985 struct CtdlMessage *msg) /* unserialized msg */
1989 static char *forder = FORDER;
1991 if (is_valid_message(msg) == 0) return; /* self check */
1994 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1995 ret->len = ret->len +
1996 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1998 lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1999 ret->ser = malloc(ret->len);
2000 if (ret->ser == NULL) {
2006 ret->ser[1] = msg->cm_anon_type;
2007 ret->ser[2] = msg->cm_format_type;
2010 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2011 ret->ser[wlen++] = (char)forder[i];
2012 strcpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
2013 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
2015 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2016 (long)ret->len, (long)wlen);
2024 * Check to see if any messages already exist in the current room which
2025 * carry the same Exclusive ID as this one. If any are found, delete them.
2027 void ReplicationChecks(struct CtdlMessage *msg) {
2028 long old_msgnum = (-1L);
2030 /* No exclusive id? Don't do anything. */
2031 if (msg == NULL) return;
2032 if (msg->cm_fields['E'] == NULL) return;
2033 if (strlen(msg->cm_fields['E']) == 0) return;
2034 lprintf(CTDL_DEBUG, "Exclusive ID: <%s>\n", msg->cm_fields['E']);
2036 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2037 if (old_msgnum > 0L) {
2038 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2039 CtdlDeleteMessages(CC->room.QRname, old_msgnum, "", 0);
2046 * Save a message to disk and submit it into the delivery system.
2048 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2049 struct recptypes *recps, /* recipients (if mail) */
2050 char *force /* force a particular room? */
2052 char submit_filename[128];
2053 char generated_timestamp[32];
2054 char hold_rm[ROOMNAMELEN];
2055 char actual_rm[ROOMNAMELEN];
2056 char force_room[ROOMNAMELEN];
2057 char content_type[SIZ]; /* We have to learn this */
2058 char recipient[SIZ];
2061 struct ctdluser userbuf;
2063 struct MetaData smi;
2064 FILE *network_fp = NULL;
2065 static int seqnum = 1;
2066 struct CtdlMessage *imsg = NULL;
2069 char *hold_R, *hold_D;
2070 char *collected_addresses = NULL;
2071 struct addresses_to_be_filed *aptr = NULL;
2073 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2074 if (is_valid_message(msg) == 0) return(-1); /* self check */
2076 /* If this message has no timestamp, we take the liberty of
2077 * giving it one, right now.
2079 if (msg->cm_fields['T'] == NULL) {
2080 lprintf(CTDL_DEBUG, "Generating timestamp\n");
2081 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2082 msg->cm_fields['T'] = strdup(generated_timestamp);
2085 /* If this message has no path, we generate one.
2087 if (msg->cm_fields['P'] == NULL) {
2088 lprintf(CTDL_DEBUG, "Generating path\n");
2089 if (msg->cm_fields['A'] != NULL) {
2090 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2091 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2092 if (isspace(msg->cm_fields['P'][a])) {
2093 msg->cm_fields['P'][a] = ' ';
2098 msg->cm_fields['P'] = strdup("unknown");
2102 if (force == NULL) {
2103 strcpy(force_room, "");
2106 strcpy(force_room, force);
2109 /* Learn about what's inside, because it's what's inside that counts */
2110 lprintf(CTDL_DEBUG, "Learning what's inside\n");
2111 if (msg->cm_fields['M'] == NULL) {
2112 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2116 switch (msg->cm_format_type) {
2118 strcpy(content_type, "text/x-citadel-variformat");
2121 strcpy(content_type, "text/plain");
2124 strcpy(content_type, "text/plain");
2125 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type: ");
2127 safestrncpy(content_type, &mptr[14],
2128 sizeof content_type);
2129 for (a = 0; a < strlen(content_type); ++a) {
2130 if ((content_type[a] == ';')
2131 || (content_type[a] == ' ')
2132 || (content_type[a] == 13)
2133 || (content_type[a] == 10)) {
2134 content_type[a] = 0;
2140 /* Goto the correct room */
2141 lprintf(CTDL_DEBUG, "Selected room %s\n",
2142 (recps) ? CC->room.QRname : SENTITEMS);
2143 strcpy(hold_rm, CC->room.QRname);
2144 strcpy(actual_rm, CC->room.QRname);
2145 if (recps != NULL) {
2146 strcpy(actual_rm, SENTITEMS);
2149 /* If the user is a twit, move to the twit room for posting */
2150 lprintf(CTDL_DEBUG, "Handling twit stuff: %s\n",
2151 (CC->user.axlevel == 2) ? config.c_twitroom : "OK");
2153 if (CC->user.axlevel == 2) {
2154 strcpy(hold_rm, actual_rm);
2155 strcpy(actual_rm, config.c_twitroom);
2159 /* ...or if this message is destined for Aide> then go there. */
2160 if (strlen(force_room) > 0) {
2161 strcpy(actual_rm, force_room);
2164 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2165 if (strcasecmp(actual_rm, CC->room.QRname)) {
2166 /* getroom(&CC->room, actual_rm); */
2167 usergoto(actual_rm, 0, 1, NULL, NULL);
2171 * If this message has no O (room) field, generate one.
2173 if (msg->cm_fields['O'] == NULL) {
2174 msg->cm_fields['O'] = strdup(CC->room.QRname);
2177 /* Perform "before save" hooks (aborting if any return nonzero) */
2178 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2179 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2181 /* If this message has an Exclusive ID, perform replication checks */
2182 lprintf(CTDL_DEBUG, "Performing replication checks\n");
2183 ReplicationChecks(msg);
2185 /* Save it to disk */
2186 lprintf(CTDL_DEBUG, "Saving to disk\n");
2187 newmsgid = send_message(msg);
2188 if (newmsgid <= 0L) return(-5);
2190 /* Write a supplemental message info record. This doesn't have to
2191 * be a critical section because nobody else knows about this message
2194 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2195 memset(&smi, 0, sizeof(struct MetaData));
2196 smi.meta_msgnum = newmsgid;
2197 smi.meta_refcount = 0;
2198 safestrncpy(smi.meta_content_type, content_type,
2199 sizeof smi.meta_content_type);
2201 /* As part of the new metadata record, measure how
2202 * big this message will be when displayed as RFC822.
2203 * Both POP and IMAP use this, and it's best to just take the hit now
2204 * instead of having to potentially measure thousands of messages when
2205 * a mailbox is opened later.
2208 if (CC->redirect_buffer != NULL) {
2209 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2212 CC->redirect_buffer = malloc(SIZ);
2213 CC->redirect_len = 0;
2214 CC->redirect_alloc = SIZ;
2215 CtdlOutputPreLoadedMsg(msg, 0L, MT_RFC822, HEADERS_ALL, 0, 1);
2216 smi.meta_rfc822_length = CC->redirect_len;
2217 free(CC->redirect_buffer);
2218 CC->redirect_buffer = NULL;
2219 CC->redirect_len = 0;
2220 CC->redirect_alloc = 0;
2224 /* Now figure out where to store the pointers */
2225 lprintf(CTDL_DEBUG, "Storing pointers\n");
2227 /* If this is being done by the networker delivering a private
2228 * message, we want to BYPASS saving the sender's copy (because there
2229 * is no local sender; it would otherwise go to the Trashcan).
2231 if ((!CC->internal_pgm) || (recps == NULL)) {
2232 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2233 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2234 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2238 /* For internet mail, drop a copy in the outbound queue room */
2240 if (recps->num_internet > 0) {
2241 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2244 /* If other rooms are specified, drop them there too. */
2246 if (recps->num_room > 0)
2247 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2248 extract_token(recipient, recps->recp_room, i,
2249 '|', sizeof recipient);
2250 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2251 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2254 /* Bump this user's messages posted counter. */
2255 lprintf(CTDL_DEBUG, "Updating user\n");
2256 lgetuser(&CC->user, CC->curr_user);
2257 CC->user.posted = CC->user.posted + 1;
2258 lputuser(&CC->user);
2260 /* If this is private, local mail, make a copy in the
2261 * recipient's mailbox and bump the reference count.
2264 if (recps->num_local > 0)
2265 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2266 extract_token(recipient, recps->recp_local, i,
2267 '|', sizeof recipient);
2268 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2270 if (getuser(&userbuf, recipient) == 0) {
2271 MailboxName(actual_rm, sizeof actual_rm,
2272 &userbuf, MAILROOM);
2273 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2274 BumpNewMailCounter(userbuf.usernum);
2277 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2278 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2282 /* Perform "after save" hooks */
2283 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2284 PerformMessageHooks(msg, EVT_AFTERSAVE);
2286 /* For IGnet mail, we have to save a new copy into the spooler for
2287 * each recipient, with the R and D fields set to the recipient and
2288 * destination-node. This has two ugly side effects: all other
2289 * recipients end up being unlisted in this recipient's copy of the
2290 * message, and it has to deliver multiple messages to the same
2291 * node. We'll revisit this again in a year or so when everyone has
2292 * a network spool receiver that can handle the new style messages.
2295 if (recps->num_ignet > 0)
2296 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2297 extract_token(recipient, recps->recp_ignet, i,
2298 '|', sizeof recipient);
2300 hold_R = msg->cm_fields['R'];
2301 hold_D = msg->cm_fields['D'];
2302 msg->cm_fields['R'] = malloc(SIZ);
2303 msg->cm_fields['D'] = malloc(128);
2304 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2305 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2307 serialize_message(&smr, msg);
2309 snprintf(submit_filename, sizeof submit_filename,
2310 #ifndef HAVE_SPOOL_DIR
2315 "/network/spoolin/netmail.%04lx.%04x.%04x",
2316 (long) getpid(), CC->cs_pid, ++seqnum);
2317 network_fp = fopen(submit_filename, "wb+");
2318 if (network_fp != NULL) {
2319 fwrite(smr.ser, smr.len, 1, network_fp);
2325 free(msg->cm_fields['R']);
2326 free(msg->cm_fields['D']);
2327 msg->cm_fields['R'] = hold_R;
2328 msg->cm_fields['D'] = hold_D;
2331 /* Go back to the room we started from */
2332 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2333 if (strcasecmp(hold_rm, CC->room.QRname))
2334 /* getroom(&CC->room, hold_rm); */
2335 usergoto(hold_rm, 0, 1, NULL, NULL);
2337 /* For internet mail, generate delivery instructions.
2338 * Yes, this is recursive. Deal with it. Infinite recursion does
2339 * not happen because the delivery instructions message does not
2340 * contain a recipient.
2343 if (recps->num_internet > 0) {
2344 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2345 instr = malloc(SIZ * 2);
2346 snprintf(instr, SIZ * 2,
2347 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2349 SPOOLMIME, newmsgid, (long)time(NULL),
2350 msg->cm_fields['A'], msg->cm_fields['N']
2353 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2354 size_t tmp = strlen(instr);
2355 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2356 snprintf(&instr[tmp], SIZ * 2 - tmp,
2357 "remote|%s|0||\n", recipient);
2360 imsg = malloc(sizeof(struct CtdlMessage));
2361 memset(imsg, 0, sizeof(struct CtdlMessage));
2362 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2363 imsg->cm_anon_type = MES_NORMAL;
2364 imsg->cm_format_type = FMT_RFC822;
2365 imsg->cm_fields['A'] = strdup("Citadel");
2366 imsg->cm_fields['M'] = instr;
2367 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2368 CtdlFreeMessage(imsg);
2372 * Any addresses to harvest for someone's address book?
2374 if ( (CC->logged_in) && (recps != NULL) ) {
2375 collected_addresses = harvest_collected_addresses(msg);
2378 if (collected_addresses != NULL) {
2379 begin_critical_section(S_ATBF);
2380 aptr = (struct addresses_to_be_filed *) malloc(sizeof(struct addresses_to_be_filed));
2382 MailboxName(actual_rm, sizeof actual_rm, &CC->user, USERCONTACTSROOM);
2383 aptr->roomname = strdup(actual_rm);
2384 aptr->collected_addresses = collected_addresses;
2386 end_critical_section(S_ATBF);
2399 * Convenience function for generating small administrative messages.
2401 void quickie_message(char *from, char *to, char *room, char *text,
2402 int format_type, char *subject)
2404 struct CtdlMessage *msg;
2405 struct recptypes *recp = NULL;
2407 msg = malloc(sizeof(struct CtdlMessage));
2408 memset(msg, 0, sizeof(struct CtdlMessage));
2409 msg->cm_magic = CTDLMESSAGE_MAGIC;
2410 msg->cm_anon_type = MES_NORMAL;
2411 msg->cm_format_type = format_type;
2412 msg->cm_fields['A'] = strdup(from);
2413 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2414 msg->cm_fields['N'] = strdup(NODENAME);
2416 msg->cm_fields['R'] = strdup(to);
2417 recp = validate_recipients(to);
2419 if (subject != NULL) {
2420 msg->cm_fields['U'] = strdup(subject);
2422 msg->cm_fields['M'] = strdup(text);
2424 CtdlSubmitMsg(msg, recp, room);
2425 CtdlFreeMessage(msg);
2426 if (recp != NULL) free(recp);
2432 * Back end function used by CtdlMakeMessage() and similar functions
2434 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2435 size_t maxlen, /* maximum message length */
2436 char *exist, /* if non-null, append to it;
2437 exist is ALWAYS freed */
2438 int crlf /* CRLF newlines instead of LF */
2442 size_t message_len = 0;
2443 size_t buffer_len = 0;
2449 if (exist == NULL) {
2456 message_len = strlen(exist);
2457 buffer_len = message_len + 4096;
2458 m = realloc(exist, buffer_len);
2465 /* flush the input if we have nowhere to store it */
2470 /* read in the lines of message text one by one */
2472 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2473 if (!strcmp(buf, terminator)) finished = 1;
2475 strcat(buf, "\r\n");
2481 if ( (!flushing) && (!finished) ) {
2482 /* Measure the line */
2483 linelen = strlen(buf);
2485 /* augment the buffer if we have to */
2486 if ((message_len + linelen) >= buffer_len) {
2487 ptr = realloc(m, (buffer_len * 2) );
2488 if (ptr == NULL) { /* flush if can't allocate */
2491 buffer_len = (buffer_len * 2);
2493 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2497 /* Add the new line to the buffer. NOTE: this loop must avoid
2498 * using functions like strcat() and strlen() because they
2499 * traverse the entire buffer upon every call, and doing that
2500 * for a multi-megabyte message slows it down beyond usability.
2502 strcpy(&m[message_len], buf);
2503 message_len += linelen;
2506 /* if we've hit the max msg length, flush the rest */
2507 if (message_len >= maxlen) flushing = 1;
2509 } while (!finished);
2517 * Build a binary message to be saved on disk.
2518 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2519 * will become part of the message. This means you are no longer
2520 * responsible for managing that memory -- it will be freed along with
2521 * the rest of the fields when CtdlFreeMessage() is called.)
2524 struct CtdlMessage *CtdlMakeMessage(
2525 struct ctdluser *author, /* author's user structure */
2526 char *recipient, /* NULL if it's not mail */
2527 char *recp_cc, /* NULL if it's not mail */
2528 char *room, /* room where it's going */
2529 int type, /* see MES_ types in header file */
2530 int format_type, /* variformat, plain text, MIME... */
2531 char *fake_name, /* who we're masquerading as */
2532 char *subject, /* Subject (optional) */
2533 char *preformatted_text /* ...or NULL to read text from client */
2535 char dest_node[SIZ];
2537 struct CtdlMessage *msg;
2539 msg = malloc(sizeof(struct CtdlMessage));
2540 memset(msg, 0, sizeof(struct CtdlMessage));
2541 msg->cm_magic = CTDLMESSAGE_MAGIC;
2542 msg->cm_anon_type = type;
2543 msg->cm_format_type = format_type;
2545 /* Don't confuse the poor folks if it's not routed mail. */
2546 strcpy(dest_node, "");
2551 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2552 msg->cm_fields['P'] = strdup(buf);
2554 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2555 msg->cm_fields['T'] = strdup(buf);
2557 if (fake_name[0]) /* author */
2558 msg->cm_fields['A'] = strdup(fake_name);
2560 msg->cm_fields['A'] = strdup(author->fullname);
2562 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2563 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2566 msg->cm_fields['O'] = strdup(CC->room.QRname);
2569 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2570 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2572 if (recipient[0] != 0) {
2573 msg->cm_fields['R'] = strdup(recipient);
2575 if (recp_cc[0] != 0) {
2576 msg->cm_fields['Y'] = strdup(recp_cc);
2578 if (dest_node[0] != 0) {
2579 msg->cm_fields['D'] = strdup(dest_node);
2582 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2583 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2586 if (subject != NULL) {
2588 if (strlen(subject) > 0) {
2589 msg->cm_fields['U'] = strdup(subject);
2593 if (preformatted_text != NULL) {
2594 msg->cm_fields['M'] = preformatted_text;
2597 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2598 config.c_maxmsglen, NULL, 0);
2606 * Check to see whether we have permission to post a message in the current
2607 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2608 * returns 0 on success.
2610 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2612 if (!(CC->logged_in)) {
2613 snprintf(errmsgbuf, n, "Not logged in.");
2614 return (ERROR + NOT_LOGGED_IN);
2617 if ((CC->user.axlevel < 2)
2618 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2619 snprintf(errmsgbuf, n, "Need to be validated to enter "
2620 "(except in %s> to sysop)", MAILROOM);
2621 return (ERROR + HIGHER_ACCESS_REQUIRED);
2624 if ((CC->user.axlevel < 4)
2625 && (CC->room.QRflags & QR_NETWORK)) {
2626 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2627 return (ERROR + HIGHER_ACCESS_REQUIRED);
2630 if ((CC->user.axlevel < 6)
2631 && (CC->room.QRflags & QR_READONLY)) {
2632 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2633 return (ERROR + HIGHER_ACCESS_REQUIRED);
2636 strcpy(errmsgbuf, "Ok");
2642 * Check to see if the specified user has Internet mail permission
2643 * (returns nonzero if permission is granted)
2645 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2647 /* Do not allow twits to send Internet mail */
2648 if (who->axlevel <= 2) return(0);
2650 /* Globally enabled? */
2651 if (config.c_restrict == 0) return(1);
2653 /* User flagged ok? */
2654 if (who->flags & US_INTERNET) return(2);
2656 /* Aide level access? */
2657 if (who->axlevel >= 6) return(3);
2659 /* No mail for you! */
2665 * Validate recipients, count delivery types and errors, and handle aliasing
2666 * FIXME check for dupes!!!!!
2667 * Returns 0 if all addresses are ok, -1 if no addresses were specified,
2668 * or the number of addresses found invalid.
2670 struct recptypes *validate_recipients(char *recipients) {
2671 struct recptypes *ret;
2672 char this_recp[SIZ];
2673 char this_recp_cooked[SIZ];
2679 struct ctdluser tempUS;
2680 struct ctdlroom tempQR;
2683 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2684 if (ret == NULL) return(NULL);
2685 memset(ret, 0, sizeof(struct recptypes));
2688 ret->num_internet = 0;
2693 if (recipients == NULL) {
2696 else if (strlen(recipients) == 0) {
2700 /* Change all valid separator characters to commas */
2701 for (i=0; i<strlen(recipients); ++i) {
2702 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2703 recipients[i] = ',';
2708 num_recps = num_tokens(recipients, ',');
2711 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2712 extract_token(this_recp, recipients, i, ',', sizeof this_recp);
2714 lprintf(CTDL_DEBUG, "Evaluating recipient #%d <%s>\n", i, this_recp);
2715 mailtype = alias(this_recp);
2716 mailtype = alias(this_recp);
2717 mailtype = alias(this_recp);
2718 for (j=0; j<=strlen(this_recp); ++j) {
2719 if (this_recp[j]=='_') {
2720 this_recp_cooked[j] = ' ';
2723 this_recp_cooked[j] = this_recp[j];
2729 if (!strcasecmp(this_recp, "sysop")) {
2731 strcpy(this_recp, config.c_aideroom);
2732 if (strlen(ret->recp_room) > 0) {
2733 strcat(ret->recp_room, "|");
2735 strcat(ret->recp_room, this_recp);
2737 else if (getuser(&tempUS, this_recp) == 0) {
2739 strcpy(this_recp, tempUS.fullname);
2740 if (strlen(ret->recp_local) > 0) {
2741 strcat(ret->recp_local, "|");
2743 strcat(ret->recp_local, this_recp);
2745 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2747 strcpy(this_recp, tempUS.fullname);
2748 if (strlen(ret->recp_local) > 0) {
2749 strcat(ret->recp_local, "|");
2751 strcat(ret->recp_local, this_recp);
2753 else if ( (!strncasecmp(this_recp, "room_", 5))
2754 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2756 if (strlen(ret->recp_room) > 0) {
2757 strcat(ret->recp_room, "|");
2759 strcat(ret->recp_room, &this_recp_cooked[5]);
2767 /* Yes, you're reading this correctly: if the target
2768 * domain points back to the local system or an attached
2769 * Citadel directory, the address is invalid. That's
2770 * because if the address were valid, we would have
2771 * already translated it to a local address by now.
2773 if (IsDirectory(this_recp)) {
2778 ++ret->num_internet;
2779 if (strlen(ret->recp_internet) > 0) {
2780 strcat(ret->recp_internet, "|");
2782 strcat(ret->recp_internet, this_recp);
2787 if (strlen(ret->recp_ignet) > 0) {
2788 strcat(ret->recp_ignet, "|");
2790 strcat(ret->recp_ignet, this_recp);
2798 if (strlen(ret->errormsg) == 0) {
2799 snprintf(append, sizeof append,
2800 "Invalid recipient: %s",
2804 snprintf(append, sizeof append,
2807 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2808 strcat(ret->errormsg, append);
2812 if (strlen(ret->display_recp) == 0) {
2813 strcpy(append, this_recp);
2816 snprintf(append, sizeof append, ", %s",
2819 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2820 strcat(ret->display_recp, append);
2825 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2826 ret->num_room + ret->num_error) == 0) {
2827 ret->num_error = (-1);
2828 strcpy(ret->errormsg, "No recipients specified.");
2831 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2832 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2833 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2834 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2835 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2836 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2844 * message entry - mode 0 (normal)
2846 void cmd_ent0(char *entargs)
2852 char masquerade_as[SIZ];
2854 int format_type = 0;
2855 char newusername[SIZ];
2856 struct CtdlMessage *msg;
2860 struct recptypes *valid = NULL;
2861 struct recptypes *valid_to = NULL;
2862 struct recptypes *valid_cc = NULL;
2863 struct recptypes *valid_bcc = NULL;
2870 post = extract_int(entargs, 0);
2871 extract_token(recp, entargs, 1, '|', sizeof recp);
2872 anon_flag = extract_int(entargs, 2);
2873 format_type = extract_int(entargs, 3);
2874 extract_token(subject, entargs, 4, '|', sizeof subject);
2875 do_confirm = extract_int(entargs, 6);
2876 extract_token(cc, entargs, 7, '|', sizeof cc);
2877 extract_token(bcc, entargs, 8, '|', sizeof bcc);
2879 /* first check to make sure the request is valid. */
2881 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2883 cprintf("%d %s\n", err, errmsg);
2887 /* Check some other permission type things. */
2890 if (CC->user.axlevel < 6) {
2891 cprintf("%d You don't have permission to masquerade.\n",
2892 ERROR + HIGHER_ACCESS_REQUIRED);
2895 extract_token(newusername, entargs, 5, '|', sizeof newusername);
2896 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2897 safestrncpy(CC->fake_postname, newusername,
2898 sizeof(CC->fake_postname) );
2899 cprintf("%d ok\n", CIT_OK);
2902 CC->cs_flags |= CS_POSTING;
2904 /* In the Mail> room we have to behave a little differently --
2905 * make sure the user has specified at least one recipient. Then
2906 * validate the recipient(s).
2908 if ( (CC->room.QRflags & QR_MAILBOX)
2909 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2911 if (CC->user.axlevel < 2) {
2912 strcpy(recp, "sysop");
2917 valid_to = validate_recipients(recp);
2918 if (valid_to->num_error > 0) {
2919 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
2924 valid_cc = validate_recipients(cc);
2925 if (valid_cc->num_error > 0) {
2926 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
2932 valid_bcc = validate_recipients(bcc);
2933 if (valid_bcc->num_error > 0) {
2934 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
2941 /* Recipient required, but none were specified */
2942 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
2946 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
2950 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
2951 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2952 cprintf("%d You do not have permission "
2953 "to send Internet mail.\n",
2954 ERROR + HIGHER_ACCESS_REQUIRED);
2962 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)
2963 && (CC->user.axlevel < 4) ) {
2964 cprintf("%d Higher access required for network mail.\n",
2965 ERROR + HIGHER_ACCESS_REQUIRED);
2972 if ((RESTRICT_INTERNET == 1)
2973 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
2974 && ((CC->user.flags & US_INTERNET) == 0)
2975 && (!CC->internal_pgm)) {
2976 cprintf("%d You don't have access to Internet mail.\n",
2977 ERROR + HIGHER_ACCESS_REQUIRED);
2986 /* Is this a room which has anonymous-only or anonymous-option? */
2987 anonymous = MES_NORMAL;
2988 if (CC->room.QRflags & QR_ANONONLY) {
2989 anonymous = MES_ANONONLY;
2991 if (CC->room.QRflags & QR_ANONOPT) {
2992 if (anon_flag == 1) { /* only if the user requested it */
2993 anonymous = MES_ANONOPT;
2997 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3001 /* If we're only checking the validity of the request, return
3002 * success without creating the message.
3005 cprintf("%d %s\n", CIT_OK,
3006 ((valid_to != NULL) ? valid_to->display_recp : "") );
3013 /* We don't need these anymore because we'll do it differently below */
3018 /* Handle author masquerading */
3019 if (CC->fake_postname[0]) {
3020 strcpy(masquerade_as, CC->fake_postname);
3022 else if (CC->fake_username[0]) {
3023 strcpy(masquerade_as, CC->fake_username);
3026 strcpy(masquerade_as, "");
3029 /* Read in the message from the client. */
3031 cprintf("%d send message\n", START_CHAT_MODE);
3033 cprintf("%d send message\n", SEND_LISTING);
3036 msg = CtdlMakeMessage(&CC->user, recp, cc,
3037 CC->room.QRname, anonymous, format_type,
3038 masquerade_as, subject, NULL);
3040 /* Put together one big recipients struct containing to/cc/bcc all in
3041 * one. This is for the envelope.
3043 char *all_recps = malloc(SIZ * 3);
3044 strcpy(all_recps, recp);
3045 if (strlen(cc) > 0) {
3046 if (strlen(all_recps) > 0) {
3047 strcat(all_recps, ",");
3049 strcat(all_recps, cc);
3051 if (strlen(bcc) > 0) {
3052 if (strlen(all_recps) > 0) {
3053 strcat(all_recps, ",");
3055 strcat(all_recps, bcc);
3057 if (strlen(all_recps) > 0) {
3058 valid = validate_recipients(all_recps);
3066 msgnum = CtdlSubmitMsg(msg, valid, "");
3069 cprintf("%ld\n", msgnum);
3071 cprintf("Message accepted.\n");
3074 cprintf("Internal error.\n");
3076 if (msg->cm_fields['E'] != NULL) {
3077 cprintf("%s\n", msg->cm_fields['E']);
3084 CtdlFreeMessage(msg);
3086 CC->fake_postname[0] = '\0';
3087 if (valid != NULL) {
3096 * API function to delete messages which match a set of criteria
3097 * (returns the actual number of messages deleted)
3099 int CtdlDeleteMessages(char *room_name, /* which room */
3100 long dmsgnum, /* or "0" for any */
3101 char *content_type, /* or "" for any */
3102 int deferred /* let TDAP sweep it later */
3106 struct ctdlroom qrbuf;
3107 struct cdbdata *cdbfr;
3108 long *msglist = NULL;
3109 long *dellist = NULL;
3112 int num_deleted = 0;
3114 struct MetaData smi;
3116 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s, %d)\n",
3117 room_name, dmsgnum, content_type, deferred);
3119 /* get room record, obtaining a lock... */
3120 if (lgetroom(&qrbuf, room_name) != 0) {
3121 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3123 return (0); /* room not found */
3125 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3127 if (cdbfr != NULL) {
3128 msglist = malloc(cdbfr->len);
3129 dellist = malloc(cdbfr->len);
3130 memcpy(msglist, cdbfr->ptr, cdbfr->len);
3131 num_msgs = cdbfr->len / sizeof(long);
3135 for (i = 0; i < num_msgs; ++i) {
3138 /* Set/clear a bit for each criterion */
3140 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
3141 delete_this |= 0x01;
3143 if (strlen(content_type) == 0) {
3144 delete_this |= 0x02;
3146 GetMetaData(&smi, msglist[i]);
3147 if (!strcasecmp(smi.meta_content_type,
3149 delete_this |= 0x02;
3153 /* Delete message only if all bits are set */
3154 if (delete_this == 0x03) {
3155 dellist[num_deleted++] = msglist[i];
3160 num_msgs = sort_msglist(msglist, num_msgs);
3161 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3162 msglist, (int)(num_msgs * sizeof(long)));
3164 qrbuf.QRhighest = msglist[num_msgs - 1];
3169 * If the delete operation is "deferred" (and technically, any delete
3170 * operation not performed by THE DREADED AUTO-PURGER ought to be
3171 * a deferred delete) then we save a pointer to the message in the
3172 * DELETED_MSGS_ROOM. This will cause the reference count to remain
3173 * at least 1, which will save the user from having to synchronously
3174 * wait for various disk-intensive operations to complete.
3176 if ( (deferred) && (num_deleted) ) {
3177 for (i=0; i<num_deleted; ++i) {
3178 CtdlCopyMsgToRoom(dellist[i], DELETED_MSGS_ROOM);
3182 /* Go through the messages we pulled out of the index, and decrement
3183 * their reference counts by 1. If this is the only room the message
3184 * was in, the reference count will reach zero and the message will
3185 * automatically be deleted from the database. We do this in a
3186 * separate pass because there might be plug-in hooks getting called,
3187 * and we don't want that happening during an S_ROOMS critical
3190 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3191 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3192 AdjRefCount(dellist[i], -1);
3195 /* Now free the memory we used, and go away. */
3196 if (msglist != NULL) free(msglist);
3197 if (dellist != NULL) free(dellist);
3198 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3199 return (num_deleted);
3205 * Check whether the current user has permission to delete messages from
3206 * the current room (returns 1 for yes, 0 for no)
3208 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3209 getuser(&CC->user, CC->curr_user);
3210 if ((CC->user.axlevel < 6)
3211 && (CC->user.usernum != CC->room.QRroomaide)
3212 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3213 && (!(CC->internal_pgm))) {
3222 * Delete message from current room
3224 void cmd_dele(char *delstr)
3229 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3230 cprintf("%d Higher access required.\n",
3231 ERROR + HIGHER_ACCESS_REQUIRED);
3234 delnum = extract_long(delstr, 0);
3236 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "", 1);
3239 cprintf("%d %d message%s deleted.\n", CIT_OK,
3240 num_deleted, ((num_deleted != 1) ? "s" : ""));
3242 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3248 * Back end API function for moves and deletes
3250 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3253 err = CtdlSaveMsgPointerInRoom(dest, msgnum, 1, NULL);
3254 if (err != 0) return(err);
3262 * move or copy a message to another room
3264 void cmd_move(char *args)
3267 char targ[ROOMNAMELEN];
3268 struct ctdlroom qtemp;
3274 num = extract_long(args, 0);
3275 extract_token(targ, args, 1, '|', sizeof targ);
3276 targ[ROOMNAMELEN - 1] = 0;
3277 is_copy = extract_int(args, 2);
3279 if (getroom(&qtemp, targ) != 0) {
3280 cprintf("%d '%s' does not exist.\n",
3281 ERROR + ROOM_NOT_FOUND, targ);
3285 getuser(&CC->user, CC->curr_user);
3286 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3288 /* Check for permission to perform this operation.
3289 * Remember: "CC->room" is source, "qtemp" is target.
3293 /* Aides can move/copy */
3294 if (CC->user.axlevel >= 6) permit = 1;
3296 /* Room aides can move/copy */
3297 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3299 /* Permit move/copy from personal rooms */
3300 if ((CC->room.QRflags & QR_MAILBOX)
3301 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3303 /* Permit only copy from public to personal room */
3305 && (!(CC->room.QRflags & QR_MAILBOX))
3306 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3308 /* User must have access to target room */
3309 if (!(ra & UA_KNOWN)) permit = 0;
3312 cprintf("%d Higher access required.\n",
3313 ERROR + HIGHER_ACCESS_REQUIRED);
3317 err = CtdlCopyMsgToRoom(num, targ);
3319 cprintf("%d Cannot store message in %s: error %d\n",
3324 /* Now delete the message from the source room,
3325 * if this is a 'move' rather than a 'copy' operation.
3328 CtdlDeleteMessages(CC->room.QRname, num, "", 0);
3331 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3337 * GetMetaData() - Get the supplementary record for a message
3339 void GetMetaData(struct MetaData *smibuf, long msgnum)
3342 struct cdbdata *cdbsmi;
3345 memset(smibuf, 0, sizeof(struct MetaData));
3346 smibuf->meta_msgnum = msgnum;
3347 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3349 /* Use the negative of the message number for its supp record index */
3350 TheIndex = (0L - msgnum);
3352 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3353 if (cdbsmi == NULL) {
3354 return; /* record not found; go with defaults */
3356 memcpy(smibuf, cdbsmi->ptr,
3357 ((cdbsmi->len > sizeof(struct MetaData)) ?
3358 sizeof(struct MetaData) : cdbsmi->len));
3365 * PutMetaData() - (re)write supplementary record for a message
3367 void PutMetaData(struct MetaData *smibuf)
3371 /* Use the negative of the message number for the metadata db index */
3372 TheIndex = (0L - smibuf->meta_msgnum);
3374 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3375 smibuf->meta_msgnum, smibuf->meta_refcount);
3377 cdb_store(CDB_MSGMAIN,
3378 &TheIndex, (int)sizeof(long),
3379 smibuf, (int)sizeof(struct MetaData));
3384 * AdjRefCount - change the reference count for a message;
3385 * delete the message if it reaches zero
3387 void AdjRefCount(long msgnum, int incr)
3390 struct MetaData smi;
3393 /* This is a *tight* critical section; please keep it that way, as
3394 * it may get called while nested in other critical sections.
3395 * Complicating this any further will surely cause deadlock!
3397 begin_critical_section(S_SUPPMSGMAIN);
3398 GetMetaData(&smi, msgnum);
3399 smi.meta_refcount += incr;
3401 end_critical_section(S_SUPPMSGMAIN);
3402 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3403 msgnum, incr, smi.meta_refcount);
3405 /* If the reference count is now zero, delete the message
3406 * (and its supplementary record as well).
3408 if (smi.meta_refcount == 0) {
3409 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3411 /* Remove from fulltext index */
3412 if (config.c_enable_fulltext) {
3413 ft_index_message(msgnum, 0);
3416 /* Remove from message base */
3418 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3419 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3421 /* Remove metadata record */
3422 delnum = (0L - msgnum);
3423 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3428 * Write a generic object to this room
3430 * Note: this could be much more efficient. Right now we use two temporary
3431 * files, and still pull the message into memory as with all others.
3433 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3434 char *content_type, /* MIME type of this object */
3435 char *tempfilename, /* Where to fetch it from */
3436 struct ctdluser *is_mailbox, /* Mailbox room? */
3437 int is_binary, /* Is encoding necessary? */
3438 int is_unique, /* Del others of this type? */
3439 unsigned int flags /* Internal save flags */
3444 struct ctdlroom qrbuf;
3445 char roomname[ROOMNAMELEN];
3446 struct CtdlMessage *msg;
3448 char *raw_message = NULL;
3449 char *encoded_message = NULL;
3450 off_t raw_length = 0;
3452 if (is_mailbox != NULL)
3453 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3455 safestrncpy(roomname, req_room, sizeof(roomname));
3456 lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3459 fp = fopen(tempfilename, "rb");
3461 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3462 tempfilename, strerror(errno));
3465 fseek(fp, 0L, SEEK_END);
3466 raw_length = ftell(fp);
3468 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3470 raw_message = malloc((size_t)raw_length + 2);
3471 fread(raw_message, (size_t)raw_length, 1, fp);
3475 encoded_message = malloc((size_t)
3476 (((raw_length * 134) / 100) + 4096 ) );
3479 encoded_message = malloc((size_t)(raw_length + 4096));
3482 sprintf(encoded_message, "Content-type: %s\n", content_type);
3485 sprintf(&encoded_message[strlen(encoded_message)],
3486 "Content-transfer-encoding: base64\n\n"
3490 sprintf(&encoded_message[strlen(encoded_message)],
3491 "Content-transfer-encoding: 7bit\n\n"
3497 &encoded_message[strlen(encoded_message)],
3503 raw_message[raw_length] = 0;
3505 &encoded_message[strlen(encoded_message)],
3513 lprintf(CTDL_DEBUG, "Allocating\n");
3514 msg = malloc(sizeof(struct CtdlMessage));
3515 memset(msg, 0, sizeof(struct CtdlMessage));
3516 msg->cm_magic = CTDLMESSAGE_MAGIC;
3517 msg->cm_anon_type = MES_NORMAL;
3518 msg->cm_format_type = 4;
3519 msg->cm_fields['A'] = strdup(CC->user.fullname);
3520 msg->cm_fields['O'] = strdup(req_room);
3521 msg->cm_fields['N'] = strdup(config.c_nodename);
3522 msg->cm_fields['H'] = strdup(config.c_humannode);
3523 msg->cm_flags = flags;
3525 msg->cm_fields['M'] = encoded_message;
3527 /* Create the requested room if we have to. */
3528 if (getroom(&qrbuf, roomname) != 0) {
3529 create_room(roomname,
3530 ( (is_mailbox != NULL) ? 5 : 3 ),
3531 "", 0, 1, 0, VIEW_BBS);
3533 /* If the caller specified this object as unique, delete all
3534 * other objects of this type that are currently in the room.
3537 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3538 CtdlDeleteMessages(roomname, 0L, content_type, 0)
3541 /* Now write the data */
3542 CtdlSubmitMsg(msg, NULL, roomname);
3543 CtdlFreeMessage(msg);
3551 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3552 config_msgnum = msgnum;
3556 char *CtdlGetSysConfig(char *sysconfname) {
3557 char hold_rm[ROOMNAMELEN];
3560 struct CtdlMessage *msg;
3563 strcpy(hold_rm, CC->room.QRname);
3564 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3565 getroom(&CC->room, hold_rm);
3570 /* We want the last (and probably only) config in this room */
3571 begin_critical_section(S_CONFIG);
3572 config_msgnum = (-1L);
3573 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3574 CtdlGetSysConfigBackend, NULL);
3575 msgnum = config_msgnum;
3576 end_critical_section(S_CONFIG);
3582 msg = CtdlFetchMessage(msgnum, 1);
3584 conf = strdup(msg->cm_fields['M']);
3585 CtdlFreeMessage(msg);
3592 getroom(&CC->room, hold_rm);
3594 if (conf != NULL) do {
3595 extract_token(buf, conf, 0, '\n', sizeof buf);
3596 strcpy(conf, &conf[strlen(buf)+1]);
3597 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3602 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3603 char temp[PATH_MAX];
3606 strcpy(temp, tmpnam(NULL));
3608 fp = fopen(temp, "w");
3609 if (fp == NULL) return;
3610 fprintf(fp, "%s", sysconfdata);
3613 /* this handy API function does all the work for us */
3614 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3620 * Determine whether a given Internet address belongs to the current user
3622 int CtdlIsMe(char *addr, int addr_buf_len)
3624 struct recptypes *recp;
3627 recp = validate_recipients(addr);
3628 if (recp == NULL) return(0);
3630 if (recp->num_local == 0) {
3635 for (i=0; i<recp->num_local; ++i) {
3636 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3637 if (!strcasecmp(addr, CC->user.fullname)) {
3649 * Citadel protocol command to do the same
3651 void cmd_isme(char *argbuf) {
3654 if (CtdlAccessCheck(ac_logged_in)) return;
3655 extract_token(addr, argbuf, 0, '|', sizeof addr);
3657 if (CtdlIsMe(addr, sizeof addr)) {
3658 cprintf("%d %s\n", CIT_OK, addr);
3661 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);