X-Git-Url: https://code.citadel.org/?a=blobdiff_plain;f=citadel%2Fmsgbase.c;h=f6d7018b2279cdd0a7fa37625287122281949aea;hb=07f9de6655fff874dfd1ccbc882e121dca6957ad;hp=edc1253ac56121a1da403a172573130f53fef92c;hpb=dc42a5dc7b7f3e75b8fafae488d7874497bf3e68;p=citadel.git diff --git a/citadel/msgbase.c b/citadel/msgbase.c index edc1253ac..f6d7018b2 100644 --- a/citadel/msgbase.c +++ b/citadel/msgbase.c @@ -29,6 +29,8 @@ #include #include #include +#include +#include #include "citadel.h" #include "server.h" #include "serv_extensions.h" @@ -47,14 +49,19 @@ #include "html.h" #include "genstamp.h" #include "internet_addressing.h" -#include "serv_fulltext.h" #include "vcard.h" #include "euidindex.h" #include "journaling.h" +#include "citadel_dirs.h" +#include "clientsocket.h" + long config_msgnum; struct addresses_to_be_filed *atbf = NULL; +/* This temp file holds the queue of operations for AdjRefCount() */ +static FILE *arcfp = NULL; + /* * This really belongs in serv_network.c, but I don't know how to export * symbols between modules. @@ -138,26 +145,15 @@ int alias(char *name) char node[64]; char testnode[64]; char buf[SIZ]; - char filename[256]; + + char original_name[256]; + safestrncpy(original_name, name, sizeof original_name); striplt(name); remove_any_whitespace_to_the_left_or_right_of_at_symbol(name); stripallbut(name, '<', '>'); - /* - * DIRTY HACK FOLLOWS! due to configs in the network dir in the - * legacy installations, we need to calculate ifdeffed here. - */ - snprintf(filename, - sizeof filename, - "%smail.aliases", -#ifndef HAVE_ETG_DIR - ctdl_spool_dir -#else - ctdl_etc_dir -#endif - ); - fp = fopen(filename, "r"); + fp = fopen(file_mail_aliases, "r"); if (fp == NULL) { fp = fopen("/dev/null", "r"); } @@ -187,7 +183,9 @@ int alias(char *name) strcpy(name, aaa); } - lprintf(CTDL_INFO, "Mail is being forwarded to %s\n", name); + if (strcasecmp(original_name, name)) { + lprintf(CTDL_INFO, "%s is being forwarded to %s\n", original_name, name); + } /* Change "user @ xxx" to "user" if xxx is an alias for this host */ for (a=0; auser, CC->curr_user); CtdlGetRelationship(&vbuf, &CC->user, &CC->room); @@ -546,10 +542,9 @@ int CtdlForEachMessage(int mode, long ref, cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long)); if (cdbfr != NULL) { msglist = (long *) cdbfr->ptr; - cdbfr->ptr = NULL; /* CtdlForEachMessage() now owns this memory */ num_msgs = cdbfr->len / sizeof(long); - cdb_free(cdbfr); } else { + if (need_to_free_re) regfree(&re); return 0; /* No messages at all? No further action. */ } @@ -562,7 +557,7 @@ int CtdlForEachMessage(int mode, long ref, /* If the caller is looking for a specific MIME type, filter * out all messages which are not of the type requested. */ - if (content_type != NULL) if (strlen(content_type) > 0) { + if (content_type != NULL) if (!IsEmptyStr(content_type)) { /* This call to GetMetaData() sits inside this loop * so that we only do the extra database read per msg @@ -575,7 +570,8 @@ int CtdlForEachMessage(int mode, long ref, */ GetMetaData(&smi, msglist[a]); - if (strcasecmp(smi.meta_content_type, content_type)) { + /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */ + if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) { msglist[a] = 0L; } } @@ -600,7 +596,51 @@ int CtdlForEachMessage(int mode, long ref, } } + /* If a search string was specified, get a message list from + * the full text index and remove messages which aren't on both + * lists. + * + * How this works: + * Since the lists are sorted and strictly ascending, and the + * output list is guaranteed to be shorter than or equal to the + * input list, we overwrite the bottom of the input list. This + * eliminates the need to memmove big chunks of the list over and + * over again. + */ + if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) { + + /* Call search module via hook mechanism. + * NULL means use any search function available. + * otherwise replace with a char * to name of search routine + */ + CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext"); + + if (num_search_msgs > 0) { + int orig_num_msgs; + + orig_num_msgs = num_msgs; + num_msgs = 0; + for (i=0; ilogged_in)) && (!(CC->internal_pgm))) { cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN); return; } + if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) { + cprintf("%d Full text index is not enabled on this server.\n", + ERROR + CMD_NOT_SUPPORTED); + return; + } + if (with_template) { unbuffer_output(); cprintf("%d Send template then receive message list\n", @@ -691,6 +743,9 @@ void cmd_msgs(char *cmdbuf) template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage)); memset(template, 0, sizeof(struct CtdlMessage)); + template->cm_magic = CTDLMESSAGE_MAGIC; + template->cm_anon_type = MES_NORMAL; + while(client_getln(buf, sizeof buf), strcmp(buf,"000")) { extract_token(tfield, buf, 0, '|', sizeof tfield); extract_token(tvalue, buf, 1, '|', sizeof tvalue); @@ -708,7 +763,8 @@ void cmd_msgs(char *cmdbuf) } CtdlForEachMessage(mode, - cm_ref, + ( (mode == MSGS_SEARCH) ? 0 : cm_ref ), + ( (mode == MSGS_SEARCH) ? search_string : NULL ), NULL, template, (with_headers ? headers_listing : simple_listing), @@ -764,7 +820,6 @@ void do_help_subst(char *buffer) * to the client. The client software may reformat it again. */ void memfmout( - int width, /* screen width to use */ char *mptr, /* where are we going to get our text from? */ char subst, /* nonzero if we should do substitutions */ char *nl) /* string to terminate lines with */ @@ -775,6 +830,7 @@ void memfmout( cit_uint8_t ch; char aaa[140]; char buffer[SIZ]; + static int width = 80; strcpy(aaa, ""); old = 255; @@ -802,15 +858,13 @@ void memfmout( old = real; real = ch; - if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) + if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) { ch = 32; + } if (((old == 13) || (old == 10)) && (isspace(real))) { cprintf("%s", nl); c = 1; } - if (ch > 126) - continue; - if (ch > 32) { if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) { cprintf("%s%s", nl, aaa); @@ -927,6 +981,27 @@ void mime_download(char *name, char *filename, char *partnum, char *disp, +/* + * Callback function for mime parser that outputs a section all at once + */ +void mime_spew_section(char *name, char *filename, char *partnum, char *disp, + void *content, char *cbtype, char *cbcharset, size_t length, + char *encoding, void *cbuserdata) +{ + int *found_it = (int *)cbuserdata; + + /* ...or if this is not the desired section */ + if (strcasecmp(CC->download_desired_section, partnum)) + return; + + *found_it = 1; + + cprintf("%d %d\n", BINARY_FOLLOWS, (int)length); + client_write(content, length); +} + + + /* * Load a message from disk into memory. * This is used by CtdlOutputMsg() and other fetch functions. @@ -1003,7 +1078,7 @@ struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body) } } if (ret->cm_fields['M'] == NULL) { - ret->cm_fields['M'] = strdup("\n"); + ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n"); } /* Perform "before read" hooks (aborting if any return nonzero) */ @@ -1038,7 +1113,11 @@ void CtdlFreeMessage(struct CtdlMessage *msg) { int i; - if (is_valid_message(msg) == 0) return; + if (is_valid_message(msg) == 0) + { + if (msg != NULL) free (msg); + return; + } for (i = 0; i < 256; ++i) if (msg->cm_fields[i] != NULL) { @@ -1127,7 +1206,7 @@ void fixed_output(char *name, char *filename, char *partnum, char *disp, ma->did_print = 1; if ( (!strcasecmp(cbtype, "text/plain")) - || (strlen(cbtype)==0) ) { + || (IsEmptyStr(cbtype)) ) { wptr = content; if (length > 0) { client_write(wptr, length); @@ -1135,8 +1214,10 @@ void fixed_output(char *name, char *filename, char *partnum, char *disp, cprintf("\n"); } } + return; } - else if (!strcasecmp(cbtype, "text/html")) { + + if (!strcasecmp(cbtype, "text/html")) { ptr = html_to_ascii(content, length, 80, 0); wlen = strlen(ptr); client_write(ptr, wlen); @@ -1144,13 +1225,20 @@ void fixed_output(char *name, char *filename, char *partnum, char *disp, cprintf("\n"); } free(ptr); + return; } - else if (PerformFixedOutputHooks(cbtype, content, length)) { + + if (ma->use_fo_hooks) { + if (PerformFixedOutputHooks(cbtype, content, length)) { /* above function returns nonzero if it handled the part */ + return; + } } - else if (strncasecmp(cbtype, "multipart/", 10)) { + + if (strncasecmp(cbtype, "multipart/", 10)) { cprintf("Part %s: %s (%s) (%ld bytes)\r\n", partnum, filename, cbtype, (long)length); + return; } } @@ -1158,6 +1246,12 @@ void fixed_output(char *name, char *filename, char *partnum, char *disp, * The client is elegant and sophisticated and wants to be choosy about * MIME content types, so figure out which multipart/alternative part * we're going to send. + * + * We use a system of weights. When we find a part that matches one of the + * MIME types we've declared as preferential, we can store it in ma->chosen_part + * and then set ma->chosen_pref to that MIME type's position in our preference + * list. If we then hit another match, we only replace the first match if + * the preference value is lower. */ void choose_preferred(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, @@ -1173,7 +1267,10 @@ void choose_preferred(char *name, char *filename, char *partnum, char *disp, for (i=0; ipreferred_formats, '|'); ++i) { extract_token(buf, CC->preferred_formats, i, '|', sizeof buf); if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) { - safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part); + if (i < ma->chosen_pref) { + safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part); + ma->chosen_pref = i; + } } } } @@ -1209,19 +1306,19 @@ void output_preferred(char *name, char *filename, char *partnum, char *disp, if (text_content[length-1] != '\n') { ++add_newline; } - cprintf("Content-type: %s", cbtype); - if (strlen(cbcharset) > 0) { + if (!IsEmptyStr(cbcharset)) { cprintf("; charset=%s", cbcharset); } cprintf("\nContent-length: %d\n", (int)(length + add_newline) ); - if (strlen(encoding) > 0) { + if (!IsEmptyStr(encoding)) { cprintf("Content-transfer-encoding: %s\n", encoding); } else { cprintf("Content-transfer-encoding: 7bit\n"); } + cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum); cprintf("\n"); client_write(content, length); if (add_newline) cprintf("\n"); @@ -1316,7 +1413,7 @@ int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */ /* Here is the weird form of this command, to process only an * encapsulated message/rfc822 section. */ - if (section) if (strlen(section)>0) if (strcmp(section, "0")) { + if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) { memset(&encap, 0, sizeof encap); safestrncpy(encap.desired_section, section, sizeof encap.desired_section); mime_parser(TheMessage->cm_fields['M'], @@ -1431,6 +1528,32 @@ int CtdlOutputPreLoadedMsg( return((CC->download_fp != NULL) ? om_ok : om_mime_error); } + /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part + * in a single server operation instead of opening a download file. + */ + if (mode == MT_SPEW_SECTION) { + if (TheMessage->cm_format_type != FMT_RFC822) { + if (do_proto) + cprintf("%d This is not a MIME message.\n", + ERROR + ILLEGAL_VALUE); + } else { + /* Parse the message text component */ + int found_it = 0; + + mptr = TheMessage->cm_fields['M']; + mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0); + /* If section wasn't found, print an error + */ + if (!found_it) { + if (do_proto) cprintf( + "%d Section %s not found.\n", + ERROR + FILE_NOT_FOUND, + CC->download_desired_section); + } + } + return((CC->download_fp != NULL) ? om_ok : om_mime_error); + } + /* now for the user-mode message reading loops */ if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS); @@ -1481,7 +1604,7 @@ int CtdlOutputPreLoadedMsg( */ suppress_f = 0; if (TheMessage->cm_fields['N'] != NULL) - if (strlen(TheMessage->cm_fields['N']) > 0) + if (!IsEmptyStr(TheMessage->cm_fields['N'])) if (haschar(TheMessage->cm_fields['N'], '.') == 0) { suppress_f = 1; } @@ -1533,6 +1656,12 @@ int CtdlOutputPreLoadedMsg( else if (i == 'Y') { cprintf("CC: %s%s", mptr, nl); } + else if (i == 'P') { + cprintf("Return-Path: %s%s", mptr, nl); + } + else if (i == 'V') { + cprintf("Envelope-To: %s%s", mptr, nl); + } else if (i == 'U') { cprintf("Subject: %s%s", mptr, nl); subject_found = 1; @@ -1549,7 +1678,16 @@ int CtdlOutputPreLoadedMsg( else if (i == 'N') safestrncpy(snode, mptr, sizeof snode); else if (i == 'R') - cprintf("To: %s%s", mptr, nl); + { + if (haschar(mptr, '@') == 0) + { + cprintf("To: %s@%s%s", mptr, config.c_fqdn, nl); + } + else + { + cprintf("To: %s%s", mptr, nl); + } + } else if (i == 'T') { datestring(datestamp, sizeof datestamp, atol(mptr), DATESTRING_RFC822); @@ -1562,7 +1700,7 @@ int CtdlOutputPreLoadedMsg( } } - for (i=0; icm_anon_type == MES_ANONOPT)) { cprintf("From: \"anonymous\" %s", nl); } - else if (strlen(fuser) > 0) { + else if (!IsEmptyStr(fuser)) { cprintf("From: \"%s\" <%s>%s", luser, fuser, nl); } else { @@ -1623,30 +1761,40 @@ START_TEXT: ++start_of_text; start_of_text = strstr(start_of_text, "\n"); ++start_of_text; + + char outbuf[1024]; + int outlen = 0; + int nllen = strlen(nl); while (ch=*mptr, ch!=0) { if (ch==13) { /* do nothing */ } - else switch(headers_only) { - case HEADERS_NONE: - if (mptr >= start_of_text) { - if (ch == 10) cprintf("%s", nl); - else cprintf("%c", ch); + else { + if ( + ((headers_only == HEADERS_NONE) && (mptr >= start_of_text)) + || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text)) + || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY)) + ) { + if (ch == 10) { + sprintf(&outbuf[outlen], "%s", nl); + outlen += nllen; } - break; - case HEADERS_ONLY: - if (mptr < start_of_text) { - if (ch == 10) cprintf("%s", nl); - else cprintf("%c", ch); + else { + outbuf[outlen++] = ch; } - break; - default: - if (ch == 10) cprintf("%s", nl); - else cprintf("%c", ch); - break; + } } ++mptr; + if (outlen > 1000) { + client_write(outbuf, outlen); + outlen = 0; + } + } + if (outlen > 0) { + client_write(outbuf, outlen); + outlen = 0; } + goto DONE; } } @@ -1680,7 +1828,7 @@ START_TEXT: buf[strlen(buf)] = ch; } } - if (strlen(buf) > 0) + if (!IsEmptyStr(buf)) cprintf("%s%s", buf, nl); } @@ -1695,7 +1843,7 @@ START_TEXT: if (mode == MT_MIME) { cprintf("Content-type: text/x-citadel-variformat\n\n"); } - memfmout(80, mptr, 0, nl); + memfmout(mptr, 0, nl); } /* If the message on disk is format 4 (MIME), we've gotta hand it @@ -1707,7 +1855,9 @@ START_TEXT: memset(&ma, 0, sizeof(struct ma_info)); if (mode == MT_MIME) { + ma.use_fo_hooks = 0; strcpy(ma.chosen_part, "1"); + ma.chosen_pref = 9999; mime_parser(mptr, NULL, *choose_preferred, *fixed_output_pre, *fixed_output_post, (void *)&ma, 0); @@ -1715,6 +1865,7 @@ START_TEXT: *output_preferred, NULL, NULL, (void *)&ma, 0); } else { + ma.use_fo_hooks = 1; mime_parser(mptr, NULL, *fixed_output, *fixed_output_pre, *fixed_output_post, (void *)&ma, 0); @@ -1767,7 +1918,7 @@ void cmd_msg2(char *cmdbuf) void cmd_msg3(char *cmdbuf) { long msgnum; - struct CtdlMessage *msg; + struct CtdlMessage *msg = NULL; struct ser_ret smr; if (CC->internal_pgm == 0) { @@ -1843,31 +1994,59 @@ void cmd_opna(char *cmdbuf) /* - * Save a message pointer into a specified room + * Open a component of a MIME message and transmit it all at once + */ +void cmd_dlat(char *cmdbuf) +{ + long msgid; + char desired_section[128]; + + msgid = extract_long(cmdbuf, 0); + extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section); + safestrncpy(CC->download_desired_section, desired_section, + sizeof CC->download_desired_section); + CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL); +} + + +/* + * Save one or more message pointers into a specified room * (Returns 0 for success, nonzero for failure) * roomname may be NULL to use the current room * * Note that the 'supplied_msg' field may be set to NULL, in which case * the message will be fetched from disk, by number, if we need to perform * replication checks. This adds an additional database read, so if the - * caller already has the message in memory then it should be supplied. + * caller already has the message in memory then it should be supplied. (Obviously + * this mode of operation only works if we're saving a single message.) */ -int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check, - struct CtdlMessage *supplied_msg) { - int i; +int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs, + int do_repl_check, struct CtdlMessage *supplied_msg) +{ + int i, j, unique; char hold_rm[ROOMNAMELEN]; struct cdbdata *cdbfr; int num_msgs; long *msglist; long highest_msg = 0L; + + long msgid = 0; struct CtdlMessage *msg = NULL; - /*lprintf(CTDL_DEBUG, - "CtdlSaveMsgPointerInRoom(room=%s, msgid=%ld, repl=%d)\n", - roomname, msgid, do_repl_check);*/ + long *msgs_to_be_merged = NULL; + int num_msgs_to_be_merged = 0; + + lprintf(CTDL_DEBUG, + "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n", + roomname, num_newmsgs, do_repl_check); strcpy(hold_rm, CC->room.QRname); + /* Sanity checks */ + if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR); + if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR); + if (num_newmsgs > 1) supplied_msg = NULL; + /* Now the regular stuff */ if (lgetroom(&CC->room, ((roomname != NULL) ? roomname : CC->room.QRname) ) @@ -1876,6 +2055,11 @@ int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check, return(ERROR + ROOM_NOT_FOUND); } + + msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs); + num_msgs_to_be_merged = 0; + + cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long)); if (cdbfr == NULL) { msglist = NULL; @@ -1887,27 +2071,34 @@ int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check, cdb_free(cdbfr); } - /* Make sure the message doesn't already exist in this room. It - * is absolutely taboo to have more than one reference to the same - * message in a room. + + /* Create a list of msgid's which were supplied by the caller, but do + * not already exist in the target room. It is absolutely taboo to + * have more than one reference to the same message in a room. */ - if (num_msgs > 0) for (i=0; iroom); /* unlock the room */ - getroom(&CC->room, hold_rm); - free(msglist); - return(ERROR + ALREADY_EXISTS); + for (i=0; i 0) for (j=0; jroom)) && (do_repl_check) ) { - if (supplied_msg != NULL) { - msg = supplied_msg; - } - else { - msg = CtdlFetchMessage(msgid, 0); - } + lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n"); - if (msg != NULL) { - ReplicationChecks(msg); - } - - } + for (i=0; icm_fields['E'] != NULL) { + index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid); + } - /* If the message has an Exclusive ID, index that... */ - if (msg != NULL) { - if (msg->cm_fields['E'] != NULL) { - index_message_by_euid(msg->cm_fields['E'], - &CC->room, msgid); + /* Free up the memory we may have allocated */ + if (msg != supplied_msg) { + CtdlFreeMessage(msg); + } + } + } } - /* Free up the memory we may have allocated */ - if ( (msg != NULL) && (msg != supplied_msg) ) { - CtdlFreeMessage(msg); + else { + lprintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n"); } + /* Submit this room for processing by hooks */ + PerformRoomHooks(&CC->room); + /* Go back to the room we were in before we wandered here... */ getroom(&CC->room, hold_rm); - /* Bump the reference count for this message. */ - AdjRefCount(msgid, +1); + /* Bump the reference count for all messages which were merged */ + for (i=0; icm_fields['I']==NULL) { @@ -2051,21 +2271,31 @@ long send_message(struct CtdlMessage *msg) { void serialize_message(struct ser_ret *ret, /* return values */ struct CtdlMessage *msg) /* unserialized msg */ { - size_t wlen; + size_t wlen, fieldlen; int i; static char *forder = FORDER; - if (is_valid_message(msg) == 0) return; /* self check */ + /* + * Check for valid message format + */ + if (is_valid_message(msg) == 0) { + lprintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n"); + ret->len = 0; + ret->ser = NULL; + return; + } ret->len = 3; for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) ret->len = ret->len + strlen(msg->cm_fields[(int)forder[i]]) + 2; - lprintf(CTDL_DEBUG, "serialize_message() calling malloc(%ld)\n", (long)ret->len); ret->ser = malloc(ret->len); if (ret->ser == NULL) { + lprintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n", + (long)ret->len, strerror(errno)); ret->len = 0; + ret->ser = NULL; return; } @@ -2075,9 +2305,10 @@ void serialize_message(struct ser_ret *ret, /* return values */ wlen = 3; for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) { + fieldlen = strlen(msg->cm_fields[(int)forder[i]]); ret->ser[wlen++] = (char)forder[i]; - strcpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]]); - wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1; + safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1); + wlen = wlen + fieldlen + 1; } if (ret->len != wlen) lprintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n", (long)ret->len, (long)wlen); @@ -2102,14 +2333,14 @@ void ReplicationChecks(struct CtdlMessage *msg) { /* No exclusive id? Don't do anything. */ if (msg == NULL) return; if (msg->cm_fields['E'] == NULL) return; - if (strlen(msg->cm_fields['E']) == 0) return; + if (IsEmptyStr(msg->cm_fields['E'])) return; /*lprintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n", msg->cm_fields['E'], CC->room.QRname);*/ old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room); if (old_msgnum > 0L) { lprintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum); - CtdlDeleteMessages(CC->room.QRname, old_msgnum, "", 0); + CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, ""); } } @@ -2137,7 +2368,8 @@ long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */ FILE *network_fp = NULL; static int seqnum = 1; struct CtdlMessage *imsg = NULL; - char *instr; + char *instr = NULL; + size_t instr_alloc = 0; struct ser_ret smr; char *hold_R, *hold_D; char *collected_addresses = NULL; @@ -2161,7 +2393,7 @@ long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */ if (msg->cm_fields['P'] == NULL) { if (msg->cm_fields['A'] != NULL) { msg->cm_fields['P'] = strdup(msg->cm_fields['A']); - for (a=0; acm_fields['P']); ++a) { + for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) { if (isspace(msg->cm_fields['P'][a])) { msg->cm_fields['P'][a] = ' '; } @@ -2194,10 +2426,10 @@ long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */ break; case 4: strcpy(content_type, "text/plain"); - mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type: "); + mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:"); if (mptr != NULL) { - safestrncpy(content_type, &mptr[14], - sizeof content_type); + safestrncpy(content_type, &mptr[13], sizeof content_type); + striplt(content_type); for (a = 0; a < strlen(content_type); ++a) { if ((content_type[a] == ';') || (content_type[a] == ' ') @@ -2227,7 +2459,7 @@ long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */ } /* ...or if this message is destined for Aide> then go there. */ - if (strlen(force_room) > 0) { + if (!IsEmptyStr(force_room)) { strcpy(actual_rm, force_room); } @@ -2345,10 +2577,36 @@ long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */ lprintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n", recipient); if (getuser(&userbuf, recipient) == 0) { - MailboxName(actual_rm, sizeof actual_rm, - &userbuf, MAILROOM); + // Add a flag so the Funambol module knows its mail + msg->cm_fields['W'] = strdup(recipient); + MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM); CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg); BumpNewMailCounter(userbuf.usernum); + if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) { + /* Generate a instruction message for the Funambol notification + * server, in the same style as the SMTP queue + */ + instr_alloc = 1024; + instr = malloc(instr_alloc); + snprintf(instr, instr_alloc, + "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n" + "bounceto|%s@%s\n", + SPOOLMIME, newmsgid, (long)time(NULL), + msg->cm_fields['A'], msg->cm_fields['N'] + ); + + imsg = malloc(sizeof(struct CtdlMessage)); + memset(imsg, 0, sizeof(struct CtdlMessage)); + imsg->cm_magic = CTDLMESSAGE_MAGIC; + imsg->cm_anon_type = MES_NORMAL; + imsg->cm_format_type = FMT_RFC822; + imsg->cm_fields['A'] = strdup("Citadel"); + imsg->cm_fields['J'] = strdup("do not journal"); + imsg->cm_fields['M'] = instr; /* imsg owns this memory now */ + imsg->cm_fields['W'] = strdup(recipient); + CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM); + CtdlFreeMessage(imsg); + } } else { lprintf(CTDL_DEBUG, "No user <%s>\n", recipient); @@ -2405,7 +2663,6 @@ long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */ /* Go back to the room we started from */ lprintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm); if (strcasecmp(hold_rm, CC->room.QRname)) - /* getroom(&CC->room, hold_rm); */ usergoto(hold_rm, 0, 1, NULL, NULL); /* For internet mail, generate delivery instructions. @@ -2416,8 +2673,9 @@ long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */ if (recps != NULL) if (recps->num_internet > 0) { lprintf(CTDL_DEBUG, "Generating delivery instructions\n"); - instr = malloc(SIZ * 2); - snprintf(instr, SIZ * 2, + instr_alloc = 1024; + instr = malloc(instr_alloc); + snprintf(instr, instr_alloc, "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n" "bounceto|%s@%s\n", SPOOLMIME, newmsgid, (long)time(NULL), @@ -2426,10 +2684,12 @@ long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */ for (i=0; irecp_internet, '|'); ++i) { size_t tmp = strlen(instr); - extract_token(recipient, recps->recp_internet, - i, '|', sizeof recipient); - snprintf(&instr[tmp], SIZ * 2 - tmp, - "remote|%s|0||\n", recipient); + extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient); + if ((tmp + strlen(recipient) + 32) > instr_alloc) { + instr_alloc = instr_alloc * 2; + instr = realloc(instr, instr_alloc); + } + snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient); } imsg = malloc(sizeof(struct CtdlMessage)); @@ -2439,7 +2699,7 @@ long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */ imsg->cm_format_type = FMT_RFC822; imsg->cm_fields['A'] = strdup("Citadel"); imsg->cm_fields['J'] = strdup("do not journal"); - imsg->cm_fields['M'] = instr; + imsg->cm_fields['M'] = instr; /* imsg owns this memory now */ CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM); CtdlFreeMessage(imsg); } @@ -2507,7 +2767,7 @@ long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */ /* * Convenience function for generating small administrative messages. */ -void quickie_message(char *from, char *to, char *room, char *text, +void quickie_message(char *from, char *fromaddr, char *to, char *room, char *text, int format_type, char *subject) { struct CtdlMessage *msg; @@ -2518,7 +2778,21 @@ void quickie_message(char *from, char *to, char *room, char *text, msg->cm_magic = CTDLMESSAGE_MAGIC; msg->cm_anon_type = MES_NORMAL; msg->cm_format_type = format_type; - msg->cm_fields['A'] = strdup(from); + + if (from != NULL) { + msg->cm_fields['A'] = strdup(from); + } + else if (fromaddr != NULL) { + msg->cm_fields['A'] = strdup(fromaddr); + if (strchr(msg->cm_fields['A'], '@')) { + *strchr(msg->cm_fields['A'], '@') = 0; + } + } + else { + msg->cm_fields['A'] = strdup("Citadel"); + } + + if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr); if (room != NULL) msg->cm_fields['O'] = strdup(room); msg->cm_fields['N'] = strdup(NODENAME); if (to != NULL) { @@ -2532,7 +2806,7 @@ void quickie_message(char *from, char *to, char *room, char *text, CtdlSubmitMsg(msg, recp, room); CtdlFreeMessage(msg); - if (recp != NULL) free(recp); + if (recp != NULL) free_recipients(recp); } @@ -2544,7 +2818,8 @@ char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */ size_t maxlen, /* maximum message length */ char *exist, /* if non-null, append to it; exist is ALWAYS freed */ - int crlf /* CRLF newlines instead of LF */ + int crlf, /* CRLF newlines instead of LF */ + int sock /* socket handle or 0 for this session's client socket */ ) { char buf[1024]; int linelen; @@ -2554,6 +2829,7 @@ char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */ char *m; int flushing = 0; int finished = 0; + int dotdot = 0; if (exist == NULL) { m = malloc(4096); @@ -2571,6 +2847,11 @@ char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */ } } + /* Do we need to change leading ".." to "." for SMTP escaping? */ + if (!strcmp(terminator, ".")) { + dotdot = 1; + } + /* flush the input if we have nowhere to store it */ if (m == NULL) { flushing = 1; @@ -2578,7 +2859,12 @@ char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */ /* read in the lines of message text one by one */ do { - if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1; + if (sock > 0) { + if (sock_getln(sock, buf, (sizeof buf - 3)) < 0) finished = 1; + } + else { + if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1; + } if (!strcmp(buf, terminator)) finished = 1; if (crlf) { strcat(buf, "\r\n"); @@ -2587,6 +2873,13 @@ char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */ strcat(buf, "\n"); } + /* Unescape SMTP-style input of two dots at the beginning of the line */ + if (dotdot) { + if (!strncmp(buf, "..", 2)) { + strcpy(buf, &buf[1]); + } + } + if ( (!flushing) && (!finished) ) { /* Measure the line */ linelen = strlen(buf); @@ -2638,12 +2931,15 @@ struct CtdlMessage *CtdlMakeMessage( int type, /* see MES_ types in header file */ int format_type, /* variformat, plain text, MIME... */ char *fake_name, /* who we're masquerading as */ + char *my_email, /* which of my email addresses to use (empty is ok) */ char *subject, /* Subject (optional) */ + char *supplied_euid, /* ...or NULL if this is irrelevant */ char *preformatted_text /* ...or NULL to read text from client */ ) { - char dest_node[SIZ]; - char buf[SIZ]; + char dest_node[256]; + char buf[1024]; struct CtdlMessage *msg; + int i; msg = malloc(sizeof(struct CtdlMessage)); memset(msg, 0, sizeof(struct CtdlMessage)); @@ -2657,8 +2953,21 @@ struct CtdlMessage *CtdlMakeMessage( striplt(recipient); striplt(recp_cc); - snprintf(buf, sizeof buf, "cit%ld", author->usernum); /* Path */ - msg->cm_fields['P'] = strdup(buf); + /* Path or Return-Path */ + if (my_email == NULL) my_email = ""; + + if (!IsEmptyStr(my_email)) { + msg->cm_fields['P'] = strdup(my_email); + } + else { + snprintf(buf, sizeof buf, "%s", author->fullname); + msg->cm_fields['P'] = strdup(buf); + } + for (i=0; (msg->cm_fields['P'][i]!=0); ++i) { + if (isspace(msg->cm_fields['P'][i])) { + msg->cm_fields['P'][i] = '_'; + } + } snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */ msg->cm_fields['T'] = strdup(buf); @@ -2688,23 +2997,44 @@ struct CtdlMessage *CtdlMakeMessage( msg->cm_fields['D'] = strdup(dest_node); } - if ( (author == &CC->user) && (strlen(CC->cs_inet_email) > 0) ) { + if (!IsEmptyStr(my_email)) { + msg->cm_fields['F'] = strdup(my_email); + } + else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) { msg->cm_fields['F'] = strdup(CC->cs_inet_email); } if (subject != NULL) { + long length; striplt(subject); - if (strlen(subject) > 0) { - msg->cm_fields['U'] = strdup(subject); + length = strlen(subject); + if (length > 0) { + long i; + long IsAscii; + IsAscii = -1; + i = 0; + while ((subject[i] != '\0') && + (IsAscii = isascii(subject[i]) != 0 )) + i++; + if (IsAscii != 0) + msg->cm_fields['U'] = strdup(subject); + else /* ok, we've got utf8 in the string. */ + { + msg->cm_fields['U'] = rfc2047encode(subject, length); + } + } } + if (supplied_euid != NULL) { + msg->cm_fields['E'] = strdup(supplied_euid); + } + if (preformatted_text != NULL) { msg->cm_fields['M'] = preformatted_text; } else { - msg->cm_fields['M'] = CtdlReadMessageBody("000", - config.c_maxmsglen, NULL, 0); + msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0); } return(msg); @@ -2717,6 +3047,7 @@ struct CtdlMessage *CtdlMakeMessage( * returns 0 on success. */ int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) { + int ra; if (!(CC->logged_in)) { snprintf(errmsgbuf, n, "Not logged in."); @@ -2730,15 +3061,9 @@ int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n) { return (ERROR + HIGHER_ACCESS_REQUIRED); } - if ((CC->user.axlevel < 4) - && (CC->room.QRflags & QR_NETWORK)) { - snprintf(errmsgbuf, n, "Need net privileges to enter here."); - return (ERROR + HIGHER_ACCESS_REQUIRED); - } - - if ((CC->user.axlevel < 6) - && (CC->room.QRflags & QR_READONLY)) { - snprintf(errmsgbuf, n, "Sorry, this is a read-only room."); + CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL); + if (!(ra & UA_POSTALLOWED)) { + snprintf(errmsgbuf, n, "Higher access is required to post in this room."); return (ERROR + HIGHER_ACCESS_REQUIRED); } @@ -2773,12 +3098,15 @@ int CtdlCheckInternetMailPermission(struct ctdluser *who) { /* * Validate recipients, count delivery types and errors, and handle aliasing * FIXME check for dupes!!!!! - * Returns 0 if all addresses are ok, -1 if no addresses were specified, - * or the number of addresses found invalid. + * + * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses + * were specified, or the number of addresses found invalid. + * + * Caller needs to free the result using free_recipients() */ struct recptypes *validate_recipients(char *supplied_recipients) { struct recptypes *ret; - char recipients[SIZ]; + char *recipients = NULL; char this_recp[256]; char this_recp_cooked[256]; char append[SIZ]; @@ -2793,23 +3121,40 @@ struct recptypes *validate_recipients(char *supplied_recipients) { /* Initialize */ ret = (struct recptypes *) malloc(sizeof(struct recptypes)); if (ret == NULL) return(NULL); - memset(ret, 0, sizeof(struct recptypes)); - ret->num_local = 0; - ret->num_internet = 0; - ret->num_ignet = 0; - ret->num_error = 0; - ret->num_room = 0; + /* Set all strings to null and numeric values to zero */ + memset(ret, 0, sizeof(struct recptypes)); if (supplied_recipients == NULL) { - strcpy(recipients, ""); + recipients = strdup(""); } else { - safestrncpy(recipients, supplied_recipients, sizeof recipients); + recipients = strdup(supplied_recipients); } + /* Allocate some memory. Yes, this allocates 500% more memory than we will + * actually need, but it's healthier for the heap than doing lots of tiny + * realloc() calls instead. + */ + + ret->errormsg = malloc(strlen(recipients) + 1024); + ret->recp_local = malloc(strlen(recipients) + 1024); + ret->recp_internet = malloc(strlen(recipients) + 1024); + ret->recp_ignet = malloc(strlen(recipients) + 1024); + ret->recp_room = malloc(strlen(recipients) + 1024); + ret->display_recp = malloc(strlen(recipients) + 1024); + + ret->errormsg[0] = 0; + ret->recp_local[0] = 0; + ret->recp_internet[0] = 0; + ret->recp_ignet[0] = 0; + ret->recp_room[0] = 0; + ret->display_recp[0] = 0; + + ret->recptypes_magic = RECPTYPES_MAGIC; + /* Change all valid separator characters to commas */ - for (i=0; i 0) { + while (!IsEmptyStr(recipients)) { for (i=0; i<=strlen(recipients); ++i) { if (recipients[i] == '\"') in_quotes = 1 - in_quotes; @@ -2840,7 +3185,7 @@ struct recptypes *validate_recipients(char *supplied_recipients) { mailtype = alias(this_recp); mailtype = alias(this_recp); mailtype = alias(this_recp); - for (j=0; j<=strlen(this_recp); ++j) { + for (j=0; !IsEmptyStr(&this_recp[j]); ++j) { if (this_recp[j]=='_') { this_recp_cooked[j] = ' '; } @@ -2854,7 +3199,7 @@ struct recptypes *validate_recipients(char *supplied_recipients) { if (!strcasecmp(this_recp, "sysop")) { ++ret->num_room; strcpy(this_recp, config.c_aideroom); - if (strlen(ret->recp_room) > 0) { + if (!IsEmptyStr(ret->recp_room)) { strcat(ret->recp_room, "|"); } strcat(ret->recp_room, this_recp); @@ -2862,7 +3207,7 @@ struct recptypes *validate_recipients(char *supplied_recipients) { else if (getuser(&tempUS, this_recp) == 0) { ++ret->num_local; strcpy(this_recp, tempUS.fullname); - if (strlen(ret->recp_local) > 0) { + if (!IsEmptyStr(ret->recp_local)) { strcat(ret->recp_local, "|"); } strcat(ret->recp_local, this_recp); @@ -2870,7 +3215,7 @@ struct recptypes *validate_recipients(char *supplied_recipients) { else if (getuser(&tempUS, this_recp_cooked) == 0) { ++ret->num_local; strcpy(this_recp, tempUS.fullname); - if (strlen(ret->recp_local) > 0) { + if (!IsEmptyStr(ret->recp_local)) { strcat(ret->recp_local, "|"); } strcat(ret->recp_local, this_recp); @@ -2878,7 +3223,7 @@ struct recptypes *validate_recipients(char *supplied_recipients) { else if ( (!strncasecmp(this_recp, "room_", 5)) && (!getroom(&tempQR, &this_recp_cooked[5])) ) { ++ret->num_room; - if (strlen(ret->recp_room) > 0) { + if (!IsEmptyStr(ret->recp_room)) { strcat(ret->recp_room, "|"); } strcat(ret->recp_room, &this_recp_cooked[5]); @@ -2895,13 +3240,13 @@ struct recptypes *validate_recipients(char *supplied_recipients) { * because if the address were valid, we would have * already translated it to a local address by now. */ - if (IsDirectory(this_recp)) { + if (IsDirectory(this_recp, 0)) { ++ret->num_error; invalid = 1; } else { ++ret->num_internet; - if (strlen(ret->recp_internet) > 0) { + if (!IsEmptyStr(ret->recp_internet)) { strcat(ret->recp_internet, "|"); } strcat(ret->recp_internet, this_recp); @@ -2909,7 +3254,7 @@ struct recptypes *validate_recipients(char *supplied_recipients) { break; case MES_IGNET: ++ret->num_ignet; - if (strlen(ret->recp_ignet) > 0) { + if (!IsEmptyStr(ret->recp_ignet)) { strcat(ret->recp_ignet, "|"); } strcat(ret->recp_ignet, this_recp); @@ -2920,26 +3265,24 @@ struct recptypes *validate_recipients(char *supplied_recipients) { break; } if (invalid) { - if (strlen(ret->errormsg) == 0) { + if (IsEmptyStr(ret->errormsg)) { snprintf(append, sizeof append, "Invalid recipient: %s", this_recp); } else { - snprintf(append, sizeof append, - ", %s", this_recp); + snprintf(append, sizeof append, ", %s", this_recp); } if ( (strlen(ret->errormsg) + strlen(append)) < SIZ) { strcat(ret->errormsg, append); } } else { - if (strlen(ret->display_recp) == 0) { + if (IsEmptyStr(ret->display_recp)) { strcpy(append, this_recp); } else { - snprintf(append, sizeof append, ", %s", - this_recp); + snprintf(append, sizeof append, ", %s", this_recp); } if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) { strcat(ret->display_recp, append); @@ -2960,10 +3303,35 @@ struct recptypes *validate_recipients(char *supplied_recipients) { lprintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet); lprintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg); + free(recipients); return(ret); } +/* + * Destructor for struct recptypes + */ +void free_recipients(struct recptypes *valid) { + + if (valid == NULL) { + return; + } + + if (valid->recptypes_magic != RECPTYPES_MAGIC) { + lprintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n"); + abort(); + } + + if (valid->errormsg != NULL) free(valid->errormsg); + if (valid->recp_local != NULL) free(valid->recp_local); + if (valid->recp_internet != NULL) free(valid->recp_internet); + if (valid->recp_ignet != NULL) free(valid->recp_ignet); + if (valid->recp_room != NULL) free(valid->recp_room); + if (valid->display_recp != NULL) free(valid->display_recp); + free(valid); +} + + /* * message entry - mode 0 (normal) @@ -2974,10 +3342,11 @@ void cmd_ent0(char *entargs) char recp[SIZ]; char cc[SIZ]; char bcc[SIZ]; - char masquerade_as[SIZ]; + char supplied_euid[128]; int anon_flag = 0; int format_type = 0; - char newusername[SIZ]; + char newusername[256]; + char newuseremail[256]; struct CtdlMessage *msg; int anonymous = 0; char errmsg[SIZ]; @@ -2987,8 +3356,12 @@ void cmd_ent0(char *entargs) struct recptypes *valid_cc = NULL; struct recptypes *valid_bcc = NULL; char subject[SIZ]; + int subject_required = 0; int do_confirm = 0; long msgnum; + int i, j; + char buf[256]; + int newuseremail_ok = 0; unbuffer_output(); @@ -2997,42 +3370,85 @@ void cmd_ent0(char *entargs) anon_flag = extract_int(entargs, 2); format_type = extract_int(entargs, 3); extract_token(subject, entargs, 4, '|', sizeof subject); + extract_token(newusername, entargs, 5, '|', sizeof newusername); do_confirm = extract_int(entargs, 6); extract_token(cc, entargs, 7, '|', sizeof cc); extract_token(bcc, entargs, 8, '|', sizeof bcc); + switch(CC->room.QRdefaultview) { + case VIEW_NOTES: + case VIEW_WIKI: + extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid); + break; + default: + supplied_euid[0] = 0; + break; + } + extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail); /* first check to make sure the request is valid. */ err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg); - if (err) { + if (err) + { cprintf("%d %s\n", err, errmsg); return; } /* Check some other permission type things. */ - if (post == 2) { - if (CC->user.axlevel < 6) { - cprintf("%d You don't have permission to masquerade.\n", - ERROR + HIGHER_ACCESS_REQUIRED); - return; + if (IsEmptyStr(newusername)) { + strcpy(newusername, CC->user.fullname); + } + if ( (CC->user.axlevel < 6) + && (strcasecmp(newusername, CC->user.fullname)) + && (strcasecmp(newusername, CC->cs_inet_fn)) + ) { + cprintf("%d You don't have permission to author messages as '%s'.\n", + ERROR + HIGHER_ACCESS_REQUIRED, + newusername + ); + return; + } + + + if (IsEmptyStr(newuseremail)) { + newuseremail_ok = 1; + } + + if (!IsEmptyStr(newuseremail)) { + if (!strcasecmp(newuseremail, CC->cs_inet_email)) { + newuseremail_ok = 1; } - extract_token(newusername, entargs, 5, '|', sizeof newusername); - memset(CC->fake_postname, 0, sizeof(CC->fake_postname) ); - safestrncpy(CC->fake_postname, newusername, - sizeof(CC->fake_postname) ); - cprintf("%d ok\n", CIT_OK); + else if (!IsEmptyStr(CC->cs_inet_other_emails)) { + j = num_tokens(CC->cs_inet_other_emails, '|'); + for (i=0; ics_inet_other_emails, i, '|', sizeof buf); + if (!strcasecmp(newuseremail, buf)) { + newuseremail_ok = 1; + } + } + } + } + + if (!newuseremail_ok) { + cprintf("%d You don't have permission to author messages as '%s'.\n", + ERROR + HIGHER_ACCESS_REQUIRED, + newuseremail + ); return; } + CC->cs_flags |= CS_POSTING; - /* In the Mail> room we have to behave a little differently -- + /* In mailbox rooms we have to behave a little differently -- * make sure the user has specified at least one recipient. Then - * validate the recipient(s). + * validate the recipient(s). We do this for the Mail> room, as + * well as any room which has the "Mailbox" view set. */ - if ( (CC->room.QRflags & QR_MAILBOX) - && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) { + if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) ) + || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) ) + ) { if (CC->user.axlevel < 2) { strcpy(recp, "sysop"); strcpy(cc, ""); @@ -3042,32 +3458,32 @@ void cmd_ent0(char *entargs) valid_to = validate_recipients(recp); if (valid_to->num_error > 0) { cprintf("%d Invalid recipient (To)\n", ERROR + NO_SUCH_USER); - free(valid_to); + free_recipients(valid_to); return; } valid_cc = validate_recipients(cc); if (valid_cc->num_error > 0) { cprintf("%d Invalid recipient (CC)\n", ERROR + NO_SUCH_USER); - free(valid_to); - free(valid_cc); + free_recipients(valid_to); + free_recipients(valid_cc); return; } valid_bcc = validate_recipients(bcc); if (valid_bcc->num_error > 0) { cprintf("%d Invalid recipient (BCC)\n", ERROR + NO_SUCH_USER); - free(valid_to); - free(valid_cc); - free(valid_bcc); + free_recipients(valid_to); + free_recipients(valid_cc); + free_recipients(valid_bcc); return; } /* Recipient required, but none were specified */ if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) { - free(valid_to); - free(valid_cc); - free(valid_bcc); + free_recipients(valid_to); + free_recipients(valid_cc); + free_recipients(valid_bcc); cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER); return; } @@ -3077,9 +3493,9 @@ void cmd_ent0(char *entargs) cprintf("%d You do not have permission " "to send Internet mail.\n", ERROR + HIGHER_ACCESS_REQUIRED); - free(valid_to); - free(valid_cc); - free(valid_bcc); + free_recipients(valid_to); + free_recipients(valid_cc); + free_recipients(valid_bcc); return; } } @@ -3088,9 +3504,9 @@ void cmd_ent0(char *entargs) && (CC->user.axlevel < 4) ) { cprintf("%d Higher access required for network mail.\n", ERROR + HIGHER_ACCESS_REQUIRED); - free(valid_to); - free(valid_cc); - free(valid_bcc); + free_recipients(valid_to); + free_recipients(valid_cc); + free_recipients(valid_bcc); return; } @@ -3100,9 +3516,9 @@ void cmd_ent0(char *entargs) && (!CC->internal_pgm)) { cprintf("%d You don't have access to Internet mail.\n", ERROR + HIGHER_ACCESS_REQUIRED); - free(valid_to); - free(valid_cc); - free(valid_bcc); + free_recipients(valid_to); + free_recipients(valid_cc); + free_recipients(valid_bcc); return; } @@ -3123,33 +3539,32 @@ void cmd_ent0(char *entargs) recp[0] = 0; } + /* Recommend to the client that the use of a message subject is + * strongly recommended in this room, if either the SUBJECTREQ flag + * is set, or if there is one or more Internet email recipients. + */ + if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1; + if (valid_to) if (valid_to->num_internet > 0) subject_required = 1; + if (valid_cc) if (valid_cc->num_internet > 0) subject_required = 1; + if (valid_bcc) if (valid_bcc->num_internet > 0) subject_required = 1; + /* If we're only checking the validity of the request, return * success without creating the message. */ if (post == 0) { - cprintf("%d %s\n", CIT_OK, - ((valid_to != NULL) ? valid_to->display_recp : "") ); - free(valid_to); - free(valid_cc); - free(valid_bcc); + cprintf("%d %s|%d\n", CIT_OK, + ((valid_to != NULL) ? valid_to->display_recp : ""), + subject_required); + free_recipients(valid_to); + free_recipients(valid_cc); + free_recipients(valid_bcc); return; } /* We don't need these anymore because we'll do it differently below */ - free(valid_to); - free(valid_cc); - free(valid_bcc); - - /* Handle author masquerading */ - if (CC->fake_postname[0]) { - strcpy(masquerade_as, CC->fake_postname); - } - else if (CC->fake_username[0]) { - strcpy(masquerade_as, CC->fake_username); - } - else { - strcpy(masquerade_as, ""); - } + free_recipients(valid_to); + free_recipients(valid_cc); + free_recipients(valid_bcc); /* Read in the message from the client. */ if (do_confirm) { @@ -3160,26 +3575,28 @@ void cmd_ent0(char *entargs) msg = CtdlMakeMessage(&CC->user, recp, cc, CC->room.QRname, anonymous, format_type, - masquerade_as, subject, NULL); + newusername, newuseremail, subject, + ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL), + NULL); /* Put together one big recipients struct containing to/cc/bcc all in * one. This is for the envelope. */ char *all_recps = malloc(SIZ * 3); strcpy(all_recps, recp); - if (strlen(cc) > 0) { - if (strlen(all_recps) > 0) { + if (!IsEmptyStr(cc)) { + if (!IsEmptyStr(all_recps)) { strcat(all_recps, ","); } strcat(all_recps, cc); } - if (strlen(bcc) > 0) { - if (strlen(all_recps) > 0) { + if (!IsEmptyStr(bcc)) { + if (!IsEmptyStr(all_recps)) { strcat(all_recps, ","); } strcat(all_recps, bcc); } - if (strlen(all_recps) > 0) { + if (!IsEmptyStr(all_recps)) { valid = validate_recipients(all_recps); } else { @@ -3208,9 +3625,8 @@ void cmd_ent0(char *entargs) CtdlFreeMessage(msg); } - CC->fake_postname[0] = '\0'; if (valid != NULL) { - free(valid); + free_recipients(valid); } return; } @@ -3222,29 +3638,36 @@ void cmd_ent0(char *entargs) * (returns the actual number of messages deleted) */ int CtdlDeleteMessages(char *room_name, /* which room */ - long dmsgnum, /* or "0" for any */ - char *content_type, /* or "" for any */ - int deferred /* let TDAP sweep it later */ + long *dmsgnums, /* array of msg numbers to be deleted */ + int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */ + char *content_type /* or "" for any. regular expressions expected. */ ) { - struct ctdlroom qrbuf; struct cdbdata *cdbfr; long *msglist = NULL; long *dellist = NULL; int num_msgs = 0; - int i; + int i, j; int num_deleted = 0; int delete_this; struct MetaData smi; + regex_t re; + regmatch_t pm; + int need_to_free_re = 0; - lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %ld, %s, %d)\n", - room_name, dmsgnum, content_type, deferred); + if (content_type) if (!IsEmptyStr(content_type)) { + regcomp(&re, content_type, 0); + need_to_free_re = 1; + } + lprintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n", + room_name, num_dmsgnums, content_type); /* get room record, obtaining a lock... */ if (lgetroom(&qrbuf, room_name) != 0) { lprintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n", room_name); + if (need_to_free_re) regfree(&re); return (0); /* room not found */ } cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long)); @@ -3262,15 +3685,25 @@ int CtdlDeleteMessages(char *room_name, /* which room */ /* Set/clear a bit for each criterion */ - if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) { + /* 0 messages in the list or a null list means that we are + * interested in deleting any messages which meet the other criteria. + */ + if ((num_dmsgnums == 0) || (dmsgnums == NULL)) { delete_this |= 0x01; } - if (strlen(content_type) == 0) { + else { + for (j=0; juser, CC->curr_user); - if ((CC->user.axlevel < 6) - && (CC->user.usernum != CC->room.QRroomaide) - && ((CC->room.QRflags & QR_MAILBOX) == 0) - && (!(CC->internal_pgm))) { - return(0); - } - return(1); + int ra; + CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL); + if (ra & UA_DELETEALLOWED) return(1); + return(0); } + /* * Delete message from current room */ -void cmd_dele(char *delstr) +void cmd_dele(char *args) { - long delnum; int num_deleted; + int i; + char msgset[SIZ]; + char msgtok[32]; + long *msgs; + int num_msgs = 0; + + extract_token(msgset, args, 0, '|', sizeof msgset); + num_msgs = num_tokens(msgset, ','); + if (num_msgs < 1) { + cprintf("%d Nothing to do.\n", CIT_OK); + return; + } if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) { cprintf("%d Higher access required.\n", ERROR + HIGHER_ACCESS_REQUIRED); return; } - delnum = extract_long(delstr, 0); - num_deleted = CtdlDeleteMessages(CC->room.QRname, delnum, "", 1); + /* + * Build our message set to be moved/copied + */ + msgs = malloc(num_msgs * sizeof(long)); + for (i=0; iroom.QRname, msgs, num_msgs, ""); + free(msgs); if (num_deleted) { cprintf("%d %d message%s deleted.\n", CIT_OK, num_deleted, ((num_deleted != 1) ? "s" : "")); } else { - cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, delnum); + cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND); } } /* - * Back end API function for moves and deletes + * Back end API function for moves and deletes (multiple messages) */ -int CtdlCopyMsgToRoom(long msgnum, char *dest) { +int CtdlCopyMsgsToRoom(long *msgnums, int num_msgs, char *dest) { int err; - err = CtdlSaveMsgPointerInRoom(dest, msgnum, 1, NULL); + err = CtdlSaveMsgPointersInRoom(dest, msgnums, num_msgs, 1, NULL); if (err != 0) return(err); return(0); @@ -3383,20 +3820,32 @@ int CtdlCopyMsgToRoom(long msgnum, char *dest) { + /* * move or copy a message to another room */ void cmd_move(char *args) { - long num; + char msgset[SIZ]; + char msgtok[32]; + long *msgs; + int num_msgs = 0; + char targ[ROOMNAMELEN]; struct ctdlroom qtemp; int err; int is_copy = 0; int ra; int permit = 0; + int i; + + extract_token(msgset, args, 0, '|', sizeof msgset); + num_msgs = num_tokens(msgset, ','); + if (num_msgs < 1) { + cprintf("%d Nothing to do.\n", CIT_OK); + return; + } - num = extract_long(args, 0); extract_token(targ, args, 1, '|', sizeof targ); convert_room_name_macros(targ, sizeof targ); targ[ROOMNAMELEN - 1] = 0; @@ -3431,6 +3880,9 @@ void cmd_move(char *args) && (!(CC->room.QRflags & QR_MAILBOX)) && (qtemp.QRflags & QR_MAILBOX)) permit = 1; + /* Permit message removal from collaborative delete rooms */ + if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1; + /* User must have access to target room */ if (!(ra & UA_KNOWN)) permit = 0; @@ -3440,10 +3892,23 @@ void cmd_move(char *args) return; } - err = CtdlCopyMsgToRoom(num, targ); + /* + * Build our message set to be moved/copied + */ + msgs = malloc(num_msgs * sizeof(long)); + for (i=0; iroom.QRname, num, "", 0); + CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, ""); } + free(msgs); - cprintf("%d Message %s.\n", CIT_OK, (is_copy ? "copied" : "moved") ); + cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") ); } @@ -3497,9 +3963,6 @@ void PutMetaData(struct MetaData *smibuf) /* Use the negative of the message number for the metadata db index */ TheIndex = (0L - smibuf->meta_msgnum); - lprintf(CTDL_DEBUG, "PutMetaData(%ld) - ref count is %d\n", - smibuf->meta_msgnum, smibuf->meta_refcount); - cdb_store(CDB_MSGMAIN, &TheIndex, (int)sizeof(long), smibuf, (int)sizeof(struct MetaData)); @@ -3507,10 +3970,112 @@ void PutMetaData(struct MetaData *smibuf) } /* - * AdjRefCount - change the reference count for a message; - * delete the message if it reaches zero + * AdjRefCount - submit an adjustment to the reference count for a message. + * (These are just queued -- we actually process them later.) */ void AdjRefCount(long msgnum, int incr) +{ + struct arcq new_arcq; + + begin_critical_section(S_SUPPMSGMAIN); + if (arcfp == NULL) { + arcfp = fopen(file_arcq, "ab+"); + } + end_critical_section(S_SUPPMSGMAIN); + + /* msgnum < 0 means that we're trying to close the file */ + if (msgnum < 0) { + lprintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n"); + begin_critical_section(S_SUPPMSGMAIN); + if (arcfp != NULL) { + fclose(arcfp); + arcfp = NULL; + } + end_critical_section(S_SUPPMSGMAIN); + return; + } + + /* + * If we can't open the queue, perform the operation synchronously. + */ + if (arcfp == NULL) { + TDAP_AdjRefCount(msgnum, incr); + return; + } + + new_arcq.arcq_msgnum = msgnum; + new_arcq.arcq_delta = incr; + fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp); + fflush(arcfp); + + return; +} + + +/* + * TDAP_ProcessAdjRefCountQueue() + * + * Process the queue of message count adjustments that was created by calls + * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount() + * for each one. This should be an "off hours" operation. + */ +int TDAP_ProcessAdjRefCountQueue(void) +{ + char file_arcq_temp[PATH_MAX]; + int r; + FILE *fp; + struct arcq arcq_rec; + int num_records_processed = 0; + + snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s2", file_arcq); + + begin_critical_section(S_SUPPMSGMAIN); + if (arcfp != NULL) { + fclose(arcfp); + arcfp = NULL; + } + + r = link(file_arcq, file_arcq_temp); + if (r != 0) { + lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno)); + end_critical_section(S_SUPPMSGMAIN); + return(num_records_processed); + } + + unlink(file_arcq); + end_critical_section(S_SUPPMSGMAIN); + + fp = fopen(file_arcq_temp, "rb"); + if (fp == NULL) { + lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno)); + return(num_records_processed); + } + + while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) { + TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta); + ++num_records_processed; + } + + fclose(fp); + r = unlink(file_arcq_temp); + if (r != 0) { + lprintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno)); + } + + return(num_records_processed); +} + + + +/* + * TDAP_AdjRefCount - adjust the reference count for a message. + * This one does it "for real" because it's called by + * the autopurger function that processes the queue + * created by AdjRefCount(). If a message's reference + * count becomes zero, we also delete the message from + * disk and de-index it. + */ +void TDAP_AdjRefCount(long msgnum, int incr) { struct MetaData smi; @@ -3525,7 +4090,7 @@ void AdjRefCount(long msgnum, int incr) smi.meta_refcount += incr; PutMetaData(&smi); end_critical_section(S_SUPPMSGMAIN); - lprintf(CTDL_DEBUG, "msg %ld ref count incr %d, is now %d\n", + lprintf(CTDL_DEBUG, "msg %ld ref count delta %d, is now %d\n", msgnum, incr, smi.meta_refcount); /* If the reference count is now zero, delete the message @@ -3533,11 +4098,9 @@ void AdjRefCount(long msgnum, int incr) */ if (smi.meta_refcount == 0) { lprintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum); - - /* Remove from fulltext index */ - if (config.c_enable_fulltext) { - ft_index_message(msgnum, 0); - } + + /* Call delete hooks with NULL room to show it has gone altogether */ + PerformDeleteHooks(NULL, msgnum); /* Remove from message base */ delnum = msgnum; @@ -3548,6 +4111,7 @@ void AdjRefCount(long msgnum, int incr) delnum = (0L - msgnum); cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long)); } + } /* @@ -3661,7 +4225,7 @@ void CtdlWriteObject(char *req_room, /* Room to stuff it in */ */ if (is_unique) { lprintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n", - CtdlDeleteMessages(roomname, 0L, content_type, 0) + CtdlDeleteMessages(roomname, NULL, 0, content_type) ); } /* Now write the data */ @@ -3696,7 +4260,7 @@ char *CtdlGetSysConfig(char *sysconfname) { /* We want the last (and probably only) config in this room */ begin_critical_section(S_CONFIG); config_msgnum = (-1L); - CtdlForEachMessage(MSGS_LAST, 1, sysconfname, NULL, + CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL, CtdlGetSysConfigBackend, NULL); msgnum = config_msgnum; end_critical_section(S_CONFIG); @@ -3720,7 +4284,7 @@ char *CtdlGetSysConfig(char *sysconfname) { if (conf != NULL) do { extract_token(buf, conf, 0, '\n', sizeof buf); strcpy(conf, &conf[strlen(buf)+1]); - } while ( (strlen(conf)>0) && (strlen(buf)>0) ); + } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) ); return(conf); } @@ -3754,19 +4318,19 @@ int CtdlIsMe(char *addr, int addr_buf_len) if (recp == NULL) return(0); if (recp->num_local == 0) { - free(recp); + free_recipients(recp); return(0); } for (i=0; inum_local; ++i) { extract_token(addr, recp->recp_local, i, '|', addr_buf_len); if (!strcasecmp(addr, CC->user.fullname)) { - free(recp); + free_recipients(recp); return(1); } } - free(recp); + free_recipients(recp); return(0); }