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 if (num_tokens(partnum, '.') < 3) {
1172 lprintf(CTDL_DEBUG, "REPLACING MA <%s> WITH <%s>\n",
1173 ma->chosen_part, partnum);
1174 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1182 * Now that we've chosen our preferred part, output it.
1184 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1185 void *content, char *cbtype, char *cbcharset, size_t length,
1186 char *encoding, void *cbuserdata)
1190 int add_newline = 0;
1194 ma = (struct ma_info *)cbuserdata;
1196 /* This is not the MIME part you're looking for... */
1197 if (strcasecmp(partnum, ma->chosen_part)) return;
1199 /* If the content-type of this part is in our preferred formats
1200 * list, we can simply output it verbatim.
1202 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1203 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1204 if (!strcasecmp(buf, cbtype)) {
1205 /* Yeah! Go! W00t!! */
1207 text_content = (char *)content;
1208 if (text_content[length-1] != '\n') {
1212 cprintf("Content-type: %s", cbtype);
1213 if (strlen(cbcharset) > 0) {
1214 cprintf("; charset=%s", cbcharset);
1216 cprintf("\nContent-length: %d\n",
1217 (int)(length + add_newline) );
1218 if (strlen(encoding) > 0) {
1219 cprintf("Content-transfer-encoding: %s\n", encoding);
1222 cprintf("Content-transfer-encoding: 7bit\n");
1225 client_write(content, length);
1226 if (add_newline) cprintf("\n");
1231 /* No translations required or possible: output as text/plain */
1232 cprintf("Content-type: text/plain\n\n");
1233 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1234 length, encoding, cbuserdata);
1239 char desired_section[64];
1246 * Callback function for
1248 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1249 void *content, char *cbtype, char *cbcharset, size_t length,
1250 char *encoding, void *cbuserdata)
1252 struct encapmsg *encap;
1254 encap = (struct encapmsg *)cbuserdata;
1256 /* Only proceed if this is the desired section... */
1257 if (!strcasecmp(encap->desired_section, partnum)) {
1258 encap->msglen = length;
1259 encap->msg = malloc(length + 2);
1260 memcpy(encap->msg, content, length);
1270 * Get a message off disk. (returns om_* values found in msgbase.h)
1273 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1274 int mode, /* how would you like that message? */
1275 int headers_only, /* eschew the message body? */
1276 int do_proto, /* do Citadel protocol responses? */
1277 int crlf, /* Use CRLF newlines instead of LF? */
1278 char *section /* NULL or a message/rfc822 section */
1280 struct CtdlMessage *TheMessage = NULL;
1281 int retcode = om_no_such_msg;
1282 struct encapmsg encap;
1284 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d, section=%s\n",
1286 (section ? section : "<>")
1289 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1290 if (do_proto) cprintf("%d Not logged in.\n",
1291 ERROR + NOT_LOGGED_IN);
1292 return(om_not_logged_in);
1295 /* FIXME: check message id against msglist for this room */
1298 * Fetch the message from disk. If we're in any sort of headers
1299 * only mode, request that we don't even bother loading the body
1302 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1303 TheMessage = CtdlFetchMessage(msg_num, 0);
1306 TheMessage = CtdlFetchMessage(msg_num, 1);
1309 if (TheMessage == NULL) {
1310 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1311 ERROR + MESSAGE_NOT_FOUND, msg_num);
1312 return(om_no_such_msg);
1315 /* Here is the weird form of this command, to process only an
1316 * encapsulated message/rfc822 section.
1319 memset(&encap, 0, sizeof encap);
1320 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1321 mime_parser(TheMessage->cm_fields['M'],
1323 *extract_encapsulated_message,
1324 NULL, NULL, (void *)&encap, 0
1326 CtdlFreeMessage(TheMessage);
1330 encap.msg[encap.msglen] = 0;
1331 TheMessage = convert_internet_message(encap.msg);
1332 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1334 /* Now we let it fall through to the bottom of this
1335 * function, because TheMessage now contains the
1336 * encapsulated message instead of the top-level
1337 * message. Isn't that neat?
1342 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1343 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1344 retcode = om_no_such_msg;
1349 /* Ok, output the message now */
1350 retcode = CtdlOutputPreLoadedMsg(
1352 headers_only, do_proto, crlf);
1353 CtdlFreeMessage(TheMessage);
1360 * Get a message off disk. (returns om_* values found in msgbase.h)
1363 int CtdlOutputPreLoadedMsg(
1364 struct CtdlMessage *TheMessage,
1365 int mode, /* how would you like that message? */
1366 int headers_only, /* eschew the message body? */
1367 int do_proto, /* do Citadel protocol responses? */
1368 int crlf /* Use CRLF newlines instead of LF? */
1374 char display_name[256];
1376 char *nl; /* newline string */
1378 int subject_found = 0;
1381 /* Buffers needed for RFC822 translation. These are all filled
1382 * using functions that are bounds-checked, and therefore we can
1383 * make them substantially smaller than SIZ.
1391 char datestamp[100];
1393 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1394 ((TheMessage == NULL) ? "NULL" : "not null"),
1395 mode, headers_only, do_proto, crlf);
1397 strcpy(mid, "unknown");
1398 nl = (crlf ? "\r\n" : "\n");
1400 if (!is_valid_message(TheMessage)) {
1402 "ERROR: invalid preloaded message for output\n");
1403 return(om_no_such_msg);
1406 /* Are we downloading a MIME component? */
1407 if (mode == MT_DOWNLOAD) {
1408 if (TheMessage->cm_format_type != FMT_RFC822) {
1410 cprintf("%d This is not a MIME message.\n",
1411 ERROR + ILLEGAL_VALUE);
1412 } else if (CC->download_fp != NULL) {
1413 if (do_proto) cprintf(
1414 "%d You already have a download open.\n",
1415 ERROR + RESOURCE_BUSY);
1417 /* Parse the message text component */
1418 mptr = TheMessage->cm_fields['M'];
1419 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1420 /* If there's no file open by this time, the requested
1421 * section wasn't found, so print an error
1423 if (CC->download_fp == NULL) {
1424 if (do_proto) cprintf(
1425 "%d Section %s not found.\n",
1426 ERROR + FILE_NOT_FOUND,
1427 CC->download_desired_section);
1430 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1433 /* now for the user-mode message reading loops */
1434 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1436 /* Does the caller want to skip the headers? */
1437 if (headers_only == HEADERS_NONE) goto START_TEXT;
1439 /* Tell the client which format type we're using. */
1440 if ( (mode == MT_CITADEL) && (do_proto) ) {
1441 cprintf("type=%d\n", TheMessage->cm_format_type);
1444 /* nhdr=yes means that we're only displaying headers, no body */
1445 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1446 && (mode == MT_CITADEL)
1449 cprintf("nhdr=yes\n");
1452 /* begin header processing loop for Citadel message format */
1454 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1456 safestrncpy(display_name, "<unknown>", sizeof display_name);
1457 if (TheMessage->cm_fields['A']) {
1458 strcpy(buf, TheMessage->cm_fields['A']);
1459 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1460 safestrncpy(display_name, "****", sizeof display_name);
1462 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1463 safestrncpy(display_name, "anonymous", sizeof display_name);
1466 safestrncpy(display_name, buf, sizeof display_name);
1468 if ((is_room_aide())
1469 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1470 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1471 size_t tmp = strlen(display_name);
1472 snprintf(&display_name[tmp],
1473 sizeof display_name - tmp,
1478 /* Don't show Internet address for users on the
1479 * local Citadel network.
1482 if (TheMessage->cm_fields['N'] != NULL)
1483 if (strlen(TheMessage->cm_fields['N']) > 0)
1484 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1488 /* Now spew the header fields in the order we like them. */
1489 safestrncpy(allkeys, FORDER, sizeof allkeys);
1490 for (i=0; i<strlen(allkeys); ++i) {
1491 k = (int) allkeys[i];
1493 if ( (TheMessage->cm_fields[k] != NULL)
1494 && (msgkeys[k] != NULL) ) {
1496 if (do_proto) cprintf("%s=%s\n",
1500 else if ((k == 'F') && (suppress_f)) {
1503 /* Masquerade display name if needed */
1505 if (do_proto) cprintf("%s=%s\n",
1507 TheMessage->cm_fields[k]
1516 /* begin header processing loop for RFC822 transfer format */
1521 strcpy(snode, NODENAME);
1522 strcpy(lnode, HUMANNODE);
1523 if (mode == MT_RFC822) {
1524 for (i = 0; i < 256; ++i) {
1525 if (TheMessage->cm_fields[i]) {
1526 mptr = TheMessage->cm_fields[i];
1529 safestrncpy(luser, mptr, sizeof luser);
1530 safestrncpy(suser, mptr, sizeof suser);
1532 else if (i == 'Y') {
1533 cprintf("CC: %s%s", mptr, nl);
1535 else if (i == 'U') {
1536 cprintf("Subject: %s%s", mptr, nl);
1540 safestrncpy(mid, mptr, sizeof mid);
1542 safestrncpy(lnode, mptr, sizeof lnode);
1544 safestrncpy(fuser, mptr, sizeof fuser);
1545 /* else if (i == 'O')
1546 cprintf("X-Citadel-Room: %s%s",
1549 safestrncpy(snode, mptr, sizeof snode);
1551 cprintf("To: %s%s", mptr, nl);
1552 else if (i == 'T') {
1553 datestring(datestamp, sizeof datestamp,
1554 atol(mptr), DATESTRING_RFC822);
1555 cprintf("Date: %s%s", datestamp, nl);
1559 if (subject_found == 0) {
1560 cprintf("Subject: (no subject)%s", nl);
1564 for (i=0; i<strlen(suser); ++i) {
1565 suser[i] = tolower(suser[i]);
1566 if (!isalnum(suser[i])) suser[i]='_';
1569 if (mode == MT_RFC822) {
1570 if (!strcasecmp(snode, NODENAME)) {
1571 safestrncpy(snode, FQDN, sizeof snode);
1574 /* Construct a fun message id */
1575 cprintf("Message-ID: <%s", mid);
1576 if (strchr(mid, '@')==NULL) {
1577 cprintf("@%s", snode);
1581 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1582 cprintf("From: \"----\" <x@x.org>%s", nl);
1584 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1585 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1587 else if (strlen(fuser) > 0) {
1588 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1591 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1594 cprintf("Organization: %s%s", lnode, nl);
1596 /* Blank line signifying RFC822 end-of-headers */
1597 if (TheMessage->cm_format_type != FMT_RFC822) {
1602 /* end header processing loop ... at this point, we're in the text */
1604 if (headers_only == HEADERS_FAST) goto DONE;
1605 mptr = TheMessage->cm_fields['M'];
1607 /* Tell the client about the MIME parts in this message */
1608 if (TheMessage->cm_format_type == FMT_RFC822) {
1609 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1610 memset(&ma, 0, sizeof(struct ma_info));
1611 mime_parser(mptr, NULL,
1612 (do_proto ? *list_this_part : NULL),
1613 (do_proto ? *list_this_pref : NULL),
1614 (do_proto ? *list_this_suff : NULL),
1617 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1618 char *start_of_text = NULL;
1619 start_of_text = strstr(mptr, "\n\r\n");
1620 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
1621 if (start_of_text == NULL) start_of_text = mptr;
1623 start_of_text = strstr(start_of_text, "\n");
1625 while (ch=*mptr, ch!=0) {
1629 else switch(headers_only) {
1631 if (mptr >= start_of_text) {
1632 if (ch == 10) cprintf("%s", nl);
1633 else cprintf("%c", ch);
1637 if (mptr < start_of_text) {
1638 if (ch == 10) cprintf("%s", nl);
1639 else cprintf("%c", ch);
1643 if (ch == 10) cprintf("%s", nl);
1644 else cprintf("%c", ch);
1653 if (headers_only == HEADERS_ONLY) {
1657 /* signify start of msg text */
1658 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1659 if (do_proto) cprintf("text\n");
1662 /* If the format type on disk is 1 (fixed-format), then we want
1663 * everything to be output completely literally ... regardless of
1664 * what message transfer format is in use.
1666 if (TheMessage->cm_format_type == FMT_FIXED) {
1667 if (mode == MT_MIME) {
1668 cprintf("Content-type: text/plain\n\n");
1671 while (ch = *mptr++, ch > 0) {
1674 if ((ch == 10) || (strlen(buf) > 250)) {
1675 cprintf("%s%s", buf, nl);
1678 buf[strlen(buf) + 1] = 0;
1679 buf[strlen(buf)] = ch;
1682 if (strlen(buf) > 0)
1683 cprintf("%s%s", buf, nl);
1686 /* If the message on disk is format 0 (Citadel vari-format), we
1687 * output using the formatter at 80 columns. This is the final output
1688 * form if the transfer format is RFC822, but if the transfer format
1689 * is Citadel proprietary, it'll still work, because the indentation
1690 * for new paragraphs is correct and the client will reformat the
1691 * message to the reader's screen width.
1693 if (TheMessage->cm_format_type == FMT_CITADEL) {
1694 if (mode == MT_MIME) {
1695 cprintf("Content-type: text/x-citadel-variformat\n\n");
1697 memfmout(80, mptr, 0, nl);
1700 /* If the message on disk is format 4 (MIME), we've gotta hand it
1701 * off to the MIME parser. The client has already been told that
1702 * this message is format 1 (fixed format), so the callback function
1703 * we use will display those parts as-is.
1705 if (TheMessage->cm_format_type == FMT_RFC822) {
1706 memset(&ma, 0, sizeof(struct ma_info));
1708 if (mode == MT_MIME) {
1709 strcpy(ma.chosen_part, "1");
1710 mime_parser(mptr, NULL,
1711 *choose_preferred, *fixed_output_pre,
1712 *fixed_output_post, (void *)&ma, 0);
1713 mime_parser(mptr, NULL,
1714 *output_preferred, NULL, NULL, (void *)&ma, 0);
1717 mime_parser(mptr, NULL,
1718 *fixed_output, *fixed_output_pre,
1719 *fixed_output_post, (void *)&ma, 0);
1724 DONE: /* now we're done */
1725 if (do_proto) cprintf("000\n");
1732 * display a message (mode 0 - Citadel proprietary)
1734 void cmd_msg0(char *cmdbuf)
1737 int headers_only = HEADERS_ALL;
1739 msgid = extract_long(cmdbuf, 0);
1740 headers_only = extract_int(cmdbuf, 1);
1742 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL);
1748 * display a message (mode 2 - RFC822)
1750 void cmd_msg2(char *cmdbuf)
1753 int headers_only = HEADERS_ALL;
1755 msgid = extract_long(cmdbuf, 0);
1756 headers_only = extract_int(cmdbuf, 1);
1758 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL);
1764 * display a message (mode 3 - IGnet raw format - internal programs only)
1766 void cmd_msg3(char *cmdbuf)
1769 struct CtdlMessage *msg;
1772 if (CC->internal_pgm == 0) {
1773 cprintf("%d This command is for internal programs only.\n",
1774 ERROR + HIGHER_ACCESS_REQUIRED);
1778 msgnum = extract_long(cmdbuf, 0);
1779 msg = CtdlFetchMessage(msgnum, 1);
1781 cprintf("%d Message %ld not found.\n",
1782 ERROR + MESSAGE_NOT_FOUND, msgnum);
1786 serialize_message(&smr, msg);
1787 CtdlFreeMessage(msg);
1790 cprintf("%d Unable to serialize message\n",
1791 ERROR + INTERNAL_ERROR);
1795 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1796 client_write((char *)smr.ser, (int)smr.len);
1803 * Display a message using MIME content types
1805 void cmd_msg4(char *cmdbuf)
1810 msgid = extract_long(cmdbuf, 0);
1811 extract_token(section, cmdbuf, 1, '|', sizeof section);
1812 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) );
1818 * Client tells us its preferred message format(s)
1820 void cmd_msgp(char *cmdbuf)
1822 safestrncpy(CC->preferred_formats, cmdbuf,
1823 sizeof(CC->preferred_formats));
1824 cprintf("%d ok\n", CIT_OK);
1829 * Open a component of a MIME message as a download file
1831 void cmd_opna(char *cmdbuf)
1834 char desired_section[128];
1836 msgid = extract_long(cmdbuf, 0);
1837 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1838 safestrncpy(CC->download_desired_section, desired_section,
1839 sizeof CC->download_desired_section);
1840 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL);
1845 * Save a message pointer into a specified room
1846 * (Returns 0 for success, nonzero for failure)
1847 * roomname may be NULL to use the current room
1849 * Note that the 'supplied_msg' field may be set to NULL, in which case
1850 * the message will be fetched from disk, by number, if we need to perform
1851 * replication checks. This adds an additional database read, so if the
1852 * caller already has the message in memory then it should be supplied.
1854 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check,
1855 struct CtdlMessage *supplied_msg) {
1857 char hold_rm[ROOMNAMELEN];
1858 struct cdbdata *cdbfr;
1861 long highest_msg = 0L;
1862 struct CtdlMessage *msg = NULL;
1864 /*lprintf(CTDL_DEBUG,
1865 "CtdlSaveMsgPointerInRoom(room=%s, msgid=%ld, repl=%d)\n",
1866 roomname, msgid, do_repl_check);*/
1868 strcpy(hold_rm, CC->room.QRname);
1870 /* Now the regular stuff */
1871 if (lgetroom(&CC->room,
1872 ((roomname != NULL) ? roomname : CC->room.QRname) )
1874 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1875 return(ERROR + ROOM_NOT_FOUND);
1878 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1879 if (cdbfr == NULL) {
1883 msglist = (long *) cdbfr->ptr;
1884 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
1885 num_msgs = cdbfr->len / sizeof(long);
1889 /* Make sure the message doesn't already exist in this room. It
1890 * is absolutely taboo to have more than one reference to the same
1891 * message in a room.
1893 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1894 if (msglist[i] == msgid) {
1895 lputroom(&CC->room); /* unlock the room */
1896 getroom(&CC->room, hold_rm);
1898 return(ERROR + ALREADY_EXISTS);
1902 /* Now add the new message */
1904 msglist = realloc(msglist, (num_msgs * sizeof(long)));
1906 if (msglist == NULL) {
1907 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1909 msglist[num_msgs - 1] = msgid;
1911 /* Sort the message list, so all the msgid's are in order */
1912 num_msgs = sort_msglist(msglist, num_msgs);
1914 /* Determine the highest message number */
1915 highest_msg = msglist[num_msgs - 1];
1917 /* Write it back to disk. */
1918 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1919 msglist, (int)(num_msgs * sizeof(long)));
1921 /* Free up the memory we used. */
1924 /* Update the highest-message pointer and unlock the room. */
1925 CC->room.QRhighest = highest_msg;
1926 lputroom(&CC->room);
1928 /* Perform replication checks if necessary */
1929 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
1930 if (supplied_msg != NULL) {
1934 msg = CtdlFetchMessage(msgid, 0);
1938 ReplicationChecks(msg);
1943 /* If the message has an Exclusive ID, index that... */
1945 if (msg->cm_fields['E'] != NULL) {
1946 index_message_by_euid(msg->cm_fields['E'],
1951 /* Free up the memory we may have allocated */
1952 if ( (msg != NULL) && (msg != supplied_msg) ) {
1953 CtdlFreeMessage(msg);
1956 /* Go back to the room we were in before we wandered here... */
1957 getroom(&CC->room, hold_rm);
1959 /* Bump the reference count for this message. */
1960 AdjRefCount(msgid, +1);
1962 /* Return success. */
1969 * Message base operation to save a new message to the message store
1970 * (returns new message number)
1972 * This is the back end for CtdlSubmitMsg() and should not be directly
1973 * called by server-side modules.
1976 long send_message(struct CtdlMessage *msg) {
1984 /* Get a new message number */
1985 newmsgid = get_new_message_number();
1986 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1988 /* Generate an ID if we don't have one already */
1989 if (msg->cm_fields['I']==NULL) {
1990 msg->cm_fields['I'] = strdup(msgidbuf);
1993 /* If the message is big, set its body aside for storage elsewhere */
1994 if (msg->cm_fields['M'] != NULL) {
1995 if (strlen(msg->cm_fields['M']) > BIGMSG) {
1997 holdM = msg->cm_fields['M'];
1998 msg->cm_fields['M'] = NULL;
2002 /* Serialize our data structure for storage in the database */
2003 serialize_message(&smr, msg);
2006 msg->cm_fields['M'] = holdM;
2010 cprintf("%d Unable to serialize message\n",
2011 ERROR + INTERNAL_ERROR);
2015 /* Write our little bundle of joy into the message base */
2016 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2017 smr.ser, smr.len) < 0) {
2018 lprintf(CTDL_ERR, "Can't store message\n");
2022 cdb_store(CDB_BIGMSGS,
2032 /* Free the memory we used for the serialized message */
2035 /* Return the *local* message ID to the caller
2036 * (even if we're storing an incoming network message)
2044 * Serialize a struct CtdlMessage into the format used on disk and network.
2046 * This function loads up a "struct ser_ret" (defined in server.h) which
2047 * contains the length of the serialized message and a pointer to the
2048 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2050 void serialize_message(struct ser_ret *ret, /* return values */
2051 struct CtdlMessage *msg) /* unserialized msg */
2055 static char *forder = FORDER;
2057 if (is_valid_message(msg) == 0) return; /* self check */
2060 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2061 ret->len = ret->len +
2062 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2064 lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
2065 ret->ser = malloc(ret->len);
2066 if (ret->ser == NULL) {
2072 ret->ser[1] = msg->cm_anon_type;
2073 ret->ser[2] = msg->cm_format_type;
2076 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2077 ret->ser[wlen++] = (char)forder[i];
2078 strcpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
2079 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
2081 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2082 (long)ret->len, (long)wlen);
2090 * Check to see if any messages already exist in the current room which
2091 * carry the same Exclusive ID as this one. If any are found, delete them.
2093 void ReplicationChecks(struct CtdlMessage *msg) {
2094 long old_msgnum = (-1L);
2096 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2098 lprintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2101 /* No exclusive id? Don't do anything. */
2102 if (msg == NULL) return;
2103 if (msg->cm_fields['E'] == NULL) return;
2104 if (strlen(msg->cm_fields['E']) == 0) return;
2105 /*lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2106 msg->cm_fields['E'], CC->room.QRname);*/
2108 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2109 if (old_msgnum > 0L) {
2110 lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2111 CtdlDeleteMessages(CC->room.QRname, old_msgnum, "", 0);
2118 * Save a message to disk and submit it into the delivery system.
2120 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2121 struct recptypes *recps, /* recipients (if mail) */
2122 char *force /* force a particular room? */
2124 char submit_filename[128];
2125 char generated_timestamp[32];
2126 char hold_rm[ROOMNAMELEN];
2127 char actual_rm[ROOMNAMELEN];
2128 char force_room[ROOMNAMELEN];
2129 char content_type[SIZ]; /* We have to learn this */
2130 char recipient[SIZ];
2133 struct ctdluser userbuf;
2135 struct MetaData smi;
2136 FILE *network_fp = NULL;
2137 static int seqnum = 1;
2138 struct CtdlMessage *imsg = NULL;
2141 char *hold_R, *hold_D;
2142 char *collected_addresses = NULL;
2143 struct addresses_to_be_filed *aptr = NULL;
2145 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2146 if (is_valid_message(msg) == 0) return(-1); /* self check */
2148 /* If this message has no timestamp, we take the liberty of
2149 * giving it one, right now.
2151 if (msg->cm_fields['T'] == NULL) {
2152 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2153 msg->cm_fields['T'] = strdup(generated_timestamp);
2156 /* If this message has no path, we generate one.
2158 if (msg->cm_fields['P'] == NULL) {
2159 if (msg->cm_fields['A'] != NULL) {
2160 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2161 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2162 if (isspace(msg->cm_fields['P'][a])) {
2163 msg->cm_fields['P'][a] = ' ';
2168 msg->cm_fields['P'] = strdup("unknown");
2172 if (force == NULL) {
2173 strcpy(force_room, "");
2176 strcpy(force_room, force);
2179 /* Learn about what's inside, because it's what's inside that counts */
2180 if (msg->cm_fields['M'] == NULL) {
2181 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2185 switch (msg->cm_format_type) {
2187 strcpy(content_type, "text/x-citadel-variformat");
2190 strcpy(content_type, "text/plain");
2193 strcpy(content_type, "text/plain");
2194 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type: ");
2196 safestrncpy(content_type, &mptr[14],
2197 sizeof content_type);
2198 for (a = 0; a < strlen(content_type); ++a) {
2199 if ((content_type[a] == ';')
2200 || (content_type[a] == ' ')
2201 || (content_type[a] == 13)
2202 || (content_type[a] == 10)) {
2203 content_type[a] = 0;
2209 /* Goto the correct room */
2210 lprintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CC->room.QRname : SENTITEMS);
2211 strcpy(hold_rm, CC->room.QRname);
2212 strcpy(actual_rm, CC->room.QRname);
2213 if (recps != NULL) {
2214 strcpy(actual_rm, SENTITEMS);
2217 /* If the user is a twit, move to the twit room for posting */
2219 if (CC->user.axlevel == 2) {
2220 strcpy(hold_rm, actual_rm);
2221 strcpy(actual_rm, config.c_twitroom);
2222 lprintf(CTDL_DEBUG, "Diverting to twit room\n");
2226 /* ...or if this message is destined for Aide> then go there. */
2227 if (strlen(force_room) > 0) {
2228 strcpy(actual_rm, force_room);
2231 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2232 if (strcasecmp(actual_rm, CC->room.QRname)) {
2233 /* getroom(&CC->room, actual_rm); */
2234 usergoto(actual_rm, 0, 1, NULL, NULL);
2238 * If this message has no O (room) field, generate one.
2240 if (msg->cm_fields['O'] == NULL) {
2241 msg->cm_fields['O'] = strdup(CC->room.QRname);
2244 /* Perform "before save" hooks (aborting if any return nonzero) */
2245 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2246 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2249 * If this message has an Exclusive ID, and the room is replication
2250 * checking enabled, then do replication checks.
2252 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2253 ReplicationChecks(msg);
2256 /* Save it to disk */
2257 lprintf(CTDL_DEBUG, "Saving to disk\n");
2258 newmsgid = send_message(msg);
2259 if (newmsgid <= 0L) return(-5);
2261 /* Write a supplemental message info record. This doesn't have to
2262 * be a critical section because nobody else knows about this message
2265 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2266 memset(&smi, 0, sizeof(struct MetaData));
2267 smi.meta_msgnum = newmsgid;
2268 smi.meta_refcount = 0;
2269 safestrncpy(smi.meta_content_type, content_type,
2270 sizeof smi.meta_content_type);
2272 /* As part of the new metadata record, measure how
2273 * big this message will be when displayed as RFC822.
2274 * Both POP and IMAP use this, and it's best to just take the hit now
2275 * instead of having to potentially measure thousands of messages when
2276 * a mailbox is opened later.
2279 if (CC->redirect_buffer != NULL) {
2280 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2283 CC->redirect_buffer = malloc(SIZ);
2284 CC->redirect_len = 0;
2285 CC->redirect_alloc = SIZ;
2286 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
2287 smi.meta_rfc822_length = CC->redirect_len;
2288 free(CC->redirect_buffer);
2289 CC->redirect_buffer = NULL;
2290 CC->redirect_len = 0;
2291 CC->redirect_alloc = 0;
2295 /* Now figure out where to store the pointers */
2296 lprintf(CTDL_DEBUG, "Storing pointers\n");
2298 /* If this is being done by the networker delivering a private
2299 * message, we want to BYPASS saving the sender's copy (because there
2300 * is no local sender; it would otherwise go to the Trashcan).
2302 if ((!CC->internal_pgm) || (recps == NULL)) {
2303 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2304 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2305 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2309 /* For internet mail, drop a copy in the outbound queue room */
2311 if (recps->num_internet > 0) {
2312 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2315 /* If other rooms are specified, drop them there too. */
2317 if (recps->num_room > 0)
2318 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2319 extract_token(recipient, recps->recp_room, i,
2320 '|', sizeof recipient);
2321 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2322 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2325 /* Bump this user's messages posted counter. */
2326 lprintf(CTDL_DEBUG, "Updating user\n");
2327 lgetuser(&CC->user, CC->curr_user);
2328 CC->user.posted = CC->user.posted + 1;
2329 lputuser(&CC->user);
2331 /* If this is private, local mail, make a copy in the
2332 * recipient's mailbox and bump the reference count.
2335 if (recps->num_local > 0)
2336 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2337 extract_token(recipient, recps->recp_local, i,
2338 '|', sizeof recipient);
2339 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2341 if (getuser(&userbuf, recipient) == 0) {
2342 MailboxName(actual_rm, sizeof actual_rm,
2343 &userbuf, MAILROOM);
2344 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2345 BumpNewMailCounter(userbuf.usernum);
2348 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2349 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2354 /* Perform "after save" hooks */
2355 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2356 PerformMessageHooks(msg, EVT_AFTERSAVE);
2358 /* For IGnet mail, we have to save a new copy into the spooler for
2359 * each recipient, with the R and D fields set to the recipient and
2360 * destination-node. This has two ugly side effects: all other
2361 * recipients end up being unlisted in this recipient's copy of the
2362 * message, and it has to deliver multiple messages to the same
2363 * node. We'll revisit this again in a year or so when everyone has
2364 * a network spool receiver that can handle the new style messages.
2367 if (recps->num_ignet > 0)
2368 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2369 extract_token(recipient, recps->recp_ignet, i,
2370 '|', sizeof recipient);
2372 hold_R = msg->cm_fields['R'];
2373 hold_D = msg->cm_fields['D'];
2374 msg->cm_fields['R'] = malloc(SIZ);
2375 msg->cm_fields['D'] = malloc(128);
2376 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2377 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2379 serialize_message(&smr, msg);
2381 snprintf(submit_filename, sizeof submit_filename,
2382 #ifndef HAVE_SPOOL_DIR
2387 "/network/spoolin/netmail.%04lx.%04x.%04x",
2388 (long) getpid(), CC->cs_pid, ++seqnum);
2389 network_fp = fopen(submit_filename, "wb+");
2390 if (network_fp != NULL) {
2391 fwrite(smr.ser, smr.len, 1, network_fp);
2397 free(msg->cm_fields['R']);
2398 free(msg->cm_fields['D']);
2399 msg->cm_fields['R'] = hold_R;
2400 msg->cm_fields['D'] = hold_D;
2403 /* Go back to the room we started from */
2404 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2405 if (strcasecmp(hold_rm, CC->room.QRname))
2406 /* getroom(&CC->room, hold_rm); */
2407 usergoto(hold_rm, 0, 1, NULL, NULL);
2409 /* For internet mail, generate delivery instructions.
2410 * Yes, this is recursive. Deal with it. Infinite recursion does
2411 * not happen because the delivery instructions message does not
2412 * contain a recipient.
2415 if (recps->num_internet > 0) {
2416 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2417 instr = malloc(SIZ * 2);
2418 snprintf(instr, SIZ * 2,
2419 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2421 SPOOLMIME, newmsgid, (long)time(NULL),
2422 msg->cm_fields['A'], msg->cm_fields['N']
2425 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2426 size_t tmp = strlen(instr);
2427 extract_token(recipient, recps->recp_internet,
2428 i, '|', sizeof recipient);
2429 snprintf(&instr[tmp], SIZ * 2 - tmp,
2430 "remote|%s|0||\n", recipient);
2433 imsg = malloc(sizeof(struct CtdlMessage));
2434 memset(imsg, 0, sizeof(struct CtdlMessage));
2435 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2436 imsg->cm_anon_type = MES_NORMAL;
2437 imsg->cm_format_type = FMT_RFC822;
2438 imsg->cm_fields['A'] = strdup("Citadel");
2439 imsg->cm_fields['M'] = instr;
2440 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2441 CtdlFreeMessage(imsg);
2445 * Any addresses to harvest for someone's address book?
2447 if ( (CC->logged_in) && (recps != NULL) ) {
2448 collected_addresses = harvest_collected_addresses(msg);
2451 if (collected_addresses != NULL) {
2452 begin_critical_section(S_ATBF);
2453 aptr = (struct addresses_to_be_filed *)
2454 malloc(sizeof(struct addresses_to_be_filed));
2456 MailboxName(actual_rm, sizeof actual_rm,
2457 &CC->user, USERCONTACTSROOM);
2458 aptr->roomname = strdup(actual_rm);
2459 aptr->collected_addresses = collected_addresses;
2461 end_critical_section(S_ATBF);
2474 * Convenience function for generating small administrative messages.
2476 void quickie_message(char *from, char *to, char *room, char *text,
2477 int format_type, char *subject)
2479 struct CtdlMessage *msg;
2480 struct recptypes *recp = NULL;
2482 msg = malloc(sizeof(struct CtdlMessage));
2483 memset(msg, 0, sizeof(struct CtdlMessage));
2484 msg->cm_magic = CTDLMESSAGE_MAGIC;
2485 msg->cm_anon_type = MES_NORMAL;
2486 msg->cm_format_type = format_type;
2487 msg->cm_fields['A'] = strdup(from);
2488 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2489 msg->cm_fields['N'] = strdup(NODENAME);
2491 msg->cm_fields['R'] = strdup(to);
2492 recp = validate_recipients(to);
2494 if (subject != NULL) {
2495 msg->cm_fields['U'] = strdup(subject);
2497 msg->cm_fields['M'] = strdup(text);
2499 CtdlSubmitMsg(msg, recp, room);
2500 CtdlFreeMessage(msg);
2501 if (recp != NULL) free(recp);
2507 * Back end function used by CtdlMakeMessage() and similar functions
2509 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2510 size_t maxlen, /* maximum message length */
2511 char *exist, /* if non-null, append to it;
2512 exist is ALWAYS freed */
2513 int crlf /* CRLF newlines instead of LF */
2517 size_t message_len = 0;
2518 size_t buffer_len = 0;
2524 if (exist == NULL) {
2531 message_len = strlen(exist);
2532 buffer_len = message_len + 4096;
2533 m = realloc(exist, buffer_len);
2540 /* flush the input if we have nowhere to store it */
2545 /* read in the lines of message text one by one */
2547 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2548 if (!strcmp(buf, terminator)) finished = 1;
2550 strcat(buf, "\r\n");
2556 if ( (!flushing) && (!finished) ) {
2557 /* Measure the line */
2558 linelen = strlen(buf);
2560 /* augment the buffer if we have to */
2561 if ((message_len + linelen) >= buffer_len) {
2562 ptr = realloc(m, (buffer_len * 2) );
2563 if (ptr == NULL) { /* flush if can't allocate */
2566 buffer_len = (buffer_len * 2);
2568 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2572 /* Add the new line to the buffer. NOTE: this loop must avoid
2573 * using functions like strcat() and strlen() because they
2574 * traverse the entire buffer upon every call, and doing that
2575 * for a multi-megabyte message slows it down beyond usability.
2577 strcpy(&m[message_len], buf);
2578 message_len += linelen;
2581 /* if we've hit the max msg length, flush the rest */
2582 if (message_len >= maxlen) flushing = 1;
2584 } while (!finished);
2592 * Build a binary message to be saved on disk.
2593 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2594 * will become part of the message. This means you are no longer
2595 * responsible for managing that memory -- it will be freed along with
2596 * the rest of the fields when CtdlFreeMessage() is called.)
2599 struct CtdlMessage *CtdlMakeMessage(
2600 struct ctdluser *author, /* author's user structure */
2601 char *recipient, /* NULL if it's not mail */
2602 char *recp_cc, /* NULL if it's not mail */
2603 char *room, /* room where it's going */
2604 int type, /* see MES_ types in header file */
2605 int format_type, /* variformat, plain text, MIME... */
2606 char *fake_name, /* who we're masquerading as */
2607 char *subject, /* Subject (optional) */
2608 char *preformatted_text /* ...or NULL to read text from client */
2610 char dest_node[SIZ];
2612 struct CtdlMessage *msg;
2614 msg = malloc(sizeof(struct CtdlMessage));
2615 memset(msg, 0, sizeof(struct CtdlMessage));
2616 msg->cm_magic = CTDLMESSAGE_MAGIC;
2617 msg->cm_anon_type = type;
2618 msg->cm_format_type = format_type;
2620 /* Don't confuse the poor folks if it's not routed mail. */
2621 strcpy(dest_node, "");
2626 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2627 msg->cm_fields['P'] = strdup(buf);
2629 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2630 msg->cm_fields['T'] = strdup(buf);
2632 if (fake_name[0]) /* author */
2633 msg->cm_fields['A'] = strdup(fake_name);
2635 msg->cm_fields['A'] = strdup(author->fullname);
2637 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2638 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2641 msg->cm_fields['O'] = strdup(CC->room.QRname);
2644 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2645 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2647 if (recipient[0] != 0) {
2648 msg->cm_fields['R'] = strdup(recipient);
2650 if (recp_cc[0] != 0) {
2651 msg->cm_fields['Y'] = strdup(recp_cc);
2653 if (dest_node[0] != 0) {
2654 msg->cm_fields['D'] = strdup(dest_node);
2657 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2658 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2661 if (subject != NULL) {
2663 if (strlen(subject) > 0) {
2664 msg->cm_fields['U'] = strdup(subject);
2668 if (preformatted_text != NULL) {
2669 msg->cm_fields['M'] = preformatted_text;
2672 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2673 config.c_maxmsglen, NULL, 0);
2681 * Check to see whether we have permission to post a message in the current
2682 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2683 * returns 0 on success.
2685 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2687 if (!(CC->logged_in)) {
2688 snprintf(errmsgbuf, n, "Not logged in.");
2689 return (ERROR + NOT_LOGGED_IN);
2692 if ((CC->user.axlevel < 2)
2693 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2694 snprintf(errmsgbuf, n, "Need to be validated to enter "
2695 "(except in %s> to sysop)", MAILROOM);
2696 return (ERROR + HIGHER_ACCESS_REQUIRED);
2699 if ((CC->user.axlevel < 4)
2700 && (CC->room.QRflags & QR_NETWORK)) {
2701 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2702 return (ERROR + HIGHER_ACCESS_REQUIRED);
2705 if ((CC->user.axlevel < 6)
2706 && (CC->room.QRflags & QR_READONLY)) {
2707 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2708 return (ERROR + HIGHER_ACCESS_REQUIRED);
2711 strcpy(errmsgbuf, "Ok");
2717 * Check to see if the specified user has Internet mail permission
2718 * (returns nonzero if permission is granted)
2720 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2722 /* Do not allow twits to send Internet mail */
2723 if (who->axlevel <= 2) return(0);
2725 /* Globally enabled? */
2726 if (config.c_restrict == 0) return(1);
2728 /* User flagged ok? */
2729 if (who->flags & US_INTERNET) return(2);
2731 /* Aide level access? */
2732 if (who->axlevel >= 6) return(3);
2734 /* No mail for you! */
2740 * Validate recipients, count delivery types and errors, and handle aliasing
2741 * FIXME check for dupes!!!!!
2742 * Returns 0 if all addresses are ok, -1 if no addresses were specified,
2743 * or the number of addresses found invalid.
2745 struct recptypes *validate_recipients(char *supplied_recipients) {
2746 struct recptypes *ret;
2747 char recipients[SIZ];
2748 char this_recp[256];
2749 char this_recp_cooked[256];
2755 struct ctdluser tempUS;
2756 struct ctdlroom tempQR;
2760 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2761 if (ret == NULL) return(NULL);
2762 memset(ret, 0, sizeof(struct recptypes));
2765 ret->num_internet = 0;
2770 if (supplied_recipients == NULL) {
2771 strcpy(recipients, "");
2774 safestrncpy(recipients, supplied_recipients, sizeof recipients);
2777 /* Change all valid separator characters to commas */
2778 for (i=0; i<strlen(recipients); ++i) {
2779 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2780 recipients[i] = ',';
2784 /* Now start extracting recipients... */
2786 while (strlen(recipients) > 0) {
2788 for (i=0; i<=strlen(recipients); ++i) {
2789 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
2790 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
2791 safestrncpy(this_recp, recipients, i+1);
2793 if (recipients[i] == ',') {
2794 strcpy(recipients, &recipients[i+1]);
2797 strcpy(recipients, "");
2804 lprintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
2806 mailtype = alias(this_recp);
2807 mailtype = alias(this_recp);
2808 mailtype = alias(this_recp);
2809 for (j=0; j<=strlen(this_recp); ++j) {
2810 if (this_recp[j]=='_') {
2811 this_recp_cooked[j] = ' ';
2814 this_recp_cooked[j] = this_recp[j];
2820 if (!strcasecmp(this_recp, "sysop")) {
2822 strcpy(this_recp, config.c_aideroom);
2823 if (strlen(ret->recp_room) > 0) {
2824 strcat(ret->recp_room, "|");
2826 strcat(ret->recp_room, this_recp);
2828 else if (getuser(&tempUS, this_recp) == 0) {
2830 strcpy(this_recp, tempUS.fullname);
2831 if (strlen(ret->recp_local) > 0) {
2832 strcat(ret->recp_local, "|");
2834 strcat(ret->recp_local, this_recp);
2836 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2838 strcpy(this_recp, tempUS.fullname);
2839 if (strlen(ret->recp_local) > 0) {
2840 strcat(ret->recp_local, "|");
2842 strcat(ret->recp_local, this_recp);
2844 else if ( (!strncasecmp(this_recp, "room_", 5))
2845 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2847 if (strlen(ret->recp_room) > 0) {
2848 strcat(ret->recp_room, "|");
2850 strcat(ret->recp_room, &this_recp_cooked[5]);
2858 /* Yes, you're reading this correctly: if the target
2859 * domain points back to the local system or an attached
2860 * Citadel directory, the address is invalid. That's
2861 * because if the address were valid, we would have
2862 * already translated it to a local address by now.
2864 if (IsDirectory(this_recp)) {
2869 ++ret->num_internet;
2870 if (strlen(ret->recp_internet) > 0) {
2871 strcat(ret->recp_internet, "|");
2873 strcat(ret->recp_internet, this_recp);
2878 if (strlen(ret->recp_ignet) > 0) {
2879 strcat(ret->recp_ignet, "|");
2881 strcat(ret->recp_ignet, this_recp);
2889 if (strlen(ret->errormsg) == 0) {
2890 snprintf(append, sizeof append,
2891 "Invalid recipient: %s",
2895 snprintf(append, sizeof append,
2898 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2899 strcat(ret->errormsg, append);
2903 if (strlen(ret->display_recp) == 0) {
2904 strcpy(append, this_recp);
2907 snprintf(append, sizeof append, ", %s",
2910 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2911 strcat(ret->display_recp, append);
2916 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2917 ret->num_room + ret->num_error) == 0) {
2918 ret->num_error = (-1);
2919 strcpy(ret->errormsg, "No recipients specified.");
2922 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2923 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2924 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2925 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2926 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2927 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2935 * message entry - mode 0 (normal)
2937 void cmd_ent0(char *entargs)
2943 char masquerade_as[SIZ];
2945 int format_type = 0;
2946 char newusername[SIZ];
2947 struct CtdlMessage *msg;
2951 struct recptypes *valid = NULL;
2952 struct recptypes *valid_to = NULL;
2953 struct recptypes *valid_cc = NULL;
2954 struct recptypes *valid_bcc = NULL;
2961 post = extract_int(entargs, 0);
2962 extract_token(recp, entargs, 1, '|', sizeof recp);
2963 anon_flag = extract_int(entargs, 2);
2964 format_type = extract_int(entargs, 3);
2965 extract_token(subject, entargs, 4, '|', sizeof subject);
2966 do_confirm = extract_int(entargs, 6);
2967 extract_token(cc, entargs, 7, '|', sizeof cc);
2968 extract_token(bcc, entargs, 8, '|', sizeof bcc);
2970 /* first check to make sure the request is valid. */
2972 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2974 cprintf("%d %s\n", err, errmsg);
2978 /* Check some other permission type things. */
2981 if (CC->user.axlevel < 6) {
2982 cprintf("%d You don't have permission to masquerade.\n",
2983 ERROR + HIGHER_ACCESS_REQUIRED);
2986 extract_token(newusername, entargs, 5, '|', sizeof newusername);
2987 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2988 safestrncpy(CC->fake_postname, newusername,
2989 sizeof(CC->fake_postname) );
2990 cprintf("%d ok\n", CIT_OK);
2993 CC->cs_flags |= CS_POSTING;
2995 /* In the Mail> room we have to behave a little differently --
2996 * make sure the user has specified at least one recipient. Then
2997 * validate the recipient(s).
2999 if ( (CC->room.QRflags & QR_MAILBOX)
3000 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
3002 if (CC->user.axlevel < 2) {
3003 strcpy(recp, "sysop");
3008 valid_to = validate_recipients(recp);
3009 if (valid_to->num_error > 0) {
3010 cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER);
3015 valid_cc = validate_recipients(cc);
3016 if (valid_cc->num_error > 0) {
3017 cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER);
3023 valid_bcc = validate_recipients(bcc);
3024 if (valid_bcc->num_error > 0) {
3025 cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER);
3032 /* Recipient required, but none were specified */
3033 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3037 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3041 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3042 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3043 cprintf("%d You do not have permission "
3044 "to send Internet mail.\n",
3045 ERROR + HIGHER_ACCESS_REQUIRED);
3053 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)
3054 && (CC->user.axlevel < 4) ) {
3055 cprintf("%d Higher access required for network mail.\n",
3056 ERROR + HIGHER_ACCESS_REQUIRED);
3063 if ((RESTRICT_INTERNET == 1)
3064 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3065 && ((CC->user.flags & US_INTERNET) == 0)
3066 && (!CC->internal_pgm)) {
3067 cprintf("%d You don't have access to Internet mail.\n",
3068 ERROR + HIGHER_ACCESS_REQUIRED);
3077 /* Is this a room which has anonymous-only or anonymous-option? */
3078 anonymous = MES_NORMAL;
3079 if (CC->room.QRflags & QR_ANONONLY) {
3080 anonymous = MES_ANONONLY;
3082 if (CC->room.QRflags & QR_ANONOPT) {
3083 if (anon_flag == 1) { /* only if the user requested it */
3084 anonymous = MES_ANONOPT;
3088 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3092 /* If we're only checking the validity of the request, return
3093 * success without creating the message.
3096 cprintf("%d %s\n", CIT_OK,
3097 ((valid_to != NULL) ? valid_to->display_recp : "") );
3104 /* We don't need these anymore because we'll do it differently below */
3109 /* Handle author masquerading */
3110 if (CC->fake_postname[0]) {
3111 strcpy(masquerade_as, CC->fake_postname);
3113 else if (CC->fake_username[0]) {
3114 strcpy(masquerade_as, CC->fake_username);
3117 strcpy(masquerade_as, "");
3120 /* Read in the message from the client. */
3122 cprintf("%d send message\n", START_CHAT_MODE);
3124 cprintf("%d send message\n", SEND_LISTING);
3127 msg = CtdlMakeMessage(&CC->user, recp, cc,
3128 CC->room.QRname, anonymous, format_type,
3129 masquerade_as, subject, NULL);
3131 /* Put together one big recipients struct containing to/cc/bcc all in
3132 * one. This is for the envelope.
3134 char *all_recps = malloc(SIZ * 3);
3135 strcpy(all_recps, recp);
3136 if (strlen(cc) > 0) {
3137 if (strlen(all_recps) > 0) {
3138 strcat(all_recps, ",");
3140 strcat(all_recps, cc);
3142 if (strlen(bcc) > 0) {
3143 if (strlen(all_recps) > 0) {
3144 strcat(all_recps, ",");
3146 strcat(all_recps, bcc);
3148 if (strlen(all_recps) > 0) {
3149 valid = validate_recipients(all_recps);
3157 msgnum = CtdlSubmitMsg(msg, valid, "");
3160 cprintf("%ld\n", msgnum);
3162 cprintf("Message accepted.\n");
3165 cprintf("Internal error.\n");
3167 if (msg->cm_fields['E'] != NULL) {
3168 cprintf("%s\n", msg->cm_fields['E']);
3175 CtdlFreeMessage(msg);
3177 CC->fake_postname[0] = '\0';
3178 if (valid != NULL) {
3187 * API function to delete messages which match a set of criteria
3188 * (returns the actual number of messages deleted)
3190 int CtdlDeleteMessages(char *room_name, /* which room */
3191 long dmsgnum, /* or "0" for any */
3192 char *content_type, /* or "" for any */
3193 int deferred /* let TDAP sweep it later */
3197 struct ctdlroom qrbuf;
3198 struct cdbdata *cdbfr;
3199 long *msglist = NULL;
3200 long *dellist = NULL;
3203 int num_deleted = 0;
3205 struct MetaData smi;
3207 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s, %d)\n",
3208 room_name, dmsgnum, content_type, deferred);
3210 /* get room record, obtaining a lock... */
3211 if (lgetroom(&qrbuf, room_name) != 0) {
3212 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
3214 return (0); /* room not found */
3216 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3218 if (cdbfr != NULL) {
3219 dellist = malloc(cdbfr->len);
3220 msglist = (long *) cdbfr->ptr;
3221 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3222 num_msgs = cdbfr->len / sizeof(long);
3226 for (i = 0; i < num_msgs; ++i) {
3229 /* Set/clear a bit for each criterion */
3231 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
3232 delete_this |= 0x01;
3234 if (strlen(content_type) == 0) {
3235 delete_this |= 0x02;
3237 GetMetaData(&smi, msglist[i]);
3238 if (!strcasecmp(smi.meta_content_type,
3240 delete_this |= 0x02;
3244 /* Delete message only if all bits are set */
3245 if (delete_this == 0x03) {
3246 dellist[num_deleted++] = msglist[i];
3251 num_msgs = sort_msglist(msglist, num_msgs);
3252 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3253 msglist, (int)(num_msgs * sizeof(long)));
3255 qrbuf.QRhighest = msglist[num_msgs - 1];
3260 * If the delete operation is "deferred" (and technically, any delete
3261 * operation not performed by THE DREADED AUTO-PURGER ought to be
3262 * a deferred delete) then we save a pointer to the message in the
3263 * DELETED_MSGS_ROOM. This will cause the reference count to remain
3264 * at least 1, which will save the user from having to synchronously
3265 * wait for various disk-intensive operations to complete.
3267 if ( (deferred) && (num_deleted) ) {
3268 for (i=0; i<num_deleted; ++i) {
3269 CtdlCopyMsgToRoom(dellist[i], DELETED_MSGS_ROOM);
3273 /* Go through the messages we pulled out of the index, and decrement
3274 * their reference counts by 1. If this is the only room the message
3275 * was in, the reference count will reach zero and the message will
3276 * automatically be deleted from the database. We do this in a
3277 * separate pass because there might be plug-in hooks getting called,
3278 * and we don't want that happening during an S_ROOMS critical
3281 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3282 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3283 AdjRefCount(dellist[i], -1);
3286 /* Now free the memory we used, and go away. */
3287 if (msglist != NULL) free(msglist);
3288 if (dellist != NULL) free(dellist);
3289 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3290 return (num_deleted);
3296 * Check whether the current user has permission to delete messages from
3297 * the current room (returns 1 for yes, 0 for no)
3299 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3300 getuser(&CC->user, CC->curr_user);
3301 if ((CC->user.axlevel < 6)
3302 && (CC->user.usernum != CC->room.QRroomaide)
3303 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3304 && (!(CC->internal_pgm))) {
3313 * Delete message from current room
3315 void cmd_dele(char *delstr)
3320 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3321 cprintf("%d Higher access required.\n",
3322 ERROR + HIGHER_ACCESS_REQUIRED);
3325 delnum = extract_long(delstr, 0);
3327 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "", 1);
3330 cprintf("%d %d message%s deleted.\n", CIT_OK,
3331 num_deleted, ((num_deleted != 1) ? "s" : ""));
3333 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3339 * Back end API function for moves and deletes
3341 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3344 err = CtdlSaveMsgPointerInRoom(dest, msgnum, 1, NULL);
3345 if (err != 0) return(err);
3353 * move or copy a message to another room
3355 void cmd_move(char *args)
3358 char targ[ROOMNAMELEN];
3359 struct ctdlroom qtemp;
3365 num = extract_long(args, 0);
3366 extract_token(targ, args, 1, '|', sizeof targ);
3367 targ[ROOMNAMELEN - 1] = 0;
3368 is_copy = extract_int(args, 2);
3370 if (getroom(&qtemp, targ) != 0) {
3371 cprintf("%d '%s' does not exist.\n",
3372 ERROR + ROOM_NOT_FOUND, targ);
3376 getuser(&CC->user, CC->curr_user);
3377 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3379 /* Check for permission to perform this operation.
3380 * Remember: "CC->room" is source, "qtemp" is target.
3384 /* Aides can move/copy */
3385 if (CC->user.axlevel >= 6) permit = 1;
3387 /* Room aides can move/copy */
3388 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3390 /* Permit move/copy from personal rooms */
3391 if ((CC->room.QRflags & QR_MAILBOX)
3392 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3394 /* Permit only copy from public to personal room */
3396 && (!(CC->room.QRflags & QR_MAILBOX))
3397 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3399 /* User must have access to target room */
3400 if (!(ra & UA_KNOWN)) permit = 0;
3403 cprintf("%d Higher access required.\n",
3404 ERROR + HIGHER_ACCESS_REQUIRED);
3408 err = CtdlCopyMsgToRoom(num, targ);
3410 cprintf("%d Cannot store message in %s: error %d\n",
3415 /* Now delete the message from the source room,
3416 * if this is a 'move' rather than a 'copy' operation.
3419 CtdlDeleteMessages(CC->room.QRname, num, "", 0);
3422 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3428 * GetMetaData() - Get the supplementary record for a message
3430 void GetMetaData(struct MetaData *smibuf, long msgnum)
3433 struct cdbdata *cdbsmi;
3436 memset(smibuf, 0, sizeof(struct MetaData));
3437 smibuf->meta_msgnum = msgnum;
3438 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3440 /* Use the negative of the message number for its supp record index */
3441 TheIndex = (0L - msgnum);
3443 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3444 if (cdbsmi == NULL) {
3445 return; /* record not found; go with defaults */
3447 memcpy(smibuf, cdbsmi->ptr,
3448 ((cdbsmi->len > sizeof(struct MetaData)) ?
3449 sizeof(struct MetaData) : cdbsmi->len));
3456 * PutMetaData() - (re)write supplementary record for a message
3458 void PutMetaData(struct MetaData *smibuf)
3462 /* Use the negative of the message number for the metadata db index */
3463 TheIndex = (0L - smibuf->meta_msgnum);
3465 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3466 smibuf->meta_msgnum, smibuf->meta_refcount);
3468 cdb_store(CDB_MSGMAIN,
3469 &TheIndex, (int)sizeof(long),
3470 smibuf, (int)sizeof(struct MetaData));
3475 * AdjRefCount - change the reference count for a message;
3476 * delete the message if it reaches zero
3478 void AdjRefCount(long msgnum, int incr)
3481 struct MetaData smi;
3484 /* This is a *tight* critical section; please keep it that way, as
3485 * it may get called while nested in other critical sections.
3486 * Complicating this any further will surely cause deadlock!
3488 begin_critical_section(S_SUPPMSGMAIN);
3489 GetMetaData(&smi, msgnum);
3490 smi.meta_refcount += incr;
3492 end_critical_section(S_SUPPMSGMAIN);
3493 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3494 msgnum, incr, smi.meta_refcount);
3496 /* If the reference count is now zero, delete the message
3497 * (and its supplementary record as well).
3499 if (smi.meta_refcount == 0) {
3500 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3502 /* Remove from fulltext index */
3503 if (config.c_enable_fulltext) {
3504 ft_index_message(msgnum, 0);
3507 /* Remove from message base */
3509 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3510 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3512 /* Remove metadata record */
3513 delnum = (0L - msgnum);
3514 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3519 * Write a generic object to this room
3521 * Note: this could be much more efficient. Right now we use two temporary
3522 * files, and still pull the message into memory as with all others.
3524 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3525 char *content_type, /* MIME type of this object */
3526 char *tempfilename, /* Where to fetch it from */
3527 struct ctdluser *is_mailbox, /* Mailbox room? */
3528 int is_binary, /* Is encoding necessary? */
3529 int is_unique, /* Del others of this type? */
3530 unsigned int flags /* Internal save flags */
3535 struct ctdlroom qrbuf;
3536 char roomname[ROOMNAMELEN];
3537 struct CtdlMessage *msg;
3539 char *raw_message = NULL;
3540 char *encoded_message = NULL;
3541 off_t raw_length = 0;
3543 if (is_mailbox != NULL)
3544 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3546 safestrncpy(roomname, req_room, sizeof(roomname));
3547 lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3550 fp = fopen(tempfilename, "rb");
3552 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3553 tempfilename, strerror(errno));
3556 fseek(fp, 0L, SEEK_END);
3557 raw_length = ftell(fp);
3559 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3561 raw_message = malloc((size_t)raw_length + 2);
3562 fread(raw_message, (size_t)raw_length, 1, fp);
3566 encoded_message = malloc((size_t)
3567 (((raw_length * 134) / 100) + 4096 ) );
3570 encoded_message = malloc((size_t)(raw_length + 4096));
3573 sprintf(encoded_message, "Content-type: %s\n", content_type);
3576 sprintf(&encoded_message[strlen(encoded_message)],
3577 "Content-transfer-encoding: base64\n\n"
3581 sprintf(&encoded_message[strlen(encoded_message)],
3582 "Content-transfer-encoding: 7bit\n\n"
3588 &encoded_message[strlen(encoded_message)],
3594 raw_message[raw_length] = 0;
3596 &encoded_message[strlen(encoded_message)],
3604 lprintf(CTDL_DEBUG, "Allocating\n");
3605 msg = malloc(sizeof(struct CtdlMessage));
3606 memset(msg, 0, sizeof(struct CtdlMessage));
3607 msg->cm_magic = CTDLMESSAGE_MAGIC;
3608 msg->cm_anon_type = MES_NORMAL;
3609 msg->cm_format_type = 4;
3610 msg->cm_fields['A'] = strdup(CC->user.fullname);
3611 msg->cm_fields['O'] = strdup(req_room);
3612 msg->cm_fields['N'] = strdup(config.c_nodename);
3613 msg->cm_fields['H'] = strdup(config.c_humannode);
3614 msg->cm_flags = flags;
3616 msg->cm_fields['M'] = encoded_message;
3618 /* Create the requested room if we have to. */
3619 if (getroom(&qrbuf, roomname) != 0) {
3620 create_room(roomname,
3621 ( (is_mailbox != NULL) ? 5 : 3 ),
3622 "", 0, 1, 0, VIEW_BBS);
3624 /* If the caller specified this object as unique, delete all
3625 * other objects of this type that are currently in the room.
3628 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3629 CtdlDeleteMessages(roomname, 0L, content_type, 0)
3632 /* Now write the data */
3633 CtdlSubmitMsg(msg, NULL, roomname);
3634 CtdlFreeMessage(msg);
3642 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3643 config_msgnum = msgnum;
3647 char *CtdlGetSysConfig(char *sysconfname) {
3648 char hold_rm[ROOMNAMELEN];
3651 struct CtdlMessage *msg;
3654 strcpy(hold_rm, CC->room.QRname);
3655 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3656 getroom(&CC->room, hold_rm);
3661 /* We want the last (and probably only) config in this room */
3662 begin_critical_section(S_CONFIG);
3663 config_msgnum = (-1L);
3664 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3665 CtdlGetSysConfigBackend, NULL);
3666 msgnum = config_msgnum;
3667 end_critical_section(S_CONFIG);
3673 msg = CtdlFetchMessage(msgnum, 1);
3675 conf = strdup(msg->cm_fields['M']);
3676 CtdlFreeMessage(msg);
3683 getroom(&CC->room, hold_rm);
3685 if (conf != NULL) do {
3686 extract_token(buf, conf, 0, '\n', sizeof buf);
3687 strcpy(conf, &conf[strlen(buf)+1]);
3688 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3693 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3694 char temp[PATH_MAX];
3697 strcpy(temp, tmpnam(NULL));
3699 fp = fopen(temp, "w");
3700 if (fp == NULL) return;
3701 fprintf(fp, "%s", sysconfdata);
3704 /* this handy API function does all the work for us */
3705 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3711 * Determine whether a given Internet address belongs to the current user
3713 int CtdlIsMe(char *addr, int addr_buf_len)
3715 struct recptypes *recp;
3718 recp = validate_recipients(addr);
3719 if (recp == NULL) return(0);
3721 if (recp->num_local == 0) {
3726 for (i=0; i<recp->num_local; ++i) {
3727 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3728 if (!strcasecmp(addr, CC->user.fullname)) {
3740 * Citadel protocol command to do the same
3742 void cmd_isme(char *argbuf) {
3745 if (CtdlAccessCheck(ac_logged_in)) return;
3746 extract_token(addr, argbuf, 0, '|', sizeof addr);
3748 if (CtdlIsMe(addr, sizeof addr)) {
3749 cprintf("%d %s\n", CIT_OK, addr);
3752 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);