X-Git-Url: https://code.citadel.org/?a=blobdiff_plain;f=citadel%2Fmsgbase.c;h=61488f1faf6a7fd178874e0f29a88cf302d0d2e4;hb=e0e892370401431821a22abbf60ac79ed59273d7;hp=ace98ecdf23df0bf905cb3733ff44f424407c350;hpb=aa06fdbb8dfa0aec19f8f159efa507796e3897bd;p=citadel.git diff --git a/citadel/msgbase.c b/citadel/msgbase.c index ace98ecdf..61488f1fa 100644 --- a/citadel/msgbase.c +++ b/citadel/msgbase.c @@ -8,9 +8,6 @@ #include #include #include -#ifdef HAVE_PTHREAD_H -#include -#endif #include #include "citadel.h" #include "server.h" @@ -20,6 +17,7 @@ #include "msgbase.h" #include "support.h" #include "sysdep_decls.h" +#include "citserver.h" #include "room_ops.h" #include "user_ops.h" #include "file_ops.h" @@ -27,193 +25,386 @@ #include "dynloader.h" #include "tools.h" #include "mime_parser.h" +#include "html.h" -#define MSGS_ALL 0 -#define MSGS_OLD 1 -#define MSGS_NEW 2 -#define MSGS_FIRST 3 -#define MSGS_LAST 4 -#define MSGS_GT 5 +#define desired_section ((char *)CtdlGetUserData(SYM_DESIRED_SECTION)) +#define ma ((struct ma_info *)CtdlGetUserData(SYM_MA_INFO)) +#define msg_repl ((struct repl *)CtdlGetUserData(SYM_REPL)) extern struct config config; +char *msgkeys[] = { + "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", + "", + "from", + "", "", "", + "exti", + "", "", + "hnod", + "msgn", + "", "", "", + "text", + "node", + "room", + "path", + "", + "rcpt", + "spec", + "time", + "subj", + "", + "", + "", + "", + "" +}; /* * This function is self explanatory. * (What can I say, I'm in a weird mood today...) */ -void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name) { +void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name) +{ int i; - for (i=0; i0) if (isspace(name[i-1])) { - strcpy(&name[i-1], &name[i]); - i = 0; - } - while (isspace(name[i+1])) { - strcpy(&name[i+1], &name[i+2]); + for (i = 0; i < strlen(name); ++i) + if (name[i] == '@') { + if (i > 0) + if (isspace(name[i - 1])) { + strcpy(&name[i - 1], &name[i]); + i = 0; + } + while (isspace(name[i + 1])) { + strcpy(&name[i + 1], &name[i + 2]); } } - } +} /* * Aliasing for network mail. * (Error messages have been commented out, because this is a server.) */ -int alias(char *name) /* process alias and routing info for mail */ - { +int alias(char *name) +{ /* process alias and routing info for mail */ FILE *fp; - int a,b; - char aaa[300],bbb[300]; - - lprintf(9, "alias() called for <%s>\n", name); + int a, b; + char aaa[300], bbb[300]; remove_any_whitespace_to_the_left_or_right_of_at_symbol(name); - fp=fopen("network/mail.aliases","r"); - if (fp==NULL) fp=fopen("/dev/null","r"); - if (fp==NULL) return(M_ERROR); - strcpy(aaa,""); strcpy(bbb,""); - while (fgets(aaa, sizeof aaa, fp)!=NULL) { - while (isspace(name[0])) strcpy(name, &name[1]); - aaa[strlen(aaa)-1] = 0; + fp = fopen("network/mail.aliases", "r"); + if (fp == NULL) + fp = fopen("/dev/null", "r"); + if (fp == NULL) + return (MES_ERROR); + strcpy(aaa, ""); + strcpy(bbb, ""); + while (fgets(aaa, sizeof aaa, fp) != NULL) { + while (isspace(name[0])) + strcpy(name, &name[1]); + aaa[strlen(aaa) - 1] = 0; strcpy(bbb, ""); - for (a=0; a1) { + for (a = 0; a < strlen(name); ++a) + if (name[a] == '!') + return (MES_INTERNET); + for (a = 0; a < strlen(name); ++a) + if (name[a] == '@') + for (b = a; b < strlen(name); ++b) + if (name[b] == '.') + return (MES_INTERNET); + b = 0; + for (a = 0; a < strlen(name); ++a) + if (name[a] == '@') + ++b; + if (b > 1) { lprintf(7, "Too many @'s in address\n"); - return(M_ERROR); - } - if (b==1) { - for (a=0; a=0)&&(strcasecmp(aaa,bbb))); - a=getstring(fp,aaa); - if (!strncmp(aaa,"use ",4)) { - strcpy(bbb,&aaa[4]); - fseek(fp,0L,0); + return (MES_ERROR); + } + if (b == 1) { + for (a = 0; a < strlen(name); ++a) + if (name[a] == '@') + strcpy(bbb, &name[a + 1]); + while (bbb[0] == 32) + strcpy(bbb, &bbb[1]); + fp = fopen("network/mail.sysinfo", "r"); + if (fp == NULL) + return (MES_ERROR); + GETSN:do { + a = getstring(fp, aaa); + } while ((a >= 0) && (strcasecmp(aaa, bbb))); + a = getstring(fp, aaa); + if (!strncmp(aaa, "use ", 4)) { + strcpy(bbb, &aaa[4]); + fseek(fp, 0L, 0); goto GETSN; - } + } fclose(fp); - if (!strncmp(aaa,"uum",3)) { - strcpy(bbb,name); - for (a=0; acm_fields[i] != NULL) { + if (msg->cm_fields[i] == NULL) { + return 1; + } + if (strcasecmp(msg->cm_fields[i], + template->cm_fields[i])) return 1; + } + } + + /* All compares succeeded: we have a match! */ + return 0; +} + + + + +/* + * API function to perform an operation for each qualifying message in the + * current room. + */ +void CtdlForEachMessage(int mode, long ref, + char *content_type, + struct CtdlMessage *compare, + void (*CallBack) (long msgnum)) +{ + + int a; + struct visit vbuf; + struct cdbdata *cdbfr; + long *msglist = NULL; + int num_msgs = 0; + long thismsg; + struct SuppMsgInfo smi; + struct CtdlMessage *msg; + + /* Learn about the user and room in question */ + get_mm(); + getuser(&CC->usersupp, CC->curr_user); + CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom); + + /* Load the message list */ + cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long)); + if (cdbfr != NULL) { + msglist = mallok(cdbfr->len); + memcpy(msglist, cdbfr->ptr, cdbfr->len); + num_msgs = cdbfr->len / sizeof(long); + cdb_free(cdbfr); + } else { + return; /* No messages at all? No further action. */ + } + + + /* If the caller is looking for a specific MIME type, then filter + * out all messages which are not of the type requested. + */ + if (num_msgs > 0) + if (content_type != NULL) + if (strlen(content_type) > 0) + for (a = 0; a < num_msgs; ++a) { + GetSuppMsgInfo(&smi, msglist[a]); + if (strcasecmp(smi.smi_content_type, content_type)) { + msglist[a] = 0L; + } + } + + num_msgs = sort_msglist(msglist, num_msgs); + + /* If a template was supplied, filter out the messages which + * don't match. (This could induce some delays!) + */ + if (num_msgs > 0) { + if (compare != NULL) { + for (a = 0; a < num_msgs; ++a) { + msg = CtdlFetchMessage(msglist[a]); + if (msg != NULL) { + if (CtdlMsgCmp(msg, compare)) { + msglist[a] = 0L; + } + CtdlFreeMessage(msg); + } + } + } } + + /* + * Now iterate through the message list, according to the + * criteria supplied by the caller. + */ + if (num_msgs > 0) + for (a = 0; a < num_msgs; ++a) { + thismsg = msglist[a]; + if ((thismsg > 0) + && ( + + (mode == MSGS_ALL) + || ((mode == MSGS_OLD) && (thismsg <= vbuf.v_lastseen)) + || ((mode == MSGS_NEW) && (thismsg > vbuf.v_lastseen)) + || ((mode == MSGS_NEW) && (thismsg >= vbuf.v_lastseen) + && (CC->usersupp.flags & US_LASTOLD)) + || ((mode == MSGS_LAST) && (a >= (num_msgs - ref))) + || ((mode == MSGS_FIRST) && (a < ref)) + || ((mode == MSGS_GT) && (thismsg > ref)) + ) + ) { + CallBack(thismsg); + } + } + phree(msglist); /* Clean up */ +} + + + /* * cmd_msgs() - get list of message #'s in this room + * implements the MSGS server command using CtdlForEachMessage() */ void cmd_msgs(char *cmdbuf) { - int a = 0; int mode = 0; char which[256]; - int cm_howmany = 0; - long cm_gt = 0L; - struct visit vbuf; + char buf[256]; + char tfield[256]; + char tvalue[256]; + int cm_ref = 0; + int i; + int with_template = 0; + struct CtdlMessage *template = NULL; - extract(which,cmdbuf,0); + extract(which, cmdbuf, 0); + cm_ref = extract_int(cmdbuf, 1); + with_template = extract_int(cmdbuf, 2); mode = MSGS_ALL; - strcat(which," "); - if (!strncasecmp(which,"OLD",3)) mode = MSGS_OLD; - if (!strncasecmp(which,"NEW",3)) mode = MSGS_NEW; - if (!strncasecmp(which,"FIRST",5)) { + strcat(which, " "); + if (!strncasecmp(which, "OLD", 3)) + mode = MSGS_OLD; + else if (!strncasecmp(which, "NEW", 3)) + mode = MSGS_NEW; + else if (!strncasecmp(which, "FIRST", 5)) mode = MSGS_FIRST; - cm_howmany = extract_int(cmdbuf,1); - } - if (!strncasecmp(which,"LAST",4)) { + else if (!strncasecmp(which, "LAST", 4)) mode = MSGS_LAST; - cm_howmany = extract_int(cmdbuf,1); - } - if (!strncasecmp(which,"GT",2)) { + else if (!strncasecmp(which, "GT", 2)) mode = MSGS_GT; - cm_gt = extract_long(cmdbuf,1); - } - if ((!(CC->logged_in))&&(!(CC->internal_pgm))) { - cprintf("%d not logged in\n",ERROR+NOT_LOGGED_IN); + if ((!(CC->logged_in)) && (!(CC->internal_pgm))) { + cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN); return; - } - get_mm(); - get_msglist(&CC->quickroom); - getuser(&CC->usersupp,CC->curr_user); - CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom); - - cprintf("%d Message list...\n",LISTING_FOLLOWS); - if (CC->num_msgs != 0) { - for (a=0; a<(CC->num_msgs); ++a) - if ((MessageFromList(a) >=0) - && ( - -(mode==MSGS_ALL) -|| ((mode==MSGS_OLD) && (MessageFromList(a) <= vbuf.v_lastseen)) -|| ((mode==MSGS_NEW) && (MessageFromList(a) > vbuf.v_lastseen)) -|| ((mode==MSGS_NEW) && (MessageFromList(a) >= vbuf.v_lastseen) - && (CC->usersupp.flags & US_LASTOLD)) -|| ((mode==MSGS_LAST)&& (a>=(CC->num_msgs-cm_howmany))) -|| ((mode==MSGS_FIRST)&&(a cm_gt)) + } - ) - ) { - cprintf("%ld\n", MessageFromList(a)); + if (with_template) { + cprintf("%d Send template then receive message list\n", + START_CHAT_MODE); + template = (struct CtdlMessage *) + mallok(sizeof(struct CtdlMessage)); + memset(template, 0, sizeof(struct CtdlMessage)); + while(client_gets(buf), strcmp(buf,"000")) { + extract(tfield, buf, 0); + extract(tvalue, buf, 1); + for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) { + if (!strcasecmp(tfield, msgkeys[i])) { + template->cm_fields[i] = + strdoop(tvalue); + } } - } - cprintf("000\n"); + } } + else { + cprintf("%d Message list...\n", LISTING_FOLLOWS); + } + + CtdlForEachMessage(mode, cm_ref, NULL, template, simple_listing); + if (template != NULL) CtdlFreeMessage(template); + cprintf("000\n"); +} + @@ -225,29 +416,29 @@ void help_subst(char *strbuf, char *source, char *dest) char workbuf[256]; int p; - while (p=pattern2(strbuf,source), (p>=0)) { - strcpy(workbuf,&strbuf[p+strlen(source)]); - strcpy(&strbuf[p],dest); - strcat(strbuf,workbuf); - } + while (p = pattern2(strbuf, source), (p >= 0)) { + strcpy(workbuf, &strbuf[p + strlen(source)]); + strcpy(&strbuf[p], dest); + strcat(strbuf, workbuf); } +} void do_help_subst(char *buffer) { char buf2[16]; - help_subst(buffer,"^nodename",config.c_nodename); - help_subst(buffer,"^humannode",config.c_humannode); - help_subst(buffer,"^fqdn",config.c_fqdn); - help_subst(buffer,"^username",CC->usersupp.fullname); - sprintf(buf2,"%ld",CC->usersupp.usernum); - help_subst(buffer,"^usernum",buf2); - help_subst(buffer,"^sysadm",config.c_sysadm); - help_subst(buffer,"^variantname",CITADEL); - sprintf(buf2,"%d",config.c_maxsessions); - help_subst(buffer,"^maxsessions",buf2); - } + help_subst(buffer, "^nodename", config.c_nodename); + help_subst(buffer, "^humannode", config.c_humannode); + help_subst(buffer, "^fqdn", config.c_fqdn); + help_subst(buffer, "^username", CC->usersupp.fullname); + sprintf(buf2, "%ld", CC->usersupp.usernum); + help_subst(buffer, "^usernum", buf2); + help_subst(buffer, "^sysadm", config.c_sysadm); + help_subst(buffer, "^variantname", CITADEL); + sprintf(buf2, "%d", config.c_maxsessions); + help_subst(buffer, "^maxsessions", buf2); +} @@ -259,72 +450,84 @@ void do_help_subst(char *buffer) * to the client. The client software may reformat it again. */ void memfmout(int width, char *mptr, char subst) - /* screen width to use */ - /* where are we going to get our text from? */ - /* nonzero if we should use hypertext mode */ - { - int a,b,c; + /* screen width to use */ + /* where are we going to get our text from? */ + /* nonzero if we should use hypertext mode */ +{ + int a, b, c; int real = 0; int old = 0; CIT_UBYTE ch; char aaa[140]; char buffer[256]; - - strcpy(aaa,""); old=255; - strcpy(buffer,""); - c=1; /* c is the current pos */ + + strcpy(aaa, ""); + old = 255; + strcpy(buffer, ""); + c = 1; /* c is the current pos */ FMTA: if (subst) { - while (ch=*mptr, ((ch!=0) && (strlen(buffer)<126) )) { - ch=*mptr++; - buffer[strlen(buffer)+1] = 0; + while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) { + ch = *mptr++; + buffer[strlen(buffer) + 1] = 0; buffer[strlen(buffer)] = ch; - } + } - if (buffer[0]=='^') do_help_subst(buffer); + if (buffer[0] == '^') + do_help_subst(buffer); - buffer[strlen(buffer)+1] = 0; - a=buffer[0]; - strcpy(buffer,&buffer[1]); - } - - else ch=*mptr++; + buffer[strlen(buffer) + 1] = 0; + a = buffer[0]; + strcpy(buffer, &buffer[1]); + } else { + ch = *mptr++; + } - old=real; - real=ch; - if (ch<=0) goto FMTEND; - - if ( ((ch==13)||(ch==10)) && (old!=13) && (old!=10) ) ch=32; - if ( ((old==13)||(old==10)) && (isspace(real)) ) { + old = real; + real = ch; + if (ch <= 0) + goto FMTEND; + + if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) + ch = 32; + if (((old == 13) || (old == 10)) && (isspace(real))) { cprintf("\n"); - c=1; - } - if (ch>126) goto FMTA; + c = 1; + } + if (ch > 126) + goto FMTA; - if (ch>32) { - if ( ((strlen(aaa)+c)>(width-5)) && (strlen(aaa)>(width-5)) ) - { cprintf("\n%s",aaa); c=strlen(aaa); aaa[0]=0; + if (ch > 32) { + if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) { + cprintf("\n%s", aaa); + c = strlen(aaa); + aaa[0] = 0; } - b=strlen(aaa); aaa[b]=ch; aaa[b+1]=0; } - if (ch==32) { - if ((strlen(aaa)+c)>(width-5)) { + b = strlen(aaa); + aaa[b] = ch; + aaa[b + 1] = 0; + } + if (ch == 32) { + if ((strlen(aaa) + c) > (width - 5)) { cprintf("\n"); - c=1; - } - cprintf("%s ",aaa); ++c; c=c+strlen(aaa); - strcpy(aaa,""); - goto FMTA; + c = 1; } - if ((ch==13)||(ch==10)) { - cprintf("%s\n",aaa); - c=1; - strcpy(aaa,""); + cprintf("%s ", aaa); + ++c; + c = c + strlen(aaa); + strcpy(aaa, ""); goto FMTA; - } + } + if ((ch == 13) || (ch == 10)) { + cprintf("%s\n", aaa); + c = 1; + strcpy(aaa, ""); + goto FMTA; + } goto FMTA; -FMTEND: cprintf("\n"); - } +FMTEND: cprintf("%s\n", aaa); +} @@ -332,61 +535,186 @@ FMTEND: cprintf("\n"); * Callback function for mime parser that simply lists the part */ void list_this_part(char *name, char *filename, char *partnum, char *disp, - void *content, char *cbtype, size_t length) { + void *content, char *cbtype, size_t length) +{ cprintf("part=%s|%s|%s|%s|%s|%d\n", name, filename, partnum, disp, cbtype, length); - } +} /* * Callback function for mime parser that wants to display text */ void fixed_output(char *name, char *filename, char *partnum, char *disp, - void *content, char *cbtype, size_t length) { + void *content, char *cbtype, size_t length) +{ + char *ptr; + + if (!strcasecmp(cbtype, "multipart/alternative")) { + strcpy(ma->prefix, partnum); + strcat(ma->prefix, "."); + ma->is_ma = 1; + ma->did_print = 0; + return; + } + + if ( (!strncasecmp(partnum, ma->prefix, strlen(ma->prefix))) + && (ma->is_ma == 1) + && (ma->did_print == 1) ) { + lprintf(9, "Skipping part %s (%s)\n", partnum, cbtype); + return; + } + + ma->did_print = 1; if (!strcasecmp(cbtype, "text/plain")) { client_write(content, length); - } - else { + } + else if (!strcasecmp(cbtype, "text/html")) { + ptr = html_to_ascii(content, 80, 0); + client_write(ptr, strlen(ptr)); + phree(ptr); + } + else if (strncasecmp(cbtype, "multipart/", 10)) { cprintf("Part %s: %s (%s) (%d bytes)\n", partnum, filename, cbtype, length); - } } +} /* * Callback function for mime parser that opens a section for downloading */ void mime_download(char *name, char *filename, char *partnum, char *disp, - void *content, char *cbtype, size_t length) { - - char tmpname[PATH_MAX]; - static int seq = 0; + void *content, char *cbtype, size_t length) +{ /* Silently go away if there's already a download open... */ - if (CC->download_fp != NULL) return; + if (CC->download_fp != NULL) + return; /* ...or if this is not the desired section */ - if (strcasecmp(CC->desired_section, partnum)) return; - - snprintf(tmpname, sizeof tmpname, - "/tmp/CitServer.download.%4x.%4x", getpid(), ++seq); - - CC->download_fp = fopen(tmpname, "wb+"); - if (CC->download_fp == NULL) return; + if (strcasecmp(desired_section, partnum)) + return; - /* Unlink the file while it's open, to guarantee that the - * temp file will always be deleted. - */ - unlink(tmpname); + CC->download_fp = tmpfile(); + if (CC->download_fp == NULL) + return; fwrite(content, length, 1, CC->download_fp); fflush(CC->download_fp); rewind(CC->download_fp); OpenCmdResult(filename, cbtype); +} + + + +/* + * Load a message from disk into memory. + * This is used by output_message() and other fetch functions. + * + * NOTE: Caller is responsible for freeing the returned CtdlMessage struct + * using the CtdlMessageFree() function. + */ +struct CtdlMessage *CtdlFetchMessage(long msgnum) +{ + struct cdbdata *dmsgtext; + struct CtdlMessage *ret = NULL; + char *mptr; + CIT_UBYTE ch; + CIT_UBYTE field_header; + size_t field_length; + + dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long)); + if (dmsgtext == NULL) { + return NULL; + } + mptr = dmsgtext->ptr; + + /* Parse the three bytes that begin EVERY message on disk. + * The first is always 0xFF, the on-disk magic number. + * The second is the anonymous/public type byte. + * The third is the format type byte (vari, fixed, or MIME). + */ + ch = *mptr++; + if (ch != 255) { + lprintf(5, "Message %ld appears to be corrupted.\n", msgnum); + cdb_free(dmsgtext); + return NULL; + } + ret = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage)); + memset(ret, 0, sizeof(struct CtdlMessage)); + + ret->cm_magic = CTDLMESSAGE_MAGIC; + ret->cm_anon_type = *mptr++; /* Anon type byte */ + ret->cm_format_type = *mptr++; /* Format type byte */ + + /* + * The rest is zero or more arbitrary fields. Load them in. + * We're done when we encounter either a zero-length field or + * have just processed the 'M' (message text) field. + */ + do { + field_length = strlen(mptr); + if (field_length == 0) + break; + field_header = *mptr++; + ret->cm_fields[field_header] = mallok(field_length); + strcpy(ret->cm_fields[field_header], mptr); + + while (*mptr++ != 0); /* advance to next field */ + + } while ((field_length > 0) && (field_header != 'M')); + + cdb_free(dmsgtext); + + /* Always make sure there's something in the msg text field */ + if (ret->cm_fields['M'] == NULL) + ret->cm_fields['M'] = strdoop("\n"); + + /* Perform "before read" hooks (aborting if any return nonzero) */ + if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) { + CtdlFreeMessage(ret); + return NULL; + } + + return (ret); +} + + +/* + * Returns 1 if the supplied pointer points to a valid Citadel message. + * If the pointer is NULL or the magic number check fails, returns 0. + */ +int is_valid_message(struct CtdlMessage *msg) { + if (msg == NULL) + return 0; + if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) { + lprintf(3, "is_valid_message() -- self-check failed\n"); + return 0; } + return 1; +} + + +/* + * 'Destructor' for struct CtdlMessage + */ +void CtdlFreeMessage(struct CtdlMessage *msg) +{ + int i; + + if (is_valid_message(msg) == 0) return; + + for (i = 0; i < 256; ++i) + if (msg->cm_fields[i] != NULL) + phree(msg->cm_fields[i]); + + msg->cm_magic = 0; /* just in case */ + phree(msg); +} @@ -394,16 +722,18 @@ void mime_download(char *name, char *filename, char *partnum, char *disp, * Get a message off disk. (return value is the message's timestamp) * */ -time_t output_message(char *msgid, int mode, int headers_only) { +void output_message(char *msgid, int mode, int headers_only) +{ long msg_num; - int a; - CIT_UBYTE ch, rch; - CIT_UBYTE format_type,anon_flag; + int a, i, k; char buf[1024]; - long msg_len; - int msg_ok = 0; + time_t xtime; + CIT_UBYTE ch; + char allkeys[256]; + char display_name[256]; + + struct CtdlMessage *TheMessage = NULL; - struct cdbdata *dmsgtext; char *mptr; /* buffers needed for RFC822 translation */ @@ -412,268 +742,227 @@ time_t output_message(char *msgid, int mode, int headers_only) { char snode[256]; char lnode[256]; char mid[256]; - time_t xtime = 0L; /* */ msg_num = atol(msgid); + safestrncpy(mid, msgid, sizeof mid); + if ((!(CC->logged_in)) && (!(CC->internal_pgm))) { + cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN); + return; + } - if ((!(CC->logged_in))&&(!(CC->internal_pgm))&&(mode!=MT_DATE)) { - cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN); - return(xtime); - } - - /* We used to need to check in the current room's message list - * to determine where the message's disk position. We no longer need - * to do this, but we do it anyway as a security measure, in order to - * prevent rogue clients from reading messages not in the current room. + /* FIX ... small security issue + * We need to check to make sure the requested message is actually + * in the current room, and set msg_ok to 1 only if it is. This + * functionality is currently missing because I'm in a hurry to replace + * broken production code with nonbroken pre-beta code. :( -- ajc + * + if (!msg_ok) { + cprintf("%d Message %ld is not in this room.\n", + ERROR, msg_num); + return; + } */ - msg_ok = 0; - if (CC->num_msgs > 0) { - for (a=0; anum_msgs; ++a) { - if (MessageFromList(a) == msg_num) { - msg_ok = 1; - } - } - } - - if (!msg_ok) { - if (mode != MT_DATE) - cprintf("%d Message %ld is not in this room.\n", - ERROR, msg_num); - return(xtime); - } - - - dmsgtext = cdb_fetch(CDB_MSGMAIN, &msg_num, sizeof(long)); - - if (dmsgtext == NULL) { - if (mode != MT_DATE) - cprintf("%d Can't find message %ld\n", - ERROR+INTERNAL_ERROR); - return(xtime); - } - - msg_len = (long) dmsgtext->len; - mptr = dmsgtext->ptr; - - /* this loop spews out the whole message if we're doing raw format */ - if (mode == MT_RAW) { - cprintf("%d %ld\n", BINARY_FOLLOWS, msg_len); - client_write(dmsgtext->ptr, (int) msg_len); - cdb_free(dmsgtext); - return(xtime); - } - - /* Otherwise, we'll start parsing it field by field... */ - ch = *mptr++; - if (ch != 255) { - cprintf("%d Illegal message format on disk\n", - ERROR+INTERNAL_ERROR); - cdb_free(dmsgtext); - return(xtime); - } - - anon_flag = *mptr++; - format_type = *mptr++; + /* + * Fetch the message from disk + */ + TheMessage = CtdlFetchMessage(msg_num); + if (TheMessage == NULL) { + cprintf("%d Can't locate msg %ld on disk\n", ERROR, msg_num); + return; + } /* Are we downloading a MIME component? */ if (mode == MT_DOWNLOAD) { - if (format_type != 4) { + if (TheMessage->cm_format_type != 4) { cprintf("%d This is not a MIME message.\n", ERROR); - } - else if (CC->download_fp != NULL) { + } else if (CC->download_fp != NULL) { cprintf("%d You already have a download open.\n", ERROR); - } - else { - /* Skip to the message body */ - while(ch = *mptr++, (ch!='M' && ch!=0)) { - buf[0] = 0; - do { - buf[strlen(buf)+1] = 0; - rch = *mptr++; - buf[strlen(buf)] = rch; - } while (rch > 0); - } - /* Now parse it */ + } else { + /* Parse the message text component */ + mptr = TheMessage->cm_fields['M']; mime_parser(mptr, NULL, *mime_download); /* If there's no file open by this time, the requested * section wasn't found, so print an error */ if (CC->download_fp == NULL) { cprintf("%d Section %s not found.\n", - ERROR+FILE_NOT_FOUND, - CC->desired_section); - } - } - cdb_free(dmsgtext); - return(xtime); - } - - /* Are we just looking for the message date? */ - if (mode == MT_DATE) while(ch = *mptr++, (ch!='M' && ch!=0)) { - buf[0] = 0; - do { - buf[strlen(buf)+1] = 0; - rch = *mptr++; - buf[strlen(buf)] = rch; - } while (rch > 0); - - if (ch=='T') { - xtime = atol(buf); - cdb_free(dmsgtext); - return(xtime); + ERROR + FILE_NOT_FOUND, + desired_section); } } - + CtdlFreeMessage(TheMessage); + return; + } /* now for the user-mode message reading loops */ - cprintf("%d Message %ld:\n",LISTING_FOLLOWS,msg_num); + cprintf("%d Message %ld:\n", LISTING_FOLLOWS, msg_num); - if (mode == MT_CITADEL) cprintf("type=%d\n", format_type); + /* Tell the client which format type we're using. If this is a + * MIME message, *lie* about it and tell the user it's fixed-format. + */ + if (mode == MT_CITADEL) { + if (TheMessage->cm_format_type == 4) + cprintf("type=1\n"); + else + cprintf("type=%d\n", TheMessage->cm_format_type); + } - if ( (anon_flag == MES_ANON) && (mode == MT_CITADEL) ) { + /* nhdr=yes means that we're only displaying headers, no body */ + if ((TheMessage->cm_anon_type == MES_ANON) && (mode == MT_CITADEL)) { cprintf("nhdr=yes\n"); - } + } /* begin header processing loop for Citadel message format */ - if ((mode == MT_CITADEL)||(mode == MT_MIME)) - while(ch = *mptr++, (ch!='M' && ch!=0)) { - buf[0] = 0; - do { - buf[strlen(buf)+1] = 0; - rch = *mptr++; - buf[strlen(buf)] = rch; - } while (rch > 0); + if ((mode == MT_CITADEL) || (mode == MT_MIME)) { - if (ch=='A') { + strcpy(display_name, ""); + if (TheMessage->cm_fields['A']) { + strcpy(buf, TheMessage->cm_fields['A']); PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG); - if (anon_flag==MES_ANON) cprintf("from=****"); - else if (anon_flag==MES_AN2) cprintf("from=anonymous"); - else cprintf("from=%s",buf); - if ((is_room_aide()) && ((anon_flag == MES_ANON) - || (anon_flag == MES_AN2))) - cprintf(" [%s]",buf); - cprintf("\n"); + if (TheMessage->cm_anon_type == MES_ANON) + strcpy(display_name, "****"); + else if (TheMessage->cm_anon_type == MES_AN2) + strcpy(display_name, "anonymous"); + else + strcpy(display_name, buf); + if ((is_room_aide()) + && ((TheMessage->cm_anon_type == MES_ANON) + || (TheMessage->cm_anon_type == MES_AN2))) { + sprintf(&display_name[strlen(display_name)], + " [%s]", buf); + } + } + + strcpy(allkeys, FORDER); + for (i=0; icm_fields[k] != NULL) { + if (k == 'A') { + cprintf("%s=%s\n", + msgkeys[k], + display_name); + } + else { + cprintf("%s=%s\n", + msgkeys[k], + TheMessage->cm_fields[k] + ); + } + } } - else if (ch=='P') cprintf("path=%s\n",buf); - else if (ch=='U') cprintf("subj=%s\n",buf); - else if (ch=='I') cprintf("msgn=%s\n",buf); - else if (ch=='H') cprintf("hnod=%s\n",buf); - else if (ch=='O') cprintf("room=%s\n",buf); - else if (ch=='N') cprintf("node=%s\n",buf); - else if (ch=='R') cprintf("rcpt=%s\n",buf); - else if (ch=='T') cprintf("time=%s\n",buf); - /* else cprintf("fld%c=%s\n",ch,buf); */ } + } + /* begin header processing loop for RFC822 transfer format */ strcpy(suser, ""); strcpy(luser, ""); strcpy(snode, NODENAME); strcpy(lnode, HUMANNODE); - if (mode == MT_RFC822) while(ch = *mptr++, (ch!='M' && ch!=0)) { - buf[0] = 0; - do { - buf[strlen(buf)+1] = 0; - rch = *mptr++; - buf[strlen(buf)] = rch; - } while (rch > 0); - - if (ch=='A') strcpy(luser, buf); - else if (ch=='P') { - cprintf("Path: %s\n",buf); - for (a=0; acm_fields[i]) { + mptr = TheMessage->cm_fields[i]; + + if (i == 'A') { + strcpy(luser, mptr); + } else if (i == 'P') { + cprintf("Path: %s\n", mptr); + for (a = 0; a < strlen(mptr); ++a) { + if (mptr[a] == '!') { + strcpy(mptr, &mptr[a + 1]); + a = 0; + } } + strcpy(suser, mptr); + } else if (i == 'U') + cprintf("Subject: %s\n", mptr); + else if (i == 'I') + strcpy(mid, mptr); + else if (i == 'H') + strcpy(lnode, mptr); + else if (i == 'O') + cprintf("X-Citadel-Room: %s\n", mptr); + else if (i == 'N') + strcpy(snode, mptr); + else if (i == 'R') + cprintf("To: %s\n", mptr); + else if (i == 'T') { + xtime = atol(mptr); + cprintf("Date: %s", asctime(localtime(&xtime))); } - strcpy(suser, buf); - } - else if (ch=='U') cprintf("Subject: %s\n",buf); - else if (ch=='I') strcpy(mid, buf); - else if (ch=='H') strcpy(lnode, buf); - else if (ch=='O') cprintf("X-Citadel-Room: %s\n",buf); - else if (ch=='N') strcpy(snode, buf); - else if (ch=='R') cprintf("To: %s\n",buf); - else if (ch=='T') { - xtime = atol(buf); - cprintf("Date: %s", asctime(localtime(&xtime))); } } + } if (mode == MT_RFC822) { if (!strcasecmp(snode, NODENAME)) { strcpy(snode, FQDN); - } + } cprintf("Message-ID: <%s@%s>\n", mid, snode); PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG); - cprintf("From: %s@%s (%s)\n", - suser, snode, luser); + cprintf("From: %s@%s (%s)\n", suser, snode, luser); cprintf("Organization: %s\n", lnode); - } + } /* end header processing loop ... at this point, we're in the text */ - if (ch==0) { - cprintf("text\n*** ?Message truncated\n000\n"); - cdb_free(dmsgtext); - return(xtime); - } + mptr = TheMessage->cm_fields['M']; - /* do some sort of MIME output */ - if (format_type == 4) { - if ((mode == MT_CITADEL)||(mode == MT_MIME)) { + /* Tell the client about the MIME parts in this message */ + if (TheMessage->cm_format_type == 4) { /* legacy textual dump */ + if (mode == MT_CITADEL) { + mime_parser(mptr, NULL, *list_this_part); + } + else if (mode == MT_MIME) { /* list parts only */ mime_parser(mptr, NULL, *list_this_part); - } - if (mode == MT_MIME) { /* If MT_MIME then it's parts only */ cprintf("000\n"); - cdb_free(dmsgtext); - return(xtime); - } + CtdlFreeMessage(TheMessage); + return; } + } if (headers_only) { - /* give 'em a length */ - msg_len = 0L; - while(ch = *mptr++, ch>0) { - ++msg_len; - } - cprintf("mlen=%ld\n", msg_len); cprintf("000\n"); - cdb_free(dmsgtext); - return(xtime); - } + CtdlFreeMessage(TheMessage); + return; + } /* signify start of msg text */ - if (mode == MT_CITADEL) cprintf("text\n"); - if ( (mode == MT_RFC822) && (format_type != 4) ) cprintf("\n"); + if (mode == MT_CITADEL) + cprintf("text\n"); + if ((mode == MT_RFC822) && (TheMessage->cm_format_type != 4)) + cprintf("\n"); /* If the format type on disk is 1 (fixed-format), then we want * everything to be output completely literally ... regardless of * what message transfer format is in use. */ - if (format_type == 1) { + if (TheMessage->cm_format_type == 1) { strcpy(buf, ""); - while(ch = *mptr++, ch>0) { - if (ch == 13) ch = 10; - if ( (ch == 10) || (strlen(buf)>250) ) { + while (ch = *mptr++, ch > 0) { + if (ch == 13) + ch = 10; + if ((ch == 10) || (strlen(buf) > 250)) { cprintf("%s\n", buf); strcpy(buf, ""); - } - else { - buf[strlen(buf)+1] = 0; + } else { + buf[strlen(buf) + 1] = 0; buf[strlen(buf)] = ch; - } } - if (strlen(buf)>0) cprintf("%s\n", buf); } + if (strlen(buf) > 0) + cprintf("%s\n", buf); + } + /* If the message on disk is format 0 (Citadel vari-format), we * output using the formatter at 80 columns. This is the final output * form if the transfer format is RFC822, but if the transfer format @@ -681,23 +970,27 @@ time_t output_message(char *msgid, int mode, int headers_only) { * for new paragraphs is correct and the client will reformat the * message to the reader's screen width. */ - if (format_type == 0) { - memfmout(80,mptr,0); - } + if (TheMessage->cm_format_type == 0) { + memfmout(80, mptr, 0); + } + /* If the message on disk is format 4 (MIME), we've gotta hand it * off to the MIME parser. The client has already been told that * this message is format 1 (fixed format), so the callback function * we use will display those parts as-is. */ - if (format_type == 4) { + if (TheMessage->cm_format_type == 4) { + CtdlAllocUserData(SYM_MA_INFO, sizeof(struct ma_info)); + memset(ma, 0, sizeof(struct ma_info)); mime_parser(mptr, NULL, *fixed_output); - } + } /* now we're done */ cprintf("000\n"); - cdb_free(dmsgtext); - return(xtime); - } + CtdlFreeMessage(TheMessage); + return; +} + /* @@ -708,12 +1001,12 @@ void cmd_msg0(char *cmdbuf) char msgid[256]; int headers_only = 0; - extract(msgid,cmdbuf,0); + extract(msgid, cmdbuf, 0); headers_only = extract_int(cmdbuf, 1); output_message(msgid, MT_CITADEL, headers_only); return; - } +} /* @@ -724,32 +1017,53 @@ void cmd_msg2(char *cmdbuf) char msgid[256]; int headers_only = 0; - extract(msgid,cmdbuf,0); - headers_only = extract_int(cmdbuf,1); + extract(msgid, cmdbuf, 0); + headers_only = extract_int(cmdbuf, 1); + + output_message(msgid, MT_RFC822, headers_only); +} + - output_message(msgid,MT_RFC822,headers_only); - } /* * display a message (mode 3 - IGnet raw format - internal programs only) */ void cmd_msg3(char *cmdbuf) { - char msgid[256]; - int headers_only = 0; + long msgnum; + struct CtdlMessage *msg; + struct ser_ret smr; if (CC->internal_pgm == 0) { cprintf("%d This command is for internal programs only.\n", ERROR); return; - } + } + + msgnum = extract_long(cmdbuf, 0); + msg = CtdlFetchMessage(msgnum); + if (msg == NULL) { + cprintf("%d Message %ld not found.\n", + ERROR, msgnum); + return; + } - extract(msgid,cmdbuf,0); - headers_only = extract_int(cmdbuf,1); + serialize_message(&smr, msg); + CtdlFreeMessage(msg); - output_message(msgid,MT_RAW,headers_only); + if (smr.len == 0) { + cprintf("%d Unable to serialize message\n", + ERROR+INTERNAL_ERROR); + return; } + cprintf("%d %ld\n", BINARY_FOLLOWS, smr.len); + client_write(smr.ser, smr.len); + phree(smr.ser); +} + + + /* * display a message (mode 4 - MIME) (FIX ... still evolving, not complete) */ @@ -760,9 +1074,7 @@ void cmd_msg4(char *cmdbuf) extract(msgid, cmdbuf, 0); output_message(msgid, MT_MIME, 0); - } - - +} /* * Open a component of a MIME message as a download file @@ -771,326 +1083,677 @@ void cmd_opna(char *cmdbuf) { char msgid[256]; + CtdlAllocUserData(SYM_DESIRED_SECTION, 64); + extract(msgid, cmdbuf, 0); - extract(CC->desired_section, cmdbuf, 1); + extract(desired_section, cmdbuf, 1); output_message(msgid, MT_DOWNLOAD, 0); +} + + +/* + * Save a message pointer into a specified room + * (Returns 0 for success, nonzero for failure) + * roomname may be NULL to use the current room + */ +int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int flags) { + int i; + char hold_rm[ROOMNAMELEN]; + struct cdbdata *cdbfr; + int num_msgs; + long *msglist; + long highest_msg = 0L; + struct CtdlMessage *msg = NULL; + + lprintf(9, "CtdlSaveMsgPointerInRoom(%s, %ld, %d)\n", + roomname, msgid, flags); + + strcpy(hold_rm, CC->quickroom.QRname); + + /* We may need to check to see if this message is real */ + if ( (flags & SM_VERIFY_GOODNESS) + || (flags & SM_DO_REPL_CHECK) + ) { + msg = CtdlFetchMessage(msgid); + if (msg == NULL) return(ERROR + ILLEGAL_VALUE); + } + + /* Perform replication checks if necessary */ + if ( (flags & SM_DO_REPL_CHECK) && (msg != NULL) ) { + + if (getroom(&CC->quickroom, + ((roomname != NULL) ? roomname : CC->quickroom.QRname) ) + != 0) { + lprintf(9, "No such room <%s>\n", roomname); + if (msg != NULL) CtdlFreeMessage(msg); + return(ERROR + ROOM_NOT_FOUND); + } + + if (ReplicationChecks(msg) != 0) { + getroom(&CC->quickroom, hold_rm); + if (msg != NULL) CtdlFreeMessage(msg); + lprintf(9, "Did replication, and newer exists\n"); + return(0); + } + } + + /* Now the regular stuff */ + if (lgetroom(&CC->quickroom, + ((roomname != NULL) ? roomname : CC->quickroom.QRname) ) + != 0) { + lprintf(9, "No such room <%s>\n", roomname); + if (msg != NULL) CtdlFreeMessage(msg); + return(ERROR + ROOM_NOT_FOUND); + } + + cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long)); + if (cdbfr == NULL) { + msglist = NULL; + num_msgs = 0; + } else { + msglist = mallok(cdbfr->len); + if (msglist == NULL) + lprintf(3, "ERROR malloc msglist!\n"); + num_msgs = cdbfr->len / sizeof(long); + memcpy(msglist, cdbfr->ptr, cdbfr->len); + 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. + */ + if (num_msgs > 0) for (i=0; iquickroom); /* unlock the room */ + getroom(&CC->quickroom, hold_rm); + if (msg != NULL) CtdlFreeMessage(msg); + return(ERROR + ALREADY_EXISTS); + } } + /* Now add the new message */ + ++num_msgs; + msglist = reallok(msglist, + (num_msgs * sizeof(long))); + + if (msglist == NULL) { + lprintf(3, "ERROR: can't realloc message list!\n"); + } + msglist[num_msgs - 1] = msgid; + + /* Sort the message list, so all the msgid's are in order */ + num_msgs = sort_msglist(msglist, num_msgs); + + /* Determine the highest message number */ + highest_msg = msglist[num_msgs - 1]; + + /* Write it back to disk. */ + cdb_store(CDB_MSGLISTS, &CC->quickroom.QRnumber, sizeof(long), + msglist, num_msgs * sizeof(long)); + + /* Free up the memory we used. */ + phree(msglist); + + /* Update the highest-message pointer and unlock the room. */ + CC->quickroom.QRhighest = highest_msg; + lputroom(&CC->quickroom); + getroom(&CC->quickroom, hold_rm); + + /* Bump the reference count for this message. */ + AdjRefCount(msgid, +1); + + /* Return success. */ + if (msg != NULL) CtdlFreeMessage(msg); + return (0); +} + /* * Message base operation to send a message to the master file * (returns new message number) + * + * This is the back end for CtdlSaveMsg() and should not be directly + * called by server-side modules. + * */ -long send_message(char *message_in_memory, /* pointer to buffer */ - size_t message_length, /* length of buffer */ - int generate_id) { /* 1 to generate an I field */ - +long send_message(struct CtdlMessage *msg, /* pointer to buffer */ + int generate_id, /* generate 'I' field? */ + FILE *save_a_copy) /* save a copy to disk? */ +{ long newmsgid; - char *actual_message; - size_t actual_length; long retval; char msgidbuf[32]; + struct ser_ret smr; /* Get a new message number */ newmsgid = get_new_message_number(); + sprintf(msgidbuf, "%ld", newmsgid); if (generate_id) { - sprintf(msgidbuf, "I%ld", newmsgid); - actual_length = message_length + strlen(msgidbuf) + 1; - actual_message = mallok(actual_length); - memcpy(actual_message, message_in_memory, 3); - memcpy(&actual_message[3], msgidbuf, (strlen(msgidbuf)+1) ); - memcpy(&actual_message[strlen(msgidbuf)+4], - &message_in_memory[3], message_length - 3); - } + msg->cm_fields['I'] = strdoop(msgidbuf); + } - else { - actual_message = message_in_memory; - actual_length = message_length; - } + serialize_message(&smr, msg); + + if (smr.len == 0) { + cprintf("%d Unable to serialize message\n", + ERROR+INTERNAL_ERROR); + return (-1L); + } /* Write our little bundle of joy into the message base */ begin_critical_section(S_MSGMAIN); - if ( cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long), - actual_message, actual_length) < 0 ) { + if (cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long), + smr.ser, smr.len) < 0) { lprintf(2, "Can't store message\n"); retval = 0L; - } - else { + } else { retval = newmsgid; - } + } end_critical_section(S_MSGMAIN); - if (generate_id) { - phree(actual_message); - } + /* If the caller specified that a copy should be saved to a particular + * file handle, do that now too. + */ + if (save_a_copy != NULL) { + fwrite(smr.ser, smr.len, 1, save_a_copy); + } - /* Finally, return the pointers */ + /* Free the memory we used for the serialized message */ + phree(smr.ser); + + /* Return the *local* message ID to the caller + * (even if we're storing an incoming network message) + */ return(retval); - } +} /* - * this is a simple file copy routine. + * Serialize a struct CtdlMessage into the format used on disk and network. + * + * This function loads up a "struct ser_ret" (defined in server.h) which + * contains the length of the serialized message and a pointer to the + * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER. */ -void copy_file(char *from, char *to) +void serialize_message(struct ser_ret *ret, /* return values */ + struct CtdlMessage *msg) /* unserialized msg */ { - FILE *ffp,*tfp; - int a; + size_t wlen; + int i; + static char *forder = FORDER; + + if (is_valid_message(msg) == 0) return; /* self check */ + + 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; - ffp=fopen(from,"r"); - if (ffp==NULL) return; - tfp=fopen(to,"w"); - if (tfp==NULL) { - fclose(ffp); + lprintf(9, "calling malloc\n"); + ret->ser = mallok(ret->len); + if (ret->ser == NULL) { + ret->len = 0; return; - } - while (a=getc(ffp), a>=0) { - putc(a,tfp); - } - fclose(ffp); - fclose(tfp); + } + + ret->ser[0] = 0xFF; + ret->ser[1] = msg->cm_anon_type; + ret->ser[2] = msg->cm_format_type; + wlen = 3; + + for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) { + ret->ser[wlen++] = (char)forder[i]; + strcpy(&ret->ser[wlen], msg->cm_fields[(int)forder[i]]); + wlen = wlen + strlen(msg->cm_fields[(int)forder[i]]) + 1; + } + if (ret->len != wlen) lprintf(3, "ERROR: len=%d wlen=%d\n", + ret->len, wlen); + return; +} + + + +/* + * Back end for the ReplicationChecks() function + */ +void check_repl(long msgnum) { + struct CtdlMessage *msg; + time_t timestamp = (-1L); + + lprintf(9, "check_repl() found message %ld\n", msgnum); + msg = CtdlFetchMessage(msgnum); + if (msg == NULL) return; + if (msg->cm_fields['T'] != NULL) { + timestamp = atol(msg->cm_fields['T']); } + CtdlFreeMessage(msg); + if (timestamp > msg_repl->highest) { + msg_repl->highest = timestamp; /* newer! */ + lprintf(9, "newer!\n"); + return; + } + lprintf(9, "older!\n"); + + /* Existing isn't newer? Then delete the old one(s). */ + CtdlDeleteMessages(CC->quickroom.QRname, msgnum, NULL); +} /* - * message base operation to save a message and install its pointers + * Check to see if any messages already exist which carry the same Extended ID + * as this one. + * + * If any are found: + * -> With older timestamps: delete them and return 0. Message will be saved. + * -> With newer timestamps: return 1. Message save will be aborted. */ -void save_message(char *mtmp, /* file containing proper message */ - char *rec, /* Recipient (if mail) */ - char *force, /* if non-zero length, force a room */ - int mailtype, /* local or remote type, see citadel.h */ - int generate_id) /* set to 1 to generate an 'I' field */ +int ReplicationChecks(struct CtdlMessage *msg) { + struct CtdlMessage *template; + int abort_this = 0; + + lprintf(9, "ReplicationChecks() started\n"); + /* No extended id? Don't do anything. */ + if (msg->cm_fields['E'] == NULL) return 0; + if (strlen(msg->cm_fields['E']) == 0) return 0; + lprintf(9, "Extended ID: <%s>\n", msg->cm_fields['E']); + + CtdlAllocUserData(SYM_REPL, sizeof(struct repl)); + strcpy(msg_repl->extended_id, msg->cm_fields['E']); + msg_repl->highest = atol(msg->cm_fields['T']); + + template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage)); + memset(template, 0, sizeof(struct CtdlMessage)); + template->cm_fields['E'] = strdoop(msg->cm_fields['E']); + + CtdlForEachMessage(MSGS_ALL, 0L, NULL, template, check_repl); + + /* If a newer message exists with the same Extended ID, abort + * this save. + */ + if (msg_repl->highest > atol(msg->cm_fields['T']) ) { + abort_this = 1; + } + + CtdlFreeMessage(template); + lprintf(9, "ReplicationChecks() returning %d\n", abort_this); + return(abort_this); +} + + + + +/* + * Save a message to disk + */ +void CtdlSaveMsg(struct CtdlMessage *msg, /* message to save */ + char *rec, /* Recipient (mail) */ + char *force, /* force a particular room? */ + int mailtype, /* local or remote type */ + int generate_id) /* 1 = generate 'I' field */ { char aaa[100]; char hold_rm[ROOMNAMELEN]; char actual_rm[ROOMNAMELEN]; char force_room[ROOMNAMELEN]; + char content_type[256]; /* We have to learn this */ char recipient[256]; long newmsgid; - char *message_in_memory; - struct stat statbuf; - size_t templen; - FILE *fp; + char *mptr; struct usersupp userbuf; int a; - static int seqnum = 0; + struct SuppMsgInfo smi; + FILE *network_fp = NULL; + static int seqnum = 1; + + lprintf(9, "CtdlSaveMsg() called\n"); + if (is_valid_message(msg) == 0) return; /* self check */ - lprintf(9, "save_message(%s,%s,%s,%d,%d)\n", - mtmp, rec, force, mailtype, generate_id); + /* If this message has no timestamp, we take the liberty of + * giving it one, right now. + */ + if (msg->cm_fields['T'] == NULL) { + sprintf(aaa, "%ld", time(NULL)); + msg->cm_fields['T'] = strdoop(aaa); + } + + lprintf(9, "checkpoint 1 \n"); + /* If this message has no path, we generate one. + */ + if (msg->cm_fields['A'] == NULL) { + msg->cm_fields['A'] = strdoop("unknown user"); + } + if (msg->cm_fields['P'] == NULL) { + msg->cm_fields['P'] = strdoop(msg->cm_fields['A']); + for (a=0; acm_fields['P']); ++a) { + if (isspace(msg->cm_fields['P'][a])) { + msg->cm_fields['P'][a] = ' '; + } + } + } strcpy(force_room, force); + lprintf(9, "checkpoint 2 \n"); /* Strip non-printable characters out of the recipient name */ strcpy(recipient, rec); - for (a=0; acm_format_type) { + case 0: + strcpy(content_type, "text/x-citadel-variformat"); + break; + case 1: + strcpy(content_type, "text/plain"); + break; + case 4: + strcpy(content_type, "text/plain"); + /* advance past header fields */ + mptr = msg->cm_fields['M']; + a = strlen(mptr); + while (--a) { + if (!strncasecmp(mptr, "Content-type: ", 14)) { + safestrncpy(content_type, mptr, + sizeof(content_type)); + strcpy(content_type, &content_type[14]); + for (a = 0; a < strlen(content_type); ++a) + if ((content_type[a] == ';') + || (content_type[a] == ' ') + || (content_type[a] == 13) + || (content_type[a] == 10)) + content_type[a] = 0; + break; + } + ++mptr; } + } - fp = fopen(mtmp, "rb"); - fread(message_in_memory, templen, 1, fp); - fclose(fp); - - newmsgid = send_message(message_in_memory, templen, generate_id); - phree(message_in_memory); - if (newmsgid <= 0L) return; - + lprintf(9, "checkpoint 4 \n"); + /* Goto the correct room */ + strcpy(hold_rm, CC->quickroom.QRname); strcpy(actual_rm, CC->quickroom.QRname); - strcpy(hold_rm, ""); - - /* If the user is a twit, move to the twit room for posting... */ - if (TWITDETECT) if (CC->usersupp.axlevel==2) { - strcpy(hold_rm, actual_rm); - strcpy(actual_rm, config.c_twitroom); - } - /* ...or if this is a private message, go to the target mailbox. */ - lprintf(9, "mailbox aliasing loop\n"); - if (strlen(recipient) > 0) { - /* mailtype = alias(recipient); */ - if (mailtype == M_LOCAL) { - if (getuser(&userbuf, recipient)!=0) { - /* User not found, goto Aide */ - strcpy(force_room, AIDEROOM); - } - else { - strcpy(hold_rm, actual_rm); - MailboxName(actual_rm, &userbuf, MAILROOM); - } - } + lprintf(9, "checkpoint 5 \n"); + /* If the user is a twit, move to the twit room for posting */ + if ( (CC->logged_in) && (TWITDETECT) ) { + if (CC->usersupp.axlevel == 2) { + strcpy(hold_rm, actual_rm); + strcpy(actual_rm, config.c_twitroom); } + } + lprintf(9, "checkpoint 6 \n"); /* ...or if this message is destined for Aide> then go there. */ - lprintf(9, "actual room forcing loop\n"); if (strlen(force_room) > 0) { - strcpy(hold_rm, actual_rm); strcpy(actual_rm, force_room); - } - - /* This call to usergoto() changes rooms if necessary. It also - * causes the latest message list to be read into memory. - */ - usergoto(actual_rm, 0); - - /* read in the quickroom record, obtaining a lock... */ - lgetroom(&CC->quickroom, actual_rm); + } - /* Fix an obscure bug */ - if (!strcasecmp(CC->quickroom.QRname, AIDEROOM)) { - CC->quickroom.QRflags = CC->quickroom.QRflags & ~QR_MAILBOX; - } + lprintf(9, "checkpoint 7 \n"); + if (strcasecmp(actual_rm, CC->quickroom.QRname)) + getroom(&CC->quickroom, actual_rm); - /* Add the message pointer to the room */ - CC->quickroom.QRhighest = AddMessageToRoom(&CC->quickroom, newmsgid); + lprintf(9, "checkpoint 8 \n"); + /* Perform "before save" hooks (aborting if any return nonzero) */ + if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return; - /* update quickroom */ - lputroom(&CC->quickroom, actual_rm); + lprintf(9, "checkpoint 9 \n"); + /* If this message has an Extended ID, perform replication checks */ + if (ReplicationChecks(msg) > 0) return; + lprintf(9, "checkpoint 10 \n"); /* Network mail - send a copy to the network program. */ - if ( (strlen(recipient)>0) && (mailtype != M_LOCAL) ) { - sprintf(aaa,"./network/spoolin/netmail.%04lx.%04x.%04x", - (long)getpid(), CC->cs_pid, ++seqnum); - copy_file(mtmp,aaa); + if ((strlen(recipient) > 0) && (mailtype != MES_LOCAL)) { + sprintf(aaa, "./network/spoolin/netmail.%04lx.%04x.%04x", + (long) getpid(), CC->cs_pid, ++seqnum); + lprintf(9, "Saving a copy to %s\n", aaa); + network_fp = fopen(aaa, "ab+"); + if (network_fp == NULL) + lprintf(2, "ERROR: %s\n", strerror(errno)); + } + + lprintf(9, "checkpoint 11 \n"); + /* Save it to disk */ + newmsgid = send_message(msg, generate_id, network_fp); + if (network_fp != NULL) { + fclose(network_fp); system("exec nohup ./netproc -i >/dev/null 2>&1 &"); - } + } + + if (newmsgid <= 0L) return; + + lprintf(9, "checkpoint 12 \n"); + /* Write a supplemental message info record. This doesn't have to + * be a critical section because nobody else knows about this message + * yet. + */ + memset(&smi, 0, sizeof(struct SuppMsgInfo)); + smi.smi_msgnum = newmsgid; + smi.smi_refcount = 0; + safestrncpy(smi.smi_content_type, content_type, 64); + PutSuppMsgInfo(&smi); + + /* Now figure out where to store the pointers */ + + lprintf(9, "checkpoint 13 \n"); + /* If this is being done by the networker delivering a private + * message, we want to BYPASS saving the sender's copy (because there + * is no local sender; it would otherwise go to the Trashcan). + */ + if ((!CC->internal_pgm) || (strlen(recipient) == 0)) { + CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0); + } + + lprintf(9, "checkpoint 14 \n"); /* Bump this user's messages posted counter. */ - lgetuser(&CC->usersupp, CC->curr_user); - CC->usersupp.posted = CC->usersupp.posted + 1; - lputuser(&CC->usersupp, CC->curr_user); + if (CC->logged_in) { + lgetuser(&CC->usersupp, CC->curr_user); + CC->usersupp.posted = CC->usersupp.posted + 1; + lputuser(&CC->usersupp); + } - /* If we've posted in a room other than the current room, then we - * have to now go back to the current room... + lprintf(9, "checkpoint 15 \n"); + /* If this is private, local mail, make a copy in the + * recipient's mailbox and bump the reference count. */ - if (strlen(hold_rm) > 0) { - usergoto(hold_rm, 0); + if ((strlen(recipient) > 0) && (mailtype == MES_LOCAL)) { + if (getuser(&userbuf, recipient) == 0) { + MailboxName(actual_rm, &userbuf, MAILROOM); + CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0); } - unlink(mtmp); /* delete the temporary file */ } + lprintf(9, "checkpoint 16 \n"); + /* Perform "after save" hooks */ + PerformMessageHooks(msg, EVT_AFTERSAVE); + + lprintf(9, "checkpoint 17 \n"); + /* */ + if (strcasecmp(hold_rm, CC->quickroom.QRname)) + getroom(&CC->quickroom, hold_rm); +} + + /* - * Generate an administrative message and post it in the Aide> room. + * Convenience function for generating small administrative messages. */ -void aide_message(char *text) +void quickie_message(char *from, char *to, char *room, char *text) { - FILE *fp; + struct CtdlMessage *msg; - fp=fopen(CC->temp,"wb"); - fprintf(fp,"%c%c%c",255,MES_NORMAL,0); - fprintf(fp,"Psysop%c",0); - fprintf(fp,"T%ld%c", time(NULL), 0); - fprintf(fp,"ACitadel%c",0); - fprintf(fp,"OAide%c",0); - fprintf(fp,"N%s%c",NODENAME,0); - fprintf(fp,"M%s\n%c",text,0); - fclose(fp); - save_message(CC->temp,"",AIDEROOM,M_LOCAL,1); - syslog(LOG_NOTICE,text); + msg = mallok(sizeof(struct CtdlMessage)); + memset(msg, 0, sizeof(struct CtdlMessage)); + msg->cm_magic = CTDLMESSAGE_MAGIC; + msg->cm_anon_type = MES_NORMAL; + msg->cm_format_type = 0; + msg->cm_fields['A'] = strdoop(from); + msg->cm_fields['O'] = strdoop(room); + msg->cm_fields['N'] = strdoop(NODENAME); + if (to != NULL) + msg->cm_fields['R'] = strdoop(to); + msg->cm_fields['M'] = strdoop(text); + + CtdlSaveMsg(msg, "", room, MES_LOCAL, 1); + CtdlFreeMessage(msg); + syslog(LOG_NOTICE, text); +} + + + +/* + * Back end function used by make_message() and similar functions + */ +char *CtdlReadMessageBody(char *terminator, size_t maxlen) { + char buf[256]; + size_t message_len = 0; + size_t buffer_len = 0; + char *ptr, *append; + char *m; + + m = mallok(4096); + if (m == NULL) { + while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) ;; + return(NULL); + } else { + buffer_len = 4096; + m[0] = 0; + message_len = 0; } + /* read in the lines of message text one by one */ + append = NULL; + while ( (client_gets(buf)>0) && strcmp(buf, terminator) ) { + + /* augment the buffer if we have to */ + if ((message_len + strlen(buf) + 2) > buffer_len) { + lprintf(9, "realloking\n"); + ptr = reallok(m, (buffer_len * 2) ); + if (ptr == NULL) { /* flush if can't allocate */ + while ( (client_gets(buf)>0) && + strcmp(buf, terminator)) ;; + return(m); + } else { + buffer_len = (buffer_len * 2); + m = ptr; + append = NULL; + lprintf(9, "buffer_len is %d\n", buffer_len); + } + } + + if (append == NULL) append = m; + while (strlen(append) > 0) ++append; + strcpy(append, buf); + strcat(append, "\n"); + message_len = message_len + strlen(buf) + 1; + + /* if we've hit the max msg length, flush the rest */ + if (message_len >= maxlen) { + while ( (client_gets(buf)>0) && strcmp(buf, terminator)) ;; + return(m); + } + } + return(m); +} + /* * Build a binary message to be saved on disk. */ -void make_message( - char *filename, /* temporary file name */ + +struct CtdlMessage *make_message( struct usersupp *author, /* author's usersupp structure */ char *recipient, /* NULL if it's not mail */ char *room, /* room where it's going */ int type, /* see MES_ types in header file */ int net_type, /* see MES_ types in header file */ int format_type, /* local or remote (see citadel.h) */ - char *fake_name) { /* who we're masquerading as */ + char *fake_name) /* who we're masquerading as */ +{ - FILE *fp; int a; - time_t now; char dest_node[32]; char buf[256]; + struct CtdlMessage *msg; + + msg = mallok(sizeof(struct CtdlMessage)); + memset(msg, 0, sizeof(struct CtdlMessage)); + msg->cm_magic = CTDLMESSAGE_MAGIC; + msg->cm_anon_type = type; + msg->cm_format_type = format_type; /* Don't confuse the poor folks if it's not routed mail. */ strcpy(dest_node, ""); - - /* If net_type is M_BINARY, split out the destination node. */ - if (net_type == M_BINARY) { - strcpy(dest_node,NODENAME); - for (a=0; ausernum); /* Path */ + msg->cm_fields['P'] = strdoop(buf); + + sprintf(buf, "%ld", time(NULL)); /* timestamp */ + msg->cm_fields['T'] = strdoop(buf); - while (isspace(recipient[strlen(recipient)-1])) - recipient[strlen(recipient)-1] = 0; - - time(&now); - fp=fopen(filename,"w"); - putc(255,fp); - putc(type,fp); /* Normal or anonymous, see MES_ flags */ - putc(format_type,fp); /* Formatted or unformatted */ - fprintf(fp,"Pcit%ld%c",author->usernum,0); /* path */ - fprintf(fp,"T%ld%c",now,0); /* date/time */ - if (fake_name[0]) - fprintf(fp,"A%s%c",fake_name,0); + if (fake_name[0]) /* author */ + msg->cm_fields['A'] = strdoop(fake_name); else - fprintf(fp,"A%s%c",author->fullname,0); /* author */ + msg->cm_fields['A'] = strdoop(author->fullname); - if (CC->quickroom.QRflags & QR_MAILBOX) { /* room */ - fprintf(fp,"O%s%c", &CC->quickroom.QRname[11], 0); - } - else { - fprintf(fp,"O%s%c",CC->quickroom.QRname,0); - } + if (CC->quickroom.QRflags & QR_MAILBOX) /* room */ + msg->cm_fields['O'] = strdoop(&CC->quickroom.QRname[11]); + else + msg->cm_fields['O'] = strdoop(CC->quickroom.QRname); - fprintf(fp,"N%s%c",NODENAME,0); /* nodename */ - fprintf(fp,"H%s%c",HUMANNODE,0); /* human nodename */ + msg->cm_fields['N'] = strdoop(NODENAME); /* nodename */ + msg->cm_fields['H'] = strdoop(HUMANNODE); /* hnodename */ - if (recipient[0]!=0) fprintf(fp, "R%s%c", recipient, 0); - if (dest_node[0]!=0) fprintf(fp, "D%s%c", dest_node, 0); + if (recipient[0] != 0) + msg->cm_fields['R'] = strdoop(recipient); + if (dest_node[0] != 0) + msg->cm_fields['D'] = strdoop(dest_node); - putc('M',fp); - while (client_gets(buf), strcmp(buf,"000")) - { - fprintf(fp,"%s\n",buf); - } - syslog(LOG_INFO, "Closing message"); - putc(0,fp); - fclose(fp); - } + msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen); + + + return(msg); +} /* - * message entry - mode 0 (normal) + * message entry - mode 0 (normal) */ void cmd_ent0(char *entargs) { @@ -1098,136 +1761,143 @@ void cmd_ent0(char *entargs) char recipient[256]; int anon_flag = 0; int format_type = 0; - char newusername[256]; /* */ - - int a,b; + char newusername[256]; + struct CtdlMessage *msg; + int a, b; int e = 0; int mtsflag = 0; struct usersupp tempUS; char buf[256]; - post = extract_int(entargs,0); - extract(recipient,entargs,1); - anon_flag = extract_int(entargs,2); - format_type = extract_int(entargs,3); + post = extract_int(entargs, 0); + extract(recipient, entargs, 1); + anon_flag = extract_int(entargs, 2); + format_type = extract_int(entargs, 3); /* first check to make sure the request is valid. */ if (!(CC->logged_in)) { - cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN); + cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN); return; - } - if ((CC->usersupp.axlevel<2)&&((CC->quickroom.QRflags&QR_MAILBOX)==0)) { + } + if ((CC->usersupp.axlevel < 2) && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) { cprintf("%d Need to be validated to enter ", - ERROR+HIGHER_ACCESS_REQUIRED); + ERROR + HIGHER_ACCESS_REQUIRED); cprintf("(except in %s> to sysop)\n", MAILROOM); return; - } - if ((CC->usersupp.axlevel<4)&&(CC->quickroom.QRflags&QR_NETWORK)) { + } + if ((CC->usersupp.axlevel < 4) && (CC->quickroom.QRflags & QR_NETWORK)) { cprintf("%d Need net privileges to enter here.\n", - ERROR+HIGHER_ACCESS_REQUIRED); + ERROR + HIGHER_ACCESS_REQUIRED); return; - } - if ((CC->usersupp.axlevel<6)&&(CC->quickroom.QRflags&QR_READONLY)) { + } + if ((CC->usersupp.axlevel < 6) && (CC->quickroom.QRflags & QR_READONLY)) { cprintf("%d Sorry, this is a read-only room.\n", - ERROR+HIGHER_ACCESS_REQUIRED); + ERROR + HIGHER_ACCESS_REQUIRED); return; - } + } + mtsflag = 0; - mtsflag=0; - - - if (post==2) { /* */ - if (CC->usersupp.axlevel<6) - { - cprintf("%d You don't have permission to do an aide post.\n", - ERROR+HIGHER_ACCESS_REQUIRED); - return; - } - extract(newusername,entargs,4); - memset(CC->fake_postname, 0, 32); - strcpy(CC->fake_postname, newusername); - cprintf("%d Ok\n",OK); - return; - } - + + if (post == 2) { + if (CC->usersupp.axlevel < 6) { + cprintf("%d You don't have permission to masquerade.\n", + ERROR + HIGHER_ACCESS_REQUIRED); + return; + } + extract(newusername, entargs, 4); + memset(CC->fake_postname, 0, 32); + strcpy(CC->fake_postname, newusername); + cprintf("%d Ok\n", OK); + return; + } CC->cs_flags |= CS_POSTING; - - buf[0]=0; + + buf[0] = 0; if (CC->quickroom.QRflags & QR_MAILBOX) { - if (CC->usersupp.axlevel>=2) { - strcpy(buf,recipient); - } - else strcpy(buf,"sysop"); - lprintf(9, "calling alias()\n"); - e=alias(buf); /* alias and mail type */ - lprintf(9, "alias() returned %d\n", e); - if ((buf[0]==0) || (e==M_ERROR)) { + if (CC->usersupp.axlevel >= 2) { + strcpy(buf, recipient); + } else + strcpy(buf, "sysop"); + e = alias(buf); /* alias and mail type */ + if ((buf[0] == 0) || (e == MES_ERROR)) { cprintf("%d Unknown address - cannot send message.\n", - ERROR+NO_SUCH_USER); + ERROR + NO_SUCH_USER); return; - } - if ((e!=M_LOCAL)&&(CC->usersupp.axlevel<4)) { + } + if ((e != MES_LOCAL) && (CC->usersupp.axlevel < 4)) { cprintf("%d Net privileges required for network mail.\n", - ERROR+HIGHER_ACCESS_REQUIRED); + ERROR + HIGHER_ACCESS_REQUIRED); return; - } - if ((RESTRICT_INTERNET==1)&&(e==M_INTERNET) - &&((CC->usersupp.flags&US_INTERNET)==0) - &&(!CC->internal_pgm) ) { + } + if ((RESTRICT_INTERNET == 1) && (e == MES_INTERNET) + && ((CC->usersupp.flags & US_INTERNET) == 0) + && (!CC->internal_pgm)) { cprintf("%d You don't have access to Internet mail.\n", - ERROR+HIGHER_ACCESS_REQUIRED); + ERROR + HIGHER_ACCESS_REQUIRED); return; - } - if (!strcasecmp(buf,"sysop")) { - mtsflag=1; + } + if (!strcasecmp(buf, "sysop")) { + mtsflag = 1; goto SKFALL; - } - if (e!=M_LOCAL) goto SKFALL; /* don't search local file */ - if (!strcasecmp(buf,CC->usersupp.fullname)) { + } + if (e != MES_LOCAL) + goto SKFALL; /* don't search local file */ + if (!strcasecmp(buf, CC->usersupp.fullname)) { cprintf("%d Can't send mail to yourself!\n", - ERROR+NO_SUCH_USER); + ERROR + NO_SUCH_USER); return; - } - + } /* Check to make sure the user exists; also get the correct - * upper/lower casing of the name. - */ + * upper/lower casing of the name. + */ a = getuser(&tempUS, buf); if (a != 0) { - cprintf("%d No such user.\n",ERROR+NO_SUCH_USER); + cprintf("%d No such user.\n", ERROR + NO_SUCH_USER); return; - } - strcpy(buf,tempUS.fullname); - } - -SKFALL: b=MES_NORMAL; - if (CC->quickroom.QRflags&QR_ANONONLY) b=MES_ANON; - if (CC->quickroom.QRflags&QR_ANONOPT) { - if (anon_flag==1) b=MES_AN2; } - if ((CC->quickroom.QRflags & QR_MAILBOX) == 0) buf[0]=0; + strcpy(buf, tempUS.fullname); + } + +SKFALL: b = MES_NORMAL; + if (CC->quickroom.QRflags & QR_ANONONLY) + b = MES_ANON; + if (CC->quickroom.QRflags & QR_ANONOPT) { + if (anon_flag == 1) + b = MES_AN2; + } + if ((CC->quickroom.QRflags & QR_MAILBOX) == 0) + buf[0] = 0; /* If we're only checking the validity of the request, return * success without creating the message. */ - if (post==0) { - cprintf("%d %s\n",OK,buf); + if (post == 0) { + cprintf("%d %s\n", OK, buf); return; - } - - cprintf("%d send message\n",SEND_LISTING); + } + + cprintf("%d send message\n", SEND_LISTING); + + /* Read in the message from the client. */ if (CC->fake_postname[0]) - make_message(CC->temp,&CC->usersupp,buf,CC->quickroom.QRname,b,e,format_type, CC->fake_postname); - else - if (CC->fake_username[0]) - make_message(CC->temp,&CC->usersupp,buf,CC->quickroom.QRname,b,e,format_type, CC->fake_username); - else - make_message(CC->temp,&CC->usersupp,buf,CC->quickroom.QRname,b,e,format_type, ""); - save_message(CC->temp,buf, (mtsflag ? AIDEROOM : ""), e,1); - CC->fake_postname[0]='\0'; + msg = make_message(&CC->usersupp, buf, + CC->quickroom.QRname, b, e, format_type, + CC->fake_postname); + else if (CC->fake_username[0]) + msg = make_message(&CC->usersupp, buf, + CC->quickroom.QRname, b, e, format_type, + CC->fake_username); + else + msg = make_message(&CC->usersupp, buf, + CC->quickroom.QRname, b, e, format_type, ""); + + if (msg != NULL) + CtdlSaveMsg(msg, buf, (mtsflag ? AIDEROOM : ""), e, 1); + CtdlFreeMessage(msg); + CC->fake_postname[0] = '\0'; return; - } +} @@ -1237,73 +1907,178 @@ SKFALL: b=MES_NORMAL; void cmd_ent3(char *entargs) { char recp[256]; - char buf[256]; int a; int e = 0; + int valid_msg = 1; + unsigned char ch, which_field; struct usersupp tempUS; long msglen; - long bloklen; - FILE *fp; + struct CtdlMessage *msg; + char *tempbuf; if (CC->internal_pgm == 0) { cprintf("%d This command is for internal programs only.\n", ERROR); return; - } + } /* See if there's a recipient, but make sure it's a real one */ extract(recp, entargs, 1); - for (a=0; a 0) { - e=alias(recp); /* alias and mail type */ - if ((recp[0]==0) || (e==M_ERROR)) { + e = alias(recp); /* alias and mail type */ + if ((recp[0] == 0) || (e == MES_ERROR)) { cprintf("%d Unknown address - cannot send message.\n", - ERROR+NO_SUCH_USER); + ERROR + NO_SUCH_USER); return; - } - if (e == M_LOCAL) { - a = getuser(&tempUS,recp); - if (a!=0) { + } + if (e == MES_LOCAL) { + a = getuser(&tempUS, recp); + if (a != 0) { cprintf("%d No such user.\n", - ERROR+NO_SUCH_USER); + ERROR + NO_SUCH_USER); return; - } } } + } /* At this point, message has been approved. */ if (extract_int(entargs, 0) == 0) { cprintf("%d OK to send\n", OK); return; - } + } + + msglen = extract_long(entargs, 2); + msg = mallok(sizeof(struct CtdlMessage)); + if (msg == NULL) { + cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR); + return; + } - /* open a temp file to hold the message */ - fp = fopen(CC->temp, "wb"); - if (fp == NULL) { - cprintf("%d Cannot open %s: %s\n", - ERROR + INTERNAL_ERROR, - CC->temp, strerror(errno) ); + memset(msg, 0, sizeof(struct CtdlMessage)); + tempbuf = mallok(msglen); + if (tempbuf == NULL) { + cprintf("%d Out of memory\n", ERROR+INTERNAL_ERROR); + phree(msg); return; - } + } - msglen = extract_long(entargs, 2); cprintf("%d %ld\n", SEND_BINARY, msglen); - while(msglen > 0L) { - bloklen = ((msglen >= 255L) ? 255 : msglen); - client_read(buf, (int)bloklen ); - fwrite(buf, (int)bloklen, 1, fp); - msglen = msglen - bloklen; + + client_read(&ch, 1); /* 0xFF magic number */ + msg->cm_magic = CTDLMESSAGE_MAGIC; + client_read(&ch, 1); /* anon type */ + msg->cm_anon_type = ch; + client_read(&ch, 1); /* format type */ + msg->cm_format_type = ch; + msglen = msglen - 3; + + while (msglen > 0) { + client_read(&which_field, 1); + if (!isalpha(which_field)) valid_msg = 0; + --msglen; + tempbuf[0] = 0; + do { + client_read(&ch, 1); + --msglen; + a = strlen(tempbuf); + tempbuf[a+1] = 0; + tempbuf[a] = ch; + } while ( (ch != 0) && (msglen > 0) ); + if (valid_msg) + msg->cm_fields[which_field] = strdoop(tempbuf); + } + + msg->cm_flags = CM_SKIP_HOOKS; + if (valid_msg) CtdlSaveMsg(msg, recp, "", e, 0); + CtdlFreeMessage(msg); + phree(tempbuf); +} + + +/* + * API function to delete messages which match a set of criteria + * (returns the actual number of messages deleted) + */ +int CtdlDeleteMessages(char *room_name, /* which room */ + long dmsgnum, /* or "0" for any */ + char *content_type /* or NULL for any */ +) +{ + + struct quickroom qrbuf; + struct cdbdata *cdbfr; + long *msglist = NULL; + int num_msgs = 0; + int i; + int num_deleted = 0; + int delete_this; + struct SuppMsgInfo smi; + + lprintf(9, "CtdlDeleteMessages(%s, %ld, %s)\n", + room_name, dmsgnum, content_type); + + /* get room record, obtaining a lock... */ + if (lgetroom(&qrbuf, room_name) != 0) { + lprintf(7, "CtdlDeleteMessages(): Room <%s> not found\n", + room_name); + return (0); /* room not found */ + } + cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long)); + + if (cdbfr != NULL) { + msglist = mallok(cdbfr->len); + memcpy(msglist, cdbfr->ptr, cdbfr->len); + num_msgs = cdbfr->len / sizeof(long); + cdb_free(cdbfr); + } + if (num_msgs > 0) { + for (i = 0; i < num_msgs; ++i) { + delete_this = 0x00; + + /* Set/clear a bit for each criterion */ + + if ((dmsgnum == 0L) || (msglist[i] == dmsgnum)) { + delete_this |= 0x01; + } + if (content_type == NULL) { + delete_this |= 0x02; + } else { + GetSuppMsgInfo(&smi, msglist[i]); + if (!strcasecmp(smi.smi_content_type, + content_type)) { + delete_this |= 0x02; + } + } + + /* Delete message only if all bits are set */ + if (delete_this == 0x03) { + AdjRefCount(msglist[i], -1); + msglist[i] = 0L; + ++num_deleted; + } } - fclose(fp); - save_message(CC->temp, recp, "", e, 0); + num_msgs = sort_msglist(msglist, num_msgs); + cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long), + msglist, (num_msgs * sizeof(long))); + + qrbuf.QRhighest = msglist[num_msgs - 1]; + phree(msglist); } + lputroom(&qrbuf); + lprintf(9, "%d message(s) deleted.\n", num_deleted); + return (num_deleted); +} + /* @@ -1312,97 +2087,255 @@ void cmd_ent3(char *entargs) void cmd_dele(char *delstr) { long delnum; - int a,ok; + int num_deleted; - getuser(&CC->usersupp,CC->curr_user); + getuser(&CC->usersupp, CC->curr_user); if ((CC->usersupp.axlevel < 6) - && (CC->usersupp.usernum != CC->quickroom.QRroomaide) - && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) { + && (CC->usersupp.usernum != CC->quickroom.QRroomaide) + && ((CC->quickroom.QRflags & QR_MAILBOX) == 0) + && (!(CC->internal_pgm))) { cprintf("%d Higher access required.\n", - ERROR+HIGHER_ACCESS_REQUIRED); + ERROR + HIGHER_ACCESS_REQUIRED); return; - } - + } delnum = extract_long(delstr, 0); - - /* get room records, obtaining a lock... */ - lgetroom(&CC->quickroom,CC->quickroom.QRname); - get_msglist(&CC->quickroom); - - ok = 0; - if (CC->num_msgs > 0) for (a=0; a<(CC->num_msgs); ++a) { - if (MessageFromList(a) == delnum) { - SetMessageInList(a, 0L); - ok = 1; - } - } - CC->num_msgs = sort_msglist(CC->msglist, CC->num_msgs); - CC->quickroom.QRhighest = MessageFromList(CC->num_msgs - 1); + num_deleted = CtdlDeleteMessages(CC->quickroom.QRname, delnum, NULL); - put_msglist(&CC->quickroom); - lputroom(&CC->quickroom,CC->quickroom.QRname); - if (ok==1) { - cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long)); - cprintf("%d Message deleted.\n",OK); - } - else cprintf("%d No message %ld.\n",ERROR,delnum); - } + if (num_deleted) { + cprintf("%d %d message%s deleted.\n", OK, + num_deleted, ((num_deleted != 1) ? "s" : "")); + } else { + cprintf("%d Message %ld not found.\n", ERROR, delnum); + } +} /* - * move a message to another room + * move or copy a message to another room */ void cmd_move(char *args) { long num; - char targ[32]; - int a; + char targ[256]; struct quickroom qtemp; - int foundit; + int err; + int is_copy = 0; - num = extract_long(args,0); - extract(targ,args,1); - - getuser(&CC->usersupp,CC->curr_user); + num = extract_long(args, 0); + extract(targ, args, 1); + targ[ROOMNAMELEN - 1] = 0; + is_copy = extract_int(args, 2); + + getuser(&CC->usersupp, CC->curr_user); if ((CC->usersupp.axlevel < 6) - && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) { + && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) { cprintf("%d Higher access required.\n", - ERROR+HIGHER_ACCESS_REQUIRED); + ERROR + HIGHER_ACCESS_REQUIRED); return; - } + } if (getroom(&qtemp, targ) != 0) { - cprintf("%d '%s' does not exist.\n",ERROR,targ); + cprintf("%d '%s' does not exist.\n", ERROR, targ); return; - } + } + + err = CtdlSaveMsgPointerInRoom(targ, num, + (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) ); + if (err != 0) { + cprintf("%d Cannot store message in %s: error %d\n", + err, targ, err); + return; + } - /* yank the message out of the current room... */ - lgetroom(&CC->quickroom, CC->quickroom.QRname); - get_msglist(&CC->quickroom); + /* Now delete the message from the source room, + * if this is a 'move' rather than a 'copy' operation. + */ + if (is_copy == 0) CtdlDeleteMessages(CC->quickroom.QRname, num, NULL); - foundit = 0; - for (a=0; a<(CC->num_msgs); ++a) { - if (MessageFromList(a) == num) { - foundit = 1; - SetMessageInList(a, 0L); - } - } - if (foundit) { - CC->num_msgs = sort_msglist(CC->msglist, CC->num_msgs); - put_msglist(&CC->quickroom); - CC->quickroom.QRhighest = MessageFromList((CC->num_msgs)-1); - } - lputroom(&CC->quickroom,CC->quickroom.QRname); - if (!foundit) { - cprintf("%d msg %ld does not exist.\n",ERROR,num); + cprintf("%d Message %s.\n", OK, (is_copy ? "copied" : "moved") ); +} + + + +/* + * GetSuppMsgInfo() - Get the supplementary record for a message + */ +void GetSuppMsgInfo(struct SuppMsgInfo *smibuf, long msgnum) +{ + + struct cdbdata *cdbsmi; + long TheIndex; + + memset(smibuf, 0, sizeof(struct SuppMsgInfo)); + smibuf->smi_msgnum = msgnum; + smibuf->smi_refcount = 1; /* Default reference count is 1 */ + + /* Use the negative of the message number for its supp record index */ + TheIndex = (0L - msgnum); + + cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long)); + if (cdbsmi == NULL) { + return; /* record not found; go with defaults */ + } + memcpy(smibuf, cdbsmi->ptr, + ((cdbsmi->len > sizeof(struct SuppMsgInfo)) ? + sizeof(struct SuppMsgInfo) : cdbsmi->len)); + cdb_free(cdbsmi); + return; +} + + +/* + * PutSuppMsgInfo() - (re)write supplementary record for a message + */ +void PutSuppMsgInfo(struct SuppMsgInfo *smibuf) +{ + long TheIndex; + + /* Use the negative of the message number for its supp record index */ + TheIndex = (0L - smibuf->smi_msgnum); + + lprintf(9, "PuttSuppMsgInfo(%ld) - ref count is %d\n", + smibuf->smi_msgnum, smibuf->smi_refcount); + + cdb_store(CDB_MSGMAIN, + &TheIndex, sizeof(long), + smibuf, sizeof(struct SuppMsgInfo)); + +} + +/* + * AdjRefCount - change the reference count for a message; + * delete the message if it reaches zero + */ +void AdjRefCount(long msgnum, int incr) +{ + + struct SuppMsgInfo smi; + long delnum; + + /* This is a *tight* critical section; please keep it that way, as + * it may get called while nested in other critical sections. + * Complicating this any further will surely cause deadlock! + */ + begin_critical_section(S_SUPPMSGMAIN); + GetSuppMsgInfo(&smi, msgnum); + smi.smi_refcount += incr; + PutSuppMsgInfo(&smi); + end_critical_section(S_SUPPMSGMAIN); + + lprintf(9, "Ref count for message <%ld> after write is <%d>\n", + msgnum, smi.smi_refcount); + + /* If the reference count is now zero, delete the message + * (and its supplementary record as well). + */ + if (smi.smi_refcount == 0) { + lprintf(9, "Deleting message <%ld>\n", msgnum); + delnum = msgnum; + cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long)); + delnum = (0L - msgnum); + cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long)); + } +} + +/* + * Write a generic object to this room + * + * Note: this could be much more efficient. Right now we use two temporary + * files, and still pull the message into memory as with all others. + */ +void CtdlWriteObject(char *req_room, /* Room to stuff it in */ + char *content_type, /* MIME type of this object */ + char *tempfilename, /* Where to fetch it from */ + struct usersupp *is_mailbox, /* Mailbox room? */ + int is_binary, /* Is encoding necessary? */ + int is_unique, /* Del others of this type? */ + unsigned int flags /* Internal save flags */ + ) +{ + + FILE *fp, *tempfp; + char filename[PATH_MAX]; + char cmdbuf[256]; + int ch; + struct quickroom qrbuf; + char roomname[ROOMNAMELEN]; + struct CtdlMessage *msg; + size_t len; + + if (is_mailbox != NULL) + MailboxName(roomname, is_mailbox, req_room); + else + safestrncpy(roomname, req_room, sizeof(roomname)); + lprintf(9, "CtdlWriteObject() to <%s> (flags=%d)\n", roomname, flags); + + strcpy(filename, tmpnam(NULL)); + fp = fopen(filename, "w"); + if (fp == NULL) return; - } - /* put the message into the target room */ - lgetroom(&qtemp, targ); - qtemp.QRhighest = AddMessageToRoom(&qtemp, num); - lputroom(&qtemp, targ); + tempfp = fopen(tempfilename, "r"); + if (tempfp == NULL) { + fclose(fp); + unlink(filename); + return; + } + + fprintf(fp, "Content-type: %s\n", content_type); + lprintf(9, "Content-type: %s\n", content_type); + + if (is_binary == 0) { + fprintf(fp, "Content-transfer-encoding: 7bit\n\n"); + while (ch = getc(tempfp), ch > 0) + putc(ch, fp); + fclose(tempfp); + putc(0, fp); + fclose(fp); + } else { + fprintf(fp, "Content-transfer-encoding: base64\n\n"); + fclose(tempfp); + fclose(fp); + sprintf(cmdbuf, "./base64 -e <%s >>%s", + tempfilename, filename); + system(cmdbuf); + } - cprintf("%d Message moved.\n", OK); + lprintf(9, "Allocating\n"); + msg = mallok(sizeof(struct CtdlMessage)); + memset(msg, 0, sizeof(struct CtdlMessage)); + msg->cm_magic = CTDLMESSAGE_MAGIC; + msg->cm_anon_type = MES_NORMAL; + msg->cm_format_type = 4; + msg->cm_fields['A'] = strdoop(CC->usersupp.fullname); + msg->cm_fields['O'] = strdoop(req_room); + msg->cm_fields['N'] = strdoop(config.c_nodename); + msg->cm_fields['H'] = strdoop(config.c_humannode); + msg->cm_flags = flags; + + lprintf(9, "Loading\n"); + fp = fopen(filename, "rb"); + fseek(fp, 0L, SEEK_END); + len = ftell(fp); + rewind(fp); + msg->cm_fields['M'] = mallok(len); + fread(msg->cm_fields['M'], len, 1, fp); + fclose(fp); + unlink(filename); + + /* Create the requested room if we have to. */ + if (getroom(&qrbuf, roomname) != 0) { + create_room(roomname, 4, "", 0); + } + /* If the caller specified this object as unique, delete all + * other objects of this type that are currently in the room. + */ + if (is_unique) { + lprintf(9, "Deleted %d other msgs of this type\n", + CtdlDeleteMessages(roomname, 0L, content_type)); } + /* Now write the data */ + CtdlSaveMsg(msg, "", roomname, MES_LOCAL, 1); + CtdlFreeMessage(msg); +}