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"
60 * This really belongs in serv_network.c, but I don't know how to export
61 * symbols between modules.
63 struct FilterList *filterlist = NULL;
67 * These are the four-character field headers we use when outputting
68 * messages in Citadel format (as opposed to RFC822 format).
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,
78 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
105 * This function is self explanatory.
106 * (What can I say, I'm in a weird mood today...)
108 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
112 for (i = 0; i < strlen(name); ++i) {
113 if (name[i] == '@') {
114 while (isspace(name[i - 1]) && i > 0) {
115 strcpy(&name[i - 1], &name[i]);
118 while (isspace(name[i + 1])) {
119 strcpy(&name[i + 1], &name[i + 2]);
127 * Aliasing for network mail.
128 * (Error messages have been commented out, because this is a server.)
130 int alias(char *name)
131 { /* process alias and routing info for mail */
134 char aaa[SIZ], bbb[SIZ];
135 char *ignetcfg = NULL;
136 char *ignetmap = NULL;
143 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
151 "mail.aliases", "r");
153 fp = fopen("/dev/null", "r");
160 while (fgets(aaa, sizeof aaa, fp) != NULL) {
161 while (isspace(name[0]))
162 strcpy(name, &name[1]);
163 aaa[strlen(aaa) - 1] = 0;
165 for (a = 0; a < strlen(aaa); ++a) {
167 strcpy(bbb, &aaa[a + 1]);
171 if (!strcasecmp(name, aaa))
176 /* Hit the Global Address Book */
177 if (CtdlDirectoryLookup(aaa, name) == 0) {
181 lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name);
183 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
184 for (a=0; a<strlen(name); ++a) {
185 if (name[a] == '@') {
186 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
188 lprintf(CTDL_INFO, "Changed to <%s>\n", name);
193 /* determine local or remote type, see citadel.h */
194 at = haschar(name, '@');
195 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
196 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
197 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
199 /* figure out the delivery mode */
200 extract_token(node, name, 1, '@', sizeof node);
202 /* If there are one or more dots in the nodename, we assume that it
203 * is an FQDN and will attempt SMTP delivery to the Internet.
205 if (haschar(node, '.') > 0) {
206 return(MES_INTERNET);
209 /* Otherwise we look in the IGnet maps for a valid Citadel node.
210 * Try directly-connected nodes first...
212 ignetcfg = CtdlGetSysConfig(IGNETCFG);
213 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
214 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
215 extract_token(testnode, buf, 0, '|', sizeof testnode);
216 if (!strcasecmp(node, testnode)) {
224 * Then try nodes that are two or more hops away.
226 ignetmap = CtdlGetSysConfig(IGNETMAP);
227 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
228 extract_token(buf, ignetmap, i, '\n', sizeof buf);
229 extract_token(testnode, buf, 0, '|', sizeof testnode);
230 if (!strcasecmp(node, testnode)) {
237 /* If we get to this point it's an invalid node name */
252 "/citadel.control", "r");
254 lprintf(CTDL_CRIT, "Cannot open citadel.control: %s\n",
258 fread((char *) &CitControl, sizeof(struct CitControl), 1, fp);
264 void simple_listing(long msgnum, void *userdata)
266 cprintf("%ld\n", msgnum);
271 /* Determine if a given message matches the fields in a message template.
272 * Return 0 for a successful match.
274 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
277 /* If there aren't any fields in the template, all messages will
280 if (template == NULL) return(0);
282 /* Null messages are bogus. */
283 if (msg == NULL) return(1);
285 for (i='A'; i<='Z'; ++i) {
286 if (template->cm_fields[i] != NULL) {
287 if (msg->cm_fields[i] == NULL) {
290 if (strcasecmp(msg->cm_fields[i],
291 template->cm_fields[i])) return 1;
295 /* All compares succeeded: we have a match! */
302 * Retrieve the "seen" message list for the current room.
304 void CtdlGetSeen(char *buf, int which_set) {
307 /* Learn about the user and room in question */
308 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
310 if (which_set == ctdlsetseen_seen)
311 safestrncpy(buf, vbuf.v_seen, SIZ);
312 if (which_set == ctdlsetseen_answered)
313 safestrncpy(buf, vbuf.v_answered, SIZ);
319 * Manipulate the "seen msgs" string (or other message set strings)
321 void CtdlSetSeen(long target_msgnum, int target_setting, int which_set,
322 struct ctdluser *which_user, struct ctdlroom *which_room) {
323 struct cdbdata *cdbfr;
335 char *is_set; /* actually an array of booleans */
338 char setstr[SIZ], lostr[SIZ], histr[SIZ];
341 lprintf(CTDL_DEBUG, "CtdlSetSeen(%ld, %d, %d)\n",
342 target_msgnum, target_setting, which_set);
344 /* Learn about the user and room in question */
345 CtdlGetRelationship(&vbuf,
346 ((which_user != NULL) ? which_user : &CC->user),
347 ((which_room != NULL) ? which_room : &CC->room)
350 /* Load the message list */
351 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
353 msglist = malloc(cdbfr->len);
354 memcpy(msglist, cdbfr->ptr, cdbfr->len);
355 num_msgs = cdbfr->len / sizeof(long);
358 return; /* No messages at all? No further action. */
361 is_set = malloc(num_msgs * sizeof(char));
362 memset(is_set, 0, (num_msgs * sizeof(char)) );
364 /* Decide which message set we're manipulating */
366 case ctdlsetseen_seen:
367 safestrncpy(vset, vbuf.v_seen, sizeof vset);
369 case ctdlsetseen_answered:
370 safestrncpy(vset, vbuf.v_answered, sizeof vset);
374 /* lprintf(CTDL_DEBUG, "before optimize: %s\n", vset); */
376 /* Translate the existing sequence set into an array of booleans */
377 num_sets = num_tokens(vset, ',');
378 for (s=0; s<num_sets; ++s) {
379 extract_token(setstr, vset, s, ',', sizeof setstr);
381 extract_token(lostr, setstr, 0, ':', sizeof lostr);
382 if (num_tokens(setstr, ':') >= 2) {
383 extract_token(histr, setstr, 1, ':', sizeof histr);
384 if (!strcmp(histr, "*")) {
385 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
389 strcpy(histr, lostr);
394 for (i = 0; i < num_msgs; ++i) {
395 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
401 /* Now translate the array of booleans back into a sequence set */
406 for (i=0; i<num_msgs; ++i) {
409 if (msglist[i] == target_msgnum) {
410 is_seen = target_setting;
417 if (lo < 0L) lo = msglist[i];
421 if ( ((is_seen == 0) && (was_seen == 1))
422 || ((is_seen == 1) && (i == num_msgs-1)) ) {
424 /* begin trim-o-matic code */
427 while ( (strlen(vset) + 20) > sizeof vset) {
428 remove_token(vset, 0, ',');
430 if (j--) break; /* loop no more than 9 times */
432 if ( (trimming) && (which_set == ctdlsetseen_seen) ) {
436 snprintf(lostr, sizeof lostr,
437 "1:%ld,%s", t, vset);
438 safestrncpy(vset, lostr, sizeof vset);
440 /* end trim-o-matic code */
448 snprintf(&vset[tmp], (sizeof vset) - tmp,
452 snprintf(&vset[tmp], (sizeof vset) - tmp,
461 /* Decide which message set we're manipulating */
463 case ctdlsetseen_seen:
464 safestrncpy(vbuf.v_seen, vset, sizeof vbuf.v_seen);
466 case ctdlsetseen_answered:
467 safestrncpy(vbuf.v_answered, vset,
468 sizeof vbuf.v_answered);
473 /* lprintf(CTDL_DEBUG, " after optimize: %s\n", vset); */
475 CtdlSetRelationship(&vbuf,
476 ((which_user != NULL) ? which_user : &CC->user),
477 ((which_room != NULL) ? which_room : &CC->room)
483 * API function to perform an operation for each qualifying message in the
484 * current room. (Returns the number of messages processed.)
486 int CtdlForEachMessage(int mode, long ref,
488 struct CtdlMessage *compare,
489 void (*CallBack) (long, void *),
495 struct cdbdata *cdbfr;
496 long *msglist = NULL;
498 int num_processed = 0;
501 struct CtdlMessage *msg;
504 int printed_lastold = 0;
506 /* Learn about the user and room in question */
508 getuser(&CC->user, CC->curr_user);
509 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
511 /* Load the message list */
512 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
514 msglist = malloc(cdbfr->len);
515 memcpy(msglist, cdbfr->ptr, cdbfr->len);
516 num_msgs = cdbfr->len / sizeof(long);
519 return 0; /* No messages at all? No further action. */
524 * Now begin the traversal.
526 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
528 /* If the caller is looking for a specific MIME type, filter
529 * out all messages which are not of the type requested.
531 if (content_type != NULL) if (strlen(content_type) > 0) {
533 /* This call to GetMetaData() sits inside this loop
534 * so that we only do the extra database read per msg
535 * if we need to. Doing the extra read all the time
536 * really kills the server. If we ever need to use
537 * metadata for another search criterion, we need to
538 * move the read somewhere else -- but still be smart
539 * enough to only do the read if the caller has
540 * specified something that will need it.
542 GetMetaData(&smi, msglist[a]);
544 if (strcasecmp(smi.meta_content_type, content_type)) {
550 num_msgs = sort_msglist(msglist, num_msgs);
552 /* If a template was supplied, filter out the messages which
553 * don't match. (This could induce some delays!)
556 if (compare != NULL) {
557 for (a = 0; a < num_msgs; ++a) {
558 msg = CtdlFetchMessage(msglist[a], 1);
560 if (CtdlMsgCmp(msg, compare)) {
563 CtdlFreeMessage(msg);
571 * Now iterate through the message list, according to the
572 * criteria supplied by the caller.
575 for (a = 0; a < num_msgs; ++a) {
576 thismsg = msglist[a];
577 if (mode == MSGS_ALL) {
581 is_seen = is_msg_in_sequence_set(
582 vbuf.v_seen, thismsg);
583 if (is_seen) lastold = thismsg;
589 || ((mode == MSGS_OLD) && (is_seen))
590 || ((mode == MSGS_NEW) && (!is_seen))
591 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
592 || ((mode == MSGS_FIRST) && (a < ref))
593 || ((mode == MSGS_GT) && (thismsg > ref))
594 || ((mode == MSGS_EQ) && (thismsg == ref))
597 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
599 CallBack(lastold, userdata);
603 if (CallBack) CallBack(thismsg, userdata);
607 free(msglist); /* Clean up */
608 return num_processed;
614 * cmd_msgs() - get list of message #'s in this room
615 * implements the MSGS server command using CtdlForEachMessage()
617 void cmd_msgs(char *cmdbuf)
626 int with_template = 0;
627 struct CtdlMessage *template = NULL;
629 extract_token(which, cmdbuf, 0, '|', sizeof which);
630 cm_ref = extract_int(cmdbuf, 1);
631 with_template = extract_int(cmdbuf, 2);
635 if (!strncasecmp(which, "OLD", 3))
637 else if (!strncasecmp(which, "NEW", 3))
639 else if (!strncasecmp(which, "FIRST", 5))
641 else if (!strncasecmp(which, "LAST", 4))
643 else if (!strncasecmp(which, "GT", 2))
646 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
647 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
653 cprintf("%d Send template then receive message list\n",
655 template = (struct CtdlMessage *)
656 malloc(sizeof(struct CtdlMessage));
657 memset(template, 0, sizeof(struct CtdlMessage));
658 while(client_getln(buf, sizeof buf), strcmp(buf,"000")) {
659 extract_token(tfield, buf, 0, '|', sizeof tfield);
660 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
661 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
662 if (!strcasecmp(tfield, msgkeys[i])) {
663 template->cm_fields[i] =
671 cprintf("%d Message list...\n", LISTING_FOLLOWS);
674 CtdlForEachMessage(mode, cm_ref,
675 NULL, template, simple_listing, NULL);
676 if (template != NULL) CtdlFreeMessage(template);
684 * help_subst() - support routine for help file viewer
686 void help_subst(char *strbuf, char *source, char *dest)
691 while (p = pattern2(strbuf, source), (p >= 0)) {
692 strcpy(workbuf, &strbuf[p + strlen(source)]);
693 strcpy(&strbuf[p], dest);
694 strcat(strbuf, workbuf);
699 void do_help_subst(char *buffer)
703 help_subst(buffer, "^nodename", config.c_nodename);
704 help_subst(buffer, "^humannode", config.c_humannode);
705 help_subst(buffer, "^fqdn", config.c_fqdn);
706 help_subst(buffer, "^username", CC->user.fullname);
707 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
708 help_subst(buffer, "^usernum", buf2);
709 help_subst(buffer, "^sysadm", config.c_sysadm);
710 help_subst(buffer, "^variantname", CITADEL);
711 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
712 help_subst(buffer, "^maxsessions", buf2);
713 help_subst(buffer, "^bbsdir", CTDLDIR);
719 * memfmout() - Citadel text formatter and paginator.
720 * Although the original purpose of this routine was to format
721 * text to the reader's screen width, all we're really using it
722 * for here is to format text out to 80 columns before sending it
723 * to the client. The client software may reformat it again.
726 int width, /* screen width to use */
727 char *mptr, /* where are we going to get our text from? */
728 char subst, /* nonzero if we should do substitutions */
729 char *nl) /* string to terminate lines with */
741 c = 1; /* c is the current pos */
745 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
747 buffer[strlen(buffer) + 1] = 0;
748 buffer[strlen(buffer)] = ch;
751 if (buffer[0] == '^')
752 do_help_subst(buffer);
754 buffer[strlen(buffer) + 1] = 0;
756 strcpy(buffer, &buffer[1]);
764 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10))
766 if (((old == 13) || (old == 10)) && (isspace(real))) {
774 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
775 cprintf("%s%s", nl, aaa);
784 if ((strlen(aaa) + c) > (width - 5)) {
793 if ((ch == 13) || (ch == 10)) {
794 cprintf("%s%s", aaa, nl);
801 cprintf("%s%s", aaa, nl);
807 * Callback function for mime parser that simply lists the part
809 void list_this_part(char *name, char *filename, char *partnum, char *disp,
810 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
814 cprintf("part=%s|%s|%s|%s|%s|%ld\n",
815 name, filename, partnum, disp, cbtype, (long)length);
819 * Callback function for multipart prefix
821 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
822 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
825 cprintf("pref=%s|%s\n", partnum, cbtype);
829 * Callback function for multipart sufffix
831 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
832 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
835 cprintf("suff=%s|%s\n", partnum, cbtype);
840 * Callback function for mime parser that opens a section for downloading
842 void mime_download(char *name, char *filename, char *partnum, char *disp,
843 void *content, char *cbtype, char *cbcharset, size_t length,
844 char *encoding, void *cbuserdata)
847 /* Silently go away if there's already a download open... */
848 if (CC->download_fp != NULL)
851 /* ...or if this is not the desired section */
852 if (strcasecmp(CC->download_desired_section, partnum))
855 CC->download_fp = tmpfile();
856 if (CC->download_fp == NULL)
859 fwrite(content, length, 1, CC->download_fp);
860 fflush(CC->download_fp);
861 rewind(CC->download_fp);
863 OpenCmdResult(filename, cbtype);
869 * Load a message from disk into memory.
870 * This is used by CtdlOutputMsg() and other fetch functions.
872 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
873 * using the CtdlMessageFree() function.
875 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
877 struct cdbdata *dmsgtext;
878 struct CtdlMessage *ret = NULL;
882 cit_uint8_t field_header;
884 lprintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
886 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
887 if (dmsgtext == NULL) {
890 mptr = dmsgtext->ptr;
891 upper_bound = mptr + dmsgtext->len;
893 /* Parse the three bytes that begin EVERY message on disk.
894 * The first is always 0xFF, the on-disk magic number.
895 * The second is the anonymous/public type byte.
896 * The third is the format type byte (vari, fixed, or MIME).
901 "Message %ld appears to be corrupted.\n",
906 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
907 memset(ret, 0, sizeof(struct CtdlMessage));
909 ret->cm_magic = CTDLMESSAGE_MAGIC;
910 ret->cm_anon_type = *mptr++; /* Anon type byte */
911 ret->cm_format_type = *mptr++; /* Format type byte */
914 * The rest is zero or more arbitrary fields. Load them in.
915 * We're done when we encounter either a zero-length field or
916 * have just processed the 'M' (message text) field.
919 if (mptr >= upper_bound) {
922 field_header = *mptr++;
923 ret->cm_fields[field_header] = strdup(mptr);
925 while (*mptr++ != 0); /* advance to next field */
927 } while ((mptr < upper_bound) && (field_header != 'M'));
931 /* Always make sure there's something in the msg text field. If
932 * it's NULL, the message text is most likely stored separately,
933 * so go ahead and fetch that. Failing that, just set a dummy
934 * body so other code doesn't barf.
936 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
937 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
938 if (dmsgtext != NULL) {
939 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
943 if (ret->cm_fields['M'] == NULL) {
944 ret->cm_fields['M'] = strdup("<no text>\n");
947 /* Perform "before read" hooks (aborting if any return nonzero) */
948 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
949 CtdlFreeMessage(ret);
958 * Returns 1 if the supplied pointer points to a valid Citadel message.
959 * If the pointer is NULL or the magic number check fails, returns 0.
961 int is_valid_message(struct CtdlMessage *msg) {
964 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
965 lprintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
973 * 'Destructor' for struct CtdlMessage
975 void CtdlFreeMessage(struct CtdlMessage *msg)
979 if (is_valid_message(msg) == 0) return;
981 for (i = 0; i < 256; ++i)
982 if (msg->cm_fields[i] != NULL) {
983 free(msg->cm_fields[i]);
986 msg->cm_magic = 0; /* just in case */
992 * Pre callback function for multipart/alternative
994 * NOTE: this differs from the standard behavior for a reason. Normally when
995 * displaying multipart/alternative you want to show the _last_ usable
996 * format in the message. Here we show the _first_ one, because it's
997 * usually text/plain. Since this set of functions is designed for text
998 * output to non-MIME-aware clients, this is the desired behavior.
1001 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1002 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1007 ma = (struct ma_info *)cbuserdata;
1008 lprintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1009 if (!strcasecmp(cbtype, "multipart/alternative")) {
1017 * Post callback function for multipart/alternative
1019 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1020 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1025 ma = (struct ma_info *)cbuserdata;
1026 lprintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1027 if (!strcasecmp(cbtype, "multipart/alternative")) {
1035 * Inline callback function for mime parser that wants to display text
1037 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1038 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1046 ma = (struct ma_info *)cbuserdata;
1048 lprintf(CTDL_DEBUG, "fixed_output() type=<%s>\n", cbtype);
1051 * If we're in the middle of a multipart/alternative scope and
1052 * we've already printed another section, skip this one.
1054 if ( (ma->is_ma == 1) && (ma->did_print == 1) ) {
1055 lprintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1060 if ( (!strcasecmp(cbtype, "text/plain"))
1061 || (strlen(cbtype)==0) ) {
1064 client_write(wptr, length);
1065 if (wptr[length-1] != '\n') {
1070 else if (!strcasecmp(cbtype, "text/html")) {
1071 ptr = html_to_ascii(content, 80, 0);
1073 client_write(ptr, wlen);
1074 if (ptr[wlen-1] != '\n') {
1079 else if (strncasecmp(cbtype, "multipart/", 10)) {
1080 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1081 partnum, filename, cbtype, (long)length);
1086 * The client is elegant and sophisticated and wants to be choosy about
1087 * MIME content types, so figure out which multipart/alternative part
1088 * we're going to send.
1090 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1091 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1098 ma = (struct ma_info *)cbuserdata;
1100 if (ma->is_ma > 0) {
1101 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1102 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1103 if (!strcasecmp(buf, cbtype)) {
1104 strcpy(ma->chosen_part, partnum);
1111 * Now that we've chosen our preferred part, output it.
1113 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1114 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1119 int add_newline = 0;
1123 ma = (struct ma_info *)cbuserdata;
1125 /* This is not the MIME part you're looking for... */
1126 if (strcasecmp(partnum, ma->chosen_part)) return;
1128 /* If the content-type of this part is in our preferred formats
1129 * list, we can simply output it verbatim.
1131 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1132 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1133 if (!strcasecmp(buf, cbtype)) {
1134 /* Yeah! Go! W00t!! */
1136 text_content = (char *)content;
1137 if (text_content[length-1] != '\n') {
1141 cprintf("Content-type: %s", cbtype);
1142 if (strlen(cbcharset) > 0) {
1143 cprintf("; charset=%s", cbcharset);
1145 cprintf("\nContent-length: %d\n",
1146 (int)(length + add_newline) );
1147 if (strlen(encoding) > 0) {
1148 cprintf("Content-transfer-encoding: %s\n", encoding);
1151 cprintf("Content-transfer-encoding: 7bit\n");
1154 client_write(content, length);
1155 if (add_newline) cprintf("\n");
1160 /* No translations required or possible: output as text/plain */
1161 cprintf("Content-type: text/plain\n\n");
1162 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1163 length, encoding, cbuserdata);
1168 * Get a message off disk. (returns om_* values found in msgbase.h)
1171 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1172 int mode, /* how would you like that message? */
1173 int headers_only, /* eschew the message body? */
1174 int do_proto, /* do Citadel protocol responses? */
1175 int crlf /* Use CRLF newlines instead of LF? */
1177 struct CtdlMessage *TheMessage = NULL;
1178 int retcode = om_no_such_msg;
1180 lprintf(CTDL_DEBUG, "CtdlOutputMsg() msgnum=%ld, mode=%d\n",
1183 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1184 if (do_proto) cprintf("%d Not logged in.\n",
1185 ERROR + NOT_LOGGED_IN);
1186 return(om_not_logged_in);
1189 /* FIXME: check message id against msglist for this room */
1192 * Fetch the message from disk. If we're in any sort of headers
1193 * only mode, request that we don't even bother loading the body
1196 if ( (headers_only == HEADERS_FAST) || (headers_only == HEADERS_ONLY) ) {
1197 TheMessage = CtdlFetchMessage(msg_num, 0);
1200 TheMessage = CtdlFetchMessage(msg_num, 1);
1203 if (TheMessage == NULL) {
1204 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1205 ERROR + MESSAGE_NOT_FOUND, msg_num);
1206 return(om_no_such_msg);
1209 retcode = CtdlOutputPreLoadedMsg(
1210 TheMessage, msg_num, mode,
1211 headers_only, do_proto, crlf);
1213 CtdlFreeMessage(TheMessage);
1220 * Get a message off disk. (returns om_* values found in msgbase.h)
1223 int CtdlOutputPreLoadedMsg(
1224 struct CtdlMessage *TheMessage,
1226 int mode, /* how would you like that message? */
1227 int headers_only, /* eschew the message body? */
1228 int do_proto, /* do Citadel protocol responses? */
1229 int crlf /* Use CRLF newlines instead of LF? */
1235 char display_name[256];
1237 char *nl; /* newline string */
1239 int subject_found = 0;
1242 /* Buffers needed for RFC822 translation. These are all filled
1243 * using functions that are bounds-checked, and therefore we can
1244 * make them substantially smaller than SIZ.
1252 char datestamp[100];
1254 lprintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %ld, %d, %d, %d, %d\n",
1255 ((TheMessage == NULL) ? "NULL" : "not null"),
1257 mode, headers_only, do_proto, crlf);
1259 snprintf(mid, sizeof mid, "%ld", msg_num);
1260 nl = (crlf ? "\r\n" : "\n");
1262 if (!is_valid_message(TheMessage)) {
1264 "ERROR: invalid preloaded message for output\n");
1265 return(om_no_such_msg);
1268 /* Are we downloading a MIME component? */
1269 if (mode == MT_DOWNLOAD) {
1270 if (TheMessage->cm_format_type != FMT_RFC822) {
1272 cprintf("%d This is not a MIME message.\n",
1273 ERROR + ILLEGAL_VALUE);
1274 } else if (CC->download_fp != NULL) {
1275 if (do_proto) cprintf(
1276 "%d You already have a download open.\n",
1277 ERROR + RESOURCE_BUSY);
1279 /* Parse the message text component */
1280 mptr = TheMessage->cm_fields['M'];
1281 ma = malloc(sizeof(struct ma_info));
1282 memset(ma, 0, sizeof(struct ma_info));
1283 mime_parser(mptr, NULL, *mime_download, NULL, NULL, (void *)ma, 0);
1285 /* If there's no file open by this time, the requested
1286 * section wasn't found, so print an error
1288 if (CC->download_fp == NULL) {
1289 if (do_proto) cprintf(
1290 "%d Section %s not found.\n",
1291 ERROR + FILE_NOT_FOUND,
1292 CC->download_desired_section);
1295 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1298 /* now for the user-mode message reading loops */
1299 if (do_proto) cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num);
1301 /* Does the caller want to skip the headers? */
1302 if (headers_only == HEADERS_NONE) goto START_TEXT;
1304 /* Tell the client which format type we're using. */
1305 if ( (mode == MT_CITADEL) && (do_proto) ) {
1306 cprintf("type=%d\n", TheMessage->cm_format_type);
1309 /* nhdr=yes means that we're only displaying headers, no body */
1310 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1311 && (mode == MT_CITADEL)
1314 cprintf("nhdr=yes\n");
1317 /* begin header processing loop for Citadel message format */
1319 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1321 safestrncpy(display_name, "<unknown>", sizeof display_name);
1322 if (TheMessage->cm_fields['A']) {
1323 strcpy(buf, TheMessage->cm_fields['A']);
1324 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1325 safestrncpy(display_name, "****", sizeof display_name);
1327 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1328 safestrncpy(display_name, "anonymous", sizeof display_name);
1331 safestrncpy(display_name, buf, sizeof display_name);
1333 if ((is_room_aide())
1334 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1335 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1336 size_t tmp = strlen(display_name);
1337 snprintf(&display_name[tmp],
1338 sizeof display_name - tmp,
1343 /* Don't show Internet address for users on the
1344 * local Citadel network.
1347 if (TheMessage->cm_fields['N'] != NULL)
1348 if (strlen(TheMessage->cm_fields['N']) > 0)
1349 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1353 /* Now spew the header fields in the order we like them. */
1354 safestrncpy(allkeys, FORDER, sizeof allkeys);
1355 for (i=0; i<strlen(allkeys); ++i) {
1356 k = (int) allkeys[i];
1358 if ( (TheMessage->cm_fields[k] != NULL)
1359 && (msgkeys[k] != NULL) ) {
1361 if (do_proto) cprintf("%s=%s\n",
1365 else if ((k == 'F') && (suppress_f)) {
1368 /* Masquerade display name if needed */
1370 if (do_proto) cprintf("%s=%s\n",
1372 TheMessage->cm_fields[k]
1381 /* begin header processing loop for RFC822 transfer format */
1386 strcpy(snode, NODENAME);
1387 strcpy(lnode, HUMANNODE);
1388 if (mode == MT_RFC822) {
1389 for (i = 0; i < 256; ++i) {
1390 if (TheMessage->cm_fields[i]) {
1391 mptr = TheMessage->cm_fields[i];
1394 safestrncpy(luser, mptr, sizeof luser);
1395 safestrncpy(suser, mptr, sizeof suser);
1397 else if (i == 'U') {
1398 cprintf("Subject: %s%s", mptr, nl);
1402 safestrncpy(mid, mptr, sizeof mid);
1404 safestrncpy(lnode, mptr, sizeof lnode);
1406 safestrncpy(fuser, mptr, sizeof fuser);
1407 /* else if (i == 'O')
1408 cprintf("X-Citadel-Room: %s%s",
1411 safestrncpy(snode, mptr, sizeof snode);
1413 cprintf("To: %s%s", mptr, nl);
1414 else if (i == 'T') {
1415 datestring(datestamp, sizeof datestamp,
1416 atol(mptr), DATESTRING_RFC822);
1417 cprintf("Date: %s%s", datestamp, nl);
1421 if (subject_found == 0) {
1422 cprintf("Subject: (no subject)%s", nl);
1426 for (i=0; i<strlen(suser); ++i) {
1427 suser[i] = tolower(suser[i]);
1428 if (!isalnum(suser[i])) suser[i]='_';
1431 if (mode == MT_RFC822) {
1432 if (!strcasecmp(snode, NODENAME)) {
1433 safestrncpy(snode, FQDN, sizeof snode);
1436 /* Construct a fun message id */
1437 cprintf("Message-ID: <%s", mid);
1438 if (strchr(mid, '@')==NULL) {
1439 cprintf("@%s", snode);
1443 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1444 // cprintf("From: x@x.org (----)%s", nl);
1445 cprintf("From: \"----\" <x@x.org>%s", nl);
1447 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
1448 // cprintf("From: x@x.org (anonymous)%s", nl);
1449 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
1451 else if (strlen(fuser) > 0) {
1452 // cprintf("From: %s (%s)%s", fuser, luser, nl);
1453 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
1456 // cprintf("From: %s@%s (%s)%s", suser, snode, luser, nl);
1457 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
1460 cprintf("Organization: %s%s", lnode, nl);
1462 /* Blank line signifying RFC822 end-of-headers */
1463 if (TheMessage->cm_format_type != FMT_RFC822) {
1468 /* end header processing loop ... at this point, we're in the text */
1470 if (headers_only == HEADERS_FAST) goto DONE;
1471 mptr = TheMessage->cm_fields['M'];
1473 /* Tell the client about the MIME parts in this message */
1474 if (TheMessage->cm_format_type == FMT_RFC822) {
1475 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1476 mime_parser(mptr, NULL,
1477 (do_proto ? *list_this_part : NULL),
1478 (do_proto ? *list_this_pref : NULL),
1479 (do_proto ? *list_this_suff : NULL),
1482 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
1483 /* FIXME ... we have to put some code in here to avoid
1484 * printing duplicate header information when both
1485 * Citadel and RFC822 headers exist. Preference should
1486 * probably be given to the RFC822 headers.
1488 int done_rfc822_hdrs = 0;
1489 while (ch=*(mptr++), ch!=0) {
1494 if (!done_rfc822_hdrs) {
1495 if (headers_only != HEADERS_NONE) {
1500 if (headers_only != HEADERS_ONLY) {
1504 if ((*(mptr) == 13) || (*(mptr) == 10)) {
1505 done_rfc822_hdrs = 1;
1509 if (done_rfc822_hdrs) {
1510 if (headers_only != HEADERS_NONE) {
1515 if (headers_only != HEADERS_ONLY) {
1519 if ((*mptr == 13) || (*mptr == 10)) {
1520 done_rfc822_hdrs = 1;
1528 if (headers_only == HEADERS_ONLY) {
1532 /* signify start of msg text */
1533 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
1534 if (do_proto) cprintf("text\n");
1537 /* If the format type on disk is 1 (fixed-format), then we want
1538 * everything to be output completely literally ... regardless of
1539 * what message transfer format is in use.
1541 if (TheMessage->cm_format_type == FMT_FIXED) {
1542 if (mode == MT_MIME) {
1543 cprintf("Content-type: text/plain\n\n");
1546 while (ch = *mptr++, ch > 0) {
1549 if ((ch == 10) || (strlen(buf) > 250)) {
1550 cprintf("%s%s", buf, nl);
1553 buf[strlen(buf) + 1] = 0;
1554 buf[strlen(buf)] = ch;
1557 if (strlen(buf) > 0)
1558 cprintf("%s%s", buf, nl);
1561 /* If the message on disk is format 0 (Citadel vari-format), we
1562 * output using the formatter at 80 columns. This is the final output
1563 * form if the transfer format is RFC822, but if the transfer format
1564 * is Citadel proprietary, it'll still work, because the indentation
1565 * for new paragraphs is correct and the client will reformat the
1566 * message to the reader's screen width.
1568 if (TheMessage->cm_format_type == FMT_CITADEL) {
1569 if (mode == MT_MIME) {
1570 cprintf("Content-type: text/x-citadel-variformat\n\n");
1572 memfmout(80, mptr, 0, nl);
1575 /* If the message on disk is format 4 (MIME), we've gotta hand it
1576 * off to the MIME parser. The client has already been told that
1577 * this message is format 1 (fixed format), so the callback function
1578 * we use will display those parts as-is.
1580 if (TheMessage->cm_format_type == FMT_RFC822) {
1581 ma = malloc(sizeof(struct ma_info));
1582 memset(ma, 0, sizeof(struct ma_info));
1584 if (mode == MT_MIME) {
1585 strcpy(ma->chosen_part, "1");
1586 mime_parser(mptr, NULL,
1587 *choose_preferred, *fixed_output_pre,
1588 *fixed_output_post, (void *)ma, 0);
1589 mime_parser(mptr, NULL,
1590 *output_preferred, NULL, NULL, (void *)ma, 0);
1593 mime_parser(mptr, NULL,
1594 *fixed_output, *fixed_output_pre,
1595 *fixed_output_post, (void *)ma, 0);
1601 DONE: /* now we're done */
1602 if (do_proto) cprintf("000\n");
1609 * display a message (mode 0 - Citadel proprietary)
1611 void cmd_msg0(char *cmdbuf)
1614 int headers_only = HEADERS_ALL;
1616 msgid = extract_long(cmdbuf, 0);
1617 headers_only = extract_int(cmdbuf, 1);
1619 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0);
1625 * display a message (mode 2 - RFC822)
1627 void cmd_msg2(char *cmdbuf)
1630 int headers_only = HEADERS_ALL;
1632 msgid = extract_long(cmdbuf, 0);
1633 headers_only = extract_int(cmdbuf, 1);
1635 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1);
1641 * display a message (mode 3 - IGnet raw format - internal programs only)
1643 void cmd_msg3(char *cmdbuf)
1646 struct CtdlMessage *msg;
1649 if (CC->internal_pgm == 0) {
1650 cprintf("%d This command is for internal programs only.\n",
1651 ERROR + HIGHER_ACCESS_REQUIRED);
1655 msgnum = extract_long(cmdbuf, 0);
1656 msg = CtdlFetchMessage(msgnum, 1);
1658 cprintf("%d Message %ld not found.\n",
1659 ERROR + MESSAGE_NOT_FOUND, msgnum);
1663 serialize_message(&smr, msg);
1664 CtdlFreeMessage(msg);
1667 cprintf("%d Unable to serialize message\n",
1668 ERROR + INTERNAL_ERROR);
1672 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
1673 client_write((char *)smr.ser, (int)smr.len);
1680 * Display a message using MIME content types
1682 void cmd_msg4(char *cmdbuf)
1686 msgid = extract_long(cmdbuf, 0);
1687 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0);
1693 * Client tells us its preferred message format(s)
1695 void cmd_msgp(char *cmdbuf)
1697 safestrncpy(CC->preferred_formats, cmdbuf,
1698 sizeof(CC->preferred_formats));
1699 cprintf("%d ok\n", CIT_OK);
1704 * Open a component of a MIME message as a download file
1706 void cmd_opna(char *cmdbuf)
1709 char desired_section[128];
1711 msgid = extract_long(cmdbuf, 0);
1712 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
1713 safestrncpy(CC->download_desired_section, desired_section, sizeof CC->download_desired_section);
1714 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1);
1719 * Save a message pointer into a specified room
1720 * (Returns 0 for success, nonzero for failure)
1721 * roomname may be NULL to use the current room
1723 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) {
1725 char hold_rm[ROOMNAMELEN];
1726 struct cdbdata *cdbfr;
1729 long highest_msg = 0L;
1730 struct CtdlMessage *msg = NULL;
1732 lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n",
1733 roomname, msgid, flags);
1735 strcpy(hold_rm, CC->room.QRname);
1737 /* We may need to check to see if this message is real */
1738 if ( (flags & SM_VERIFY_GOODNESS)
1739 || (flags & SM_DO_REPL_CHECK)
1741 msg = CtdlFetchMessage(msgid, 1);
1742 if (msg == NULL) return(ERROR + ILLEGAL_VALUE);
1745 /* Perform replication checks if necessary */
1746 if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) {
1748 if (getroom(&CC->room,
1749 ((roomname != NULL) ? roomname : CC->room.QRname) )
1751 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1752 if (msg != NULL) CtdlFreeMessage(msg);
1753 return(ERROR + ROOM_NOT_FOUND);
1756 if (ReplicationChecks(msg) != 0) {
1757 getroom(&CC->room, hold_rm);
1758 if (msg != NULL) CtdlFreeMessage(msg);
1760 "Did replication, and newer exists\n");
1765 /* Now the regular stuff */
1766 if (lgetroom(&CC->room,
1767 ((roomname != NULL) ? roomname : CC->room.QRname) )
1769 lprintf(CTDL_ERR, "No such room <%s>\n", roomname);
1770 if (msg != NULL) CtdlFreeMessage(msg);
1771 return(ERROR + ROOM_NOT_FOUND);
1774 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1775 if (cdbfr == NULL) {
1779 msglist = malloc(cdbfr->len);
1780 if (msglist == NULL)
1781 lprintf(CTDL_ALERT, "ERROR malloc msglist!\n");
1782 num_msgs = cdbfr->len / sizeof(long);
1783 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1788 /* Make sure the message doesn't already exist in this room. It
1789 * is absolutely taboo to have more than one reference to the same
1790 * message in a room.
1792 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
1793 if (msglist[i] == msgid) {
1794 lputroom(&CC->room); /* unlock the room */
1795 getroom(&CC->room, hold_rm);
1796 if (msg != NULL) CtdlFreeMessage(msg);
1798 return(ERROR + ALREADY_EXISTS);
1802 /* Now add the new message */
1804 msglist = realloc(msglist, (num_msgs * sizeof(long)));
1806 if (msglist == NULL) {
1807 lprintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
1809 msglist[num_msgs - 1] = msgid;
1811 /* Sort the message list, so all the msgid's are in order */
1812 num_msgs = sort_msglist(msglist, num_msgs);
1814 /* Determine the highest message number */
1815 highest_msg = msglist[num_msgs - 1];
1817 /* Write it back to disk. */
1818 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
1819 msglist, (int)(num_msgs * sizeof(long)));
1821 /* Free up the memory we used. */
1824 /* Update the highest-message pointer and unlock the room. */
1825 CC->room.QRhighest = highest_msg;
1826 lputroom(&CC->room);
1827 getroom(&CC->room, hold_rm);
1829 /* Bump the reference count for this message. */
1830 if ((flags & SM_DONT_BUMP_REF)==0) {
1831 AdjRefCount(msgid, +1);
1834 /* Return success. */
1835 if (msg != NULL) CtdlFreeMessage(msg);
1842 * Message base operation to save a new message to the message store
1843 * (returns new message number)
1845 * This is the back end for CtdlSubmitMsg() and should not be directly
1846 * called by server-side modules.
1849 long send_message(struct CtdlMessage *msg) {
1857 /* Get a new message number */
1858 newmsgid = get_new_message_number();
1859 snprintf(msgidbuf, sizeof msgidbuf, "%ld@%s", newmsgid, config.c_fqdn);
1861 /* Generate an ID if we don't have one already */
1862 if (msg->cm_fields['I']==NULL) {
1863 msg->cm_fields['I'] = strdup(msgidbuf);
1866 /* If the message is big, set its body aside for storage elsewhere */
1867 if (msg->cm_fields['M'] != NULL) {
1868 if (strlen(msg->cm_fields['M']) > BIGMSG) {
1870 holdM = msg->cm_fields['M'];
1871 msg->cm_fields['M'] = NULL;
1875 /* Serialize our data structure for storage in the database */
1876 serialize_message(&smr, msg);
1879 msg->cm_fields['M'] = holdM;
1883 cprintf("%d Unable to serialize message\n",
1884 ERROR + INTERNAL_ERROR);
1888 /* Write our little bundle of joy into the message base */
1889 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
1890 smr.ser, smr.len) < 0) {
1891 lprintf(CTDL_ERR, "Can't store message\n");
1895 cdb_store(CDB_BIGMSGS,
1905 /* Free the memory we used for the serialized message */
1908 /* Return the *local* message ID to the caller
1909 * (even if we're storing an incoming network message)
1917 * Serialize a struct CtdlMessage into the format used on disk and network.
1919 * This function loads up a "struct ser_ret" (defined in server.h) which
1920 * contains the length of the serialized message and a pointer to the
1921 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
1923 void serialize_message(struct ser_ret *ret, /* return values */
1924 struct CtdlMessage *msg) /* unserialized msg */
1928 static char *forder = FORDER;
1930 if (is_valid_message(msg) == 0) return; /* self check */
1933 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
1934 ret->len = ret->len +
1935 strlen(msg->cm_fields[(int)forder[i]]) + 2;
1937 lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len);
1938 ret->ser = malloc(ret->len);
1939 if (ret->ser == NULL) {
1945 ret->ser[1] = msg->cm_anon_type;
1946 ret->ser[2] = msg->cm_format_type;
1949 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
1950 ret->ser[wlen++] = (char)forder[i];
1951 strcpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]]);
1952 wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1;
1954 if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
1955 (long)ret->len, (long)wlen);
1963 * Back end for the ReplicationChecks() function
1965 void check_repl(long msgnum, void *userdata) {
1966 lprintf(CTDL_DEBUG, "check_repl() replacing message %ld\n", msgnum);
1967 CtdlDeleteMessages(CC->room.QRname, msgnum, "", 0);
1972 * Check to see if any messages already exist which carry the same Exclusive ID
1973 * as this one. If any are found, delete them.
1976 int ReplicationChecks(struct CtdlMessage *msg) {
1977 struct CtdlMessage *template;
1980 /* No exclusive id? Don't do anything. */
1981 if (msg->cm_fields['E'] == NULL) return 0;
1982 if (strlen(msg->cm_fields['E']) == 0) return 0;
1983 lprintf(CTDL_DEBUG, "Exclusive ID: <%s>\n", msg->cm_fields['E']);
1985 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1986 memset(template, 0, sizeof(struct CtdlMessage));
1987 template->cm_fields['E'] = strdup(msg->cm_fields['E']);
1989 CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl, NULL);
1991 CtdlFreeMessage(template);
1992 lprintf(CTDL_DEBUG, "ReplicationChecks() returning %d\n", abort_this);
2000 * Save a message to disk and submit it into the delivery system.
2002 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2003 struct recptypes *recps, /* recipients (if mail) */
2004 char *force /* force a particular room? */
2006 char submit_filename[128];
2007 char generated_timestamp[32];
2008 char hold_rm[ROOMNAMELEN];
2009 char actual_rm[ROOMNAMELEN];
2010 char force_room[ROOMNAMELEN];
2011 char content_type[SIZ]; /* We have to learn this */
2012 char recipient[SIZ];
2015 struct ctdluser userbuf;
2017 struct MetaData smi;
2018 FILE *network_fp = NULL;
2019 static int seqnum = 1;
2020 struct CtdlMessage *imsg = NULL;
2023 char *hold_R, *hold_D;
2025 lprintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2026 if (is_valid_message(msg) == 0) return(-1); /* self check */
2028 /* If this message has no timestamp, we take the liberty of
2029 * giving it one, right now.
2031 if (msg->cm_fields['T'] == NULL) {
2032 lprintf(CTDL_DEBUG, "Generating timestamp\n");
2033 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2034 msg->cm_fields['T'] = strdup(generated_timestamp);
2037 /* If this message has no path, we generate one.
2039 if (msg->cm_fields['P'] == NULL) {
2040 lprintf(CTDL_DEBUG, "Generating path\n");
2041 if (msg->cm_fields['A'] != NULL) {
2042 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2043 for (a=0; a<strlen(msg->cm_fields['P']); ++a) {
2044 if (isspace(msg->cm_fields['P'][a])) {
2045 msg->cm_fields['P'][a] = ' ';
2050 msg->cm_fields['P'] = strdup("unknown");
2054 if (force == NULL) {
2055 strcpy(force_room, "");
2058 strcpy(force_room, force);
2061 /* Learn about what's inside, because it's what's inside that counts */
2062 lprintf(CTDL_DEBUG, "Learning what's inside\n");
2063 if (msg->cm_fields['M'] == NULL) {
2064 lprintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2068 switch (msg->cm_format_type) {
2070 strcpy(content_type, "text/x-citadel-variformat");
2073 strcpy(content_type, "text/plain");
2076 strcpy(content_type, "text/plain");
2077 mptr = bmstrstr(msg->cm_fields['M'],
2078 "Content-type: ", strncasecmp);
2080 safestrncpy(content_type, &mptr[14],
2081 sizeof content_type);
2082 for (a = 0; a < strlen(content_type); ++a) {
2083 if ((content_type[a] == ';')
2084 || (content_type[a] == ' ')
2085 || (content_type[a] == 13)
2086 || (content_type[a] == 10)) {
2087 content_type[a] = 0;
2093 /* Goto the correct room */
2094 lprintf(CTDL_DEBUG, "Selected room %s\n",
2095 (recps) ? CC->room.QRname : SENTITEMS);
2096 strcpy(hold_rm, CC->room.QRname);
2097 strcpy(actual_rm, CC->room.QRname);
2098 if (recps != NULL) {
2099 strcpy(actual_rm, SENTITEMS);
2102 /* If the user is a twit, move to the twit room for posting */
2103 lprintf(CTDL_DEBUG, "Handling twit stuff: %s\n",
2104 (CC->user.axlevel == 2) ? config.c_twitroom : "OK");
2106 if (CC->user.axlevel == 2) {
2107 strcpy(hold_rm, actual_rm);
2108 strcpy(actual_rm, config.c_twitroom);
2112 /* ...or if this message is destined for Aide> then go there. */
2113 if (strlen(force_room) > 0) {
2114 strcpy(actual_rm, force_room);
2117 lprintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2118 if (strcasecmp(actual_rm, CC->room.QRname)) {
2119 /* getroom(&CC->room, actual_rm); */
2120 usergoto(actual_rm, 0, 1, NULL, NULL);
2124 * If this message has no O (room) field, generate one.
2126 if (msg->cm_fields['O'] == NULL) {
2127 msg->cm_fields['O'] = strdup(CC->room.QRname);
2130 /* Perform "before save" hooks (aborting if any return nonzero) */
2131 lprintf(CTDL_DEBUG, "Performing before-save hooks\n");
2132 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2134 /* If this message has an Exclusive ID, perform replication checks */
2135 lprintf(CTDL_DEBUG, "Performing replication checks\n");
2136 if (ReplicationChecks(msg) > 0) return(-4);
2138 /* Save it to disk */
2139 lprintf(CTDL_DEBUG, "Saving to disk\n");
2140 newmsgid = send_message(msg);
2141 if (newmsgid <= 0L) return(-5);
2143 /* Write a supplemental message info record. This doesn't have to
2144 * be a critical section because nobody else knows about this message
2147 lprintf(CTDL_DEBUG, "Creating MetaData record\n");
2148 memset(&smi, 0, sizeof(struct MetaData));
2149 smi.meta_msgnum = newmsgid;
2150 smi.meta_refcount = 0;
2151 safestrncpy(smi.meta_content_type, content_type,
2152 sizeof smi.meta_content_type);
2154 /* As part of the new metadata record, measure how
2155 * big this message will be when displayed as RFC822.
2156 * Both POP and IMAP use this, and it's best to just take the hit now
2157 * instead of having to potentially measure thousands of messages when
2158 * a mailbox is opened later.
2161 if (CC->redirect_buffer != NULL) {
2162 lprintf(CTDL_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2165 CC->redirect_buffer = malloc(SIZ);
2166 CC->redirect_len = 0;
2167 CC->redirect_alloc = SIZ;
2168 CtdlOutputPreLoadedMsg(msg, 0L, MT_RFC822, HEADERS_ALL, 0, 1);
2169 smi.meta_rfc822_length = CC->redirect_len;
2170 free(CC->redirect_buffer);
2171 CC->redirect_buffer = NULL;
2172 CC->redirect_len = 0;
2173 CC->redirect_alloc = 0;
2177 /* Now figure out where to store the pointers */
2178 lprintf(CTDL_DEBUG, "Storing pointers\n");
2180 /* If this is being done by the networker delivering a private
2181 * message, we want to BYPASS saving the sender's copy (because there
2182 * is no local sender; it would otherwise go to the Trashcan).
2184 if ((!CC->internal_pgm) || (recps == NULL)) {
2185 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0) != 0) {
2186 lprintf(CTDL_ERR, "ERROR saving message pointer!\n");
2187 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2192 /* For internet mail, drop a copy in the outbound queue room */
2194 if (recps->num_internet > 0) {
2195 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0);
2198 /* If other rooms are specified, drop them there too. */
2200 if (recps->num_room > 0)
2201 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2202 extract_token(recipient, recps->recp_room, i,
2203 '|', sizeof recipient);
2204 lprintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2205 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0);
2208 /* Bump this user's messages posted counter. */
2209 lprintf(CTDL_DEBUG, "Updating user\n");
2210 lgetuser(&CC->user, CC->curr_user);
2211 CC->user.posted = CC->user.posted + 1;
2212 lputuser(&CC->user);
2214 /* If this is private, local mail, make a copy in the
2215 * recipient's mailbox and bump the reference count.
2218 if (recps->num_local > 0)
2219 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2220 extract_token(recipient, recps->recp_local, i,
2221 '|', sizeof recipient);
2222 lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2224 if (getuser(&userbuf, recipient) == 0) {
2225 MailboxName(actual_rm, sizeof actual_rm,
2226 &userbuf, MAILROOM);
2227 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0);
2228 BumpNewMailCounter(userbuf.usernum);
2231 lprintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2232 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2237 /* Perform "after save" hooks */
2238 lprintf(CTDL_DEBUG, "Performing after-save hooks\n");
2239 PerformMessageHooks(msg, EVT_AFTERSAVE);
2241 /* For IGnet mail, we have to save a new copy into the spooler for
2242 * each recipient, with the R and D fields set to the recipient and
2243 * destination-node. This has two ugly side effects: all other
2244 * recipients end up being unlisted in this recipient's copy of the
2245 * message, and it has to deliver multiple messages to the same
2246 * node. We'll revisit this again in a year or so when everyone has
2247 * a network spool receiver that can handle the new style messages.
2250 if (recps->num_ignet > 0)
2251 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2252 extract_token(recipient, recps->recp_ignet, i,
2253 '|', sizeof recipient);
2255 hold_R = msg->cm_fields['R'];
2256 hold_D = msg->cm_fields['D'];
2257 msg->cm_fields['R'] = malloc(SIZ);
2258 msg->cm_fields['D'] = malloc(128);
2259 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2260 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2262 serialize_message(&smr, msg);
2264 snprintf(submit_filename, sizeof submit_filename,
2265 #ifndef HAVE_SPOOL_DIR
2270 "/network/spoolin/netmail.%04lx.%04x.%04x",
2271 (long) getpid(), CC->cs_pid, ++seqnum);
2272 network_fp = fopen(submit_filename, "wb+");
2273 if (network_fp != NULL) {
2274 fwrite(smr.ser, smr.len, 1, network_fp);
2280 free(msg->cm_fields['R']);
2281 free(msg->cm_fields['D']);
2282 msg->cm_fields['R'] = hold_R;
2283 msg->cm_fields['D'] = hold_D;
2286 /* Go back to the room we started from */
2287 lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
2288 if (strcasecmp(hold_rm, CC->room.QRname))
2289 /* getroom(&CC->room, hold_rm); */
2290 usergoto(hold_rm, 0, 1, NULL, NULL);
2292 /* For internet mail, generate delivery instructions.
2293 * Yes, this is recursive. Deal with it. Infinite recursion does
2294 * not happen because the delivery instructions message does not
2295 * contain a recipient.
2298 if (recps->num_internet > 0) {
2299 lprintf(CTDL_DEBUG, "Generating delivery instructions\n");
2300 instr = malloc(SIZ * 2);
2301 snprintf(instr, SIZ * 2,
2302 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2304 SPOOLMIME, newmsgid, (long)time(NULL),
2305 msg->cm_fields['A'], msg->cm_fields['N']
2308 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
2309 size_t tmp = strlen(instr);
2310 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
2311 snprintf(&instr[tmp], SIZ * 2 - tmp,
2312 "remote|%s|0||\n", recipient);
2315 imsg = malloc(sizeof(struct CtdlMessage));
2316 memset(imsg, 0, sizeof(struct CtdlMessage));
2317 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2318 imsg->cm_anon_type = MES_NORMAL;
2319 imsg->cm_format_type = FMT_RFC822;
2320 imsg->cm_fields['A'] = strdup("Citadel");
2321 imsg->cm_fields['M'] = instr;
2322 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM);
2323 CtdlFreeMessage(imsg);
2332 * Convenience function for generating small administrative messages.
2334 void quickie_message(char *from, char *to, char *room, char *text,
2335 int format_type, char *subject)
2337 struct CtdlMessage *msg;
2338 struct recptypes *recp = NULL;
2340 msg = malloc(sizeof(struct CtdlMessage));
2341 memset(msg, 0, sizeof(struct CtdlMessage));
2342 msg->cm_magic = CTDLMESSAGE_MAGIC;
2343 msg->cm_anon_type = MES_NORMAL;
2344 msg->cm_format_type = format_type;
2345 msg->cm_fields['A'] = strdup(from);
2346 if (room != NULL) msg->cm_fields['O'] = strdup(room);
2347 msg->cm_fields['N'] = strdup(NODENAME);
2349 msg->cm_fields['R'] = strdup(to);
2350 recp = validate_recipients(to);
2352 if (subject != NULL) {
2353 msg->cm_fields['U'] = strdup(subject);
2355 msg->cm_fields['M'] = strdup(text);
2357 CtdlSubmitMsg(msg, recp, room);
2358 CtdlFreeMessage(msg);
2359 if (recp != NULL) free(recp);
2365 * Back end function used by CtdlMakeMessage() and similar functions
2367 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
2368 size_t maxlen, /* maximum message length */
2369 char *exist, /* if non-null, append to it;
2370 exist is ALWAYS freed */
2371 int crlf /* CRLF newlines instead of LF */
2375 size_t message_len = 0;
2376 size_t buffer_len = 0;
2382 if (exist == NULL) {
2389 message_len = strlen(exist);
2390 buffer_len = message_len + 4096;
2391 m = realloc(exist, buffer_len);
2398 /* flush the input if we have nowhere to store it */
2403 /* read in the lines of message text one by one */
2405 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
2406 if (!strcmp(buf, terminator)) finished = 1;
2408 strcat(buf, "\r\n");
2414 if ( (!flushing) && (!finished) ) {
2415 /* Measure the line */
2416 linelen = strlen(buf);
2418 /* augment the buffer if we have to */
2419 if ((message_len + linelen) >= buffer_len) {
2420 ptr = realloc(m, (buffer_len * 2) );
2421 if (ptr == NULL) { /* flush if can't allocate */
2424 buffer_len = (buffer_len * 2);
2426 lprintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
2430 /* Add the new line to the buffer. NOTE: this loop must avoid
2431 * using functions like strcat() and strlen() because they
2432 * traverse the entire buffer upon every call, and doing that
2433 * for a multi-megabyte message slows it down beyond usability.
2435 strcpy(&m[message_len], buf);
2436 message_len += linelen;
2439 /* if we've hit the max msg length, flush the rest */
2440 if (message_len >= maxlen) flushing = 1;
2442 } while (!finished);
2450 * Build a binary message to be saved on disk.
2451 * (NOTE: if you supply 'preformatted_text', the buffer you give it
2452 * will become part of the message. This means you are no longer
2453 * responsible for managing that memory -- it will be freed along with
2454 * the rest of the fields when CtdlFreeMessage() is called.)
2457 struct CtdlMessage *CtdlMakeMessage(
2458 struct ctdluser *author, /* author's user structure */
2459 char *recipient, /* NULL if it's not mail */
2460 char *room, /* room where it's going */
2461 int type, /* see MES_ types in header file */
2462 int format_type, /* variformat, plain text, MIME... */
2463 char *fake_name, /* who we're masquerading as */
2464 char *subject, /* Subject (optional) */
2465 char *preformatted_text /* ...or NULL to read text from client */
2467 char dest_node[SIZ];
2469 struct CtdlMessage *msg;
2471 msg = malloc(sizeof(struct CtdlMessage));
2472 memset(msg, 0, sizeof(struct CtdlMessage));
2473 msg->cm_magic = CTDLMESSAGE_MAGIC;
2474 msg->cm_anon_type = type;
2475 msg->cm_format_type = format_type;
2477 /* Don't confuse the poor folks if it's not routed mail. */
2478 strcpy(dest_node, "");
2482 snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */
2483 msg->cm_fields['P'] = strdup(buf);
2485 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
2486 msg->cm_fields['T'] = strdup(buf);
2488 if (fake_name[0]) /* author */
2489 msg->cm_fields['A'] = strdup(fake_name);
2491 msg->cm_fields['A'] = strdup(author->fullname);
2493 if (CC->room.QRflags & QR_MAILBOX) { /* room */
2494 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
2497 msg->cm_fields['O'] = strdup(CC->room.QRname);
2500 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
2501 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
2503 if (recipient[0] != 0) {
2504 msg->cm_fields['R'] = strdup(recipient);
2506 if (dest_node[0] != 0) {
2507 msg->cm_fields['D'] = strdup(dest_node);
2510 if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) {
2511 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
2514 if (subject != NULL) {
2516 if (strlen(subject) > 0) {
2517 msg->cm_fields['U'] = strdup(subject);
2521 if (preformatted_text != NULL) {
2522 msg->cm_fields['M'] = preformatted_text;
2525 msg->cm_fields['M'] = CtdlReadMessageBody("000",
2526 config.c_maxmsglen, NULL, 0);
2534 * Check to see whether we have permission to post a message in the current
2535 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
2536 * returns 0 on success.
2538 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) {
2540 if (!(CC->logged_in)) {
2541 snprintf(errmsgbuf, n, "Not logged in.");
2542 return (ERROR + NOT_LOGGED_IN);
2545 if ((CC->user.axlevel < 2)
2546 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
2547 snprintf(errmsgbuf, n, "Need to be validated to enter "
2548 "(except in %s> to sysop)", MAILROOM);
2549 return (ERROR + HIGHER_ACCESS_REQUIRED);
2552 if ((CC->user.axlevel < 4)
2553 && (CC->room.QRflags & QR_NETWORK)) {
2554 snprintf(errmsgbuf, n, "Need net privileges to enter here.");
2555 return (ERROR + HIGHER_ACCESS_REQUIRED);
2558 if ((CC->user.axlevel < 6)
2559 && (CC->room.QRflags & QR_READONLY)) {
2560 snprintf(errmsgbuf, n, "Sorry, this is a read-only room.");
2561 return (ERROR + HIGHER_ACCESS_REQUIRED);
2564 strcpy(errmsgbuf, "Ok");
2570 * Check to see if the specified user has Internet mail permission
2571 * (returns nonzero if permission is granted)
2573 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
2575 /* Do not allow twits to send Internet mail */
2576 if (who->axlevel <= 2) return(0);
2578 /* Globally enabled? */
2579 if (config.c_restrict == 0) return(1);
2581 /* User flagged ok? */
2582 if (who->flags & US_INTERNET) return(2);
2584 /* Aide level access? */
2585 if (who->axlevel >= 6) return(3);
2587 /* No mail for you! */
2594 * Validate recipients, count delivery types and errors, and handle aliasing
2595 * FIXME check for dupes!!!!!
2597 struct recptypes *validate_recipients(char *recipients) {
2598 struct recptypes *ret;
2599 char this_recp[SIZ];
2600 char this_recp_cooked[SIZ];
2606 struct ctdluser tempUS;
2607 struct ctdlroom tempQR;
2610 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
2611 if (ret == NULL) return(NULL);
2612 memset(ret, 0, sizeof(struct recptypes));
2615 ret->num_internet = 0;
2620 if (recipients == NULL) {
2623 else if (strlen(recipients) == 0) {
2627 /* Change all valid separator characters to commas */
2628 for (i=0; i<strlen(recipients); ++i) {
2629 if ((recipients[i] == ';') || (recipients[i] == '|')) {
2630 recipients[i] = ',';
2635 num_recps = num_tokens(recipients, ',');
2638 if (num_recps > 0) for (i=0; i<num_recps; ++i) {
2639 extract_token(this_recp, recipients, i, ',', sizeof this_recp);
2641 lprintf(CTDL_DEBUG, "Evaluating recipient #%d <%s>\n", i, this_recp);
2642 mailtype = alias(this_recp);
2643 mailtype = alias(this_recp);
2644 mailtype = alias(this_recp);
2645 for (j=0; j<=strlen(this_recp); ++j) {
2646 if (this_recp[j]=='_') {
2647 this_recp_cooked[j] = ' ';
2650 this_recp_cooked[j] = this_recp[j];
2656 if (!strcasecmp(this_recp, "sysop")) {
2658 strcpy(this_recp, config.c_aideroom);
2659 if (strlen(ret->recp_room) > 0) {
2660 strcat(ret->recp_room, "|");
2662 strcat(ret->recp_room, this_recp);
2664 else if (getuser(&tempUS, this_recp) == 0) {
2666 strcpy(this_recp, tempUS.fullname);
2667 if (strlen(ret->recp_local) > 0) {
2668 strcat(ret->recp_local, "|");
2670 strcat(ret->recp_local, this_recp);
2672 else if (getuser(&tempUS, this_recp_cooked) == 0) {
2674 strcpy(this_recp, tempUS.fullname);
2675 if (strlen(ret->recp_local) > 0) {
2676 strcat(ret->recp_local, "|");
2678 strcat(ret->recp_local, this_recp);
2680 else if ( (!strncasecmp(this_recp, "room_", 5))
2681 && (!getroom(&tempQR, &this_recp_cooked[5])) ) {
2683 if (strlen(ret->recp_room) > 0) {
2684 strcat(ret->recp_room, "|");
2686 strcat(ret->recp_room, &this_recp_cooked[5]);
2694 /* Yes, you're reading this correctly: if the target
2695 * domain points back to the local system or an attached
2696 * Citadel directory, the address is invalid. That's
2697 * because if the address were valid, we would have
2698 * already translated it to a local address by now.
2700 if (IsDirectory(this_recp)) {
2705 ++ret->num_internet;
2706 if (strlen(ret->recp_internet) > 0) {
2707 strcat(ret->recp_internet, "|");
2709 strcat(ret->recp_internet, this_recp);
2714 if (strlen(ret->recp_ignet) > 0) {
2715 strcat(ret->recp_ignet, "|");
2717 strcat(ret->recp_ignet, this_recp);
2725 if (strlen(ret->errormsg) == 0) {
2726 snprintf(append, sizeof append,
2727 "Invalid recipient: %s",
2731 snprintf(append, sizeof append,
2734 if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) {
2735 strcat(ret->errormsg, append);
2739 if (strlen(ret->display_recp) == 0) {
2740 strcpy(append, this_recp);
2743 snprintf(append, sizeof append, ", %s",
2746 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
2747 strcat(ret->display_recp, append);
2752 if ((ret->num_local + ret->num_internet + ret->num_ignet +
2753 ret->num_room + ret->num_error) == 0) {
2755 strcpy(ret->errormsg, "No recipients specified.");
2758 lprintf(CTDL_DEBUG, "validate_recipients()\n");
2759 lprintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
2760 lprintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
2761 lprintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
2762 lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
2763 lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
2771 * message entry - mode 0 (normal)
2773 void cmd_ent0(char *entargs)
2777 char masquerade_as[SIZ];
2779 int format_type = 0;
2780 char newusername[SIZ];
2781 struct CtdlMessage *msg;
2785 struct recptypes *valid = NULL;
2792 post = extract_int(entargs, 0);
2793 extract_token(recp, entargs, 1, '|', sizeof recp);
2794 anon_flag = extract_int(entargs, 2);
2795 format_type = extract_int(entargs, 3);
2796 extract_token(subject, entargs, 4, '|', sizeof subject);
2797 do_confirm = extract_int(entargs, 6);
2799 /* first check to make sure the request is valid. */
2801 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg);
2803 cprintf("%d %s\n", err, errmsg);
2807 /* Check some other permission type things. */
2810 if (CC->user.axlevel < 6) {
2811 cprintf("%d You don't have permission to masquerade.\n",
2812 ERROR + HIGHER_ACCESS_REQUIRED);
2815 extract_token(newusername, entargs, 5, '|', sizeof newusername);
2816 memset(CC->fake_postname, 0, sizeof(CC->fake_postname) );
2817 safestrncpy(CC->fake_postname, newusername,
2818 sizeof(CC->fake_postname) );
2819 cprintf("%d ok\n", CIT_OK);
2822 CC->cs_flags |= CS_POSTING;
2824 /* In the Mail> room we have to behave a little differently --
2825 * make sure the user has specified at least one recipient. Then
2826 * validate the recipient(s).
2828 if ( (CC->room.QRflags & QR_MAILBOX)
2829 && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) {
2831 if (CC->user.axlevel < 2) {
2832 strcpy(recp, "sysop");
2835 valid = validate_recipients(recp);
2836 if (valid->num_error > 0) {
2838 ERROR + NO_SUCH_USER, valid->errormsg);
2842 if (valid->num_internet > 0) {
2843 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
2844 cprintf("%d You do not have permission "
2845 "to send Internet mail.\n",
2846 ERROR + HIGHER_ACCESS_REQUIRED);
2852 if ( ( (valid->num_internet + valid->num_ignet) > 0)
2853 && (CC->user.axlevel < 4) ) {
2854 cprintf("%d Higher access required for network mail.\n",
2855 ERROR + HIGHER_ACCESS_REQUIRED);
2860 if ((RESTRICT_INTERNET == 1) && (valid->num_internet > 0)
2861 && ((CC->user.flags & US_INTERNET) == 0)
2862 && (!CC->internal_pgm)) {
2863 cprintf("%d You don't have access to Internet mail.\n",
2864 ERROR + HIGHER_ACCESS_REQUIRED);
2871 /* Is this a room which has anonymous-only or anonymous-option? */
2872 anonymous = MES_NORMAL;
2873 if (CC->room.QRflags & QR_ANONONLY) {
2874 anonymous = MES_ANONONLY;
2876 if (CC->room.QRflags & QR_ANONOPT) {
2877 if (anon_flag == 1) { /* only if the user requested it */
2878 anonymous = MES_ANONOPT;
2882 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
2886 /* If we're only checking the validity of the request, return
2887 * success without creating the message.
2890 cprintf("%d %s\n", CIT_OK,
2891 ((valid != NULL) ? valid->display_recp : "") );
2896 /* Handle author masquerading */
2897 if (CC->fake_postname[0]) {
2898 strcpy(masquerade_as, CC->fake_postname);
2900 else if (CC->fake_username[0]) {
2901 strcpy(masquerade_as, CC->fake_username);
2904 strcpy(masquerade_as, "");
2907 /* Read in the message from the client. */
2909 cprintf("%d send message\n", START_CHAT_MODE);
2911 cprintf("%d send message\n", SEND_LISTING);
2913 msg = CtdlMakeMessage(&CC->user, recp,
2914 CC->room.QRname, anonymous, format_type,
2915 masquerade_as, subject, NULL);
2918 msgnum = CtdlSubmitMsg(msg, valid, "");
2921 cprintf("%ld\n", msgnum);
2923 cprintf("Message accepted.\n");
2926 cprintf("Internal error.\n");
2928 if (msg->cm_fields['E'] != NULL) {
2929 cprintf("%s\n", msg->cm_fields['E']);
2936 CtdlFreeMessage(msg);
2938 CC->fake_postname[0] = '\0';
2946 * API function to delete messages which match a set of criteria
2947 * (returns the actual number of messages deleted)
2949 int CtdlDeleteMessages(char *room_name, /* which room */
2950 long dmsgnum, /* or "0" for any */
2951 char *content_type, /* or "" for any */
2952 int deferred /* let TDAP sweep it later */
2956 struct ctdlroom qrbuf;
2957 struct cdbdata *cdbfr;
2958 long *msglist = NULL;
2959 long *dellist = NULL;
2962 int num_deleted = 0;
2964 struct MetaData smi;
2966 lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s, %d)\n",
2967 room_name, dmsgnum, content_type, deferred);
2969 /* get room record, obtaining a lock... */
2970 if (lgetroom(&qrbuf, room_name) != 0) {
2971 lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
2973 return (0); /* room not found */
2975 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
2977 if (cdbfr != NULL) {
2978 msglist = malloc(cdbfr->len);
2979 dellist = malloc(cdbfr->len);
2980 memcpy(msglist, cdbfr->ptr, cdbfr->len);
2981 num_msgs = cdbfr->len / sizeof(long);
2985 for (i = 0; i < num_msgs; ++i) {
2988 /* Set/clear a bit for each criterion */
2990 if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) {
2991 delete_this |= 0x01;
2993 if (strlen(content_type) == 0) {
2994 delete_this |= 0x02;
2996 GetMetaData(&smi, msglist[i]);
2997 if (!strcasecmp(smi.meta_content_type,
2999 delete_this |= 0x02;
3003 /* Delete message only if all bits are set */
3004 if (delete_this == 0x03) {
3005 dellist[num_deleted++] = msglist[i];
3010 num_msgs = sort_msglist(msglist, num_msgs);
3011 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3012 msglist, (int)(num_msgs * sizeof(long)));
3014 qrbuf.QRhighest = msglist[num_msgs - 1];
3019 * If the delete operation is "deferred" (and technically, any delete
3020 * operation not performed by THE DREADED AUTO-PURGER ought to be
3021 * a deferred delete) then we save a pointer to the message in the
3022 * DELETED_MSGS_ROOM. This will cause the reference count to remain
3023 * at least 1, which will save the user from having to synchronously
3024 * wait for various disk-intensive operations to complete.
3026 if ( (deferred) && (num_deleted) ) {
3027 for (i=0; i<num_deleted; ++i) {
3028 CtdlCopyMsgToRoom(dellist[i], DELETED_MSGS_ROOM);
3032 /* Go through the messages we pulled out of the index, and decrement
3033 * their reference counts by 1. If this is the only room the message
3034 * was in, the reference count will reach zero and the message will
3035 * automatically be deleted from the database. We do this in a
3036 * separate pass because there might be plug-in hooks getting called,
3037 * and we don't want that happening during an S_ROOMS critical
3040 if (num_deleted) for (i=0; i<num_deleted; ++i) {
3041 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3042 AdjRefCount(dellist[i], -1);
3045 /* Now free the memory we used, and go away. */
3046 if (msglist != NULL) free(msglist);
3047 if (dellist != NULL) free(dellist);
3048 lprintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
3049 return (num_deleted);
3055 * Check whether the current user has permission to delete messages from
3056 * the current room (returns 1 for yes, 0 for no)
3058 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
3059 getuser(&CC->user, CC->curr_user);
3060 if ((CC->user.axlevel < 6)
3061 && (CC->user.usernum != CC->room.QRroomaide)
3062 && ((CC->room.QRflags & QR_MAILBOX) == 0)
3063 && (!(CC->internal_pgm))) {
3072 * Delete message from current room
3074 void cmd_dele(char *delstr)
3079 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
3080 cprintf("%d Higher access required.\n",
3081 ERROR + HIGHER_ACCESS_REQUIRED);
3084 delnum = extract_long(delstr, 0);
3086 num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "", 1);
3089 cprintf("%d %d message%s deleted.\n", CIT_OK,
3090 num_deleted, ((num_deleted != 1) ? "s" : ""));
3092 cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum);
3098 * Back end API function for moves and deletes
3100 int CtdlCopyMsgToRoom(long msgnum, char *dest) {
3103 err = CtdlSaveMsgPointerInRoom(dest, msgnum,
3104 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
3105 if (err != 0) return(err);
3113 * move or copy a message to another room
3115 void cmd_move(char *args)
3118 char targ[ROOMNAMELEN];
3119 struct ctdlroom qtemp;
3125 num = extract_long(args, 0);
3126 extract_token(targ, args, 1, '|', sizeof targ);
3127 targ[ROOMNAMELEN - 1] = 0;
3128 is_copy = extract_int(args, 2);
3130 if (getroom(&qtemp, targ) != 0) {
3131 cprintf("%d '%s' does not exist.\n",
3132 ERROR + ROOM_NOT_FOUND, targ);
3136 getuser(&CC->user, CC->curr_user);
3137 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
3139 /* Check for permission to perform this operation.
3140 * Remember: "CC->room" is source, "qtemp" is target.
3144 /* Aides can move/copy */
3145 if (CC->user.axlevel >= 6) permit = 1;
3147 /* Room aides can move/copy */
3148 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
3150 /* Permit move/copy from personal rooms */
3151 if ((CC->room.QRflags & QR_MAILBOX)
3152 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3154 /* Permit only copy from public to personal room */
3156 && (!(CC->room.QRflags & QR_MAILBOX))
3157 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
3159 /* User must have access to target room */
3160 if (!(ra & UA_KNOWN)) permit = 0;
3163 cprintf("%d Higher access required.\n",
3164 ERROR + HIGHER_ACCESS_REQUIRED);
3168 err = CtdlCopyMsgToRoom(num, targ);
3170 cprintf("%d Cannot store message in %s: error %d\n",
3175 /* Now delete the message from the source room,
3176 * if this is a 'move' rather than a 'copy' operation.
3179 CtdlDeleteMessages(CC->room.QRname, num, "", 0);
3182 cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
3188 * GetMetaData() - Get the supplementary record for a message
3190 void GetMetaData(struct MetaData *smibuf, long msgnum)
3193 struct cdbdata *cdbsmi;
3196 memset(smibuf, 0, sizeof(struct MetaData));
3197 smibuf->meta_msgnum = msgnum;
3198 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3200 /* Use the negative of the message number for its supp record index */
3201 TheIndex = (0L - msgnum);
3203 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3204 if (cdbsmi == NULL) {
3205 return; /* record not found; go with defaults */
3207 memcpy(smibuf, cdbsmi->ptr,
3208 ((cdbsmi->len > sizeof(struct MetaData)) ?
3209 sizeof(struct MetaData) : cdbsmi->len));
3216 * PutMetaData() - (re)write supplementary record for a message
3218 void PutMetaData(struct MetaData *smibuf)
3222 /* Use the negative of the message number for the metadata db index */
3223 TheIndex = (0L - smibuf->meta_msgnum);
3225 lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n",
3226 smibuf->meta_msgnum, smibuf->meta_refcount);
3228 cdb_store(CDB_MSGMAIN,
3229 &TheIndex, (int)sizeof(long),
3230 smibuf, (int)sizeof(struct MetaData));
3235 * AdjRefCount - change the reference count for a message;
3236 * delete the message if it reaches zero
3238 void AdjRefCount(long msgnum, int incr)
3241 struct MetaData smi;
3244 /* This is a *tight* critical section; please keep it that way, as
3245 * it may get called while nested in other critical sections.
3246 * Complicating this any further will surely cause deadlock!
3248 begin_critical_section(S_SUPPMSGMAIN);
3249 GetMetaData(&smi, msgnum);
3250 smi.meta_refcount += incr;
3252 end_critical_section(S_SUPPMSGMAIN);
3253 lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n",
3254 msgnum, incr, smi.meta_refcount);
3256 /* If the reference count is now zero, delete the message
3257 * (and its supplementary record as well).
3258 * FIXME ... defer this so it doesn't keep the user waiting.
3260 if (smi.meta_refcount == 0) {
3261 lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
3263 /* Remove from fulltext index */
3264 if (config.c_enable_fulltext) {
3265 ft_index_message(msgnum, 0);
3268 /* Remove from message base */
3270 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3271 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3273 /* Remove metadata record */
3274 delnum = (0L - msgnum);
3275 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3280 * Write a generic object to this room
3282 * Note: this could be much more efficient. Right now we use two temporary
3283 * files, and still pull the message into memory as with all others.
3285 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3286 char *content_type, /* MIME type of this object */
3287 char *tempfilename, /* Where to fetch it from */
3288 struct ctdluser *is_mailbox, /* Mailbox room? */
3289 int is_binary, /* Is encoding necessary? */
3290 int is_unique, /* Del others of this type? */
3291 unsigned int flags /* Internal save flags */
3296 struct ctdlroom qrbuf;
3297 char roomname[ROOMNAMELEN];
3298 struct CtdlMessage *msg;
3300 char *raw_message = NULL;
3301 char *encoded_message = NULL;
3302 off_t raw_length = 0;
3304 if (is_mailbox != NULL)
3305 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3307 safestrncpy(roomname, req_room, sizeof(roomname));
3308 lprintf(CTDL_DEBUG, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags);
3311 fp = fopen(tempfilename, "rb");
3313 lprintf(CTDL_CRIT, "Cannot open %s: %s\n",
3314 tempfilename, strerror(errno));
3317 fseek(fp, 0L, SEEK_END);
3318 raw_length = ftell(fp);
3320 lprintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
3322 raw_message = malloc((size_t)raw_length + 2);
3323 fread(raw_message, (size_t)raw_length, 1, fp);
3327 encoded_message = malloc((size_t)
3328 (((raw_length * 134) / 100) + 4096 ) );
3331 encoded_message = malloc((size_t)(raw_length + 4096));
3334 sprintf(encoded_message, "Content-type: %s\n", content_type);
3337 sprintf(&encoded_message[strlen(encoded_message)],
3338 "Content-transfer-encoding: base64\n\n"
3342 sprintf(&encoded_message[strlen(encoded_message)],
3343 "Content-transfer-encoding: 7bit\n\n"
3349 &encoded_message[strlen(encoded_message)],
3355 raw_message[raw_length] = 0;
3357 &encoded_message[strlen(encoded_message)],
3365 lprintf(CTDL_DEBUG, "Allocating\n");
3366 msg = malloc(sizeof(struct CtdlMessage));
3367 memset(msg, 0, sizeof(struct CtdlMessage));
3368 msg->cm_magic = CTDLMESSAGE_MAGIC;
3369 msg->cm_anon_type = MES_NORMAL;
3370 msg->cm_format_type = 4;
3371 msg->cm_fields['A'] = strdup(CC->user.fullname);
3372 msg->cm_fields['O'] = strdup(req_room);
3373 msg->cm_fields['N'] = strdup(config.c_nodename);
3374 msg->cm_fields['H'] = strdup(config.c_humannode);
3375 msg->cm_flags = flags;
3377 msg->cm_fields['M'] = encoded_message;
3379 /* Create the requested room if we have to. */
3380 if (getroom(&qrbuf, roomname) != 0) {
3381 create_room(roomname,
3382 ( (is_mailbox != NULL) ? 5 : 3 ),
3383 "", 0, 1, 0, VIEW_BBS);
3385 /* If the caller specified this object as unique, delete all
3386 * other objects of this type that are currently in the room.
3389 lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
3390 CtdlDeleteMessages(roomname, 0L, content_type, 0)
3393 /* Now write the data */
3394 CtdlSubmitMsg(msg, NULL, roomname);
3395 CtdlFreeMessage(msg);
3403 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
3404 config_msgnum = msgnum;
3408 char *CtdlGetSysConfig(char *sysconfname) {
3409 char hold_rm[ROOMNAMELEN];
3412 struct CtdlMessage *msg;
3415 strcpy(hold_rm, CC->room.QRname);
3416 if (getroom(&CC->room, SYSCONFIGROOM) != 0) {
3417 getroom(&CC->room, hold_rm);
3422 /* We want the last (and probably only) config in this room */
3423 begin_critical_section(S_CONFIG);
3424 config_msgnum = (-1L);
3425 CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL,
3426 CtdlGetSysConfigBackend, NULL);
3427 msgnum = config_msgnum;
3428 end_critical_section(S_CONFIG);
3434 msg = CtdlFetchMessage(msgnum, 1);
3436 conf = strdup(msg->cm_fields['M']);
3437 CtdlFreeMessage(msg);
3444 getroom(&CC->room, hold_rm);
3446 if (conf != NULL) do {
3447 extract_token(buf, conf, 0, '\n', sizeof buf);
3448 strcpy(conf, &conf[strlen(buf)+1]);
3449 } while ( (strlen(conf)>0) && (strlen(buf)>0) );
3454 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
3455 char temp[PATH_MAX];
3458 strcpy(temp, tmpnam(NULL));
3460 fp = fopen(temp, "w");
3461 if (fp == NULL) return;
3462 fprintf(fp, "%s", sysconfdata);
3465 /* this handy API function does all the work for us */
3466 CtdlWriteObject(SYSCONFIGROOM, sysconfname, temp, NULL, 0, 1, 0);
3472 * Determine whether a given Internet address belongs to the current user
3474 int CtdlIsMe(char *addr, int addr_buf_len)
3476 struct recptypes *recp;
3479 recp = validate_recipients(addr);
3480 if (recp == NULL) return(0);
3482 if (recp->num_local == 0) {
3487 for (i=0; i<recp->num_local; ++i) {
3488 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
3489 if (!strcasecmp(addr, CC->user.fullname)) {
3501 * Citadel protocol command to do the same
3503 void cmd_isme(char *argbuf) {
3506 if (CtdlAccessCheck(ac_logged_in)) return;
3507 extract_token(addr, argbuf, 0, '|', sizeof addr);
3509 if (CtdlIsMe(addr, sizeof addr)) {
3510 cprintf("%d %s\n", CIT_OK, addr);
3513 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);