#include #include #include #include #include #include #include #include #include #include #include extern const char *__progname; #include "accum.h" #include "folder.h" #include "format.h" #include "system.h" #include "msgarg.h" #include "context.h" #include "profile.h" #include "message.h" static const char *progtag = 0; static char *replarg = 0; static int naddr = 0; static char **addrs = 0; static char *draftarg = 0; static FOLDER *msgfolder; static int msgnumber; static FOLDER *draftfolder; static int draftmsgno; static MESSAGE *mdraft; static void add_addr(char *a) { addrs = realloc(addrs,(naddr+1)*sizeof(*addrs)); addrs[naddr++] = a; } static void handleargs(int ac, char **av) { int skip; int errs; errs = 0; skip = 0; for (ac--,av++;ac;ac--,av++) { if (skip > 0) { skip --; continue; } if (ac > 1) { if (!strcmp(*av,"-prog")) { progtag = av[1]; skip ++; continue; } else if (!strcmp(*av,"-repl")) { replarg = av[1]; skip ++; continue; } else if (!strcmp(*av,"-to")) { add_addr(av[1]); skip ++; continue; } else if (!strcmp(*av,"-draft")) { draftarg = av[1]; skip ++; continue; } } if (**av == '-') { fprintf(stderr,"%s: unrecognized flag %s\n",__progname,*av); errs ++; continue; } add_addr(*av); } if (draftarg && (naddr > 0)) { fprintf(stderr,"%s: can't give addresses with -draft\n",__progname); errs ++; } if (draftarg && replarg) { fprintf(stderr,"%s: can't give both -draft and -repl\n",__progname); errs ++; } if (replarg && (naddr > 0)) { fprintf(stderr,"%s: can't give addresses with -repl\n",__progname); errs ++; } if (!draftarg && !replarg && (naddr < 1)) { fprintf(stderr,"%s: must give -draft or -repl, or at least one address\n",__progname); errs ++; } if (errs) exit(1); } static void getmsgnumber(int *msgs, int nmsg, void *ivp) { if (nmsg > 1) { fprintf(stderr,"%s: can't specify multiple messages\n",__progname); exit(1); } if (nmsg < 1) { fprintf(stderr,"%s: no messages matched\n",__progname); exit(1); } *(int *)ivp = *msgs; } static void report_repl_err(const char *msg, void *mvp) { MESSAGE *m; m = mvp; fprintf(stderr,"%s: -repl program error for +%s:%d: %s\n",__progname,folder_name(message_folder(m)),message_number(m),msg); } static void new_draft(void) { const char *fname; char *ep; fname = profile_lookup(system_get_profile(),"drafts"); if (fname) { if (!folder_lookup(fname,&draftfolder,&ep) || *ep) { fprintf(stderr,"%s: warning: invalid {drafts}\n",__progname); fname = 0; } } if (! fname) { if (!folder_lookup("drafts",&draftfolder,0)) { fprintf(stderr,"%s: can't find a valid drafts folder\n",__progname); exit(1); } } folder_lock(draftfolder,FOLDER_WAIT|FOLDER_EXCLUSIVE); mdraft = folder_new_create(draftfolder); if (! mdraft) { fprintf(stderr,"%s: can't create draft in +%s: %s\n",__progname,folder_name(draftfolder),strerror(errno)); exit(1); } draftmsgno = message_number(mdraft); } static void close_draft(void) { message_free(mdraft); folder_unlock(draftfolder); } static int old_draft(int errexit) { folder_lock(draftfolder,FOLDER_WAIT|FOLDER_EXCLUSIVE); mdraft = message_make(draftfolder,draftmsgno); if (message_open(mdraft,O_RDWR|O_EXLOCK,0) < 0) { if (errexit) { fprintf(stderr,"%s: draft +%s:%d disappeared (%s)\n",__progname,folder_name(draftfolder),draftmsgno,strerror(errno)); exit(1); } return(-1); } return(0); } static void create_draft_repl(char *arg) { ARG a; char *cfn; MESSAGE *mfrom; system_lock(SYS_WAIT|SYS_SHARED|SYS_FAILEXIT); if (! msgarg_parse(arg,&a,0)) { fprintf(stderr,"%s: invalid message spec to -repl\n",__progname); exit(1); } if (a.type == AT_NOMSG) { fprintf(stderr,"%s: no message to reply to\n",__progname); exit(1); } msgfolder = 0; if (a.folder) { folder_lookup(a.folder,&msgfolder,0); } else { cfn = context_lookup("folder"); if (cfn) { folder_lookup(cfn,&msgfolder,0); free(cfn); } } if (! msgfolder) { fprintf(stderr,"%s: no current folder - must specify a folder\n",__progname); exit(1); } folder_lock(msgfolder,FOLDER_WAIT|FOLDER_SHARED); msgarg_handle(&a,msgfolder,getmsgnumber,&msgnumber); mfrom = message_make(msgfolder,msgnumber); if (message_open(mfrom,O_RDONLY|O_SHLOCK,0) < 0) { fprintf(stderr,"%s: can't open/lock +%s:%d: %s\n",__progname,folder_name(message_folder(mfrom)),message_number(mfrom),strerror(errno)); exit(1); } new_draft(); format_run(format_program(system_get_profile(),progtag?:"repl","\ :wsstrip L( s+ Lw 1 $| so \" \" $?f ?? $+ Lb ?. sx s- L) ;\ :outhdr @outstring @outstring \"\\n\" @outstring ;\ 0\ L( \"From\" @hdrget t?s Lw sx s- sx 1 + L) s-\ L( \"Cc\" @hdrget t?s Lw sx s- sx 1 + L) s-\ \", \" $!+ \"To: \" \\outhdr\ \"USER\" eg t?s ?? \"From: \" \\outhdr ?| s- ?.\ \"Subject\" @hdrget t?s ?? sx s- 3 $| so \"re:\" $?l ?? $+ ?| sx s- \\wsstrip \"Re: \" sx $+ ?. \"Subject: \" \\outhdr ?| s- ?.\ \"\\n\" @outstring\ { s- { \"\" @hdrget t?i ?? s- s- { @bodyline } \"\" ?| \": \" sx $+ $+ ?. } @unixfrom t?i ?? s- s+ Cc ?. }\ L( s+ Cc t?s Lw \"> \" @outstring \"\\n\" $*+ \"\\n> \" $!+ @outstring \"\\n\" @outstring L)\ s- s-\ ",EXT_UNIXFROM,EXT_HDRRESET,EXT_HDRGET,EXT_HDRMATCH,EXT_BODYLINE, EXT_OUTSTRING,EXT_FILTER,EXT_DUMPBODY,EXT_END),mfrom,0,report_repl_err,mfrom, FOPT_OUTFD,fileno(message_fp(mdraft)), FOPT_END); message_unlock(mfrom); message_close(mfrom); message_free(mfrom); folder_unlock(msgfolder); close_draft(); system_unlock(); } static void report_send_err(const char *msg, void *cookie __attribute__((__unused__))) { fprintf(stderr,"%s: send program error: %s\n",__progname,msg); } static void create_draft_send(char **av, int nav) { FMT_STACK *sv; int i; sv = malloc((nav+1)*sizeof(*sv)); for (i=0;i= 0) { line[w++] = '\0'; if (avx < maxnav) { av[avx++] = line + beg; } else { fprintf(stderr,"%s: too many arguments, dropping `%s'\n",__progname,line+beg); } beg = -1; } } beg = -1; avx = 0; q = 0; w = 0; for (o=0;line[o];o++) { if (q || !isspace(line[o])) { if (! (q & Q_B)) { if ( ((q & Q_S) && (line[o] == '\'')) || ((q & Q_D) && (line[o] == '"')) ) { q = 0; continue; } if (line[o] == '\\') { q |= Q_B; continue; } if (! q) { switch (line[o]) { case '\'': q |= Q_S; continue; break; case '"': q |= Q_D; continue; break; } } } if (w != o) line[w] = line[o]; if (beg < 0) beg = w; w ++; q &= ~Q_B; } else { endarg(); } } endarg(); return(avx); } static int copy_fd_to_fd(int ffd, int tfd) { int r; int w; char *bp; int warned; int err; char buf[8192]; err = 0; warned = 0; while (1) { r = read(ffd,&buf[0],sizeof(buf)); if (r < 0) { printf("%s: warning: read error: %s\n",__progname,strerror(errno)); err ++; break; } if (r == 0) break; bp = &buf[0]; while (r > 0) { w = write(tfd,bp,r); if (w != r) { if (w < 0) { printf("%s: warning: write error: %s\n",__progname,strerror(errno)); err ++; break; } if (! warned) { printf("%s: warning: short write (wanted %d, did %d) - retrying\n",__progname,r,w); err ++; warned = 1; } if (w == 0) { printf("%s: warning: zero write\n",__progname); err ++; break; } } bp += w; r -= w; } } return(err); } static int copy_draft_to_fd(int fd, int alreadyopen) { int err; if (! alreadyopen) old_draft(1); err = copy_fd_to_fd(fileno(message_fp(mdraft)),fd); if (! alreadyopen) close_draft(); return(err); } static int copy_fd_to_draft(int fd, int alreadyopen) { int err; if (! alreadyopen) old_draft(1); err = copy_fd_to_fd(fd,fileno(message_fp(mdraft))); if (! alreadyopen) close_draft(); return(err); } static void copy_draft_to_fd_removing_fcc(int fd) { int c; FILE *f; FILE *df; static int writefn(void *cookie __attribute__((__unused__)), const char *buf, int len) { return(write(fd,buf,len)); } static int send_header(MESSAGE *msg __attribute__((__unused__)), const char *hdrname, const char *hdrval, void *cookie __attribute__((__unused__))) { if (strcasecmp(hdrname,"fcc")) fprintf(f,"%s: %s\n",hdrname,hdrval); return(0); } f = fwopen(0,writefn); message_header_scan(mdraft,send_header,0); fprintf(f,"\n"); message_body_begin(mdraft); df = message_fp(mdraft); while (1) { c = getc(df); if (c == EOF) break; putc(c,f); } fclose(f); } static void dmove(int f, int t) { if (f == t) return; dup2(f,t); close(f); } static void send_to_cmd(char **av) { int p[2]; int pid; while (1) { if (pipe(&p[0]) < 0) { fprintf(stderr,"%s: pipe: %s\n",__progname,strerror(errno)); sleep(5); continue; } break; } while (1) { pid = fork(); if (pid < 0) { fprintf(stderr,"%s: fork: %s\n",__progname,strerror(errno)); sleep(5); continue; } break; } if (pid == 0) { close(p[1]); dmove(p[0],0); execvp(av[0],(void *)av); fprintf(stderr,"%s: exec %s: %s\n",__progname,av[0],strerror(errno)); fflush(stderr); _exit(1); } close(p[0]); copy_draft_to_fd_removing_fcc(p[1]); close(p[1]); } static void send_fcc(void) { int nav; char **av; const char *pgm; const char *hvp; const char *hvp0; int i; static void fend(void) { char *t; if (hvp0 == 0) return; t = malloc((hvp-hvp0)+1); bcopy(hvp0,t,hvp-hvp0); t[hvp-hvp0] = '\0'; av = realloc(av,(nav+2)*sizeof(char *)); av[nav++] = t; hvp0 = 0; } static int send_fcc_header(MESSAGE *msg __attribute__((__unused__)), const char *hdrname, const char *hdrval, void *cookie __attribute__((__unused__))) { if (strcasecmp(hdrname,"fcc")) return(0); hvp0 = 0; for (hvp=hdrval;*hvp;hvp++) { if ((*hvp == ',') || isspace(*hvp)) { fend(); } else if (hvp0 == 0) { hvp0 = hvp; } } fend(); return(0); } system_lock(SYS_WAIT|SYS_SHARED|SYS_FAILEXIT); old_draft(1); pgm = profile_lookup(system_get_profile(),"mmsend-mmrcv"); if (pgm == 0) pgm = "mmrcv"; nav = 1; av = malloc(2*sizeof(char *)); av[0] = strdup(pgm); message_header_scan(mdraft,send_fcc_header,0); if (nav > 1) { av[nav] = 0; send_to_cmd(av); } for (i=0;i= (sizeof(eav)/sizeof(eav[0]))-1) { printf("%s: too many arguments\n",av[0]); return(1); } while (1) { if (pipe(&p[0]) < 0) { fprintf(stderr,"%s: pipe: %s\n",__progname,strerror(errno)); sleep(5); continue; } break; } system_lock(SYS_WAIT|SYS_SHARED|SYS_FAILEXIT); old_draft(1); path = message_path(mdraft); if (ac == 1) { if (testmode) { printf("%s: no test program\n",av[0]); return(1); } cmd = profile_lookup(system_get_profile(),"editor"); if (! cmd) cmd = getenv("EDITOR"); if (cmd) { cmdcopy = strdup(cmd); eac = cmdline_split(cmdcopy,&eav[0],(sizeof(eav)/sizeof(eav[0]))-2); } else { cmdcopy = 0; eac = 1; eav[0] = strdup(_PATH_VI); } } else { cmdcopy = 0; bcopy(av+1,&eav[0],(ac-1)*sizeof(char *)); eav[0] = strdup(eav[0]); eac = ac - 1; } eav[eac++] = strdup(path); eav[eac] = 0; while (1) { pid = fork(); if (pid < 0) { fprintf(stderr,"%s: fork: %s\n",__progname,strerror(errno)); sleep(5); continue; } break; } fcntl(p[1],F_SETFD,1); if (pid == 0) { close(p[0]); execvp(eav[0],(void *)eav); printf("%s: exec %s: %s\n",av[0],eav[0],strerror(errno)); fflush(stdout); write(p[1],"",1); _exit(1); } close(p[1]); close_draft(); system_unlock(); err = 0; while (1) { wpid = wait3(&wstat,WUNTRACED,0); if (wpid != pid) continue; if (WIFEXITED(wstat)) { char junk; if ( (read(p[0],&junk,1) != 1) && (WEXITSTATUS(wstat) != 0) ) { if (! testmode) printf("%s: warning: editor exited with status %d\n",__progname,WEXITSTATUS(wstat)); err = 1; } break; } if (WIFSIGNALED(wstat)) { printf("%s: warning: %s died on signal %d (%s)%s\n",__progname,testmode?"test program":"editor",WTERMSIG(wstat),strsignal(WTERMSIG(wstat)),WCOREDUMP(wstat)?", with core dump":""); err = 1; break; } if (WIFSTOPPED(wstat)) { kill(getpid(),WSTOPSIG(wstat)); kill(pid,SIGCONT); } } close(p[0]); if (cmdcopy) { free(cmdcopy); } else { free(eav[0]); } free(eav[eac-1]); return(err); } static int cmd_edit(int ac, char **av) { return(cmd_edit_test(ac,av,0)); } static int cmd_test(int ac, char **av) { return(cmd_edit_test(ac,av,1)); } static int cmd_filter(int ac, char **av) { const char *path; int pid; int eac; char *eav[32]; int wpid; int wstat; #define PP_C 0 #define PP_T 1 #define PP__N 2 int pp[PP__N][2]; int i; int tf; int ask; char tmppath[64]; if (ac >= (sizeof(eav)/sizeof(eav[0]))-1) { printf("%s: too many arguments\n",av[0]); return(1); } if (ac < 2) { printf("%s: no command given\n",av[0]); return(1); } strcpy(&tmppath[0],"/tmp/mmsend.XXXXXX"); tf = mkstemp(&tmppath[0]); if (tf < 0) { fprintf(stderr,"%s: mkstemp: %s\n",__progname,strerror(errno)); return(1); } unlink(&tmppath[0]); for (i=0;i */ if (copy_draft_to_fd(pp[PP_T][1],1)) ask |= 1; close(pp[PP_T][1]); while (1) { wpid = wait3(&wstat,WUNTRACED,0); if (wpid != pid) continue; if (WIFEXITED(wstat)) { char junk; if ( (read(pp[PP_C][0],&junk,1) != 1) && (WEXITSTATUS(wstat) != 0) ) { printf("%s: warning: filter exited with status %d\n",__progname,WEXITSTATUS(wstat)); } if (WEXITSTATUS(wstat) != 0) ask |= 2; break; } if (WIFSIGNALED(wstat)) { printf("%s: warning: editor died on signal %d (%s)%s\n",__progname,WTERMSIG(wstat),strsignal(WTERMSIG(wstat)),WCOREDUMP(wstat)?", with core dump":""); ask |= 4; break; } if (WIFSTOPPED(wstat)) { kill(getpid(),WSTOPSIG(wstat)); kill(pid,SIGCONT); } } close(pp[PP_C][0]); if (!ask || confirm("copy the output back to the draft anyway")) { lseek(tf,0,SEEK_SET); rewind(message_fp(mdraft)); copy_fd_to_draft(tf,1); ftruncate(fileno(message_fp(mdraft)),lseek(tf,0,SEEK_CUR)); } close(tf); close_draft(); system_unlock(); #undef PP_T #undef PP__N return(0); } static int cmd_send(int ac, char **av) { if (ac == 1) { send_fcc(); send_mail(); destroy_draft(); exit(0); } printf("%s: takes no arguments\n",av[0]); return(1); } static int cmd_Send(int ac, char **av) { if (ac == 1) { send_fcc(); send_mail(); return(0); } printf("%s: takes no arguments\n",av[0]); return(1); } static int cmd_abort(int ac, char **av) { if (ac == 1) { destroy_draft(); exit(0); } printf("%s: takes no arguments\n",av[0]); return(1); } static int cmd_quit(int ac, char **av) { if (ac == 1) exit(0); printf("%s: takes no arguments\n",av[0]); return(1); } static int cmd_echo(int ac, char **av) { int i; if (ac < 2) return(0); printf("%s",av[1]); for (i=2;i> 1; for (c=0;c 0) return; if (iq && (isspace(c) || (c == '\\') || (c == '\'') || (c == '"'))) { iq --; genchar('\\'); genchar(c); iq ++; } else { addchar(c); } } static void genstr(char *s) { while (*s) genchar(*s++); } if (0) { aliaserr_brk: accum_free(a); return(0); } while (1) { asprintf(&buf,"mmsend-alias-%s",av[0]); val = profile_lookup(system_get_profile(),buf); free(buf); if (val == 0) return(0); a = accum_init(ACCUM_NULLTERM); iq = 0; ifi = 0; iti = 0; vp = val; while (*vp) { switch (*vp) { case '\\': if ((vp[1] == '\\') || (vp[1] == '$')) vp ++; break; case '$': switch (vp[1]) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { int n; int star; int quote; n = vp[1] - '0'; vp += 2; star = 0; quote = 0; if (0) { char opener; char closer; case '(': quote = 0; opener = '('; closer = ')'; if (0) { case '"': quote = 1; opener = '"'; closer = '"'; } n = isdigit(*vp) ? 0 : 1; star = 0; vp += 2; while (isdigit(*vp)) { n = (n * 10) + (*vp - '0'); vp ++; } if (*vp == '*') { star = 1; vp ++; } if (*vp != closer) aliaserr("incorrectly closed $%c...%c",opener,closer); vp ++; } if (n < *acp) { while (1) { if (quote) iq ++; genstr(av[n]); if (quote) iq --; if (star) { n ++; if (n < *acp) { genchar(' '); continue; } } break; } } continue; } break; case 'Q': switch (vp[2]) { case '<': iq ++; vp += 3; break; case '>': if (iq < 1) aliaserr("unmatched $Q>"); iq --; vp += 3; break; default: aliaserr("invalid character after $Q"); break; } continue; break; case '?': switch (vp[2]) { case '|': if (ifi+iti < 1) aliaserr("improper $? nesting"); switch (ifi) { case 0: ifi = 1; iti --; break; case 1: ifi = 0; iti ++; break; } vp += 3; break; case '.': if (ifi+iti < 1) aliaserr("$?. without opening $?...:"); if (ifi > 0) ifi --; else iti --; vp += 3; break; case 'e': { int n; vp += 3; n = 0; while (isdigit(*vp)) { n = (10 * n) + (*vp - '0'); vp ++; } if (*vp != ':') aliaserr("incorrectly terminated conditional"); vp ++; if (n >= *acp) ifi ++; else iti ++; } break; default: aliaserr("invalid character after $?"); break; } continue; break; case '$': vp += 2; genchar('$'); break; default: aliaserr("invalid character after $"); break; } break; } genchar(*vp++); } if (iq) aliaserr("unclosed $Q<"); if (ifi || iti) aliaserr("unclosed $?...:"); buf = accum_buf(a); *acp = cmdline_split(buf,av,nav); free(*freep); *freep = buf; } } static int do_whatnow_cmd(char *cmd) { char *avfree; char *av[64]; int ac; int i; static struct { const char *cmd; int (*impl)(int, char **); } cmds[] = { { "f", cmd_file }, { "file", cmd_file }, { "e", cmd_edit }, { "edit", cmd_edit }, { "|", cmd_filter }, { "filter", cmd_filter }, { "s", cmd_send }, { "send", cmd_send }, { "S", cmd_Send }, { "Send", cmd_Send }, { "abort", cmd_abort }, { "q", cmd_quit }, { "x", cmd_quit }, { "quit", cmd_quit }, { "exit", cmd_quit }, { "echo", cmd_echo }, { "multi", cmd_multi }, { "cond", cmd_cond }, { "test", cmd_test }, { "noerr", cmd_noerr }, { "fail", cmd_fail }, { 0 } }; avfree = 0; ac = cmdline_split(cmd,&av[0],sizeof(av)/sizeof(av[0])); if (ac < 1) return(0); system_lock(SYS_WAIT|SYS_SHARED|SYS_FAILEXIT); while (alias_expand(&avfree,&ac,av,sizeof(av)/sizeof(av[0]))) ; system_unlock(); for (i=0;cmds[i].cmd;i++) if (!strcmp(av[0],cmds[i].cmd)) break; if (! cmds[i].cmd) { printf("`%s' - command unrecognized.\n",av[0]); i = 1; } else { i = (*cmds[i].impl)(ac,av); } if (avfree) free(avfree); return(i); } static void whatnow_loop(void) __attribute__ ((noreturn)); static void whatnow_loop(void) { char cmdline[1024]; while (1) { printf("What now? "); fflush(stdout); if (fgets(&cmdline[0],sizeof(cmdline),stdin) != &cmdline[0]) { strcpy(&cmdline[0],"quit"); printf("quit\n"); } else { int l; l = strlen(&cmdline[0]); if ((l > 0) && (cmdline[l-1] == '\n')) { cmdline[l--] = '\0'; } else { int c; do c = getchar(); while ((c != EOF) && (c != '\n')); } } do_whatnow_cmd(&cmdline[0]); } } int main(int, char **); int main(int ac, char **av) { handleargs(ac,av); if (replarg) create_draft_repl(replarg); if (naddr > 0) create_draft_send(addrs,naddr); if (draftarg) find_draft(draftarg); whatnow_loop(); }