| #include "common.h" |
| #include "send.h" |
| |
| /* globals to all files */ |
| int rmail; |
| char *thissys, *altthissys; |
| int nflg; |
| int xflg; |
| int debug; |
| int rflg; |
| int iflg = 1; |
| int nosummary; |
| |
| /* global to this file */ |
| static String *errstring; |
| static message *mp; |
| static int interrupt; |
| static int savemail; |
| static Biobuf in; |
| static int forked; |
| static int add822headers = 1; |
| static String *arglist; |
| |
| /* predeclared */ |
| static int send(dest *, message *, int); |
| static void lesstedious(void); |
| static void save_mail(message *); |
| static int complain_mail(dest *, message *); |
| static int pipe_mail(dest *, message *); |
| static void appaddr(String *, dest *); |
| static void mkerrstring(String *, message *, dest *, dest *, char *, int); |
| static int replymsg(String *, message *, dest *); |
| static int catchint(void*, char*); |
| |
| void |
| usage(void) |
| { |
| fprint(2, "usage: mail [-birtx] list-of-addresses\n"); |
| exits("usage"); |
| } |
| |
| void |
| main(int argc, char *argv[]) |
| { |
| dest *dp=0; |
| int checkforward; |
| char *base; |
| int rv; |
| |
| /* process args */ |
| ARGBEGIN{ |
| case '#': |
| nflg = 1; |
| break; |
| case 'b': |
| add822headers = 0; |
| break; |
| case 'x': |
| nflg = 1; |
| xflg = 1; |
| break; |
| case 'd': |
| debug = 1; |
| break; |
| case 'i': |
| iflg = 0; |
| break; |
| case 'r': |
| rflg = 1; |
| break; |
| default: |
| usage(); |
| }ARGEND |
| |
| while(*argv){ |
| if(shellchars(*argv)){ |
| fprint(2, "illegal characters in destination\n"); |
| exits("syntax"); |
| } |
| d_insert(&dp, d_new(s_copy(*argv++))); |
| } |
| |
| if (dp == 0) |
| usage(); |
| arglist = d_to(dp); |
| |
| /* |
| * get context: |
| * - whether we're rmail or mail |
| */ |
| base = basename(argv0); |
| checkforward = rmail = (strcmp(base, "rmail")==0) | rflg; |
| thissys = sysname_read(); |
| altthissys = alt_sysname_read(); |
| if(rmail) |
| add822headers = 0; |
| |
| /* |
| * read the mail. If an interrupt occurs while reading, save in |
| * dead.letter |
| */ |
| if (!nflg) { |
| Binit(&in, 0, OREAD); |
| if(!rmail) |
| atnotify(catchint, 1); |
| mp = m_read(&in, rmail, !iflg); |
| if (mp == 0) |
| exit(0); |
| if (interrupt != 0) { |
| save_mail(mp); |
| exit(1); |
| } |
| } else { |
| mp = m_new(); |
| if(default_from(mp) < 0){ |
| fprint(2, "%s: can't determine login name\n", argv0); |
| exit(1); |
| } |
| } |
| errstring = s_new(); |
| getrules(); |
| |
| /* |
| * If this is a gateway, translate the sender address into a local |
| * address. This only happens if mail to the local address is |
| * forwarded to the sender. |
| */ |
| gateway(mp); |
| |
| /* |
| * Protect against shell characters in the sender name for |
| * security reasons. |
| */ |
| mp->sender = escapespecial(mp->sender); |
| if (shellchars(s_to_c(mp->sender))) |
| mp->replyaddr = s_copy("postmaster"); |
| else |
| mp->replyaddr = s_clone(mp->sender); |
| |
| /* |
| * reject messages that have been looping for too long |
| */ |
| if(mp->received > 32) |
| exit(refuse(dp, mp, "possible forward loop", 0, 0)); |
| |
| /* |
| * reject messages that are too long. We don't do it earlier |
| * in m_read since we haven't set up enough things yet. |
| */ |
| if(mp->size < 0) |
| exit(refuse(dp, mp, "message too long", 0, 0)); |
| |
| rv = send(dp, mp, checkforward); |
| if(savemail) |
| save_mail(mp); |
| if(mp) |
| m_free(mp); |
| exit(rv); |
| } |
| |
| /* send a message to a list of sites */ |
| static int |
| send(dest *destp, message *mp, int checkforward) |
| { |
| dest *dp; /* destination being acted upon */ |
| dest *bound; /* bound destinations */ |
| int errors=0; |
| |
| /* bind the destinations to actions */ |
| bound = up_bind(destp, mp, checkforward); |
| if(add822headers && mp->haveto == 0){ |
| if(nosummary) |
| mp->to = d_to(bound); |
| else |
| mp->to = arglist; |
| } |
| |
| /* loop through and execute commands */ |
| for (dp = d_rm(&bound); dp != 0; dp = d_rm(&bound)) { |
| switch (dp->status) { |
| case d_cat: |
| errors += cat_mail(dp, mp); |
| break; |
| case d_pipeto: |
| case d_pipe: |
| if (!rmail && !nflg && !forked) { |
| forked = 1; |
| lesstedious(); |
| } |
| errors += pipe_mail(dp, mp); |
| break; |
| default: |
| errors += complain_mail(dp, mp); |
| break; |
| } |
| } |
| |
| return errors; |
| } |
| |
| /* avoid user tedium (as Mike Lesk said in a previous version) */ |
| static void |
| lesstedious(void) |
| { |
| int i; |
| |
| if(debug) |
| return; |
| |
| switch(fork()){ |
| case -1: |
| break; |
| case 0: |
| sysdetach(); |
| for(i=0; i<3; i++) |
| close(i); |
| savemail = 0; |
| break; |
| default: |
| exit(0); |
| } |
| } |
| |
| |
| /* save the mail */ |
| static void |
| save_mail(message *mp) |
| { |
| Biobuf *fp; |
| String *file; |
| |
| file = s_new(); |
| deadletter(file); |
| fp = sysopen(s_to_c(file), "cAt", 0660); |
| if (fp == 0) |
| return; |
| m_bprint(mp, fp); |
| sysclose(fp); |
| fprint(2, "saved in %s\n", s_to_c(file)); |
| s_free(file); |
| } |
| |
| /* remember the interrupt happened */ |
| |
| static int |
| catchint(void *a, char *msg) |
| { |
| USED(a); |
| if(strstr(msg, "interrupt") || strstr(msg, "hangup")) { |
| interrupt = 1; |
| return 1; |
| } |
| return 0; |
| } |
| |
| /* dispose of incorrect addresses */ |
| static int |
| complain_mail(dest *dp, message *mp) |
| { |
| char *msg; |
| |
| switch (dp->status) { |
| case d_undefined: |
| msg = "Invalid address"; /* a little different, for debugging */ |
| break; |
| case d_syntax: |
| msg = "invalid address"; |
| break; |
| case d_unknown: |
| msg = "unknown user"; |
| break; |
| case d_eloop: |
| case d_loop: |
| msg = "forwarding loop"; |
| break; |
| case d_noforward: |
| if(dp->pstat && *s_to_c(dp->repl2)) |
| return refuse(dp, mp, s_to_c(dp->repl2), dp->pstat, 0); |
| else |
| msg = "destination unknown or forwarding disallowed"; |
| break; |
| case d_pipe: |
| msg = "broken pipe"; |
| break; |
| case d_cat: |
| msg = "broken cat"; |
| break; |
| case d_translate: |
| if(dp->pstat && *s_to_c(dp->repl2)) |
| return refuse(dp, mp, s_to_c(dp->repl2), dp->pstat, 0); |
| else |
| msg = "name translation failed"; |
| break; |
| case d_alias: |
| msg = "broken alias"; |
| break; |
| case d_badmbox: |
| msg = "corrupted mailbox"; |
| break; |
| case d_resource: |
| return refuse(dp, mp, "out of some resource. Try again later.", 0, 1); |
| default: |
| msg = "unknown d_"; |
| break; |
| } |
| if (nflg) { |
| print("%s: %s\n", msg, s_to_c(dp->addr)); |
| return 0; |
| } |
| return refuse(dp, mp, msg, 0, 0); |
| } |
| |
| /* dispose of remote addresses */ |
| static int |
| pipe_mail(dest *dp, message *mp) |
| { |
| dest *next, *list=0; |
| String *cmd; |
| process *pp; |
| int status; |
| char *none; |
| String *errstring=s_new(); |
| |
| if (dp->status == d_pipeto) |
| none = "none"; |
| else |
| none = 0; |
| /* |
| * collect the arguments |
| */ |
| next = d_rm_same(&dp); |
| if(xflg) |
| cmd = s_new(); |
| else |
| cmd = s_clone(next->repl1); |
| for(; next != 0; next = d_rm_same(&dp)){ |
| if(xflg){ |
| s_append(cmd, s_to_c(next->addr)); |
| s_append(cmd, "\n"); |
| } else { |
| if (next->repl2 != 0) { |
| s_append(cmd, " "); |
| s_append(cmd, s_to_c(next->repl2)); |
| } |
| } |
| d_insert(&list, next); |
| } |
| |
| if (nflg) { |
| if(xflg) |
| print("%s", s_to_c(cmd)); |
| else |
| print("%s\n", s_to_c(cmd)); |
| s_free(cmd); |
| return 0; |
| } |
| |
| /* |
| * run the process |
| */ |
| pp = proc_start(s_to_c(cmd), instream(), 0, outstream(), 1, none); |
| if(pp==0 || pp->std[0]==0 || pp->std[2]==0) |
| return refuse(list, mp, "out of processes, pipes, or memory", 0, 1); |
| pipesig(0); |
| m_print(mp, pp->std[0]->fp, thissys, 0); |
| pipesigoff(); |
| stream_free(pp->std[0]); |
| pp->std[0] = 0; |
| while(s_read_line(pp->std[2]->fp, errstring)) |
| ; |
| status = proc_wait(pp); |
| proc_free(pp); |
| s_free(cmd); |
| |
| /* |
| * return status |
| */ |
| if (status != 0) |
| return refuse(list, mp, s_to_c(errstring), status, 0); |
| loglist(list, mp, "remote"); |
| return 0; |
| } |
| |
| static void |
| appaddr(String *sp, dest *dp) |
| { |
| dest *parent; |
| String *s; |
| |
| if (dp->parent != 0) { |
| for(parent=dp->parent; parent->parent!=0; parent=parent->parent) |
| ; |
| s = unescapespecial(s_clone(parent->addr)); |
| s_append(sp, s_to_c(s)); |
| s_free(s); |
| s_append(sp, "' alias `"); |
| } |
| s = unescapespecial(s_clone(dp->addr)); |
| s_append(sp, s_to_c(s)); |
| s_free(s); |
| } |
| |
| /* |
| * reject delivery |
| * |
| * returns 0 - if mail has been disposed of |
| * other - if mail has not been disposed |
| */ |
| int |
| refuse(dest *list, message *mp, char *cp, int status, int outofresources) |
| { |
| String *errstring=s_new(); |
| dest *dp; |
| int rv; |
| |
| dp = d_rm(&list); |
| mkerrstring(errstring, mp, dp, list, cp, status); |
| |
| /* |
| * log first in case we get into trouble |
| */ |
| logrefusal(dp, mp, s_to_c(errstring)); |
| |
| /* |
| * bulk mail is never replied to, if we're out of resources, |
| * let the sender try again |
| */ |
| if(rmail){ |
| /* accept it or request a retry */ |
| if(outofresources){ |
| fprint(2, "Mail %s\n", s_to_c(errstring)); |
| rv = 1; /* try again later */ |
| } else if(mp->bulk) |
| rv = 0; /* silently discard bulk */ |
| else |
| rv = replymsg(errstring, mp, dp); /* try later if we can't reply */ |
| } else { |
| /* aysnchronous delivery only happens if !rmail */ |
| if(forked){ |
| /* |
| * if spun off for asynchronous delivery, we own the mail now. |
| * return it or dump it on the floor. rv really doesn't matter. |
| */ |
| rv = 0; |
| if(!outofresources && !mp->bulk) |
| replymsg(errstring, mp, dp); |
| } else { |
| fprint(2, "Mail %s\n", s_to_c(errstring)); |
| savemail = 1; |
| rv = 1; |
| } |
| } |
| |
| s_free(errstring); |
| return rv; |
| } |
| |
| /* make the error message */ |
| static void |
| mkerrstring(String *errstring, message *mp, dest *dp, dest *list, char *cp, int status) |
| { |
| dest *next; |
| char smsg[64]; |
| String *sender; |
| |
| sender = unescapespecial(s_clone(mp->sender)); |
| |
| /* list all aliases */ |
| s_append(errstring, " from '"); |
| s_append(errstring, s_to_c(sender)); |
| s_append(errstring, "'\nto '"); |
| appaddr(errstring, dp); |
| for(next = d_rm(&list); next != 0; next = d_rm(&list)) { |
| s_append(errstring, "'\nand '"); |
| appaddr(errstring, next); |
| d_insert(&dp, next); |
| } |
| s_append(errstring, "'\nfailed with error '"); |
| s_append(errstring, cp); |
| s_append(errstring, "'.\n"); |
| |
| /* >> and | deserve different flavored messages */ |
| switch(dp->status) { |
| case d_pipe: |
| s_append(errstring, "The mailer `"); |
| s_append(errstring, s_to_c(dp->repl1)); |
| sprint(smsg, "' returned error status %x.\n\n", status); |
| s_append(errstring, smsg); |
| break; |
| } |
| |
| s_free(sender); |
| } |
| |
| /* |
| * create a new boundary |
| */ |
| static String* |
| mkboundary(void) |
| { |
| char buf[32]; |
| int i; |
| static int already; |
| |
| if(already == 0){ |
| srand((time(0)<<16)|getpid()); |
| already = 1; |
| } |
| strcpy(buf, "upas-"); |
| for(i = 5; i < sizeof(buf)-1; i++) |
| buf[i] = 'a' + nrand(26); |
| buf[i] = 0; |
| return s_copy(buf); |
| } |
| |
| /* |
| * reply with up to 1024 characters of the |
| * original message |
| */ |
| static int |
| replymsg(String *errstring, message *mp, dest *dp) |
| { |
| message *refp = m_new(); |
| dest *ndp; |
| char *rcvr; |
| int rv; |
| String *boundary; |
| |
| boundary = mkboundary(); |
| |
| refp->bulk = 1; |
| refp->rfc822headers = 1; |
| rcvr = dp->status==d_eloop ? "postmaster" : s_to_c(mp->replyaddr); |
| ndp = d_new(s_copy(rcvr)); |
| s_append(refp->sender, "postmaster"); |
| s_append(refp->replyaddr, "/dev/null"); |
| s_append(refp->date, thedate()); |
| refp->haveto = 1; |
| s_append(refp->body, "To: "); |
| s_append(refp->body, rcvr); |
| s_append(refp->body, "\n"); |
| s_append(refp->body, "Subject: bounced mail\n"); |
| s_append(refp->body, "MIME-Version: 1.0\n"); |
| s_append(refp->body, "Content-Type: multipart/mixed;\n"); |
| s_append(refp->body, "\tboundary=\""); |
| s_append(refp->body, s_to_c(boundary)); |
| s_append(refp->body, "\"\n"); |
| s_append(refp->body, "Content-Disposition: inline\n"); |
| s_append(refp->body, "\n"); |
| s_append(refp->body, "This is a multi-part message in MIME format.\n"); |
| s_append(refp->body, "--"); |
| s_append(refp->body, s_to_c(boundary)); |
| s_append(refp->body, "\n"); |
| s_append(refp->body, "Content-Disposition: inline\n"); |
| s_append(refp->body, "Content-Type: text/plain; charset=\"US-ASCII\"\n"); |
| s_append(refp->body, "Content-Transfer-Encoding: 7bit\n"); |
| s_append(refp->body, "\n"); |
| s_append(refp->body, "The attached mail"); |
| s_append(refp->body, s_to_c(errstring)); |
| s_append(refp->body, "--"); |
| s_append(refp->body, s_to_c(boundary)); |
| s_append(refp->body, "\n"); |
| s_append(refp->body, "Content-Type: message/rfc822\n"); |
| s_append(refp->body, "Content-Disposition: inline\n\n"); |
| s_append(refp->body, s_to_c(mp->body)); |
| s_append(refp->body, "--"); |
| s_append(refp->body, s_to_c(boundary)); |
| s_append(refp->body, "--\n"); |
| |
| refp->size = s_len(refp->body); |
| rv = send(ndp, refp, 0); |
| m_free(refp); |
| d_free(ndp); |
| return rv; |
| } |