575 lines
12 KiB
C
575 lines
12 KiB
C
#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;
|
|
}
|