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"
49 #include "mime_parser.h"
52 #include "internet_addressing.h"
54 extern struct config config;
59 * This really belongs in serv_network.c, but I don't know how to export
60 * symbols between modules.
62 struct FilterList *filterlist = NULL;
66 * These are the four-character field headers we use when outputting
67 * messages in Citadel format (as opposed to RFC822 format).
70 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
71 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
72 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
73 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
74 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
75 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
76 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
77 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
104 * This function is self explanatory.
105 * (What can I say, I'm in a weird mood today...)
107 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
111 for (i = 0; i < strlen(name); ++i) {
112 if (name[i] == '@') {
113 while (isspace(name[i - 1]) && i > 0) {
114 strcpy(&name[i - 1], &name[i]);
117 while (isspace(name[i + 1])) {
118 strcpy(&name[i + 1], &name[i + 2]);
126 * Aliasing for network mail.
127 * (Error messages have been commented out, because this is a server.)
129 int alias(char *name)
130 { /* process alias and routing info for mail */
133 char aaa[SIZ], bbb[SIZ];
134 char *ignetcfg = NULL;
135 char *ignetmap = NULL;
142 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
144 fp = fopen("network/mail.aliases", "r");
146 fp = fopen("/dev/null", "r");
153 while (fgets(aaa, sizeof aaa, fp) != NULL) {
154 while (isspace(name[0]))
155 strcpy(name, &name[1]);
156 aaa[strlen(aaa) - 1] = 0;
158 for (a = 0; a < strlen(aaa); ++a) {
160 strcpy(bbb, &aaa[a + 1]);
164 if (!strcasecmp(name, aaa))
169 /* Hit the Global Address Book */
170 if (CtdlDirectoryLookup(aaa, name) == 0) {
174 lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name);
176 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
177 for (a=0; a<strlen(name); ++a) {
178 if (name[a] == '@') {
179 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
181 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
186 /* determine local or remote type, see citadel.h */
187 at = haschar(name, '@');
188 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
189 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
190 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
192 /* figure out the delivery mode */
193 extract_token(node, name, 1, '@');
195 /* If there are one or more dots in the nodename, we assume that it
196 * is an FQDN and will attempt SMTP delivery to the Internet.
198 if (haschar(node, '.') > 0) {
199 return(MES_INTERNET);
202 /* Otherwise we look in the IGnet maps for a valid Citadel node.
203 * Try directly-connected nodes first...
205 ignetcfg = CtdlGetSysConfig(IGNETCFG);
206 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
207 extract_token(buf, ignetcfg, i, '\n');
208 extract_token(testnode, buf, 0, '|');
209 if (!strcasecmp(node, testnode)) {
217 * Then try nodes that are two or more hops away.
219 ignetmap = CtdlGetSysConfig(IGNETMAP);
220 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
221 extract_token(buf, ignetmap, i, '\n');
222 extract_token(testnode, buf, 0, '|');
223 if (!strcasecmp(node, testnode)) {
230 /* If we get to this point it's an invalid node name */
239 fp = fopen("citadel.control", "r");
241 lprintf(CTDL_CRIT, "Cannot open citadel.control: %s\n",
245 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
251 void simple_listing(long msgnum, void *userdata)
253 cprintf("%ld\n", msgnum);
258 /* Determine if a given message matches the fields in a message template.
259 * Return 0 for a successful match.
261 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
264 /* If there aren't any fields in the template, all messages will
267 if (template == NULL) return(0);
269 /* Null messages are bogus. */
270 if (msg == NULL) return(1);
272 for (i='A'; i<='Z'; ++i) {
273 if (template->cm_fields[i] != NULL) {
274 if (msg->cm_fields[i] == NULL) {
277 if (strcasecmp(msg->cm_fields[i],
278 template->cm_fields[i])) return 1;
282 /* All compares succeeded: we have a match! */
289 * Retrieve the "seen" message list for the current room.
291 void CtdlGetSeen(char *buf, int which_set) {
294 /* Learn about the user and room in question */
295 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
297 if (which_set == ctdlsetseen_seen)
298 safestrncpy(buf, vbuf.v_seen, SIZ);
299 if (which_set == ctdlsetseen_answered)
300 safestrncpy(buf, vbuf.v_answered, SIZ);
306 * Manipulate the "seen msgs" string (or other message set strings)
308 void CtdlSetSeen(long target_msgnum, int target_setting, int which_set) {
310 struct cdbdata *cdbfr;
321 lprintf(CTDL_DEBUG, "CtdlSetSeen(%ld, %d, %d)\n",
322 target_msgnum, target_setting, which_set);
324 /* Learn about the user and room in question */
325 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
327 /* Load the message list */
328 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
330 msglist = malloc(cdbfr->len);
331 memcpy(msglist, cdbfr->ptr, cdbfr->len);
332 num_msgs = cdbfr->len / sizeof(long);
335 return; /* No messages at all? No further action. */
338 /* Decide which message set we're manipulating */
339 if (which_set == ctdlsetseen_seen) strcpy(vset, vbuf.v_seen);
340 if (which_set == ctdlsetseen_answered) strcpy(vset, vbuf.v_answered);
342 lprintf(CTDL_DEBUG, "before optimize: %s\n", vset);
345 for (i=0; i<num_msgs; ++i) {
348 if (msglist[i] == target_msgnum) {
349 is_seen = target_setting;
352 if (is_msg_in_mset(vset, msglist[i])) {
358 if (lo < 0L) lo = msglist[i];
361 if ( ((is_seen == 0) && (was_seen == 1))
362 || ((is_seen == 1) && (i == num_msgs-1)) ) {
365 if ( (strlen(newseen) + 20) > SIZ) {
366 strcpy(newseen, &newseen[20]);
369 tmp = strlen(newseen);
371 strcat(newseen, ",");
375 snprintf(&newseen[tmp], sizeof newseen - tmp,
379 snprintf(&newseen[tmp], sizeof newseen - tmp,
388 /* Decide which message set we're manipulating */
389 if (which_set == ctdlsetseen_seen) strcpy(vbuf.v_seen, newseen);
390 if (which_set == ctdlsetseen_answered) strcpy(vbuf.v_answered, newseen);
392 lprintf(CTDL_DEBUG, " after optimize: %s\n", newseen);
394 CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
399 * API function to perform an operation for each qualifying message in the
400 * current room. (Returns the number of messages processed.)
402 int CtdlForEachMessage(int mode, long ref,
404 struct CtdlMessage *compare,
405 void (*CallBack) (long, void *),
411 struct cdbdata *cdbfr;
412 long *msglist = NULL;
414 int num_processed = 0;
417 struct CtdlMessage *msg;
420 int printed_lastold = 0;
422 /* Learn about the user and room in question */
424 getuser(&CC->user, CC->curr_user);
425 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
427 /* Load the message list */
428 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
430 msglist = malloc(cdbfr->len);
431 memcpy(msglist, cdbfr->ptr, cdbfr->len);
432 num_msgs = cdbfr->len / sizeof(long);
435 return 0; /* No messages at all? No further action. */
440 * Now begin the traversal.
442 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
444 /* If the caller is looking for a specific MIME type, filter
445 * out all messages which are not of the type requested.
447 if (content_type != NULL) if (strlen(content_type) > 0) {
449 /* This call to GetMetaData() sits inside this loop
450 * so that we only do the extra database read per msg
451 * if we need to. Doing the extra read all the time
452 * really kills the server. If we ever need to use
453 * metadata for another search criterion, we need to
454 * move the read somewhere else -- but still be smart
455 * enough to only do the read if the caller has
456 * specified something that will need it.
458 GetMetaData(&smi, msglist[a]);
460 if (strcasecmp(smi.meta_content_type, content_type)) {
466 num_msgs = sort_msglist(msglist, num_msgs);
468 /* If a template was supplied, filter out the messages which
469 * don't match. (This could induce some delays!)
472 if (compare != NULL) {
473 for (a = 0; a < num_msgs; ++a) {
474 msg = CtdlFetchMessage(msglist[a], 1);
476 if (CtdlMsgCmp(msg, compare)) {
479 CtdlFreeMessage(msg);
487 * Now iterate through the message list, according to the
488 * criteria supplied by the caller.
491 for (a = 0; a < num_msgs; ++a) {
492 thismsg = msglist[a];
493 is_seen = is_msg_in_mset(vbuf.v_seen, thismsg);
494 if (is_seen) lastold = thismsg;
499 || ((mode == MSGS_OLD) && (is_seen))
500 || ((mode == MSGS_NEW) && (!is_seen))
501 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
502 || ((mode == MSGS_FIRST) && (a < ref))
503 || ((mode == MSGS_GT) && (thismsg > ref))
504 || ((mode == MSGS_EQ) && (thismsg == ref))
507 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
509 CallBack(lastold, userdata);
513 if (CallBack) CallBack(thismsg, userdata);
517 free(msglist); /* Clean up */
518 return num_processed;
524 * cmd_msgs() - get list of message #'s in this room
525 * implements the MSGS server command using CtdlForEachMessage()
527 void cmd_msgs(char *cmdbuf)
536 int with_template = 0;
537 struct CtdlMessage *template = NULL;
539 extract(which, cmdbuf, 0);
540 cm_ref = extract_int(cmdbuf, 1);
541 with_template = extract_int(cmdbuf, 2);
545 if (!strncasecmp(which, "OLD", 3))
547 else if (!strncasecmp(which, "NEW", 3))
549 else if (!strncasecmp(which, "FIRST", 5))
551 else if (!strncasecmp(which, "LAST", 4))
553 else if (!strncasecmp(which, "GT", 2))
556 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
557 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
563 cprintf("%d Send template then receive message list\n",
565 template = (struct CtdlMessage *)
566 malloc(sizeof(struct CtdlMessage));
567 memset(template, 0, sizeof(struct CtdlMessage));
568 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
569 extract(tfield, buf, 0);
570 extract(tvalue, buf, 1);
571 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
572 if (!strcasecmp(tfield, msgkeys[i])) {
573 template->cm_fields[i] =
581 cprintf("%d Message list...\n", LISTING_FOLLOWS);
584 CtdlForEachMessage(mode, cm_ref,
585 NULL, template, simple_listing, NULL);
586 if (template != NULL) CtdlFreeMessage(template);
594 * help_subst() - support routine for help file viewer
596 void help_subst(char *strbuf, char *source, char *dest)
601 while (p = pattern2(strbuf, source), (p >= 0)) {
602 strcpy(workbuf, &strbuf[p + strlen(source)]);
603 strcpy(&strbuf[p], dest);
604 strcat(strbuf, workbuf);
609 void do_help_subst(char *buffer)
613 help_subst(buffer, "^nodename", config.c_nodename);
614 help_subst(buffer, "^humannode", config.c_humannode);
615 help_subst(buffer, "^fqdn", config.c_fqdn);
616 help_subst(buffer, "^username", CC->user.fullname);
617 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
618 help_subst(buffer, "^usernum", buf2);
619 help_subst(buffer, "^sysadm", config.c_sysadm);
620 help_subst(buffer, "^variantname", CITADEL);
621 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
622 help_subst(buffer, "^maxsessions", buf2);
623 help_subst(buffer, "^bbsdir", BBSDIR);
629 * memfmout() - Citadel text formatter and paginator.
630 * Although the original purpose of this routine was to format
631 * text to the reader's screen width, all we're really using it
632 * for here is to format text out to 80 columns before sending it
633 * to the client. The client software may reformat it again.
636 int width, /* screen width to use */
637 char *mptr, /* where are we going to get our text from? */
638 char subst, /* nonzero if we should do substitutions */
639 char *nl) /* string to terminate lines with */
651 c = 1; /* c is the current pos */
655 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
657 buffer[strlen(buffer) + 1] = 0;
658 buffer[strlen(buffer)] = ch;
661 if (buffer[0] == '^')
662 do_help_subst(buffer);
664 buffer[strlen(buffer) + 1] = 0;
666 strcpy(buffer, &buffer[1]);
674 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
676 if (((old == 13) || (old == 10)) && (isspace(real))) {
684 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
685 cprintf("%s%s", nl, aaa);
694 if ((strlen(aaa) + c) > (width - 5)) {
703 if ((ch == 13) || (ch == 10)) {
704 cprintf("%s%s", aaa, nl);
711 cprintf("%s%s", aaa, nl);
717 * Callback function for mime parser that simply lists the part
719 void list_this_part(char *name, char *filename, char *partnum, char *disp,
720 void *content, char *cbtype, size_t length, char *encoding,
724 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
725 name, filename, partnum, disp, cbtype, (long)length);
729 * Callback function for multipart prefix
731 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
732 void *content, char *cbtype, size_t length, char *encoding,
735 cprintf("pref=%s|%s\n", partnum, cbtype);
739 * Callback function for multipart sufffix
741 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
742 void *content, char *cbtype, size_t length, char *encoding,
745 cprintf("suff=%s|%s\n", partnum, cbtype);
750 * Callback function for mime parser that opens a section for downloading
752 void mime_download(char *name, char *filename, char *partnum, char *disp,
753 void *content, char *cbtype, size_t length, char *encoding,
757 /* Silently go away if there's already a download open... */
758 if (CC->download_fp != NULL)
761 /* ...or if this is not the desired section */
762 if (strcasecmp(CC->download_desired_section, partnum))
765 CC->download_fp = tmpfile();
766 if (CC->download_fp == NULL)
769 fwrite(content, length, 1, CC->download_fp);
770 fflush(CC->download_fp);
771 rewind(CC->download_fp);
773 OpenCmdResult(filename, cbtype);
779 * Load a message from disk into memory.
780 * This is used by CtdlOutputMsg() and other fetch functions.
782 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
783 * using the CtdlMessageFree() function.
785 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
787 struct cdbdata *dmsgtext;
788 struct CtdlMessage *ret = NULL;
792 cit_uint8_t field_header;
794 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
796 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
797 if (dmsgtext == NULL) {
800 mptr = dmsgtext->ptr;
801 upper_bound = mptr + dmsgtext->len;
803 /* Parse the three bytes that begin EVERY message on disk.
804 * The first is always 0xFF, the on-disk magic number.
805 * The second is the anonymous/public type byte.
806 * The third is the format type byte (vari, fixed, or MIME).
811 "Message %ld appears to be corrupted.\n",
816 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
817 memset(ret, 0, sizeof(struct CtdlMessage));
819 ret->cm_magic = CTDLMESSAGE_MAGIC;
820 ret->cm_anon_type = *mptr++; /* Anon type byte */
821 ret->cm_format_type = *mptr++; /* Format type byte */
824 * The rest is zero or more arbitrary fields. Load them in.
825 * We're done when we encounter either a zero-length field or
826 * have just processed the 'M' (message text) field.
829 if (mptr >= upper_bound) {
832 field_header = *mptr++;
833 ret->cm_fields[field_header] = strdup(mptr);
835 while (*mptr++ != 0); /* advance to next field */
837 } while ((mptr < upper_bound) && (field_header != 'M'));
841 /* Always make sure there's something in the msg text field. If
842 * it's NULL, the message text is most likely stored separately,
843 * so go ahead and fetch that. Failing that, just set a dummy
844 * body so other code doesn't barf.
846 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
847 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
848 if (dmsgtext != NULL) {
849 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
853 if (ret->cm_fields['M'] == NULL) {
854 ret->cm_fields['M'] = strdup("<no text>\n");
857 /* Perform "before read" hooks (aborting if any return nonzero) */
858 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
859 CtdlFreeMessage(ret);
868 * Returns 1 if the supplied pointer points to a valid Citadel message.
869 * If the pointer is NULL or the magic number check fails, returns 0.
871 int is_valid_message(struct CtdlMessage *msg) {
874 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
875 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
883 * 'Destructor' for struct CtdlMessage
885 void CtdlFreeMessage(struct CtdlMessage *msg)
889 if (is_valid_message(msg) == 0) return;
891 for (i = 0; i < 256; ++i)
892 if (msg->cm_fields[i] != NULL) {
893 free(msg->cm_fields[i]);
896 msg->cm_magic = 0; /* just in case */
902 * Pre callback function for multipart/alternative
904 * NOTE: this differs from the standard behavior for a reason. Normally when
905 * displaying multipart/alternative you want to show the _last_ usable
906 * format in the message. Here we show the _first_ one, because it's
907 * usually text/plain. Since this set of functions is designed for text
908 * output to non-MIME-aware clients, this is the desired behavior.
911 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
912 void *content, char *cbtype, size_t length, char *encoding,
917 ma = (struct ma_info *)cbuserdata;
918 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
919 if (!strcasecmp(cbtype, "multipart/alternative")) {
927 * Post callback function for multipart/alternative
929 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
930 void *content, char *cbtype, size_t length, char *encoding,
935 ma = (struct ma_info *)cbuserdata;
936 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
937 if (!strcasecmp(cbtype, "multipart/alternative")) {
945 * Inline callback function for mime parser that wants to display text
947 void fixed_output(char *name, char *filename, char *partnum, char *disp,
948 void *content, char *cbtype, size_t length, char *encoding,
956 ma = (struct ma_info *)cbuserdata;
958 lprintf(CTDL_DEBUG, "fixed_output() type=<%s>\n", cbtype);
961 * If we're in the middle of a multipart/alternative scope and
962 * we've already printed another section, skip this one.
964 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
965 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
970 if ( (!strcasecmp(cbtype, "text/plain"))
971 || (strlen(cbtype)==0) ) {
974 client_write(wptr, length);
975 if (wptr[length-1] != '\n') {
980 else if (!strcasecmp(cbtype, "text/html")) {
981 ptr = html_to_ascii(content, 80, 0);
983 client_write(ptr, wlen);
984 if (ptr[wlen-1] != '\n') {
989 else if (strncasecmp(cbtype, "multipart/", 10)) {
990 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
991 partnum, filename, cbtype, (long)length);
996 * The client is elegant and sophisticated and wants to be choosy about
997 * MIME content types, so figure out which multipart/alternative part
998 * we're going to send.
1000 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1001 void *content, char *cbtype, size_t length, char *encoding,
1008 ma = (struct ma_info *)cbuserdata;
1010 if (ma->is_ma > 0) {
1011 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1012 extract(buf, CC->preferred_formats, i);
1013 if (!strcasecmp(buf, cbtype)) {
1014 strcpy(ma->chosen_part, partnum);
1021 * Now that we've chosen our preferred part, output it.
1023 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1024 void *content, char *cbtype, size_t length, char *encoding,
1029 int add_newline = 0;
1033 ma = (struct ma_info *)cbuserdata;
1035 /* This is not the MIME part you're looking for... */
1036 if (strcasecmp(partnum, ma->chosen_part)) return;
1038 /* If the content-type of this part is in our preferred formats
1039 * list, we can simply output it verbatim.
1041 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1042 extract(buf, CC->preferred_formats, i);
1043 if (!strcasecmp(buf, cbtype)) {
1044 /* Yeah! Go! W00t!! */
1046 text_content = (char *)content;
1047 if (text_content[length-1] != '\n') {
1051 cprintf("Content-type: %s\n", cbtype);
1052 cprintf("Content-length: %d\n",
1053 (int)(length + add_newline) );
1054 if (strlen(encoding) > 0) {
1055 cprintf("Content-transfer-encoding: %s\n", encoding);
1058 cprintf("Content-transfer-encoding: 7bit\n");
1061 client_write(content, length);
1062 if (add_newline) cprintf("\n");
1067 /* No translations required or possible: output as text/plain */
1068 cprintf("Content-type: text/plain\n\n");
1069 fixed_output(name, filename, partnum, disp, content, cbtype,
1070 length, encoding, cbuserdata);
1075 * Get a message off disk. (returns om_* values found in msgbase.h)
1078 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1079 int mode, /* how would you like that message? */
1080 int headers_only, /* eschew the message body? */
1081 int do_proto, /* do Citadel protocol responses? */
1082 int crlf /* Use CRLF newlines instead of LF? */
1084 struct CtdlMessage *TheMessage = NULL;
1085 int retcode = om_no_such_msg;
1087 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
1090 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1091 if (do_proto) cprintf("%d Not logged in.\n",
1092 ERROR + NOT_LOGGED_IN);
1093 return(om_not_logged_in);
1096 /* FIXME: check message id against msglist for this room */
1099 * Fetch the message from disk. If we're in sooper-fast headers
1100 * only mode, request that we don't even bother loading the body
1103 if (headers_only == HEADERS_FAST) {
1104 TheMessage = CtdlFetchMessage(msg_num, 0);
1107 TheMessage = CtdlFetchMessage(msg_num, 1);
1110 if (TheMessage == NULL) {
1111 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1112 ERROR + MESSAGE_NOT_FOUND, msg_num);
1113 return(om_no_such_msg);
1116 retcode = CtdlOutputPreLoadedMsg(
1117 TheMessage, msg_num, mode,
1118 headers_only, do_proto, crlf);
1120 CtdlFreeMessage(TheMessage);
1127 * Get a message off disk. (returns om_* values found in msgbase.h)
1130 int CtdlOutputPreLoadedMsg(
1131 struct CtdlMessage *TheMessage,
1133 int mode, /* how would you like that message? */
1134 int headers_only, /* eschew the message body? */
1135 int do_proto, /* do Citadel protocol responses? */
1136 int crlf /* Use CRLF newlines instead of LF? */
1142 char display_name[256];
1144 char *nl; /* newline string */
1146 int subject_found = 0;
1149 /* Buffers needed for RFC822 translation. These are all filled
1150 * using functions that are bounds-checked, and therefore we can
1151 * make them substantially smaller than SIZ.
1159 char datestamp[100];
1161 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %ld, %d, %d, %d, %d\n",
1162 ((TheMessage == NULL) ? "NULL" : "not null"),
1164 mode, headers_only, do_proto, crlf);
1166 snprintf(mid, sizeof mid, "%ld", msg_num);
1167 nl = (crlf ? "\r\n" : "\n");
1169 if (!is_valid_message(TheMessage)) {
1171 "ERROR: invalid preloaded message for output\n");
1172 return(om_no_such_msg);
1175 /* Are we downloading a MIME component? */
1176 if (mode == MT_DOWNLOAD) {
1177 if (TheMessage->cm_format_type != FMT_RFC822) {
1179 cprintf("%d This is not a MIME message.\n",
1180 ERROR + ILLEGAL_VALUE);
1181 } else if (CC->download_fp != NULL) {
1182 if (do_proto) cprintf(
1183 "%d You already have a download open.\n",
1184 ERROR + RESOURCE_BUSY);
1186 /* Parse the message text component */
1187 mptr = TheMessage->cm_fields['M'];
1188 ma = malloc(sizeof(struct ma_info));
1189 memset(ma, 0, sizeof(struct ma_info));
1190 mime_parser(mptr, NULL, *mime_download, NULL, NULL, (void *)ma, 0);
1192 /* If there's no file open by this time, the requested
1193 * section wasn't found, so print an error
1195 if (CC->download_fp == NULL) {
1196 if (do_proto) cprintf(
1197 "%d Section %s not found.\n",
1198 ERROR + FILE_NOT_FOUND,
1199 CC->download_desired_section);
1202 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1205 /* now for the user-mode message reading loops */
1206 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1208 /* Does the caller want to skip the headers? */
1209 if (headers_only == HEADERS_NONE) goto START_TEXT;
1211 /* Tell the client which format type we're using. */
1212 if ( (mode == MT_CITADEL) && (do_proto) ) {
1213 cprintf("type=%d\n", TheMessage->cm_format_type);
1216 /* nhdr=yes means that we're only displaying headers, no body */
1217 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1218 && (mode == MT_CITADEL)
1221 cprintf("nhdr=yes\n");
1224 /* begin header processing loop for Citadel message format */
1226 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1228 safestrncpy(display_name, "<unknown>", sizeof display_name);
1229 if (TheMessage->cm_fields['A']) {
1230 strcpy(buf, TheMessage->cm_fields['A']);
1231 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1232 safestrncpy(display_name, "****", sizeof display_name);
1234 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1235 safestrncpy(display_name, "anonymous", sizeof display_name);
1238 safestrncpy(display_name, buf, sizeof display_name);
1240 if ((is_room_aide())
1241 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1242 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1243 size_t tmp = strlen(display_name);
1244 snprintf(&display_name[tmp],
1245 sizeof display_name - tmp,
1250 /* Don't show Internet address for users on the
1251 * local Citadel network.
1254 if (TheMessage->cm_fields['N'] != NULL)
1255 if (strlen(TheMessage->cm_fields['N']) > 0)
1256 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1260 /* Now spew the header fields in the order we like them. */
1261 safestrncpy(allkeys, FORDER, sizeof allkeys);
1262 for (i=0; i<strlen(allkeys); ++i) {
1263 k = (int) allkeys[i];
1265 if ( (TheMessage->cm_fields[k] != NULL)
1266 && (msgkeys[k] != NULL) ) {
1268 if (do_proto) cprintf("%s=%s\n",
1272 else if ((k == 'F') && (suppress_f)) {
1275 /* Masquerade display name if needed */
1277 if (do_proto) cprintf("%s=%s\n",
1279 TheMessage->cm_fields[k]
1288 /* begin header processing loop for RFC822 transfer format */
1293 strcpy(snode, NODENAME);
1294 strcpy(lnode, HUMANNODE);
1295 if (mode == MT_RFC822) {
1296 for (i = 0; i < 256; ++i) {
1297 if (TheMessage->cm_fields[i]) {
1298 mptr = TheMessage->cm_fields[i];
1301 safestrncpy(luser, mptr, sizeof luser);
1302 safestrncpy(suser, mptr, sizeof suser);
1304 else if (i == 'U') {
1305 cprintf("Subject: %s%s", mptr, nl);
1309 safestrncpy(mid, mptr, sizeof mid);
1311 safestrncpy(lnode, mptr, sizeof lnode);
1313 safestrncpy(fuser, mptr, sizeof fuser);
1315 cprintf("X-Citadel-Room: %s%s",
1318 safestrncpy(snode, mptr, sizeof snode);
1320 cprintf("To: %s%s", mptr, nl);
1321 else if (i == 'T') {
1322 datestring(datestamp, sizeof datestamp,
1323 atol(mptr), DATESTRING_RFC822);
1324 cprintf("Date: %s%s", datestamp, nl);
1328 if (subject_found == 0) {
1329 cprintf("Subject: (no subject)%s", nl);
1333 for (i=0; i<strlen(suser); ++i) {
1334 suser[i] = tolower(suser[i]);
1335 if (!isalnum(suser[i])) suser[i]='_';
1338 if (mode == MT_RFC822) {
1339 if (!strcasecmp(snode, NODENAME)) {
1340 safestrncpy(snode, FQDN, sizeof snode);
1343 /* Construct a fun message id */
1344 cprintf("Message-ID: <%s", mid);
1345 if (strchr(mid, '@')==NULL) {
1346 cprintf("@%s", snode);
1350 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1351 cprintf("From: x@x.org (----)%s", nl);
1353 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1354 cprintf("From: x@x.org (anonymous)%s", nl);
1356 else if (strlen(fuser) > 0) {
1357 cprintf("From: %s (%s)%s", fuser, luser, nl);
1360 cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1363 cprintf("Organization: %s%s", lnode, nl);
1365 /* Blank line signifying RFC822 end-of-headers */
1366 if (TheMessage->cm_format_type != FMT_RFC822) {
1371 /* end header processing loop ... at this point, we're in the text */
1373 if (headers_only == HEADERS_FAST) goto DONE;
1374 mptr = TheMessage->cm_fields['M'];
1376 /* Tell the client about the MIME parts in this message */
1377 if (TheMessage->cm_format_type == FMT_RFC822) {
1378 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1379 mime_parser(mptr, NULL,
1385 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1386 /* FIXME ... we have to put some code in here to avoid
1387 * printing duplicate header information when both
1388 * Citadel and RFC822 headers exist. Preference should
1389 * probably be given to the RFC822 headers.
1391 int done_rfc822_hdrs = 0;
1392 while (ch=*(mptr++), ch!=0) {
1397 if (!done_rfc822_hdrs) {
1398 if (headers_only != HEADERS_NONE) {
1403 if (headers_only != HEADERS_ONLY) {
1407 if ((*(mptr) == 13) || (*(mptr) == 10)) {
1408 done_rfc822_hdrs = 1;
1412 if (done_rfc822_hdrs) {
1413 if (headers_only != HEADERS_NONE) {
1418 if (headers_only != HEADERS_ONLY) {
1422 if ((*mptr == 13) || (*mptr == 10)) {
1423 done_rfc822_hdrs = 1;
1431 if (headers_only == HEADERS_ONLY) {
1435 /* signify start of msg text */
1436 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1437 if (do_proto) cprintf("text\n");
1440 /* If the format type on disk is 1 (fixed-format), then we want
1441 * everything to be output completely literally ... regardless of
1442 * what message transfer format is in use.
1444 if (TheMessage->cm_format_type == FMT_FIXED) {
1445 if (mode == MT_MIME) {
1446 cprintf("Content-type: text/plain\n\n");
1449 while (ch = *mptr++, ch > 0) {
1452 if ((ch == 10) || (strlen(buf) > 250)) {
1453 cprintf("%s%s", buf, nl);
1456 buf[strlen(buf) + 1] = 0;
1457 buf[strlen(buf)] = ch;
1460 if (strlen(buf) > 0)
1461 cprintf("%s%s", buf, nl);
1464 /* If the message on disk is format 0 (Citadel vari-format), we
1465 * output using the formatter at 80 columns. This is the final output
1466 * form if the transfer format is RFC822, but if the transfer format
1467 * is Citadel proprietary, it'll still work, because the indentation
1468 * for new paragraphs is correct and the client will reformat the
1469 * message to the reader's screen width.
1471 if (TheMessage->cm_format_type == FMT_CITADEL) {
1472 if (mode == MT_MIME) {
1473 cprintf("Content-type: text/x-citadel-variformat\n\n");
1475 memfmout(80, mptr, 0, nl);
1478 /* If the message on disk is format 4 (MIME), we've gotta hand it
1479 * off to the MIME parser. The client has already been told that
1480 * this message is format 1 (fixed format), so the callback function
1481 * we use will display those parts as-is.
1483 if (TheMessage->cm_format_type == FMT_RFC822) {
1484 ma = malloc(sizeof(struct ma_info));
1485 memset(ma, 0, sizeof(struct ma_info));
1487 if (mode == MT_MIME) {
1488 strcpy(ma->chosen_part, "1");
1489 mime_parser(mptr, NULL,
1490 *choose_preferred, *fixed_output_pre,
1491 *fixed_output_post, (void *)ma, 0);
1492 mime_parser(mptr, NULL,
1493 *output_preferred, NULL, NULL, (void *)ma, 0);
1496 mime_parser(mptr, NULL,
1497 *fixed_output, *fixed_output_pre,
1498 *fixed_output_post, (void *)ma, 0);
1504 DONE: /* now we're done */
1505 if (do_proto) cprintf("000\n");
1512 * display a message (mode 0 - Citadel proprietary)
1514 void cmd_msg0(char *cmdbuf)
1517 int headers_only = HEADERS_ALL;
1519 msgid = extract_long(cmdbuf, 0);
1520 headers_only = extract_int(cmdbuf, 1);
1522 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1528 * display a message (mode 2 - RFC822)
1530 void cmd_msg2(char *cmdbuf)
1533 int headers_only = HEADERS_ALL;
1535 msgid = extract_long(cmdbuf, 0);
1536 headers_only = extract_int(cmdbuf, 1);
1538 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1544 * display a message (mode 3 - IGnet raw format - internal programs only)
1546 void cmd_msg3(char *cmdbuf)
1549 struct CtdlMessage *msg;
1552 if (CC->internal_pgm == 0) {
1553 cprintf("%d This command is for internal programs only.\n",
1554 ERROR + HIGHER_ACCESS_REQUIRED);
1558 msgnum = extract_long(cmdbuf, 0);
1559 msg = CtdlFetchMessage(msgnum, 1);
1561 cprintf("%d Message %ld not found.\n",
1562 ERROR + MESSAGE_NOT_FOUND, msgnum);
1566 serialize_message(&smr, msg);
1567 CtdlFreeMessage(msg);
1570 cprintf("%d Unable to serialize message\n",
1571 ERROR + INTERNAL_ERROR);
1575 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1576 client_write((char *)smr.ser, (int)smr.len);
1583 * Display a message using MIME content types
1585 void cmd_msg4(char *cmdbuf)
1589 msgid = extract_long(cmdbuf, 0);
1590 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1596 * Client tells us its preferred message format(s)
1598 void cmd_msgp(char *cmdbuf)
1600 safestrncpy(CC->preferred_formats, cmdbuf,
1601 sizeof(CC->preferred_formats));
1602 cprintf("%d ok\n", CIT_OK);
1607 * Open a component of a MIME message as a download file
1609 void cmd_opna(char *cmdbuf)
1612 char desired_section[SIZ];
1614 msgid = extract_long(cmdbuf, 0);
1615 extract(desired_section, cmdbuf, 1);
1616 safestrncpy(CC->download_desired_section, desired_section, sizeof CC->download_desired_section);
1618 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1623 * Save a message pointer into a specified room
1624 * (Returns 0 for success, nonzero for failure)
1625 * roomname may be NULL to use the current room
1627 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1629 char hold_rm[ROOMNAMELEN];
1630 struct cdbdata *cdbfr;
1633 long highest_msg = 0L;
1634 struct CtdlMessage *msg = NULL;
1636 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1637 roomname, msgid, flags);
1639 strcpy(hold_rm, CC->room.QRname);
1641 /* We may need to check to see if this message is real */
1642 if ( (flags & SM_VERIFY_GOODNESS)
1643 || (flags & SM_DO_REPL_CHECK)
1645 msg = CtdlFetchMessage(msgid, 1);
1646 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1649 /* Perform replication checks if necessary */
1650 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1652 if (getroom(&CC->room,
1653 ((roomname != NULL) ? roomname : CC->room.QRname) )
1655 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1656 if (msg != NULL) CtdlFreeMessage(msg);
1657 return(ERROR + ROOM_NOT_FOUND);
1660 if (ReplicationChecks(msg) != 0) {
1661 getroom(&CC->room, hold_rm);
1662 if (msg != NULL) CtdlFreeMessage(msg);
1664 "Did replication, and newer exists\n");
1669 /* Now the regular stuff */
1670 if (lgetroom(&CC->room,
1671 ((roomname != NULL) ? roomname : CC->room.QRname) )
1673 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1674 if (msg != NULL) CtdlFreeMessage(msg);
1675 return(ERROR + ROOM_NOT_FOUND);
1678 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1679 if (cdbfr == NULL) {
1683 msglist = malloc(cdbfr->len);
1684 if (msglist == NULL)
1685 lprintf(CTDL_ALERT, "ERROR malloc msglist!\n");
1686 num_msgs = cdbfr->len / sizeof(long);
1687 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1692 /* Make sure the message doesn't already exist in this room. It
1693 * is absolutely taboo to have more than one reference to the same
1694 * message in a room.
1696 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1697 if (msglist[i] == msgid) {
1698 lputroom(&CC->room); /* unlock the room */
1699 getroom(&CC->room, hold_rm);
1700 if (msg != NULL) CtdlFreeMessage(msg);
1702 return(ERROR + ALREADY_EXISTS);
1706 /* Now add the new message */
1708 msglist = realloc(msglist, (num_msgs * sizeof(long)));
1710 if (msglist == NULL) {
1711 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1713 msglist[num_msgs - 1] = msgid;
1715 /* Sort the message list, so all the msgid's are in order */
1716 num_msgs = sort_msglist(msglist, num_msgs);
1718 /* Determine the highest message number */
1719 highest_msg = msglist[num_msgs - 1];
1721 /* Write it back to disk. */
1722 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1723 msglist, (int)(num_msgs * sizeof(long)));
1725 /* Free up the memory we used. */
1728 /* Update the highest-message pointer and unlock the room. */
1729 CC->room.QRhighest = highest_msg;
1730 lputroom(&CC->room);
1731 getroom(&CC->room, hold_rm);
1733 /* Bump the reference count for this message. */
1734 if ((flags & SM_DONT_BUMP_REF)==0) {
1735 AdjRefCount(msgid, +1);
1738 /* Return success. */
1739 if (msg != NULL) CtdlFreeMessage(msg);
1746 * Message base operation to save a new message to the message store
1747 * (returns new message number)
1749 * This is the back end for CtdlSubmitMsg() and should not be directly
1750 * called by server-side modules.
1753 long send_message(struct CtdlMessage *msg) {
1761 /* Get a new message number */
1762 newmsgid = get_new_message_number();
1763 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1765 /* Generate an ID if we don't have one already */
1766 if (msg->cm_fields['I']==NULL) {
1767 msg->cm_fields['I'] = strdup(msgidbuf);
1770 /* If the message is big, set its body aside for storage elsewhere */
1771 if (msg->cm_fields['M'] != NULL) {
1772 if (strlen(msg->cm_fields['M']) > BIGMSG) {
1774 holdM = msg->cm_fields['M'];
1775 msg->cm_fields['M'] = NULL;
1779 /* Serialize our data structure for storage in the database */
1780 serialize_message(&smr, msg);
1783 msg->cm_fields['M'] = holdM;
1787 cprintf("%d Unable to serialize message\n",
1788 ERROR + INTERNAL_ERROR);
1792 /* Write our little bundle of joy into the message base */
1793 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
1794 smr.ser, smr.len) < 0) {
1795 lprintf(CTDL_ERR, "Can't store message\n");
1799 cdb_store(CDB_BIGMSGS,
1809 /* Free the memory we used for the serialized message */
1812 /* Return the *local* message ID to the caller
1813 * (even if we're storing an incoming network message)
1821 * Serialize a struct CtdlMessage into the format used on disk and network.
1823 * This function loads up a "struct ser_ret" (defined in server.h) which
1824 * contains the length of the serialized message and a pointer to the
1825 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1827 void serialize_message(struct ser_ret *ret, /* return values */
1828 struct CtdlMessage *msg) /* unserialized msg */
1832 static char *forder = FORDER;
1834 if (is_valid_message(msg) == 0) return; /* self check */
1837 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1838 ret->len = ret->len +
1839 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1841 lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1842 ret->ser = malloc(ret->len);
1843 if (ret->ser == NULL) {
1849 ret->ser[1] = msg->cm_anon_type;
1850 ret->ser[2] = msg->cm_format_type;
1853 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1854 ret->ser[wlen++] = (char)forder[i];
1855 strcpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1856 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1858 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
1859 (long)ret->len, (long)wlen);
1867 * Back end for the ReplicationChecks() function
1869 void check_repl(long msgnum, void *userdata) {
1870 lprintf(CTDL_DEBUG, "check_repl() replacing message %ld\n", msgnum);
1871 CtdlDeleteMessages(CC->room.QRname, msgnum, "");
1876 * Check to see if any messages already exist which carry the same Exclusive ID
1877 * as this one. If any are found, delete them.
1880 int ReplicationChecks(struct CtdlMessage *msg) {
1881 struct CtdlMessage *template;
1884 /* No exclusive id? Don't do anything. */
1885 if (msg->cm_fields['E'] == NULL) return 0;
1886 if (strlen(msg->cm_fields['E']) == 0) return 0;
1887 lprintf(CTDL_DEBUG, "Exclusive ID: <%s>\n", msg->cm_fields['E']);
1889 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1890 memset(template, 0, sizeof(struct CtdlMessage));
1891 template->cm_fields['E'] = strdup(msg->cm_fields['E']);
1893 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl, NULL);
1895 CtdlFreeMessage(template);
1896 lprintf(CTDL_DEBUG, "ReplicationChecks() returning %d\n", abort_this);
1904 * Save a message to disk and submit it into the delivery system.
1906 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
1907 struct recptypes *recps, /* recipients (if mail) */
1908 char *force /* force a particular room? */
1910 char submit_filename[128];
1911 char generated_timestamp[32];
1912 char hold_rm[ROOMNAMELEN];
1913 char actual_rm[ROOMNAMELEN];
1914 char force_room[ROOMNAMELEN];
1915 char content_type[SIZ]; /* We have to learn this */
1916 char recipient[SIZ];
1919 struct ctdluser userbuf;
1921 struct MetaData smi;
1922 FILE *network_fp = NULL;
1923 static int seqnum = 1;
1924 struct CtdlMessage *imsg = NULL;
1927 char *hold_R, *hold_D;
1929 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
1930 if (is_valid_message(msg) == 0) return(-1); /* self check */
1932 /* If this message has no timestamp, we take the liberty of
1933 * giving it one, right now.
1935 if (msg->cm_fields['T'] == NULL) {
1936 lprintf(CTDL_DEBUG, "Generating timestamp\n");
1937 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
1938 msg->cm_fields['T'] = strdup(generated_timestamp);
1941 /* If this message has no path, we generate one.
1943 if (msg->cm_fields['P'] == NULL) {
1944 lprintf(CTDL_DEBUG, "Generating path\n");
1945 if (msg->cm_fields['A'] != NULL) {
1946 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
1947 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
1948 if (isspace(msg->cm_fields['P'][a])) {
1949 msg->cm_fields['P'][a] = ' ';
1954 msg->cm_fields['P'] = strdup("unknown");
1958 if (force == NULL) {
1959 strcpy(force_room, "");
1962 strcpy(force_room, force);
1965 /* Learn about what's inside, because it's what's inside that counts */
1966 lprintf(CTDL_DEBUG, "Learning what's inside\n");
1967 if (msg->cm_fields['M'] == NULL) {
1968 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
1972 switch (msg->cm_format_type) {
1974 strcpy(content_type, "text/x-citadel-variformat");
1977 strcpy(content_type, "text/plain");
1980 strcpy(content_type, "text/plain");
1981 mptr = bmstrstr(msg->cm_fields['M'],
1982 "Content-type: ", strncasecmp);
1984 safestrncpy(content_type, &mptr[14],
1985 sizeof content_type);
1986 for (a = 0; a < strlen(content_type); ++a) {
1987 if ((content_type[a] == ';')
1988 || (content_type[a] == ' ')
1989 || (content_type[a] == 13)
1990 || (content_type[a] == 10)) {
1991 content_type[a] = 0;
1997 /* Goto the correct room */
1998 lprintf(CTDL_DEBUG, "Selected room %s\n",
1999 (recps) ? CC->room.QRname : SENTITEMS);
2000 strcpy(hold_rm, CC->room.QRname);
2001 strcpy(actual_rm, CC->room.QRname);
2002 if (recps != NULL) {
2003 strcpy(actual_rm, SENTITEMS);
2006 /* If the user is a twit, move to the twit room for posting */
2007 lprintf(CTDL_DEBUG, "Handling twit stuff: %s\n",
2008 (CC->user.axlevel == 2) ? config.c_twitroom : "OK");
2010 if (CC->user.axlevel == 2) {
2011 strcpy(hold_rm, actual_rm);
2012 strcpy(actual_rm, config.c_twitroom);
2016 /* ...or if this message is destined for Aide> then go there. */
2017 if (strlen(force_room) > 0) {
2018 strcpy(actual_rm, force_room);
2021 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2022 if (strcasecmp(actual_rm, CC->room.QRname)) {
2023 getroom(&CC->room, actual_rm);
2027 * If this message has no O (room) field, generate one.
2029 if (msg->cm_fields['O'] == NULL) {
2030 msg->cm_fields['O'] = strdup(CC->room.QRname);
2033 /* Perform "before save" hooks (aborting if any return nonzero) */
2034 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2035 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2037 /* If this message has an Exclusive ID, perform replication checks */
2038 lprintf(CTDL_DEBUG, "Performing replication checks\n");
2039 if (ReplicationChecks(msg) > 0) return(-4);
2041 /* Save it to disk */
2042 lprintf(CTDL_DEBUG, "Saving to disk\n");
2043 newmsgid = send_message(msg);
2044 if (newmsgid <= 0L) return(-5);
2046 /* Write a supplemental message info record. This doesn't have to
2047 * be a critical section because nobody else knows about this message
2050 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2051 memset(&smi, 0, sizeof(struct MetaData));
2052 smi.meta_msgnum = newmsgid;
2053 smi.meta_refcount = 0;
2054 safestrncpy(smi.meta_content_type, content_type, sizeof smi.meta_content_type);
2057 /* Now figure out where to store the pointers */
2058 lprintf(CTDL_DEBUG, "Storing pointers\n");
2060 /* If this is being done by the networker delivering a private
2061 * message, we want to BYPASS saving the sender's copy (because there
2062 * is no local sender; it would otherwise go to the Trashcan).
2064 if ((!CC->internal_pgm) || (recps == NULL)) {
2065 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
2066 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2067 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0);
2071 /* For internet mail, drop a copy in the outbound queue room */
2073 if (recps->num_internet > 0) {
2074 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
2077 /* If other rooms are specified, drop them there too. */
2079 if (recps->num_room > 0)
2080 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2081 extract(recipient, recps->recp_room, i);
2082 lprintf(CTDL_DEBUG, "Delivering to local room <%s>\n", recipient);
2083 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
2086 /* Bump this user's messages posted counter. */
2087 lprintf(CTDL_DEBUG, "Updating user\n");
2088 lgetuser(&CC->user, CC->curr_user);
2089 CC->user.posted = CC->user.posted + 1;
2090 lputuser(&CC->user);
2092 /* If this is private, local mail, make a copy in the
2093 * recipient's mailbox and bump the reference count.
2096 if (recps->num_local > 0)
2097 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2098 extract(recipient, recps->recp_local, i);
2099 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2101 if (getuser(&userbuf, recipient) == 0) {
2102 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2103 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
2104 BumpNewMailCounter(userbuf.usernum);
2107 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2108 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0);
2112 /* Perform "after save" hooks */
2113 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2114 PerformMessageHooks(msg, EVT_AFTERSAVE);
2116 /* For IGnet mail, we have to save a new copy into the spooler for
2117 * each recipient, with the R and D fields set to the recipient and
2118 * destination-node. This has two ugly side effects: all other
2119 * recipients end up being unlisted in this recipient's copy of the
2120 * message, and it has to deliver multiple messages to the same
2121 * node. We'll revisit this again in a year or so when everyone has
2122 * a network spool receiver that can handle the new style messages.
2125 if (recps->num_ignet > 0)
2126 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2127 extract(recipient, recps->recp_ignet, i);
2129 hold_R = msg->cm_fields['R'];
2130 hold_D = msg->cm_fields['D'];
2131 msg->cm_fields['R'] = malloc(SIZ);
2132 msg->cm_fields['D'] = malloc(SIZ);
2133 extract_token(msg->cm_fields['R'], recipient, 0, '@');
2134 extract_token(msg->cm_fields['D'], recipient, 1, '@');
2136 serialize_message(&smr, msg);
2138 snprintf(submit_filename, sizeof submit_filename,
2139 "./network/spoolin/netmail.%04lx.%04x.%04x",
2140 (long) getpid(), CC->cs_pid, ++seqnum);
2141 network_fp = fopen(submit_filename, "wb+");
2142 if (network_fp != NULL) {
2143 fwrite(smr.ser, smr.len, 1, network_fp);
2149 free(msg->cm_fields['R']);
2150 free(msg->cm_fields['D']);
2151 msg->cm_fields['R'] = hold_R;
2152 msg->cm_fields['D'] = hold_D;
2155 /* Go back to the room we started from */
2156 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2157 if (strcasecmp(hold_rm, CC->room.QRname))
2158 getroom(&CC->room, hold_rm);
2160 /* For internet mail, generate delivery instructions.
2161 * Yes, this is recursive. Deal with it. Infinite recursion does
2162 * not happen because the delivery instructions message does not
2163 * contain a recipient.
2166 if (recps->num_internet > 0) {
2167 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2168 instr = malloc(SIZ * 2);
2169 snprintf(instr, SIZ * 2,
2170 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2172 SPOOLMIME, newmsgid, (long)time(NULL),
2173 msg->cm_fields['A'], msg->cm_fields['N']
2176 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2177 size_t tmp = strlen(instr);
2178 extract(recipient, recps->recp_internet, i);
2179 snprintf(&instr[tmp], SIZ * 2 - tmp,
2180 "remote|%s|0||\n", recipient);
2183 imsg = malloc(sizeof(struct CtdlMessage));
2184 memset(imsg, 0, sizeof(struct CtdlMessage));
2185 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2186 imsg->cm_anon_type = MES_NORMAL;
2187 imsg->cm_format_type = FMT_RFC822;
2188 imsg->cm_fields['A'] = strdup("Citadel");
2189 imsg->cm_fields['M'] = instr;
2190 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2191 CtdlFreeMessage(imsg);
2200 * Convenience function for generating small administrative messages.
2202 void quickie_message(char *from, char *to, char *room, char *text,
2203 int format_type, char *subject)
2205 struct CtdlMessage *msg;
2206 struct recptypes *recp = NULL;
2208 msg = malloc(sizeof(struct CtdlMessage));
2209 memset(msg, 0, sizeof(struct CtdlMessage));
2210 msg->cm_magic = CTDLMESSAGE_MAGIC;
2211 msg->cm_anon_type = MES_NORMAL;
2212 msg->cm_format_type = format_type;
2213 msg->cm_fields['A'] = strdup(from);
2214 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2215 msg->cm_fields['N'] = strdup(NODENAME);
2217 msg->cm_fields['R'] = strdup(to);
2218 recp = validate_recipients(to);
2220 if (subject != NULL) {
2221 msg->cm_fields['U'] = strdup(subject);
2223 msg->cm_fields['M'] = strdup(text);
2225 CtdlSubmitMsg(msg, recp, room);
2226 CtdlFreeMessage(msg);
2227 if (recp != NULL) free(recp);
2233 * Back end function used by CtdlMakeMessage() and similar functions
2235 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2236 size_t maxlen, /* maximum message length */
2237 char *exist, /* if non-null, append to it;
2238 exist is ALWAYS freed */
2239 int crlf /* CRLF newlines instead of LF */
2243 size_t message_len = 0;
2244 size_t buffer_len = 0;
2250 if (exist == NULL) {
2257 message_len = strlen(exist);
2258 buffer_len = message_len + 4096;
2259 m = realloc(exist, buffer_len);
2266 /* flush the input if we have nowhere to store it */
2271 /* read in the lines of message text one by one */
2273 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2274 if (!strcmp(buf, terminator)) finished = 1;
2276 strcat(buf, "\r\n");
2282 if ( (!flushing) && (!finished) ) {
2283 /* Measure the line */
2284 linelen = strlen(buf);
2286 /* augment the buffer if we have to */
2287 if ((message_len + linelen) >= buffer_len) {
2288 ptr = realloc(m, (buffer_len * 2) );
2289 if (ptr == NULL) { /* flush if can't allocate */
2292 buffer_len = (buffer_len * 2);
2294 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2298 /* Add the new line to the buffer. NOTE: this loop must avoid
2299 * using functions like strcat() and strlen() because they
2300 * traverse the entire buffer upon every call, and doing that
2301 * for a multi-megabyte message slows it down beyond usability.
2303 strcpy(&m[message_len], buf);
2304 message_len += linelen;
2307 /* if we've hit the max msg length, flush the rest */
2308 if (message_len >= maxlen) flushing = 1;
2310 } while (!finished);
2318 * Build a binary message to be saved on disk.
2319 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2320 * will become part of the message. This means you are no longer
2321 * responsible for managing that memory -- it will be freed along with
2322 * the rest of the fields when CtdlFreeMessage() is called.)
2325 struct CtdlMessage *CtdlMakeMessage(
2326 struct ctdluser *author, /* author's user structure */
2327 char *recipient, /* NULL if it's not mail */
2328 char *room, /* room where it's going */
2329 int type, /* see MES_ types in header file */
2330 int format_type, /* variformat, plain text, MIME... */
2331 char *fake_name, /* who we're masquerading as */
2332 char *subject, /* Subject (optional) */
2333 char *preformatted_text /* ...or NULL to read text from client */
2335 char dest_node[SIZ];
2337 struct CtdlMessage *msg;
2339 msg = malloc(sizeof(struct CtdlMessage));
2340 memset(msg, 0, sizeof(struct CtdlMessage));
2341 msg->cm_magic = CTDLMESSAGE_MAGIC;
2342 msg->cm_anon_type = type;
2343 msg->cm_format_type = format_type;
2345 /* Don't confuse the poor folks if it's not routed mail. */
2346 strcpy(dest_node, "");
2350 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2351 msg->cm_fields['P'] = strdup(buf);
2353 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2354 msg->cm_fields['T'] = strdup(buf);
2356 if (fake_name[0]) /* author */
2357 msg->cm_fields['A'] = strdup(fake_name);
2359 msg->cm_fields['A'] = strdup(author->fullname);
2361 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2362 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2365 msg->cm_fields['O'] = strdup(CC->room.QRname);
2368 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2369 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2371 if (recipient[0] != 0) {
2372 msg->cm_fields['R'] = strdup(recipient);
2374 if (dest_node[0] != 0) {
2375 msg->cm_fields['D'] = strdup(dest_node);
2378 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2379 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2382 if (subject != NULL) {
2384 if (strlen(subject) > 0) {
2385 msg->cm_fields['U'] = strdup(subject);
2389 if (preformatted_text != NULL) {
2390 msg->cm_fields['M'] = preformatted_text;
2393 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2394 config.c_maxmsglen, NULL, 0);
2402 * Check to see whether we have permission to post a message in the current
2403 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2404 * returns 0 on success.
2406 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2408 if (!(CC->logged_in)) {
2409 snprintf(errmsgbuf, n, "Not logged in.");
2410 return (ERROR + NOT_LOGGED_IN);
2413 if ((CC->user.axlevel < 2)
2414 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2415 snprintf(errmsgbuf, n, "Need to be validated to enter "
2416 "(except in %s> to sysop)", MAILROOM);
2417 return (ERROR + HIGHER_ACCESS_REQUIRED);
2420 if ((CC->user.axlevel < 4)
2421 && (CC->room.QRflags & QR_NETWORK)) {
2422 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2423 return (ERROR + HIGHER_ACCESS_REQUIRED);
2426 if ((CC->user.axlevel < 6)
2427 && (CC->room.QRflags & QR_READONLY)) {
2428 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2429 return (ERROR + HIGHER_ACCESS_REQUIRED);
2432 strcpy(errmsgbuf, "Ok");
2438 * Check to see if the specified user has Internet mail permission
2439 * (returns nonzero if permission is granted)
2441 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2443 /* Do not allow twits to send Internet mail */
2444 if (who->axlevel <= 2) return(0);
2446 /* Globally enabled? */
2447 if (config.c_restrict == 0) return(1);
2449 /* User flagged ok? */
2450 if (who->flags & US_INTERNET) return(2);
2452 /* Aide level access? */
2453 if (who->axlevel >= 6) return(3);
2455 /* No mail for you! */
2462 * Validate recipients, count delivery types and errors, and handle aliasing
2463 * FIXME check for dupes!!!!!
2465 struct recptypes *validate_recipients(char *recipients) {
2466 struct recptypes *ret;
2467 char this_recp[SIZ];
2468 char this_recp_cooked[SIZ];
2474 struct ctdluser tempUS;
2475 struct ctdlroom tempQR;
2478 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2479 if (ret == NULL) return(NULL);
2480 memset(ret, 0, sizeof(struct recptypes));
2483 ret->num_internet = 0;
2488 if (recipients == NULL) {
2491 else if (strlen(recipients) == 0) {
2495 /* Change all valid separator characters to commas */
2496 for (i=0; i<strlen(recipients); ++i) {
2497 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2498 recipients[i] = ',';
2503 num_recps = num_tokens(recipients, ',');
2506 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2507 extract_token(this_recp, recipients, i, ',');
2509 lprintf(CTDL_DEBUG, "Evaluating recipient #%d <%s>\n", i, this_recp);
2510 mailtype = alias(this_recp);
2511 mailtype = alias(this_recp);
2512 mailtype = alias(this_recp);
2513 for (j=0; j<=strlen(this_recp); ++j) {
2514 if (this_recp[j]=='_') {
2515 this_recp_cooked[j] = ' ';
2518 this_recp_cooked[j] = this_recp[j];
2524 if (!strcasecmp(this_recp, "sysop")) {
2526 strcpy(this_recp, config.c_aideroom);
2527 if (strlen(ret->recp_room) > 0) {
2528 strcat(ret->recp_room, "|");
2530 strcat(ret->recp_room, this_recp);
2532 else if (getuser(&tempUS, this_recp) == 0) {
2534 strcpy(this_recp, tempUS.fullname);
2535 if (strlen(ret->recp_local) > 0) {
2536 strcat(ret->recp_local, "|");
2538 strcat(ret->recp_local, this_recp);
2540 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2542 strcpy(this_recp, tempUS.fullname);
2543 if (strlen(ret->recp_local) > 0) {
2544 strcat(ret->recp_local, "|");
2546 strcat(ret->recp_local, this_recp);
2548 else if ( (!strncasecmp(this_recp, "room_", 5))
2549 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2551 if (strlen(ret->recp_room) > 0) {
2552 strcat(ret->recp_room, "|");
2554 strcat(ret->recp_room, &this_recp_cooked[5]);
2562 /* Yes, you're reading this correctly: if the target
2563 * domain points back to the local system or an attached
2564 * Citadel directory, the address is invalid. That's
2565 * because if the address were valid, we would have
2566 * already translated it to a local address by now.
2568 if (IsDirectory(this_recp)) {
2573 ++ret->num_internet;
2574 if (strlen(ret->recp_internet) > 0) {
2575 strcat(ret->recp_internet, "|");
2577 strcat(ret->recp_internet, this_recp);
2582 if (strlen(ret->recp_ignet) > 0) {
2583 strcat(ret->recp_ignet, "|");
2585 strcat(ret->recp_ignet, this_recp);
2593 if (strlen(ret->errormsg) == 0) {
2594 snprintf(append, sizeof append,
2595 "Invalid recipient: %s",
2599 snprintf(append, sizeof append,
2602 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2603 strcat(ret->errormsg, append);
2607 if (strlen(ret->display_recp) == 0) {
2608 strcpy(append, this_recp);
2611 snprintf(append, sizeof append, ", %s",
2614 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2615 strcat(ret->display_recp, append);
2620 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2621 ret->num_room + ret->num_error) == 0) {
2623 strcpy(ret->errormsg, "No recipients specified.");
2626 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2627 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2628 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2629 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2630 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2631 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2639 * message entry - mode 0 (normal)
2641 void cmd_ent0(char *entargs)
2645 char masquerade_as[SIZ];
2647 int format_type = 0;
2648 char newusername[SIZ];
2649 struct CtdlMessage *msg;
2653 struct recptypes *valid = NULL;
2660 post = extract_int(entargs, 0);
2661 extract(recp, entargs, 1);
2662 anon_flag = extract_int(entargs, 2);
2663 format_type = extract_int(entargs, 3);
2664 extract(subject, entargs, 4);
2665 do_confirm = extract_int(entargs, 6);
2667 /* first check to make sure the request is valid. */
2669 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2671 cprintf("%d %s\n", err, errmsg);
2675 /* Check some other permission type things. */
2678 if (CC->user.axlevel < 6) {
2679 cprintf("%d You don't have permission to masquerade.\n",
2680 ERROR + HIGHER_ACCESS_REQUIRED);
2683 extract(newusername, entargs, 5);
2684 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2685 safestrncpy(CC->fake_postname, newusername,
2686 sizeof(CC->fake_postname) );
2687 cprintf("%d ok\n", CIT_OK);
2690 CC->cs_flags |= CS_POSTING;
2692 /* In the Mail> room we have to behave a little differently --
2693 * make sure the user has specified at least one recipient. Then
2694 * validate the recipient(s).
2696 if ( (CC->room.QRflags & QR_MAILBOX)
2697 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2699 if (CC->user.axlevel < 2) {
2700 strcpy(recp, "sysop");
2703 valid = validate_recipients(recp);
2704 if (valid->num_error > 0) {
2706 ERROR + NO_SUCH_USER, valid->errormsg);
2710 if (valid->num_internet > 0) {
2711 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2712 cprintf("%d You do not have permission "
2713 "to send Internet mail.\n",
2714 ERROR + HIGHER_ACCESS_REQUIRED);
2720 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2721 && (CC->user.axlevel < 4) ) {
2722 cprintf("%d Higher access required for network mail.\n",
2723 ERROR + HIGHER_ACCESS_REQUIRED);
2728 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2729 && ((CC->user.flags & US_INTERNET) == 0)
2730 && (!CC->internal_pgm)) {
2731 cprintf("%d You don't have access to Internet mail.\n",
2732 ERROR + HIGHER_ACCESS_REQUIRED);
2739 /* Is this a room which has anonymous-only or anonymous-option? */
2740 anonymous = MES_NORMAL;
2741 if (CC->room.QRflags & QR_ANONONLY) {
2742 anonymous = MES_ANONONLY;
2744 if (CC->room.QRflags & QR_ANONOPT) {
2745 if (anon_flag == 1) { /* only if the user requested it */
2746 anonymous = MES_ANONOPT;
2750 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
2754 /* If we're only checking the validity of the request, return
2755 * success without creating the message.
2758 cprintf("%d %s\n", CIT_OK,
2759 ((valid != NULL) ? valid->display_recp : "") );
2764 /* Handle author masquerading */
2765 if (CC->fake_postname[0]) {
2766 strcpy(masquerade_as, CC->fake_postname);
2768 else if (CC->fake_username[0]) {
2769 strcpy(masquerade_as, CC->fake_username);
2772 strcpy(masquerade_as, "");
2775 /* Read in the message from the client. */
2777 cprintf("%d send message\n", START_CHAT_MODE);
2779 cprintf("%d send message\n", SEND_LISTING);
2781 msg = CtdlMakeMessage(&CC->user, recp,
2782 CC->room.QRname, anonymous, format_type,
2783 masquerade_as, subject, NULL);
2786 msgnum = CtdlSubmitMsg(msg, valid, "");
2789 cprintf("%ld\n", msgnum);
2791 cprintf("Message accepted.\n");
2794 cprintf("Internal error.\n");
2796 if (msg->cm_fields['E'] != NULL) {
2797 cprintf("%s\n", msg->cm_fields['E']);
2804 CtdlFreeMessage(msg);
2806 CC->fake_postname[0] = '\0';
2814 * API function to delete messages which match a set of criteria
2815 * (returns the actual number of messages deleted)
2817 int CtdlDeleteMessages(char *room_name, /* which room */
2818 long dmsgnum, /* or "0" for any */
2819 char *content_type /* or "" for any */
2823 struct ctdlroom qrbuf;
2824 struct cdbdata *cdbfr;
2825 long *msglist = NULL;
2826 long *dellist = NULL;
2829 int num_deleted = 0;
2831 struct MetaData smi;
2833 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s)\n",
2834 room_name, dmsgnum, content_type);
2836 /* get room record, obtaining a lock... */
2837 if (lgetroom(&qrbuf, room_name) != 0) {
2838 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
2840 return (0); /* room not found */
2842 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2844 if (cdbfr != NULL) {
2845 msglist = malloc(cdbfr->len);
2846 dellist = malloc(cdbfr->len);
2847 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2848 num_msgs = cdbfr->len / sizeof(long);
2852 for (i = 0; i < num_msgs; ++i) {
2855 /* Set/clear a bit for each criterion */
2857 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2858 delete_this |= 0x01;
2860 if (strlen(content_type) == 0) {
2861 delete_this |= 0x02;
2863 GetMetaData(&smi, msglist[i]);
2864 if (!strcasecmp(smi.meta_content_type,
2866 delete_this |= 0x02;
2870 /* Delete message only if all bits are set */
2871 if (delete_this == 0x03) {
2872 dellist[num_deleted++] = msglist[i];
2877 num_msgs = sort_msglist(msglist, num_msgs);
2878 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
2879 msglist, (int)(num_msgs * sizeof(long)));
2881 qrbuf.QRhighest = msglist[num_msgs - 1];
2885 /* Go through the messages we pulled out of the index, and decrement
2886 * their reference counts by 1. If this is the only room the message
2887 * was in, the reference count will reach zero and the message will
2888 * automatically be deleted from the database. We do this in a
2889 * separate pass because there might be plug-in hooks getting called,
2890 * and we don't want that happening during an S_ROOMS critical
2893 if (num_deleted) for (i=0; i<num_deleted; ++i) {
2894 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
2895 AdjRefCount(dellist[i], -1);
2898 /* Now free the memory we used, and go away. */
2899 if (msglist != NULL) free(msglist);
2900 if (dellist != NULL) free(dellist);
2901 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
2902 return (num_deleted);
2908 * Check whether the current user has permission to delete messages from
2909 * the current room (returns 1 for yes, 0 for no)
2911 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
2912 getuser(&CC->user, CC->curr_user);
2913 if ((CC->user.axlevel < 6)
2914 && (CC->user.usernum != CC->room.QRroomaide)
2915 && ((CC->room.QRflags & QR_MAILBOX) == 0)
2916 && (!(CC->internal_pgm))) {
2925 * Delete message from current room
2927 void cmd_dele(char *delstr)
2932 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
2933 cprintf("%d Higher access required.\n",
2934 ERROR + HIGHER_ACCESS_REQUIRED);
2937 delnum = extract_long(delstr, 0);
2939 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "");
2942 cprintf("%d %d message%s deleted.\n", CIT_OK,
2943 num_deleted, ((num_deleted != 1) ? "s" : ""));
2945 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
2951 * Back end API function for moves and deletes
2953 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
2956 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
2957 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
2958 if (err != 0) return(err);
2966 * move or copy a message to another room
2968 void cmd_move(char *args)
2972 struct ctdlroom qtemp;
2978 num = extract_long(args, 0);
2979 extract(targ, args, 1);
2980 targ[ROOMNAMELEN - 1] = 0;
2981 is_copy = extract_int(args, 2);
2983 if (getroom(&qtemp, targ) != 0) {
2984 cprintf("%d '%s' does not exist.\n",
2985 ERROR + ROOM_NOT_FOUND, targ);
2989 getuser(&CC->user, CC->curr_user);
2990 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
2992 /* Check for permission to perform this operation.
2993 * Remember: "CC->room" is source, "qtemp" is target.
2997 /* Aides can move/copy */
2998 if (CC->user.axlevel >= 6) permit = 1;
3000 /* Room aides can move/copy */
3001 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3003 /* Permit move/copy from personal rooms */
3004 if ((CC->room.QRflags & QR_MAILBOX)
3005 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3007 /* Permit only copy from public to personal room */
3009 && (!(CC->room.QRflags & QR_MAILBOX))
3010 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3012 /* User must have access to target room */
3013 if (!(ra & UA_KNOWN)) permit = 0;
3016 cprintf("%d Higher access required.\n",
3017 ERROR + HIGHER_ACCESS_REQUIRED);
3021 err = CtdlCopyMsgToRoom(num, targ);
3023 cprintf("%d Cannot store message in %s: error %d\n",
3028 /* Now delete the message from the source room,
3029 * if this is a 'move' rather than a 'copy' operation.
3032 CtdlDeleteMessages(CC->room.QRname, num, "");
3035 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3041 * GetMetaData() - Get the supplementary record for a message
3043 void GetMetaData(struct MetaData *smibuf, long msgnum)
3046 struct cdbdata *cdbsmi;
3049 memset(smibuf, 0, sizeof(struct MetaData));
3050 smibuf->meta_msgnum = msgnum;
3051 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3053 /* Use the negative of the message number for its supp record index */
3054 TheIndex = (0L - msgnum);
3056 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3057 if (cdbsmi == NULL) {
3058 return; /* record not found; go with defaults */
3060 memcpy(smibuf, cdbsmi->ptr,
3061 ((cdbsmi->len > sizeof(struct MetaData)) ?
3062 sizeof(struct MetaData) : cdbsmi->len));
3069 * PutMetaData() - (re)write supplementary record for a message
3071 void PutMetaData(struct MetaData *smibuf)
3075 /* Use the negative of the message number for the metadata db index */
3076 TheIndex = (0L - smibuf->meta_msgnum);
3078 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3079 smibuf->meta_msgnum, smibuf->meta_refcount);
3081 cdb_store(CDB_MSGMAIN,
3082 &TheIndex, (int)sizeof(long),
3083 smibuf, (int)sizeof(struct MetaData));
3088 * AdjRefCount - change the reference count for a message;
3089 * delete the message if it reaches zero
3091 void AdjRefCount(long msgnum, int incr)
3094 struct MetaData smi;
3097 /* This is a *tight* critical section; please keep it that way, as
3098 * it may get called while nested in other critical sections.
3099 * Complicating this any further will surely cause deadlock!
3101 begin_critical_section(S_SUPPMSGMAIN);
3102 GetMetaData(&smi, msgnum);
3103 lprintf(CTDL_DEBUG, "Ref count for message <%ld> before write is <%d>\n",
3104 msgnum, smi.meta_refcount);
3105 smi.meta_refcount += incr;
3107 end_critical_section(S_SUPPMSGMAIN);
3108 lprintf(CTDL_DEBUG, "Ref count for message <%ld> after write is <%d>\n",
3109 msgnum, smi.meta_refcount);
3111 /* If the reference count is now zero, delete the message
3112 * (and its supplementary record as well).
3114 if (smi.meta_refcount == 0) {
3115 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3117 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3118 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3120 /* We have to delete the metadata record too! */
3121 delnum = (0L - msgnum);
3122 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3127 * Write a generic object to this room
3129 * Note: this could be much more efficient. Right now we use two temporary
3130 * files, and still pull the message into memory as with all others.
3132 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3133 char *content_type, /* MIME type of this object */
3134 char *tempfilename, /* Where to fetch it from */
3135 struct ctdluser *is_mailbox, /* Mailbox room? */
3136 int is_binary, /* Is encoding necessary? */
3137 int is_unique, /* Del others of this type? */
3138 unsigned int flags /* Internal save flags */
3143 struct ctdlroom qrbuf;
3144 char roomname[ROOMNAMELEN];
3145 struct CtdlMessage *msg;
3147 char *raw_message = NULL;
3148 char *encoded_message = NULL;
3149 off_t raw_length = 0;
3151 if (is_mailbox != NULL)
3152 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3154 safestrncpy(roomname, req_room, sizeof(roomname));
3155 lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3158 fp = fopen(tempfilename, "rb");
3160 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3161 tempfilename, strerror(errno));
3164 fseek(fp, 0L, SEEK_END);
3165 raw_length = ftell(fp);
3167 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3169 raw_message = malloc((size_t)raw_length + 2);
3170 fread(raw_message, (size_t)raw_length, 1, fp);
3174 encoded_message = malloc((size_t)
3175 (((raw_length * 134) / 100) + 4096 ) );
3178 encoded_message = malloc((size_t)(raw_length + 4096));
3181 sprintf(encoded_message, "Content-type: %s\n", content_type);
3184 sprintf(&encoded_message[strlen(encoded_message)],
3185 "Content-transfer-encoding: base64\n\n"
3189 sprintf(&encoded_message[strlen(encoded_message)],
3190 "Content-transfer-encoding: 7bit\n\n"
3196 &encoded_message[strlen(encoded_message)],
3202 raw_message[raw_length] = 0;
3204 &encoded_message[strlen(encoded_message)],
3212 lprintf(CTDL_DEBUG, "Allocating\n");
3213 msg = malloc(sizeof(struct CtdlMessage));
3214 memset(msg, 0, sizeof(struct CtdlMessage));
3215 msg->cm_magic = CTDLMESSAGE_MAGIC;
3216 msg->cm_anon_type = MES_NORMAL;
3217 msg->cm_format_type = 4;
3218 msg->cm_fields['A'] = strdup(CC->user.fullname);
3219 msg->cm_fields['O'] = strdup(req_room);
3220 msg->cm_fields['N'] = strdup(config.c_nodename);
3221 msg->cm_fields['H'] = strdup(config.c_humannode);
3222 msg->cm_flags = flags;
3224 msg->cm_fields['M'] = encoded_message;
3226 /* Create the requested room if we have to. */
3227 if (getroom(&qrbuf, roomname) != 0) {
3228 create_room(roomname,
3229 ( (is_mailbox != NULL) ? 5 : 3 ),
3230 "", 0, 1, 0, VIEW_BBS);
3232 /* If the caller specified this object as unique, delete all
3233 * other objects of this type that are currently in the room.
3236 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3237 CtdlDeleteMessages(roomname, 0L, content_type));
3239 /* Now write the data */
3240 CtdlSubmitMsg(msg, NULL, roomname);
3241 CtdlFreeMessage(msg);
3249 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3250 config_msgnum = msgnum;
3254 char *CtdlGetSysConfig(char *sysconfname) {
3255 char hold_rm[ROOMNAMELEN];
3258 struct CtdlMessage *msg;
3261 strcpy(hold_rm, CC->room.QRname);
3262 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3263 getroom(&CC->room, hold_rm);
3268 /* We want the last (and probably only) config in this room */
3269 begin_critical_section(S_CONFIG);
3270 config_msgnum = (-1L);
3271 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3272 CtdlGetSysConfigBackend, NULL);
3273 msgnum = config_msgnum;
3274 end_critical_section(S_CONFIG);
3280 msg = CtdlFetchMessage(msgnum, 1);
3282 conf = strdup(msg->cm_fields['M']);
3283 CtdlFreeMessage(msg);
3290 getroom(&CC->room, hold_rm);
3292 if (conf != NULL) do {
3293 extract_token(buf, conf, 0, '\n');
3294 strcpy(conf, &conf[strlen(buf)+1]);
3295 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3300 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3301 char temp[PATH_MAX];
3304 strcpy(temp, tmpnam(NULL));
3306 fp = fopen(temp, "w");
3307 if (fp == NULL) return;
3308 fprintf(fp, "%s", sysconfdata);
3311 /* this handy API function does all the work for us */
3312 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3318 * Determine whether a given Internet address belongs to the current user
3320 int CtdlIsMe(char *addr) {
3321 struct recptypes *recp;
3324 recp = validate_recipients(addr);
3325 if (recp == NULL) return(0);
3327 if (recp->num_local == 0) {
3332 for (i=0; i<recp->num_local; ++i) {
3333 extract(addr, recp->recp_local, i);
3334 if (!strcasecmp(addr, CC->user.fullname)) {
3346 * Citadel protocol command to do the same
3348 void cmd_isme(char *argbuf) {
3351 if (CtdlAccessCheck(ac_logged_in)) return;
3352 extract(addr, argbuf, 0);
3354 if (CtdlIsMe(addr)) {
3355 cprintf("%d %s\n", CIT_OK, addr);
3358 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);