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);
146 stripallbut(name, '<', '>');
154 "mail.aliases", "r");
156 fp = fopen("/dev/null", "r");
163 while (fgets(aaa, sizeof aaa, fp) != NULL) {
164 while (isspace(name[0]))
165 strcpy(name, &name[1]);
166 aaa[strlen(aaa) - 1] = 0;
168 for (a = 0; a < strlen(aaa); ++a) {
170 strcpy(bbb, &aaa[a + 1]);
174 if (!strcasecmp(name, aaa))
179 /* Hit the Global Address Book */
180 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
184 lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name);
186 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
187 for (a=0; a<strlen(name); ++a) {
188 if (name[a] == '@') {
189 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
191 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
196 /* determine local or remote type, see citadel.h */
197 at = haschar(name, '@');
198 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
199 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
200 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
202 /* figure out the delivery mode */
203 extract_token(node, name, 1, '@', sizeof node);
205 /* If there are one or more dots in the nodename, we assume that it
206 * is an FQDN and will attempt SMTP delivery to the Internet.
208 if (haschar(node, '.') > 0) {
209 return(MES_INTERNET);
212 /* Otherwise we look in the IGnet maps for a valid Citadel node.
213 * Try directly-connected nodes first...
215 ignetcfg = CtdlGetSysConfig(IGNETCFG);
216 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
217 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
218 extract_token(testnode, buf, 0, '|', sizeof testnode);
219 if (!strcasecmp(node, testnode)) {
227 * Then try nodes that are two or more hops away.
229 ignetmap = CtdlGetSysConfig(IGNETMAP);
230 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
231 extract_token(buf, ignetmap, i, '\n', sizeof buf);
232 extract_token(testnode, buf, 0, '|', sizeof testnode);
233 if (!strcasecmp(node, testnode)) {
240 /* If we get to this point it's an invalid node name */
255 "/citadel.control", "r");
257 lprintf(CTDL_CRIT, "Cannot open citadel.control: %s\n",
261 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
267 * Back end for the MSGS command: output message number only.
269 void simple_listing(long msgnum, void *userdata)
271 cprintf("%ld\n", msgnum);
277 * Back end for the MSGS command: output header summary.
279 void headers_listing(long msgnum, void *userdata)
281 struct CtdlMessage *msg;
283 msg = CtdlFetchMessage(msgnum, 0);
285 cprintf("%ld|0|||||\n", msgnum);
289 cprintf("%ld|%s|%s|%s|%s|%s|\n",
291 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
292 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
293 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
294 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
295 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
297 CtdlFreeMessage(msg);
302 /* Determine if a given message matches the fields in a message template.
303 * Return 0 for a successful match.
305 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
308 /* If there aren't any fields in the template, all messages will
311 if (template == NULL) return(0);
313 /* Null messages are bogus. */
314 if (msg == NULL) return(1);
316 for (i='A'; i<='Z'; ++i) {
317 if (template->cm_fields[i] != NULL) {
318 if (msg->cm_fields[i] == NULL) {
321 if (strcasecmp(msg->cm_fields[i],
322 template->cm_fields[i])) return 1;
326 /* All compares succeeded: we have a match! */
333 * Retrieve the "seen" message list for the current room.
335 void CtdlGetSeen(char *buf, int which_set) {
338 /* Learn about the user and room in question */
339 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
341 if (which_set == ctdlsetseen_seen)
342 safestrncpy(buf, vbuf.v_seen, SIZ);
343 if (which_set == ctdlsetseen_answered)
344 safestrncpy(buf, vbuf.v_answered, SIZ);
350 * Manipulate the "seen msgs" string (or other message set strings)
352 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
353 int target_setting, int which_set,
354 struct ctdluser *which_user, struct ctdlroom *which_room) {
355 struct cdbdata *cdbfr;
367 char *is_set; /* actually an array of booleans */
370 char setstr[SIZ], lostr[SIZ], histr[SIZ];
373 lprintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %d, %d)\n",
374 num_target_msgnums, target_msgnums[0],
375 target_setting, which_set);
377 /* Learn about the user and room in question */
378 CtdlGetRelationship(&vbuf,
379 ((which_user != NULL) ? which_user : &CC->user),
380 ((which_room != NULL) ? which_room : &CC->room)
383 /* Load the message list */
384 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
386 msglist = (long *) cdbfr->ptr;
387 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
388 num_msgs = cdbfr->len / sizeof(long);
391 return; /* No messages at all? No further action. */
394 is_set = malloc(num_msgs * sizeof(char));
395 memset(is_set, 0, (num_msgs * sizeof(char)) );
397 /* Decide which message set we're manipulating */
399 case ctdlsetseen_seen:
400 safestrncpy(vset, vbuf.v_seen, sizeof vset);
402 case ctdlsetseen_answered:
403 safestrncpy(vset, vbuf.v_answered, sizeof vset);
407 /* lprintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
409 /* Translate the existing sequence set into an array of booleans */
410 num_sets = num_tokens(vset, ',');
411 for (s=0; s<num_sets; ++s) {
412 extract_token(setstr, vset, s, ',', sizeof setstr);
414 extract_token(lostr, setstr, 0, ':', sizeof lostr);
415 if (num_tokens(setstr, ':') >= 2) {
416 extract_token(histr, setstr, 1, ':', sizeof histr);
417 if (!strcmp(histr, "*")) {
418 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
422 strcpy(histr, lostr);
427 for (i = 0; i < num_msgs; ++i) {
428 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
434 /* Now translate the array of booleans back into a sequence set */
439 for (i=0; i<num_msgs; ++i) {
441 is_seen = is_set[i]; /* Default to existing setting */
443 for (k=0; k<num_target_msgnums; ++k) {
444 if (msglist[i] == target_msgnums[k]) {
445 is_seen = target_setting;
450 if (lo < 0L) lo = msglist[i];
454 if ( ((is_seen == 0) && (was_seen == 1))
455 || ((is_seen == 1) && (i == num_msgs-1)) ) {
457 /* begin trim-o-matic code */
460 while ( (strlen(vset) + 20) > sizeof vset) {
461 remove_token(vset, 0, ',');
463 if (j--) break; /* loop no more than 9 times */
465 if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
469 snprintf(lostr, sizeof lostr,
470 "1:%ld,%s", t, vset);
471 safestrncpy(vset, lostr, sizeof vset);
473 /* end trim-o-matic code */
481 snprintf(&vset[tmp], (sizeof vset) - tmp,
485 snprintf(&vset[tmp], (sizeof vset) - tmp,
494 /* Decide which message set we're manipulating */
496 case ctdlsetseen_seen:
497 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
499 case ctdlsetseen_answered:
500 safestrncpy(vbuf.v_answered, vset,
501 sizeof vbuf.v_answered);
506 /* lprintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
508 CtdlSetRelationship(&vbuf,
509 ((which_user != NULL) ? which_user : &CC->user),
510 ((which_room != NULL) ? which_room : &CC->room)
516 * API function to perform an operation for each qualifying message in the
517 * current room. (Returns the number of messages processed.)
519 int CtdlForEachMessage(int mode, long ref,
521 struct CtdlMessage *compare,
522 void (*CallBack) (long, void *),
528 struct cdbdata *cdbfr;
529 long *msglist = NULL;
531 int num_processed = 0;
534 struct CtdlMessage *msg;
537 int printed_lastold = 0;
539 /* Learn about the user and room in question */
541 getuser(&CC->user, CC->curr_user);
542 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
544 /* Load the message list */
545 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
547 msglist = (long *) cdbfr->ptr;
548 cdbfr->ptr = NULL; /* CtdlForEachMessage() now owns this memory */
549 num_msgs = cdbfr->len / sizeof(long);
552 return 0; /* No messages at all? No further action. */
557 * Now begin the traversal.
559 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
561 /* If the caller is looking for a specific MIME type, filter
562 * out all messages which are not of the type requested.
564 if (content_type != NULL) if (strlen(content_type) > 0) {
566 /* This call to GetMetaData() sits inside this loop
567 * so that we only do the extra database read per msg
568 * if we need to. Doing the extra read all the time
569 * really kills the server. If we ever need to use
570 * metadata for another search criterion, we need to
571 * move the read somewhere else -- but still be smart
572 * enough to only do the read if the caller has
573 * specified something that will need it.
575 GetMetaData(&smi, msglist[a]);
577 if (strcasecmp(smi.meta_content_type, content_type)) {
583 num_msgs = sort_msglist(msglist, num_msgs);
585 /* If a template was supplied, filter out the messages which
586 * don't match. (This could induce some delays!)
589 if (compare != NULL) {
590 for (a = 0; a < num_msgs; ++a) {
591 msg = CtdlFetchMessage(msglist[a], 1);
593 if (CtdlMsgCmp(msg, compare)) {
596 CtdlFreeMessage(msg);
604 * Now iterate through the message list, according to the
605 * criteria supplied by the caller.
608 for (a = 0; a < num_msgs; ++a) {
609 thismsg = msglist[a];
610 if (mode == MSGS_ALL) {
614 is_seen = is_msg_in_sequence_set(
615 vbuf.v_seen, thismsg);
616 if (is_seen) lastold = thismsg;
622 || ((mode == MSGS_OLD) && (is_seen))
623 || ((mode == MSGS_NEW) && (!is_seen))
624 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
625 || ((mode == MSGS_FIRST) && (a < ref))
626 || ((mode == MSGS_GT) && (thismsg > ref))
627 || ((mode == MSGS_EQ) && (thismsg == ref))
630 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
632 CallBack(lastold, userdata);
636 if (CallBack) CallBack(thismsg, userdata);
640 free(msglist); /* Clean up */
641 return num_processed;
647 * cmd_msgs() - get list of message #'s in this room
648 * implements the MSGS server command using CtdlForEachMessage()
650 void cmd_msgs(char *cmdbuf)
659 int with_template = 0;
660 struct CtdlMessage *template = NULL;
661 int with_headers = 0;
663 extract_token(which, cmdbuf, 0, '|', sizeof which);
664 cm_ref = extract_int(cmdbuf, 1);
665 with_template = extract_int(cmdbuf, 2);
666 with_headers = extract_int(cmdbuf, 3);
670 if (!strncasecmp(which, "OLD", 3))
672 else if (!strncasecmp(which, "NEW", 3))
674 else if (!strncasecmp(which, "FIRST", 5))
676 else if (!strncasecmp(which, "LAST", 4))
678 else if (!strncasecmp(which, "GT", 2))
681 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
682 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
688 cprintf("%d Send template then receive message list\n",
690 template = (struct CtdlMessage *)
691 malloc(sizeof(struct CtdlMessage));
692 memset(template, 0, sizeof(struct CtdlMessage));
693 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
694 extract_token(tfield, buf, 0, '|', sizeof tfield);
695 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
696 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
697 if (!strcasecmp(tfield, msgkeys[i])) {
698 template->cm_fields[i] =
706 cprintf("%d \n", LISTING_FOLLOWS);
709 CtdlForEachMessage(mode,
713 (with_headers ? headers_listing : simple_listing),
716 if (template != NULL) CtdlFreeMessage(template);
724 * help_subst() - support routine for help file viewer
726 void help_subst(char *strbuf, char *source, char *dest)
731 while (p = pattern2(strbuf, source), (p >= 0)) {
732 strcpy(workbuf, &strbuf[p + strlen(source)]);
733 strcpy(&strbuf[p], dest);
734 strcat(strbuf, workbuf);
739 void do_help_subst(char *buffer)
743 help_subst(buffer, "^nodename", config.c_nodename);
744 help_subst(buffer, "^humannode", config.c_humannode);
745 help_subst(buffer, "^fqdn", config.c_fqdn);
746 help_subst(buffer, "^username", CC->user.fullname);
747 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
748 help_subst(buffer, "^usernum", buf2);
749 help_subst(buffer, "^sysadm", config.c_sysadm);
750 help_subst(buffer, "^variantname", CITADEL);
751 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
752 help_subst(buffer, "^maxsessions", buf2);
753 help_subst(buffer, "^bbsdir", CTDLDIR);
759 * memfmout() - Citadel text formatter and paginator.
760 * Although the original purpose of this routine was to format
761 * text to the reader's screen width, all we're really using it
762 * for here is to format text out to 80 columns before sending it
763 * to the client. The client software may reformat it again.
766 int width, /* screen width to use */
767 char *mptr, /* where are we going to get our text from? */
768 char subst, /* nonzero if we should do substitutions */
769 char *nl) /* string to terminate lines with */
781 c = 1; /* c is the current pos */
785 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
787 buffer[strlen(buffer) + 1] = 0;
788 buffer[strlen(buffer)] = ch;
791 if (buffer[0] == '^')
792 do_help_subst(buffer);
794 buffer[strlen(buffer) + 1] = 0;
796 strcpy(buffer, &buffer[1]);
804 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
806 if (((old == 13) || (old == 10)) && (isspace(real))) {
814 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
815 cprintf("%s%s", nl, aaa);
824 if ((strlen(aaa) + c) > (width - 5)) {
833 if ((ch == 13) || (ch == 10)) {
834 cprintf("%s%s", aaa, nl);
841 cprintf("%s%s", aaa, nl);
847 * Callback function for mime parser that simply lists the part
849 void list_this_part(char *name, char *filename, char *partnum, char *disp,
850 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
855 ma = (struct ma_info *)cbuserdata;
856 if (ma->is_ma == 0) {
857 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
858 name, filename, partnum, disp, cbtype, (long)length);
863 * Callback function for multipart prefix
865 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
866 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
871 ma = (struct ma_info *)cbuserdata;
872 if (!strcasecmp(cbtype, "multipart/alternative")) {
876 if (ma->is_ma == 0) {
877 cprintf("pref=%s|%s\n", partnum, cbtype);
882 * Callback function for multipart sufffix
884 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
885 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
890 ma = (struct ma_info *)cbuserdata;
891 if (ma->is_ma == 0) {
892 cprintf("suff=%s|%s\n", partnum, cbtype);
894 if (!strcasecmp(cbtype, "multipart/alternative")) {
901 * Callback function for mime parser that opens a section for downloading
903 void mime_download(char *name, char *filename, char *partnum, char *disp,
904 void *content, char *cbtype, char *cbcharset, size_t length,
905 char *encoding, void *cbuserdata)
908 /* Silently go away if there's already a download open... */
909 if (CC->download_fp != NULL)
912 /* ...or if this is not the desired section */
913 if (strcasecmp(CC->download_desired_section, partnum))
916 CC->download_fp = tmpfile();
917 if (CC->download_fp == NULL)
920 fwrite(content, length, 1, CC->download_fp);
921 fflush(CC->download_fp);
922 rewind(CC->download_fp);
924 OpenCmdResult(filename, cbtype);
930 * Load a message from disk into memory.
931 * This is used by CtdlOutputMsg() and other fetch functions.
933 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
934 * using the CtdlMessageFree() function.
936 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
938 struct cdbdata *dmsgtext;
939 struct CtdlMessage *ret = NULL;
943 cit_uint8_t field_header;
945 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
947 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
948 if (dmsgtext == NULL) {
951 mptr = dmsgtext->ptr;
952 upper_bound = mptr + dmsgtext->len;
954 /* Parse the three bytes that begin EVERY message on disk.
955 * The first is always 0xFF, the on-disk magic number.
956 * The second is the anonymous/public type byte.
957 * The third is the format type byte (vari, fixed, or MIME).
962 "Message %ld appears to be corrupted.\n",
967 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
968 memset(ret, 0, sizeof(struct CtdlMessage));
970 ret->cm_magic = CTDLMESSAGE_MAGIC;
971 ret->cm_anon_type = *mptr++; /* Anon type byte */
972 ret->cm_format_type = *mptr++; /* Format type byte */
975 * The rest is zero or more arbitrary fields. Load them in.
976 * We're done when we encounter either a zero-length field or
977 * have just processed the 'M' (message text) field.
980 if (mptr >= upper_bound) {
983 field_header = *mptr++;
984 ret->cm_fields[field_header] = strdup(mptr);
986 while (*mptr++ != 0); /* advance to next field */
988 } while ((mptr < upper_bound) && (field_header != 'M'));
992 /* Always make sure there's something in the msg text field. If
993 * it's NULL, the message text is most likely stored separately,
994 * so go ahead and fetch that. Failing that, just set a dummy
995 * body so other code doesn't barf.
997 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
998 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
999 if (dmsgtext != NULL) {
1000 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1004 if (ret->cm_fields['M'] == NULL) {
1005 ret->cm_fields['M'] = strdup("<no text>\n");
1008 /* Perform "before read" hooks (aborting if any return nonzero) */
1009 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1010 CtdlFreeMessage(ret);
1019 * Returns 1 if the supplied pointer points to a valid Citadel message.
1020 * If the pointer is NULL or the magic number check fails, returns 0.
1022 int is_valid_message(struct CtdlMessage *msg) {
1025 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1026 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1034 * 'Destructor' for struct CtdlMessage
1036 void CtdlFreeMessage(struct CtdlMessage *msg)
1040 if (is_valid_message(msg) == 0) return;
1042 for (i = 0; i < 256; ++i)
1043 if (msg->cm_fields[i] != NULL) {
1044 free(msg->cm_fields[i]);
1047 msg->cm_magic = 0; /* just in case */
1053 * Pre callback function for multipart/alternative
1055 * NOTE: this differs from the standard behavior for a reason. Normally when
1056 * displaying multipart/alternative you want to show the _last_ usable
1057 * format in the message. Here we show the _first_ one, because it's
1058 * usually text/plain. Since this set of functions is designed for text
1059 * output to non-MIME-aware clients, this is the desired behavior.
1062 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1063 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1068 ma = (struct ma_info *)cbuserdata;
1069 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1070 if (!strcasecmp(cbtype, "multipart/alternative")) {
1078 * Post callback function for multipart/alternative
1080 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1081 void *content, char *cbtype, char *cbcharset, size_t length,
1082 char *encoding, void *cbuserdata)
1086 ma = (struct ma_info *)cbuserdata;
1087 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1088 if (!strcasecmp(cbtype, "multipart/alternative")) {
1096 * Inline callback function for mime parser that wants to display text
1098 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1099 void *content, char *cbtype, char *cbcharset, size_t length,
1100 char *encoding, void *cbuserdata)
1107 ma = (struct ma_info *)cbuserdata;
1110 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1111 partnum, filename, cbtype, (long)length);
1114 * If we're in the middle of a multipart/alternative scope and
1115 * we've already printed another section, skip this one.
1117 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
1118 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n",
1124 if ( (!strcasecmp(cbtype, "text/plain"))
1125 || (strlen(cbtype)==0) ) {
1128 client_write(wptr, length);
1129 if (wptr[length-1] != '\n') {
1134 else if (!strcasecmp(cbtype, "text/html")) {
1135 ptr = html_to_ascii(content, length, 80, 0);
1137 client_write(ptr, wlen);
1138 if (ptr[wlen-1] != '\n') {
1143 else if (PerformFixedOutputHooks(cbtype, content, length)) {
1144 /* above function returns nonzero if it handled the part */
1146 else if (strncasecmp(cbtype, "multipart/", 10)) {
1147 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1148 partnum, filename, cbtype, (long)length);
1153 * The client is elegant and sophisticated and wants to be choosy about
1154 * MIME content types, so figure out which multipart/alternative part
1155 * we're going to send.
1157 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1158 void *content, char *cbtype, char *cbcharset, size_t length,
1159 char *encoding, void *cbuserdata)
1165 ma = (struct ma_info *)cbuserdata;
1167 if (ma->is_ma > 0) {
1168 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1169 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1170 if (!strcasecmp(buf, cbtype)) {
1171 strcpy(ma->chosen_part, partnum);
1178 * Now that we've chosen our preferred part, output it.
1180 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1181 void *content, char *cbtype, char *cbcharset, size_t length,
1182 char *encoding, void *cbuserdata)
1186 int add_newline = 0;
1190 ma = (struct ma_info *)cbuserdata;
1192 /* This is not the MIME part you're looking for... */
1193 if (strcasecmp(partnum, ma->chosen_part)) return;
1195 /* If the content-type of this part is in our preferred formats
1196 * list, we can simply output it verbatim.
1198 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1199 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1200 if (!strcasecmp(buf, cbtype)) {
1201 /* Yeah! Go! W00t!! */
1203 text_content = (char *)content;
1204 if (text_content[length-1] != '\n') {
1208 cprintf("Content-type: %s", cbtype);
1209 if (strlen(cbcharset) > 0) {
1210 cprintf("; charset=%s", cbcharset);
1212 cprintf("\nContent-length: %d\n",
1213 (int)(length + add_newline) );
1214 if (strlen(encoding) > 0) {
1215 cprintf("Content-transfer-encoding: %s\n", encoding);
1218 cprintf("Content-transfer-encoding: 7bit\n");
1221 client_write(content, length);
1222 if (add_newline) cprintf("\n");
1227 /* No translations required or possible: output as text/plain */
1228 cprintf("Content-type: text/plain\n\n");
1229 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1230 length, encoding, cbuserdata);
1235 char desired_section[64];
1242 * Callback function for
1244 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1245 void *content, char *cbtype, char *cbcharset, size_t length,
1246 char *encoding, void *cbuserdata)
1248 struct encapmsg *encap;
1250 encap = (struct encapmsg *)cbuserdata;
1252 /* Only proceed if this is the desired section... */
1253 if (!strcasecmp(encap->desired_section, partnum)) {
1254 encap->msglen = length;
1255 encap->msg = malloc(length + 2);
1256 memcpy(encap->msg, content, length);
1266 * Get a message off disk. (returns om_* values found in msgbase.h)
1269 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1270 int mode, /* how would you like that message? */
1271 int headers_only, /* eschew the message body? */
1272 int do_proto, /* do Citadel protocol responses? */
1273 int crlf, /* Use CRLF newlines instead of LF? */
1274 char *section /* NULL or a message/rfc822 section */
1276 struct CtdlMessage *TheMessage = NULL;
1277 int retcode = om_no_such_msg;
1278 struct encapmsg encap;
1280 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1282 (section ? section : "<>")
1285 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1286 if (do_proto) cprintf("%d Not logged in.\n",
1287 ERROR + NOT_LOGGED_IN);
1288 return(om_not_logged_in);
1291 /* FIXME: check message id against msglist for this room */
1294 * Fetch the message from disk. If we're in any sort of headers
1295 * only mode, request that we don't even bother loading the body
1298 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1299 TheMessage = CtdlFetchMessage(msg_num, 0);
1302 TheMessage = CtdlFetchMessage(msg_num, 1);
1305 if (TheMessage == NULL) {
1306 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1307 ERROR + MESSAGE_NOT_FOUND, msg_num);
1308 return(om_no_such_msg);
1311 /* Here is the weird form of this command, to process only an
1312 * encapsulated message/rfc822 section.
1315 memset(&encap, 0, sizeof encap);
1316 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1317 mime_parser(TheMessage->cm_fields['M'],
1319 *extract_encapsulated_message,
1320 NULL, NULL, (void *)&encap, 0
1322 CtdlFreeMessage(TheMessage);
1326 encap.msg[encap.msglen] = 0;
1327 TheMessage = convert_internet_message(encap.msg);
1328 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1330 /* Now we let it fall through to the bottom of this
1331 * function, because TheMessage now contains the
1332 * encapsulated message instead of the top-level
1333 * message. Isn't that neat?
1338 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1339 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1340 retcode = om_no_such_msg;
1345 /* Ok, output the message now */
1346 retcode = CtdlOutputPreLoadedMsg(
1348 headers_only, do_proto, crlf);
1349 CtdlFreeMessage(TheMessage);
1356 * Get a message off disk. (returns om_* values found in msgbase.h)
1359 int CtdlOutputPreLoadedMsg(
1360 struct CtdlMessage *TheMessage,
1361 int mode, /* how would you like that message? */
1362 int headers_only, /* eschew the message body? */
1363 int do_proto, /* do Citadel protocol responses? */
1364 int crlf /* Use CRLF newlines instead of LF? */
1370 char display_name[256];
1372 char *nl; /* newline string */
1374 int subject_found = 0;
1377 /* Buffers needed for RFC822 translation. These are all filled
1378 * using functions that are bounds-checked, and therefore we can
1379 * make them substantially smaller than SIZ.
1387 char datestamp[100];
1389 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1390 ((TheMessage == NULL) ? "NULL" : "not null"),
1391 mode, headers_only, do_proto, crlf);
1393 strcpy(mid, "unknown");
1394 nl = (crlf ? "\r\n" : "\n");
1396 if (!is_valid_message(TheMessage)) {
1398 "ERROR: invalid preloaded message for output\n");
1399 return(om_no_such_msg);
1402 /* Are we downloading a MIME component? */
1403 if (mode == MT_DOWNLOAD) {
1404 if (TheMessage->cm_format_type != FMT_RFC822) {
1406 cprintf("%d This is not a MIME message.\n",
1407 ERROR + ILLEGAL_VALUE);
1408 } else if (CC->download_fp != NULL) {
1409 if (do_proto) cprintf(
1410 "%d You already have a download open.\n",
1411 ERROR + RESOURCE_BUSY);
1413 /* Parse the message text component */
1414 mptr = TheMessage->cm_fields['M'];
1415 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1416 /* If there's no file open by this time, the requested
1417 * section wasn't found, so print an error
1419 if (CC->download_fp == NULL) {
1420 if (do_proto) cprintf(
1421 "%d Section %s not found.\n",
1422 ERROR + FILE_NOT_FOUND,
1423 CC->download_desired_section);
1426 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1429 /* now for the user-mode message reading loops */
1430 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1432 /* Does the caller want to skip the headers? */
1433 if (headers_only == HEADERS_NONE) goto START_TEXT;
1435 /* Tell the client which format type we're using. */
1436 if ( (mode == MT_CITADEL) && (do_proto) ) {
1437 cprintf("type=%d\n", TheMessage->cm_format_type);
1440 /* nhdr=yes means that we're only displaying headers, no body */
1441 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1442 && (mode == MT_CITADEL)
1445 cprintf("nhdr=yes\n");
1448 /* begin header processing loop for Citadel message format */
1450 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1452 safestrncpy(display_name, "<unknown>", sizeof display_name);
1453 if (TheMessage->cm_fields['A']) {
1454 strcpy(buf, TheMessage->cm_fields['A']);
1455 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1456 safestrncpy(display_name, "****", sizeof display_name);
1458 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1459 safestrncpy(display_name, "anonymous", sizeof display_name);
1462 safestrncpy(display_name, buf, sizeof display_name);
1464 if ((is_room_aide())
1465 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1466 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1467 size_t tmp = strlen(display_name);
1468 snprintf(&display_name[tmp],
1469 sizeof display_name - tmp,
1474 /* Don't show Internet address for users on the
1475 * local Citadel network.
1478 if (TheMessage->cm_fields['N'] != NULL)
1479 if (strlen(TheMessage->cm_fields['N']) > 0)
1480 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1484 /* Now spew the header fields in the order we like them. */
1485 safestrncpy(allkeys, FORDER, sizeof allkeys);
1486 for (i=0; i<strlen(allkeys); ++i) {
1487 k = (int) allkeys[i];
1489 if ( (TheMessage->cm_fields[k] != NULL)
1490 && (msgkeys[k] != NULL) ) {
1492 if (do_proto) cprintf("%s=%s\n",
1496 else if ((k == 'F') && (suppress_f)) {
1499 /* Masquerade display name if needed */
1501 if (do_proto) cprintf("%s=%s\n",
1503 TheMessage->cm_fields[k]
1512 /* begin header processing loop for RFC822 transfer format */
1517 strcpy(snode, NODENAME);
1518 strcpy(lnode, HUMANNODE);
1519 if (mode == MT_RFC822) {
1520 for (i = 0; i < 256; ++i) {
1521 if (TheMessage->cm_fields[i]) {
1522 mptr = TheMessage->cm_fields[i];
1525 safestrncpy(luser, mptr, sizeof luser);
1526 safestrncpy(suser, mptr, sizeof suser);
1528 else if (i == 'Y') {
1529 cprintf("CC: %s%s", mptr, nl);
1531 else if (i == 'U') {
1532 cprintf("Subject: %s%s", mptr, nl);
1536 safestrncpy(mid, mptr, sizeof mid);
1538 safestrncpy(lnode, mptr, sizeof lnode);
1540 safestrncpy(fuser, mptr, sizeof fuser);
1541 /* else if (i == 'O')
1542 cprintf("X-Citadel-Room: %s%s",
1545 safestrncpy(snode, mptr, sizeof snode);
1547 cprintf("To: %s%s", mptr, nl);
1548 else if (i == 'T') {
1549 datestring(datestamp, sizeof datestamp,
1550 atol(mptr), DATESTRING_RFC822);
1551 cprintf("Date: %s%s", datestamp, nl);
1555 if (subject_found == 0) {
1556 cprintf("Subject: (no subject)%s", nl);
1560 for (i=0; i<strlen(suser); ++i) {
1561 suser[i] = tolower(suser[i]);
1562 if (!isalnum(suser[i])) suser[i]='_';
1565 if (mode == MT_RFC822) {
1566 if (!strcasecmp(snode, NODENAME)) {
1567 safestrncpy(snode, FQDN, sizeof snode);
1570 /* Construct a fun message id */
1571 cprintf("Message-ID: <%s", mid);
1572 if (strchr(mid, '@')==NULL) {
1573 cprintf("@%s", snode);
1577 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1578 cprintf("From: \"----\" <x@x.org>%s", nl);
1580 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1581 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1583 else if (strlen(fuser) > 0) {
1584 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1587 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1590 cprintf("Organization: %s%s", lnode, nl);
1592 /* Blank line signifying RFC822 end-of-headers */
1593 if (TheMessage->cm_format_type != FMT_RFC822) {
1598 /* end header processing loop ... at this point, we're in the text */
1600 if (headers_only == HEADERS_FAST) goto DONE;
1601 mptr = TheMessage->cm_fields['M'];
1603 /* Tell the client about the MIME parts in this message */
1604 if (TheMessage->cm_format_type == FMT_RFC822) {
1605 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1606 memset(&ma, 0, sizeof(struct ma_info));
1607 mime_parser(mptr, NULL,
1608 (do_proto ? *list_this_part : NULL),
1609 (do_proto ? *list_this_pref : NULL),
1610 (do_proto ? *list_this_suff : NULL),
1613 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1614 char *start_of_text = NULL;
1615 start_of_text = strstr(mptr, "\n\r\n");
1616 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1617 if (start_of_text == NULL) start_of_text = mptr;
1619 start_of_text = strstr(start_of_text, "\n");
1621 while (ch=*mptr, ch!=0) {
1625 else switch(headers_only) {
1627 if (mptr >= start_of_text) {
1628 if (ch == 10) cprintf("%s", nl);
1629 else cprintf("%c", ch);
1633 if (mptr < start_of_text) {
1634 if (ch == 10) cprintf("%s", nl);
1635 else cprintf("%c", ch);
1639 if (ch == 10) cprintf("%s", nl);
1640 else cprintf("%c", ch);
1649 if (headers_only == HEADERS_ONLY) {
1653 /* signify start of msg text */
1654 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1655 if (do_proto) cprintf("text\n");
1658 /* If the format type on disk is 1 (fixed-format), then we want
1659 * everything to be output completely literally ... regardless of
1660 * what message transfer format is in use.
1662 if (TheMessage->cm_format_type == FMT_FIXED) {
1663 if (mode == MT_MIME) {
1664 cprintf("Content-type: text/plain\n\n");
1667 while (ch = *mptr++, ch > 0) {
1670 if ((ch == 10) || (strlen(buf) > 250)) {
1671 cprintf("%s%s", buf, nl);
1674 buf[strlen(buf) + 1] = 0;
1675 buf[strlen(buf)] = ch;
1678 if (strlen(buf) > 0)
1679 cprintf("%s%s", buf, nl);
1682 /* If the message on disk is format 0 (Citadel vari-format), we
1683 * output using the formatter at 80 columns. This is the final output
1684 * form if the transfer format is RFC822, but if the transfer format
1685 * is Citadel proprietary, it'll still work, because the indentation
1686 * for new paragraphs is correct and the client will reformat the
1687 * message to the reader's screen width.
1689 if (TheMessage->cm_format_type == FMT_CITADEL) {
1690 if (mode == MT_MIME) {
1691 cprintf("Content-type: text/x-citadel-variformat\n\n");
1693 memfmout(80, mptr, 0, nl);
1696 /* If the message on disk is format 4 (MIME), we've gotta hand it
1697 * off to the MIME parser. The client has already been told that
1698 * this message is format 1 (fixed format), so the callback function
1699 * we use will display those parts as-is.
1701 if (TheMessage->cm_format_type == FMT_RFC822) {
1702 memset(&ma, 0, sizeof(struct ma_info));
1704 if (mode == MT_MIME) {
1705 strcpy(ma.chosen_part, "1");
1706 mime_parser(mptr, NULL,
1707 *choose_preferred, *fixed_output_pre,
1708 *fixed_output_post, (void *)&ma, 0);
1709 mime_parser(mptr, NULL,
1710 *output_preferred, NULL, NULL, (void *)&ma, 0);
1713 mime_parser(mptr, NULL,
1714 *fixed_output, *fixed_output_pre,
1715 *fixed_output_post, (void *)&ma, 0);
1720 DONE: /* now we're done */
1721 if (do_proto) cprintf("000\n");
1728 * display a message (mode 0 - Citadel proprietary)
1730 void cmd_msg0(char *cmdbuf)
1733 int headers_only = HEADERS_ALL;
1735 msgid = extract_long(cmdbuf, 0);
1736 headers_only = extract_int(cmdbuf, 1);
1738 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL);
1744 * display a message (mode 2 - RFC822)
1746 void cmd_msg2(char *cmdbuf)
1749 int headers_only = HEADERS_ALL;
1751 msgid = extract_long(cmdbuf, 0);
1752 headers_only = extract_int(cmdbuf, 1);
1754 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL);
1760 * display a message (mode 3 - IGnet raw format - internal programs only)
1762 void cmd_msg3(char *cmdbuf)
1765 struct CtdlMessage *msg;
1768 if (CC->internal_pgm == 0) {
1769 cprintf("%d This command is for internal programs only.\n",
1770 ERROR + HIGHER_ACCESS_REQUIRED);
1774 msgnum = extract_long(cmdbuf, 0);
1775 msg = CtdlFetchMessage(msgnum, 1);
1777 cprintf("%d Message %ld not found.\n",
1778 ERROR + MESSAGE_NOT_FOUND, msgnum);
1782 serialize_message(&smr, msg);
1783 CtdlFreeMessage(msg);
1786 cprintf("%d Unable to serialize message\n",
1787 ERROR + INTERNAL_ERROR);
1791 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1792 client_write((char *)smr.ser, (int)smr.len);
1799 * Display a message using MIME content types
1801 void cmd_msg4(char *cmdbuf)
1806 msgid = extract_long(cmdbuf, 0);
1807 extract_token(section, cmdbuf, 1, '|', sizeof section);
1808 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) );
1814 * Client tells us its preferred message format(s)
1816 void cmd_msgp(char *cmdbuf)
1818 safestrncpy(CC->preferred_formats, cmdbuf,
1819 sizeof(CC->preferred_formats));
1820 cprintf("%d ok\n", CIT_OK);
1825 * Open a component of a MIME message as a download file
1827 void cmd_opna(char *cmdbuf)
1830 char desired_section[128];
1832 msgid = extract_long(cmdbuf, 0);
1833 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1834 safestrncpy(CC->download_desired_section, desired_section,
1835 sizeof CC->download_desired_section);
1836 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL);
1841 * Save a message pointer into a specified room
1842 * (Returns 0 for success, nonzero for failure)
1843 * roomname may be NULL to use the current room
1845 * Note that the 'supplied_msg' field may be set to NULL, in which case
1846 * the message will be fetched from disk, by number, if we need to perform
1847 * replication checks. This adds an additional database read, so if the
1848 * caller already has the message in memory then it should be supplied.
1850 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check,
1851 struct CtdlMessage *supplied_msg) {
1853 char hold_rm[ROOMNAMELEN];
1854 struct cdbdata *cdbfr;
1857 long highest_msg = 0L;
1858 struct CtdlMessage *msg = NULL;
1860 /*lprintf(CTDL_DEBUG,
1861 "CtdlSaveMsgPointerInRoom(room=%s, msgid=%ld, repl=%d)\n",
1862 roomname, msgid, do_repl_check);*/
1864 strcpy(hold_rm, CC->room.QRname);
1866 /* Now the regular stuff */
1867 if (lgetroom(&CC->room,
1868 ((roomname != NULL) ? roomname : CC->room.QRname) )
1870 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1871 return(ERROR + ROOM_NOT_FOUND);
1874 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1875 if (cdbfr == NULL) {
1879 msglist = (long *) cdbfr->ptr;
1880 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
1881 num_msgs = cdbfr->len / sizeof(long);
1885 /* Make sure the message doesn't already exist in this room. It
1886 * is absolutely taboo to have more than one reference to the same
1887 * message in a room.
1889 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1890 if (msglist[i] == msgid) {
1891 lputroom(&CC->room); /* unlock the room */
1892 getroom(&CC->room, hold_rm);
1894 return(ERROR + ALREADY_EXISTS);
1898 /* Now add the new message */
1900 msglist = realloc(msglist, (num_msgs * sizeof(long)));
1902 if (msglist == NULL) {
1903 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1905 msglist[num_msgs - 1] = msgid;
1907 /* Sort the message list, so all the msgid's are in order */
1908 num_msgs = sort_msglist(msglist, num_msgs);
1910 /* Determine the highest message number */
1911 highest_msg = msglist[num_msgs - 1];
1913 /* Write it back to disk. */
1914 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1915 msglist, (int)(num_msgs * sizeof(long)));
1917 /* Free up the memory we used. */
1920 /* Update the highest-message pointer and unlock the room. */
1921 CC->room.QRhighest = highest_msg;
1922 lputroom(&CC->room);
1924 /* Perform replication checks if necessary */
1925 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
1926 if (supplied_msg != NULL) {
1930 msg = CtdlFetchMessage(msgid, 0);
1934 ReplicationChecks(msg);
1939 /* If the message has an Exclusive ID, index that... */
1941 if (msg->cm_fields['E'] != NULL) {
1942 index_message_by_euid(msg->cm_fields['E'],
1947 /* Free up the memory we may have allocated */
1948 if ( (msg != NULL) && (msg != supplied_msg) ) {
1949 CtdlFreeMessage(msg);
1952 /* Go back to the room we were in before we wandered here... */
1953 getroom(&CC->room, hold_rm);
1955 /* Bump the reference count for this message. */
1956 AdjRefCount(msgid, +1);
1958 /* Return success. */
1965 * Message base operation to save a new message to the message store
1966 * (returns new message number)
1968 * This is the back end for CtdlSubmitMsg() and should not be directly
1969 * called by server-side modules.
1972 long send_message(struct CtdlMessage *msg) {
1980 /* Get a new message number */
1981 newmsgid = get_new_message_number();
1982 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1984 /* Generate an ID if we don't have one already */
1985 if (msg->cm_fields['I']==NULL) {
1986 msg->cm_fields['I'] = strdup(msgidbuf);
1989 /* If the message is big, set its body aside for storage elsewhere */
1990 if (msg->cm_fields['M'] != NULL) {
1991 if (strlen(msg->cm_fields['M']) > BIGMSG) {
1993 holdM = msg->cm_fields['M'];
1994 msg->cm_fields['M'] = NULL;
1998 /* Serialize our data structure for storage in the database */
1999 serialize_message(&smr, msg);
2002 msg->cm_fields['M'] = holdM;
2006 cprintf("%d Unable to serialize message\n",
2007 ERROR + INTERNAL_ERROR);
2011 /* Write our little bundle of joy into the message base */
2012 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2013 smr.ser, smr.len) < 0) {
2014 lprintf(CTDL_ERR, "Can't store message\n");
2018 cdb_store(CDB_BIGMSGS,
2028 /* Free the memory we used for the serialized message */
2031 /* Return the *local* message ID to the caller
2032 * (even if we're storing an incoming network message)
2040 * Serialize a struct CtdlMessage into the format used on disk and network.
2042 * This function loads up a "struct ser_ret" (defined in server.h) which
2043 * contains the length of the serialized message and a pointer to the
2044 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2046 void serialize_message(struct ser_ret *ret, /* return values */
2047 struct CtdlMessage *msg) /* unserialized msg */
2051 static char *forder = FORDER;
2053 if (is_valid_message(msg) == 0) return; /* self check */
2056 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2057 ret->len = ret->len +
2058 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2060 lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
2061 ret->ser = malloc(ret->len);
2062 if (ret->ser == NULL) {
2068 ret->ser[1] = msg->cm_anon_type;
2069 ret->ser[2] = msg->cm_format_type;
2072 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2073 ret->ser[wlen++] = (char)forder[i];
2074 strcpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
2075 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
2077 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2078 (long)ret->len, (long)wlen);
2086 * Check to see if any messages already exist in the current room which
2087 * carry the same Exclusive ID as this one. If any are found, delete them.
2089 void ReplicationChecks(struct CtdlMessage *msg) {
2090 long old_msgnum = (-1L);
2092 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2094 lprintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2097 /* No exclusive id? Don't do anything. */
2098 if (msg == NULL) return;
2099 if (msg->cm_fields['E'] == NULL) return;
2100 if (strlen(msg->cm_fields['E']) == 0) return;
2101 /*lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2102 msg->cm_fields['E'], CC->room.QRname);*/
2104 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2105 if (old_msgnum > 0L) {
2106 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2107 CtdlDeleteMessages(CC->room.QRname, old_msgnum, "", 0);
2114 * Save a message to disk and submit it into the delivery system.
2116 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2117 struct recptypes *recps, /* recipients (if mail) */
2118 char *force /* force a particular room? */
2120 char submit_filename[128];
2121 char generated_timestamp[32];
2122 char hold_rm[ROOMNAMELEN];
2123 char actual_rm[ROOMNAMELEN];
2124 char force_room[ROOMNAMELEN];
2125 char content_type[SIZ]; /* We have to learn this */
2126 char recipient[SIZ];
2129 struct ctdluser userbuf;
2131 struct MetaData smi;
2132 FILE *network_fp = NULL;
2133 static int seqnum = 1;
2134 struct CtdlMessage *imsg = NULL;
2137 char *hold_R, *hold_D;
2138 char *collected_addresses = NULL;
2139 struct addresses_to_be_filed *aptr = NULL;
2141 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2142 if (is_valid_message(msg) == 0) return(-1); /* self check */
2144 /* If this message has no timestamp, we take the liberty of
2145 * giving it one, right now.
2147 if (msg->cm_fields['T'] == NULL) {
2148 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2149 msg->cm_fields['T'] = strdup(generated_timestamp);
2152 /* If this message has no path, we generate one.
2154 if (msg->cm_fields['P'] == NULL) {
2155 if (msg->cm_fields['A'] != NULL) {
2156 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2157 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2158 if (isspace(msg->cm_fields['P'][a])) {
2159 msg->cm_fields['P'][a] = ' ';
2164 msg->cm_fields['P'] = strdup("unknown");
2168 if (force == NULL) {
2169 strcpy(force_room, "");
2172 strcpy(force_room, force);
2175 /* Learn about what's inside, because it's what's inside that counts */
2176 if (msg->cm_fields['M'] == NULL) {
2177 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2181 switch (msg->cm_format_type) {
2183 strcpy(content_type, "text/x-citadel-variformat");
2186 strcpy(content_type, "text/plain");
2189 strcpy(content_type, "text/plain");
2190 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type: ");
2192 safestrncpy(content_type, &mptr[14],
2193 sizeof content_type);
2194 for (a = 0; a < strlen(content_type); ++a) {
2195 if ((content_type[a] == ';')
2196 || (content_type[a] == ' ')
2197 || (content_type[a] == 13)
2198 || (content_type[a] == 10)) {
2199 content_type[a] = 0;
2205 /* Goto the correct room */
2206 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
2207 strcpy(hold_rm, CC->room.QRname);
2208 strcpy(actual_rm, CC->room.QRname);
2209 if (recps != NULL) {
2210 strcpy(actual_rm, SENTITEMS);
2213 /* If the user is a twit, move to the twit room for posting */
2215 if (CC->user.axlevel == 2) {
2216 strcpy(hold_rm, actual_rm);
2217 strcpy(actual_rm, config.c_twitroom);
2218 lprintf(CTDL_DEBUG, "Diverting to twit room\n");
2222 /* ...or if this message is destined for Aide> then go there. */
2223 if (strlen(force_room) > 0) {
2224 strcpy(actual_rm, force_room);
2227 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2228 if (strcasecmp(actual_rm, CC->room.QRname)) {
2229 /* getroom(&CC->room, actual_rm); */
2230 usergoto(actual_rm, 0, 1, NULL, NULL);
2234 * If this message has no O (room) field, generate one.
2236 if (msg->cm_fields['O'] == NULL) {
2237 msg->cm_fields['O'] = strdup(CC->room.QRname);
2240 /* Perform "before save" hooks (aborting if any return nonzero) */
2241 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2242 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2245 * If this message has an Exclusive ID, and the room is replication
2246 * checking enabled, then do replication checks.
2248 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2249 ReplicationChecks(msg);
2252 /* Save it to disk */
2253 lprintf(CTDL_DEBUG, "Saving to disk\n");
2254 newmsgid = send_message(msg);
2255 if (newmsgid <= 0L) return(-5);
2257 /* Write a supplemental message info record. This doesn't have to
2258 * be a critical section because nobody else knows about this message
2261 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2262 memset(&smi, 0, sizeof(struct MetaData));
2263 smi.meta_msgnum = newmsgid;
2264 smi.meta_refcount = 0;
2265 safestrncpy(smi.meta_content_type, content_type,
2266 sizeof smi.meta_content_type);
2268 /* As part of the new metadata record, measure how
2269 * big this message will be when displayed as RFC822.
2270 * Both POP and IMAP use this, and it's best to just take the hit now
2271 * instead of having to potentially measure thousands of messages when
2272 * a mailbox is opened later.
2275 if (CC->redirect_buffer != NULL) {
2276 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2279 CC->redirect_buffer = malloc(SIZ);
2280 CC->redirect_len = 0;
2281 CC->redirect_alloc = SIZ;
2282 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2283 smi.meta_rfc822_length = CC->redirect_len;
2284 free(CC->redirect_buffer);
2285 CC->redirect_buffer = NULL;
2286 CC->redirect_len = 0;
2287 CC->redirect_alloc = 0;
2291 /* Now figure out where to store the pointers */
2292 lprintf(CTDL_DEBUG, "Storing pointers\n");
2294 /* If this is being done by the networker delivering a private
2295 * message, we want to BYPASS saving the sender's copy (because there
2296 * is no local sender; it would otherwise go to the Trashcan).
2298 if ((!CC->internal_pgm) || (recps == NULL)) {
2299 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2300 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2301 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2305 /* For internet mail, drop a copy in the outbound queue room */
2307 if (recps->num_internet > 0) {
2308 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2311 /* If other rooms are specified, drop them there too. */
2313 if (recps->num_room > 0)
2314 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2315 extract_token(recipient, recps->recp_room, i,
2316 '|', sizeof recipient);
2317 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2318 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2321 /* Bump this user's messages posted counter. */
2322 lprintf(CTDL_DEBUG, "Updating user\n");
2323 lgetuser(&CC->user, CC->curr_user);
2324 CC->user.posted = CC->user.posted + 1;
2325 lputuser(&CC->user);
2327 /* If this is private, local mail, make a copy in the
2328 * recipient's mailbox and bump the reference count.
2331 if (recps->num_local > 0)
2332 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2333 extract_token(recipient, recps->recp_local, i,
2334 '|', sizeof recipient);
2335 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2337 if (getuser(&userbuf, recipient) == 0) {
2338 MailboxName(actual_rm, sizeof actual_rm,
2339 &userbuf, MAILROOM);
2340 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2341 BumpNewMailCounter(userbuf.usernum);
2344 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2345 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2350 /* Perform "after save" hooks */
2351 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2352 PerformMessageHooks(msg, EVT_AFTERSAVE);
2354 /* For IGnet mail, we have to save a new copy into the spooler for
2355 * each recipient, with the R and D fields set to the recipient and
2356 * destination-node. This has two ugly side effects: all other
2357 * recipients end up being unlisted in this recipient's copy of the
2358 * message, and it has to deliver multiple messages to the same
2359 * node. We'll revisit this again in a year or so when everyone has
2360 * a network spool receiver that can handle the new style messages.
2363 if (recps->num_ignet > 0)
2364 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2365 extract_token(recipient, recps->recp_ignet, i,
2366 '|', sizeof recipient);
2368 hold_R = msg->cm_fields['R'];
2369 hold_D = msg->cm_fields['D'];
2370 msg->cm_fields['R'] = malloc(SIZ);
2371 msg->cm_fields['D'] = malloc(128);
2372 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2373 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2375 serialize_message(&smr, msg);
2377 snprintf(submit_filename, sizeof submit_filename,
2378 #ifndef HAVE_SPOOL_DIR
2383 "/network/spoolin/netmail.%04lx.%04x.%04x",
2384 (long) getpid(), CC->cs_pid, ++seqnum);
2385 network_fp = fopen(submit_filename, "wb+");
2386 if (network_fp != NULL) {
2387 fwrite(smr.ser, smr.len, 1, network_fp);
2393 free(msg->cm_fields['R']);
2394 free(msg->cm_fields['D']);
2395 msg->cm_fields['R'] = hold_R;
2396 msg->cm_fields['D'] = hold_D;
2399 /* Go back to the room we started from */
2400 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2401 if (strcasecmp(hold_rm, CC->room.QRname))
2402 /* getroom(&CC->room, hold_rm); */
2403 usergoto(hold_rm, 0, 1, NULL, NULL);
2405 /* For internet mail, generate delivery instructions.
2406 * Yes, this is recursive. Deal with it. Infinite recursion does
2407 * not happen because the delivery instructions message does not
2408 * contain a recipient.
2411 if (recps->num_internet > 0) {
2412 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2413 instr = malloc(SIZ * 2);
2414 snprintf(instr, SIZ * 2,
2415 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2417 SPOOLMIME, newmsgid, (long)time(NULL),
2418 msg->cm_fields['A'], msg->cm_fields['N']
2421 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2422 size_t tmp = strlen(instr);
2423 extract_token(recipient, recps->recp_internet,
2424 i, '|', sizeof recipient);
2425 snprintf(&instr[tmp], SIZ * 2 - tmp,
2426 "remote|%s|0||\n", recipient);
2429 imsg = malloc(sizeof(struct CtdlMessage));
2430 memset(imsg, 0, sizeof(struct CtdlMessage));
2431 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2432 imsg->cm_anon_type = MES_NORMAL;
2433 imsg->cm_format_type = FMT_RFC822;
2434 imsg->cm_fields['A'] = strdup("Citadel");
2435 imsg->cm_fields['M'] = instr;
2436 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2437 CtdlFreeMessage(imsg);
2441 * Any addresses to harvest for someone's address book?
2443 if ( (CC->logged_in) && (recps != NULL) ) {
2444 collected_addresses = harvest_collected_addresses(msg);
2447 if (collected_addresses != NULL) {
2448 begin_critical_section(S_ATBF);
2449 aptr = (struct addresses_to_be_filed *)
2450 malloc(sizeof(struct addresses_to_be_filed));
2452 MailboxName(actual_rm, sizeof actual_rm,
2453 &CC->user, USERCONTACTSROOM);
2454 aptr->roomname = strdup(actual_rm);
2455 aptr->collected_addresses = collected_addresses;
2457 end_critical_section(S_ATBF);
2470 * Convenience function for generating small administrative messages.
2472 void quickie_message(char *from, char *to, char *room, char *text,
2473 int format_type, char *subject)
2475 struct CtdlMessage *msg;
2476 struct recptypes *recp = NULL;
2478 msg = malloc(sizeof(struct CtdlMessage));
2479 memset(msg, 0, sizeof(struct CtdlMessage));
2480 msg->cm_magic = CTDLMESSAGE_MAGIC;
2481 msg->cm_anon_type = MES_NORMAL;
2482 msg->cm_format_type = format_type;
2483 msg->cm_fields['A'] = strdup(from);
2484 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2485 msg->cm_fields['N'] = strdup(NODENAME);
2487 msg->cm_fields['R'] = strdup(to);
2488 recp = validate_recipients(to);
2490 if (subject != NULL) {
2491 msg->cm_fields['U'] = strdup(subject);
2493 msg->cm_fields['M'] = strdup(text);
2495 CtdlSubmitMsg(msg, recp, room);
2496 CtdlFreeMessage(msg);
2497 if (recp != NULL) free(recp);
2503 * Back end function used by CtdlMakeMessage() and similar functions
2505 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2506 size_t maxlen, /* maximum message length */
2507 char *exist, /* if non-null, append to it;
2508 exist is ALWAYS freed */
2509 int crlf /* CRLF newlines instead of LF */
2513 size_t message_len = 0;
2514 size_t buffer_len = 0;
2520 if (exist == NULL) {
2527 message_len = strlen(exist);
2528 buffer_len = message_len + 4096;
2529 m = realloc(exist, buffer_len);
2536 /* flush the input if we have nowhere to store it */
2541 /* read in the lines of message text one by one */
2543 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2544 if (!strcmp(buf, terminator)) finished = 1;
2546 strcat(buf, "\r\n");
2552 if ( (!flushing) && (!finished) ) {
2553 /* Measure the line */
2554 linelen = strlen(buf);
2556 /* augment the buffer if we have to */
2557 if ((message_len + linelen) >= buffer_len) {
2558 ptr = realloc(m, (buffer_len * 2) );
2559 if (ptr == NULL) { /* flush if can't allocate */
2562 buffer_len = (buffer_len * 2);
2564 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2568 /* Add the new line to the buffer. NOTE: this loop must avoid
2569 * using functions like strcat() and strlen() because they
2570 * traverse the entire buffer upon every call, and doing that
2571 * for a multi-megabyte message slows it down beyond usability.
2573 strcpy(&m[message_len], buf);
2574 message_len += linelen;
2577 /* if we've hit the max msg length, flush the rest */
2578 if (message_len >= maxlen) flushing = 1;
2580 } while (!finished);
2588 * Build a binary message to be saved on disk.
2589 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2590 * will become part of the message. This means you are no longer
2591 * responsible for managing that memory -- it will be freed along with
2592 * the rest of the fields when CtdlFreeMessage() is called.)
2595 struct CtdlMessage *CtdlMakeMessage(
2596 struct ctdluser *author, /* author's user structure */
2597 char *recipient, /* NULL if it's not mail */
2598 char *recp_cc, /* NULL if it's not mail */
2599 char *room, /* room where it's going */
2600 int type, /* see MES_ types in header file */
2601 int format_type, /* variformat, plain text, MIME... */
2602 char *fake_name, /* who we're masquerading as */
2603 char *subject, /* Subject (optional) */
2604 char *preformatted_text /* ...or NULL to read text from client */
2606 char dest_node[SIZ];
2608 struct CtdlMessage *msg;
2610 msg = malloc(sizeof(struct CtdlMessage));
2611 memset(msg, 0, sizeof(struct CtdlMessage));
2612 msg->cm_magic = CTDLMESSAGE_MAGIC;
2613 msg->cm_anon_type = type;
2614 msg->cm_format_type = format_type;
2616 /* Don't confuse the poor folks if it's not routed mail. */
2617 strcpy(dest_node, "");
2622 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2623 msg->cm_fields['P'] = strdup(buf);
2625 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2626 msg->cm_fields['T'] = strdup(buf);
2628 if (fake_name[0]) /* author */
2629 msg->cm_fields['A'] = strdup(fake_name);
2631 msg->cm_fields['A'] = strdup(author->fullname);
2633 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2634 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2637 msg->cm_fields['O'] = strdup(CC->room.QRname);
2640 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2641 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2643 if (recipient[0] != 0) {
2644 msg->cm_fields['R'] = strdup(recipient);
2646 if (recp_cc[0] != 0) {
2647 msg->cm_fields['Y'] = strdup(recp_cc);
2649 if (dest_node[0] != 0) {
2650 msg->cm_fields['D'] = strdup(dest_node);
2653 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2654 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2657 if (subject != NULL) {
2659 if (strlen(subject) > 0) {
2660 msg->cm_fields['U'] = strdup(subject);
2664 if (preformatted_text != NULL) {
2665 msg->cm_fields['M'] = preformatted_text;
2668 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2669 config.c_maxmsglen, NULL, 0);
2677 * Check to see whether we have permission to post a message in the current
2678 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2679 * returns 0 on success.
2681 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2683 if (!(CC->logged_in)) {
2684 snprintf(errmsgbuf, n, "Not logged in.");
2685 return (ERROR + NOT_LOGGED_IN);
2688 if ((CC->user.axlevel < 2)
2689 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2690 snprintf(errmsgbuf, n, "Need to be validated to enter "
2691 "(except in %s> to sysop)", MAILROOM);
2692 return (ERROR + HIGHER_ACCESS_REQUIRED);
2695 if ((CC->user.axlevel < 4)
2696 && (CC->room.QRflags & QR_NETWORK)) {
2697 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2698 return (ERROR + HIGHER_ACCESS_REQUIRED);
2701 if ((CC->user.axlevel < 6)
2702 && (CC->room.QRflags & QR_READONLY)) {
2703 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2704 return (ERROR + HIGHER_ACCESS_REQUIRED);
2707 strcpy(errmsgbuf, "Ok");
2713 * Check to see if the specified user has Internet mail permission
2714 * (returns nonzero if permission is granted)
2716 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2718 /* Do not allow twits to send Internet mail */
2719 if (who->axlevel <= 2) return(0);
2721 /* Globally enabled? */
2722 if (config.c_restrict == 0) return(1);
2724 /* User flagged ok? */
2725 if (who->flags & US_INTERNET) return(2);
2727 /* Aide level access? */
2728 if (who->axlevel >= 6) return(3);
2730 /* No mail for you! */
2736 * Validate recipients, count delivery types and errors, and handle aliasing
2737 * FIXME check for dupes!!!!!
2738 * Returns 0 if all addresses are ok, -1 if no addresses were specified,
2739 * or the number of addresses found invalid.
2741 struct recptypes *validate_recipients(char *supplied_recipients) {
2742 struct recptypes *ret;
2743 char recipients[SIZ];
2744 char this_recp[256];
2745 char this_recp_cooked[256];
2751 struct ctdluser tempUS;
2752 struct ctdlroom tempQR;
2756 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2757 if (ret == NULL) return(NULL);
2758 memset(ret, 0, sizeof(struct recptypes));
2761 ret->num_internet = 0;
2766 if (supplied_recipients == NULL) {
2767 strcpy(recipients, "");
2770 safestrncpy(recipients, supplied_recipients, sizeof recipients);
2773 /* Change all valid separator characters to commas */
2774 for (i=0; i<strlen(recipients); ++i) {
2775 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2776 recipients[i] = ',';
2780 /* Now start extracting recipients... */
2782 while (strlen(recipients) > 0) {
2784 for (i=0; i<=strlen(recipients); ++i) {
2785 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
2786 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
2787 safestrncpy(this_recp, recipients, i+1);
2789 if (recipients[i] == ',') {
2790 strcpy(recipients, &recipients[i+1]);
2793 strcpy(recipients, "");
2800 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
2802 mailtype = alias(this_recp);
2803 mailtype = alias(this_recp);
2804 mailtype = alias(this_recp);
2805 for (j=0; j<=strlen(this_recp); ++j) {
2806 if (this_recp[j]=='_') {
2807 this_recp_cooked[j] = ' ';
2810 this_recp_cooked[j] = this_recp[j];
2816 if (!strcasecmp(this_recp, "sysop")) {
2818 strcpy(this_recp, config.c_aideroom);
2819 if (strlen(ret->recp_room) > 0) {
2820 strcat(ret->recp_room, "|");
2822 strcat(ret->recp_room, this_recp);
2824 else if (getuser(&tempUS, this_recp) == 0) {
2826 strcpy(this_recp, tempUS.fullname);
2827 if (strlen(ret->recp_local) > 0) {
2828 strcat(ret->recp_local, "|");
2830 strcat(ret->recp_local, this_recp);
2832 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2834 strcpy(this_recp, tempUS.fullname);
2835 if (strlen(ret->recp_local) > 0) {
2836 strcat(ret->recp_local, "|");
2838 strcat(ret->recp_local, this_recp);
2840 else if ( (!strncasecmp(this_recp, "room_", 5))
2841 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2843 if (strlen(ret->recp_room) > 0) {
2844 strcat(ret->recp_room, "|");
2846 strcat(ret->recp_room, &this_recp_cooked[5]);
2854 /* Yes, you're reading this correctly: if the target
2855 * domain points back to the local system or an attached
2856 * Citadel directory, the address is invalid. That's
2857 * because if the address were valid, we would have
2858 * already translated it to a local address by now.
2860 if (IsDirectory(this_recp)) {
2865 ++ret->num_internet;
2866 if (strlen(ret->recp_internet) > 0) {
2867 strcat(ret->recp_internet, "|");
2869 strcat(ret->recp_internet, this_recp);
2874 if (strlen(ret->recp_ignet) > 0) {
2875 strcat(ret->recp_ignet, "|");
2877 strcat(ret->recp_ignet, this_recp);
2885 if (strlen(ret->errormsg) == 0) {
2886 snprintf(append, sizeof append,
2887 "Invalid recipient: %s",
2891 snprintf(append, sizeof append,
2894 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2895 strcat(ret->errormsg, append);
2899 if (strlen(ret->display_recp) == 0) {
2900 strcpy(append, this_recp);
2903 snprintf(append, sizeof append, ", %s",
2906 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2907 strcat(ret->display_recp, append);
2912 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2913 ret->num_room + ret->num_error) == 0) {
2914 ret->num_error = (-1);
2915 strcpy(ret->errormsg, "No recipients specified.");
2918 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2919 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2920 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2921 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2922 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2923 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2931 * message entry - mode 0 (normal)
2933 void cmd_ent0(char *entargs)
2939 char masquerade_as[SIZ];
2941 int format_type = 0;
2942 char newusername[SIZ];
2943 struct CtdlMessage *msg;
2947 struct recptypes *valid = NULL;
2948 struct recptypes *valid_to = NULL;
2949 struct recptypes *valid_cc = NULL;
2950 struct recptypes *valid_bcc = NULL;
2957 post = extract_int(entargs, 0);
2958 extract_token(recp, entargs, 1, '|', sizeof recp);
2959 anon_flag = extract_int(entargs, 2);
2960 format_type = extract_int(entargs, 3);
2961 extract_token(subject, entargs, 4, '|', sizeof subject);
2962 do_confirm = extract_int(entargs, 6);
2963 extract_token(cc, entargs, 7, '|', sizeof cc);
2964 extract_token(bcc, entargs, 8, '|', sizeof bcc);
2966 /* first check to make sure the request is valid. */
2968 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2970 cprintf("%d %s\n", err, errmsg);
2974 /* Check some other permission type things. */
2977 if (CC->user.axlevel < 6) {
2978 cprintf("%d You don't have permission to masquerade.\n",
2979 ERROR + HIGHER_ACCESS_REQUIRED);
2982 extract_token(newusername, entargs, 5, '|', sizeof newusername);
2983 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2984 safestrncpy(CC->fake_postname, newusername,
2985 sizeof(CC->fake_postname) );
2986 cprintf("%d ok\n", CIT_OK);
2989 CC->cs_flags |= CS_POSTING;
2991 /* In the Mail> room we have to behave a little differently --
2992 * make sure the user has specified at least one recipient. Then
2993 * validate the recipient(s).
2995 if ( (CC->room.QRflags & QR_MAILBOX)
2996 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2998 if (CC->user.axlevel < 2) {
2999 strcpy(recp, "sysop");
3004 valid_to = validate_recipients(recp);
3005 if (valid_to->num_error > 0) {
3006 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
3011 valid_cc = validate_recipients(cc);
3012 if (valid_cc->num_error > 0) {
3013 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
3019 valid_bcc = validate_recipients(bcc);
3020 if (valid_bcc->num_error > 0) {
3021 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
3028 /* Recipient required, but none were specified */
3029 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3033 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3037 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3038 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3039 cprintf("%d You do not have permission "
3040 "to send Internet mail.\n",
3041 ERROR + HIGHER_ACCESS_REQUIRED);
3049 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)
3050 && (CC->user.axlevel < 4) ) {
3051 cprintf("%d Higher access required for network mail.\n",
3052 ERROR + HIGHER_ACCESS_REQUIRED);
3059 if ((RESTRICT_INTERNET == 1)
3060 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3061 && ((CC->user.flags & US_INTERNET) == 0)
3062 && (!CC->internal_pgm)) {
3063 cprintf("%d You don't have access to Internet mail.\n",
3064 ERROR + HIGHER_ACCESS_REQUIRED);
3073 /* Is this a room which has anonymous-only or anonymous-option? */
3074 anonymous = MES_NORMAL;
3075 if (CC->room.QRflags & QR_ANONONLY) {
3076 anonymous = MES_ANONONLY;
3078 if (CC->room.QRflags & QR_ANONOPT) {
3079 if (anon_flag == 1) { /* only if the user requested it */
3080 anonymous = MES_ANONOPT;
3084 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3088 /* If we're only checking the validity of the request, return
3089 * success without creating the message.
3092 cprintf("%d %s\n", CIT_OK,
3093 ((valid_to != NULL) ? valid_to->display_recp : "") );
3100 /* We don't need these anymore because we'll do it differently below */
3105 /* Handle author masquerading */
3106 if (CC->fake_postname[0]) {
3107 strcpy(masquerade_as, CC->fake_postname);
3109 else if (CC->fake_username[0]) {
3110 strcpy(masquerade_as, CC->fake_username);
3113 strcpy(masquerade_as, "");
3116 /* Read in the message from the client. */
3118 cprintf("%d send message\n", START_CHAT_MODE);
3120 cprintf("%d send message\n", SEND_LISTING);
3123 msg = CtdlMakeMessage(&CC->user, recp, cc,
3124 CC->room.QRname, anonymous, format_type,
3125 masquerade_as, subject, NULL);
3127 /* Put together one big recipients struct containing to/cc/bcc all in
3128 * one. This is for the envelope.
3130 char *all_recps = malloc(SIZ * 3);
3131 strcpy(all_recps, recp);
3132 if (strlen(cc) > 0) {
3133 if (strlen(all_recps) > 0) {
3134 strcat(all_recps, ",");
3136 strcat(all_recps, cc);
3138 if (strlen(bcc) > 0) {
3139 if (strlen(all_recps) > 0) {
3140 strcat(all_recps, ",");
3142 strcat(all_recps, bcc);
3144 if (strlen(all_recps) > 0) {
3145 valid = validate_recipients(all_recps);
3153 msgnum = CtdlSubmitMsg(msg, valid, "");
3156 cprintf("%ld\n", msgnum);
3158 cprintf("Message accepted.\n");
3161 cprintf("Internal error.\n");
3163 if (msg->cm_fields['E'] != NULL) {
3164 cprintf("%s\n", msg->cm_fields['E']);
3171 CtdlFreeMessage(msg);
3173 CC->fake_postname[0] = '\0';
3174 if (valid != NULL) {
3183 * API function to delete messages which match a set of criteria
3184 * (returns the actual number of messages deleted)
3186 int CtdlDeleteMessages(char *room_name, /* which room */
3187 long dmsgnum, /* or "0" for any */
3188 char *content_type, /* or "" for any */
3189 int deferred /* let TDAP sweep it later */
3193 struct ctdlroom qrbuf;
3194 struct cdbdata *cdbfr;
3195 long *msglist = NULL;
3196 long *dellist = NULL;
3199 int num_deleted = 0;
3201 struct MetaData smi;
3203 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s, %d)\n",
3204 room_name, dmsgnum, content_type, deferred);
3206 /* get room record, obtaining a lock... */
3207 if (lgetroom(&qrbuf, room_name) != 0) {
3208 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3210 return (0); /* room not found */
3212 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3214 if (cdbfr != NULL) {
3215 dellist = malloc(cdbfr->len);
3216 msglist = (long *) cdbfr->ptr;
3217 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3218 num_msgs = cdbfr->len / sizeof(long);
3222 for (i = 0; i < num_msgs; ++i) {
3225 /* Set/clear a bit for each criterion */
3227 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
3228 delete_this |= 0x01;
3230 if (strlen(content_type) == 0) {
3231 delete_this |= 0x02;
3233 GetMetaData(&smi, msglist[i]);
3234 if (!strcasecmp(smi.meta_content_type,
3236 delete_this |= 0x02;
3240 /* Delete message only if all bits are set */
3241 if (delete_this == 0x03) {
3242 dellist[num_deleted++] = msglist[i];
3247 num_msgs = sort_msglist(msglist, num_msgs);
3248 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3249 msglist, (int)(num_msgs * sizeof(long)));
3251 qrbuf.QRhighest = msglist[num_msgs - 1];
3256 * If the delete operation is "deferred" (and technically, any delete
3257 * operation not performed by THE DREADED AUTO-PURGER ought to be
3258 * a deferred delete) then we save a pointer to the message in the
3259 * DELETED_MSGS_ROOM. This will cause the reference count to remain
3260 * at least 1, which will save the user from having to synchronously
3261 * wait for various disk-intensive operations to complete.
3263 if ( (deferred) && (num_deleted) ) {
3264 for (i=0; i<num_deleted; ++i) {
3265 CtdlCopyMsgToRoom(dellist[i], DELETED_MSGS_ROOM);
3269 /* Go through the messages we pulled out of the index, and decrement
3270 * their reference counts by 1. If this is the only room the message
3271 * was in, the reference count will reach zero and the message will
3272 * automatically be deleted from the database. We do this in a
3273 * separate pass because there might be plug-in hooks getting called,
3274 * and we don't want that happening during an S_ROOMS critical
3277 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3278 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3279 AdjRefCount(dellist[i], -1);
3282 /* Now free the memory we used, and go away. */
3283 if (msglist != NULL) free(msglist);
3284 if (dellist != NULL) free(dellist);
3285 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3286 return (num_deleted);
3292 * Check whether the current user has permission to delete messages from
3293 * the current room (returns 1 for yes, 0 for no)
3295 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3296 getuser(&CC->user, CC->curr_user);
3297 if ((CC->user.axlevel < 6)
3298 && (CC->user.usernum != CC->room.QRroomaide)
3299 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3300 && (!(CC->internal_pgm))) {
3309 * Delete message from current room
3311 void cmd_dele(char *delstr)
3316 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3317 cprintf("%d Higher access required.\n",
3318 ERROR + HIGHER_ACCESS_REQUIRED);
3321 delnum = extract_long(delstr, 0);
3323 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "", 1);
3326 cprintf("%d %d message%s deleted.\n", CIT_OK,
3327 num_deleted, ((num_deleted != 1) ? "s" : ""));
3329 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3335 * Back end API function for moves and deletes
3337 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3340 err = CtdlSaveMsgPointerInRoom(dest, msgnum, 1, NULL);
3341 if (err != 0) return(err);
3349 * move or copy a message to another room
3351 void cmd_move(char *args)
3354 char targ[ROOMNAMELEN];
3355 struct ctdlroom qtemp;
3361 num = extract_long(args, 0);
3362 extract_token(targ, args, 1, '|', sizeof targ);
3363 targ[ROOMNAMELEN - 1] = 0;
3364 is_copy = extract_int(args, 2);
3366 if (getroom(&qtemp, targ) != 0) {
3367 cprintf("%d '%s' does not exist.\n",
3368 ERROR + ROOM_NOT_FOUND, targ);
3372 getuser(&CC->user, CC->curr_user);
3373 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3375 /* Check for permission to perform this operation.
3376 * Remember: "CC->room" is source, "qtemp" is target.
3380 /* Aides can move/copy */
3381 if (CC->user.axlevel >= 6) permit = 1;
3383 /* Room aides can move/copy */
3384 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3386 /* Permit move/copy from personal rooms */
3387 if ((CC->room.QRflags & QR_MAILBOX)
3388 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3390 /* Permit only copy from public to personal room */
3392 && (!(CC->room.QRflags & QR_MAILBOX))
3393 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3395 /* User must have access to target room */
3396 if (!(ra & UA_KNOWN)) permit = 0;
3399 cprintf("%d Higher access required.\n",
3400 ERROR + HIGHER_ACCESS_REQUIRED);
3404 err = CtdlCopyMsgToRoom(num, targ);
3406 cprintf("%d Cannot store message in %s: error %d\n",
3411 /* Now delete the message from the source room,
3412 * if this is a 'move' rather than a 'copy' operation.
3415 CtdlDeleteMessages(CC->room.QRname, num, "", 0);
3418 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3424 * GetMetaData() - Get the supplementary record for a message
3426 void GetMetaData(struct MetaData *smibuf, long msgnum)
3429 struct cdbdata *cdbsmi;
3432 memset(smibuf, 0, sizeof(struct MetaData));
3433 smibuf->meta_msgnum = msgnum;
3434 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3436 /* Use the negative of the message number for its supp record index */
3437 TheIndex = (0L - msgnum);
3439 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3440 if (cdbsmi == NULL) {
3441 return; /* record not found; go with defaults */
3443 memcpy(smibuf, cdbsmi->ptr,
3444 ((cdbsmi->len > sizeof(struct MetaData)) ?
3445 sizeof(struct MetaData) : cdbsmi->len));
3452 * PutMetaData() - (re)write supplementary record for a message
3454 void PutMetaData(struct MetaData *smibuf)
3458 /* Use the negative of the message number for the metadata db index */
3459 TheIndex = (0L - smibuf->meta_msgnum);
3461 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3462 smibuf->meta_msgnum, smibuf->meta_refcount);
3464 cdb_store(CDB_MSGMAIN,
3465 &TheIndex, (int)sizeof(long),
3466 smibuf, (int)sizeof(struct MetaData));
3471 * AdjRefCount - change the reference count for a message;
3472 * delete the message if it reaches zero
3474 void AdjRefCount(long msgnum, int incr)
3477 struct MetaData smi;
3480 /* This is a *tight* critical section; please keep it that way, as
3481 * it may get called while nested in other critical sections.
3482 * Complicating this any further will surely cause deadlock!
3484 begin_critical_section(S_SUPPMSGMAIN);
3485 GetMetaData(&smi, msgnum);
3486 smi.meta_refcount += incr;
3488 end_critical_section(S_SUPPMSGMAIN);
3489 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3490 msgnum, incr, smi.meta_refcount);
3492 /* If the reference count is now zero, delete the message
3493 * (and its supplementary record as well).
3495 if (smi.meta_refcount == 0) {
3496 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3498 /* Remove from fulltext index */
3499 if (config.c_enable_fulltext) {
3500 ft_index_message(msgnum, 0);
3503 /* Remove from message base */
3505 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3506 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3508 /* Remove metadata record */
3509 delnum = (0L - msgnum);
3510 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3515 * Write a generic object to this room
3517 * Note: this could be much more efficient. Right now we use two temporary
3518 * files, and still pull the message into memory as with all others.
3520 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3521 char *content_type, /* MIME type of this object */
3522 char *tempfilename, /* Where to fetch it from */
3523 struct ctdluser *is_mailbox, /* Mailbox room? */
3524 int is_binary, /* Is encoding necessary? */
3525 int is_unique, /* Del others of this type? */
3526 unsigned int flags /* Internal save flags */
3531 struct ctdlroom qrbuf;
3532 char roomname[ROOMNAMELEN];
3533 struct CtdlMessage *msg;
3535 char *raw_message = NULL;
3536 char *encoded_message = NULL;
3537 off_t raw_length = 0;
3539 if (is_mailbox != NULL)
3540 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3542 safestrncpy(roomname, req_room, sizeof(roomname));
3543 lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3546 fp = fopen(tempfilename, "rb");
3548 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3549 tempfilename, strerror(errno));
3552 fseek(fp, 0L, SEEK_END);
3553 raw_length = ftell(fp);
3555 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3557 raw_message = malloc((size_t)raw_length + 2);
3558 fread(raw_message, (size_t)raw_length, 1, fp);
3562 encoded_message = malloc((size_t)
3563 (((raw_length * 134) / 100) + 4096 ) );
3566 encoded_message = malloc((size_t)(raw_length + 4096));
3569 sprintf(encoded_message, "Content-type: %s\n", content_type);
3572 sprintf(&encoded_message[strlen(encoded_message)],
3573 "Content-transfer-encoding: base64\n\n"
3577 sprintf(&encoded_message[strlen(encoded_message)],
3578 "Content-transfer-encoding: 7bit\n\n"
3584 &encoded_message[strlen(encoded_message)],
3590 raw_message[raw_length] = 0;
3592 &encoded_message[strlen(encoded_message)],
3600 lprintf(CTDL_DEBUG, "Allocating\n");
3601 msg = malloc(sizeof(struct CtdlMessage));
3602 memset(msg, 0, sizeof(struct CtdlMessage));
3603 msg->cm_magic = CTDLMESSAGE_MAGIC;
3604 msg->cm_anon_type = MES_NORMAL;
3605 msg->cm_format_type = 4;
3606 msg->cm_fields['A'] = strdup(CC->user.fullname);
3607 msg->cm_fields['O'] = strdup(req_room);
3608 msg->cm_fields['N'] = strdup(config.c_nodename);
3609 msg->cm_fields['H'] = strdup(config.c_humannode);
3610 msg->cm_flags = flags;
3612 msg->cm_fields['M'] = encoded_message;
3614 /* Create the requested room if we have to. */
3615 if (getroom(&qrbuf, roomname) != 0) {
3616 create_room(roomname,
3617 ( (is_mailbox != NULL) ? 5 : 3 ),
3618 "", 0, 1, 0, VIEW_BBS);
3620 /* If the caller specified this object as unique, delete all
3621 * other objects of this type that are currently in the room.
3624 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3625 CtdlDeleteMessages(roomname, 0L, content_type, 0)
3628 /* Now write the data */
3629 CtdlSubmitMsg(msg, NULL, roomname);
3630 CtdlFreeMessage(msg);
3638 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3639 config_msgnum = msgnum;
3643 char *CtdlGetSysConfig(char *sysconfname) {
3644 char hold_rm[ROOMNAMELEN];
3647 struct CtdlMessage *msg;
3650 strcpy(hold_rm, CC->room.QRname);
3651 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3652 getroom(&CC->room, hold_rm);
3657 /* We want the last (and probably only) config in this room */
3658 begin_critical_section(S_CONFIG);
3659 config_msgnum = (-1L);
3660 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3661 CtdlGetSysConfigBackend, NULL);
3662 msgnum = config_msgnum;
3663 end_critical_section(S_CONFIG);
3669 msg = CtdlFetchMessage(msgnum, 1);
3671 conf = strdup(msg->cm_fields['M']);
3672 CtdlFreeMessage(msg);
3679 getroom(&CC->room, hold_rm);
3681 if (conf != NULL) do {
3682 extract_token(buf, conf, 0, '\n', sizeof buf);
3683 strcpy(conf, &conf[strlen(buf)+1]);
3684 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3689 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3690 char temp[PATH_MAX];
3693 strcpy(temp, tmpnam(NULL));
3695 fp = fopen(temp, "w");
3696 if (fp == NULL) return;
3697 fprintf(fp, "%s", sysconfdata);
3700 /* this handy API function does all the work for us */
3701 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3707 * Determine whether a given Internet address belongs to the current user
3709 int CtdlIsMe(char *addr, int addr_buf_len)
3711 struct recptypes *recp;
3714 recp = validate_recipients(addr);
3715 if (recp == NULL) return(0);
3717 if (recp->num_local == 0) {
3722 for (i=0; i<recp->num_local; ++i) {
3723 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3724 if (!strcasecmp(addr, CC->user.fullname)) {
3736 * Citadel protocol command to do the same
3738 void cmd_isme(char *argbuf) {
3741 if (CtdlAccessCheck(ac_logged_in)) return;
3742 extract_token(addr, argbuf, 0, '|', sizeof addr);
3744 if (CtdlIsMe(addr, sizeof addr)) {
3745 cprintf("%d %s\n", CIT_OK, addr);
3748 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);