/* * smtpconvo - hold an SMTP conversation * * smtpconvo [-v] [-raw] [-helo helo-as] server[/[port]] envelope-sender * envelope-recipient [envelope-recipient ...] body-file * * body-file is expected to be already formatted as an 822/2822 * message; it should not have had the hidden-dot algorithm applied - * smtpconvo will do hidden-dots. body-file may use CRLF or NL line * breaks; smtpconvo will convert to CRLF for transmission if * necessary. (-raw suppresses the conversion to CRLF.) */ #include #include #include #include #include #include #include #include #include extern const char *__progname; static int verbose = 0; static int rawflag = 0; static const char *helo_as = 0; static struct addrinfo *servaddrs; static const char *sender; static const char * const *recips; static int nrecip; static const char *bodyfile; static FILE *bodyf; static int s; static char *lastsmtpsend; static unsigned char srbuf[8192]; static int srfill; static int srptr; static void usage(void) __attribute__((__noreturn__)); static void usage(void) { printf("Usage: %s [-v] [-raw] [-helo helo-as] server[/port] sender recip [recip [recip ...]] bodyfile\n",__progname); exit(1); } static void setserver(char *arg) { char *slash; int i; const char *portstr; struct addrinfo hints; slash = rindex(arg,'/'); if (slash) { *slash++ = '\0'; portstr = slash; } else { portstr = "smtp"; } hints.ai_flags = 0; hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; hints.ai_addrlen = 0; hints.ai_canonname = 0; hints.ai_addr = 0; hints.ai_next = 0; i = getaddrinfo(arg,portstr,&hints,&servaddrs); if (i) { fprintf(stderr,"%s: %s/%s: %s\n",__progname,arg,portstr,gai_strerror(i)); usage(); } } static void setbodyfile(const char *name) { bodyfile = name; bodyf = fopen(name,"r"); if (bodyf == 0) { fprintf(stderr,"%s: %s: %s\n",__progname,name,strerror(errno)); usage(); } } static void makeconn(void) { struct addrinfo *ai; int printederr; char hn[NI_MAXHOST]; char pn[NI_MAXSERV]; char *txt; printederr = 0; for (ai=servaddrs;ai;ai=ai->ai_next) { s = socket(ai->ai_family,ai->ai_socktype,ai->ai_protocol); if (s < 0) { if (errno != EPROTONOSUPPORT) { fprintf(stderr,"%s: socket (af%d): %s\n",__progname,ai->ai_family,strerror(errno)); printederr = 1; } continue; } if (getnameinfo(ai->ai_addr,ai->ai_addrlen,&hn[0],NI_MAXHOST,&pn[0],NI_MAXSERV,NI_NUMERICHOST|NI_NUMERICSERV)) { asprintf(&txt,"[can't get text form for af%d: %s]",ai->ai_family,strerror(errno)); } else { asprintf(&txt,"%s/%s",&hn[0],&pn[0]); } if (verbose) printf("Connect to %s...",txt); if (connect(s,ai->ai_addr,ai->ai_addrlen) >= 0) { if (verbose) printf("success\n"); if (printederr) { fprintf(stderr,"%s: success\n",txt); free(txt); } srfill = 0; srptr = 0; return; } if (verbose) { printf("%s\n",strerror(errno)); } else { fprintf(stderr,"%s: connect to %s: %s\n",__progname,txt,strerror(errno)); printederr = 1; } } exit(1); } static int srchr(void) { if (srptr >= srfill) { srfill = read(s,&srbuf[0],sizeof(srbuf)); if (srfill < 0) { fprintf(stderr,"%s: network read error: %s\n",__progname,strerror(errno)); exit(1); } if (srfill == 0) { fprintf(stderr,"%s: unexpected network EOF\n",__progname); exit(1); } srptr = 0; } return(srbuf[srptr++]); } static void fprint_pref(FILE *f, const char *s, const char *pref) { int atnl; atnl = 1; while (*s) { if ((s[0] == '\r') && (s[1] == '\n')) { fprintf(f,"\n"); atnl = 1; s += 2; continue; } if (atnl) fprintf(f,"%s",pref); atnl = 0; putc(*s,f); s ++; } if (! atnl) fprintf(f,"\n"); } static void getreply(void (*chk)(char *reply)) { int curcol; static char *resp = 0; static int ralloc = 0; int rlen; int c; int lastcc0; static void savec(int c) { if (rlen >= ralloc) resp = realloc(resp,ralloc=rlen+16); resp[rlen++] = c; } rlen = 0; curcol = 0; while (1) { c = srchr(); if (curcol == 0) lastcc0 = rlen; savec(c); curcol ++; if ((c == '\n') && (rlen > 1) && (resp[rlen-2] == '\r')) { if ((rlen-lastcc0 > 4) && (resp[lastcc0+3] == ' ')) { savec(0); if (verbose) fprint_pref(stdout,resp,"<<< "); (*chk)(resp); return; } curcol = 0; } } } static void smtpsend(const char *, ...) __attribute__((__format__(__printf__,1,2))); static void smtpsend(const char *fmt, ...) { va_list ap; int lsslen; FILE *f; static int wf(void *cookie __attribute__((__unused__)), const char *data, int len) { lastsmtpsend = realloc(lastsmtpsend,lsslen+len+1); bcopy(data,lastsmtpsend+lsslen,len); lsslen += len; return(write(s,data,len)); } free(lastsmtpsend); lastsmtpsend = malloc(1); lsslen = 0; f = fwopen(0,&wf); va_start(ap,fmt); vfprintf(f,fmt,ap); va_end(ap); fprintf(f,"\r\n"); fclose(f); lastsmtpsend[lsslen] = '\0'; if (verbose) fprint_pref(stdout,lastsmtpsend,">>> "); } static void mustbe2xx(char *resp) { if (resp[0] != '2') { if (verbose) { fprintf(stderr,"%s: SMTP error\n",__progname); } else { fprintf(stderr,"%s: SMTP error:\n",__progname); if (lastsmtpsend) fprint_pref(stderr,lastsmtpsend,">>> "); fprint_pref(stderr,resp,"<<< "); } exit(1); } } static void mustbe354(char *resp) { if (strncmp(resp,"354",3)) { if (verbose) { fprintf(stderr,"%s: SMTP error\n",__progname); } else { fprintf(stderr,"%s: SMTP error:\n",__progname); if (lastsmtpsend) fprint_pref(stderr,lastsmtpsend,">>> "); fprint_pref(stderr,resp,"<<< "); } exit(1); } } static void copydata(void) { int c; int pc; int ppc; FILE *sw; static int wf(void *cookie __attribute__((__unused__)), const char *data, int len) { return(write(s,data,len)); } pc = '\r'; c = '\n'; sw = fwopen(0,wf); while (1) { ppc = pc; pc = c; c = getc(bodyf); if (c == EOF) break; if ((c == '.') && (pc == '\n') && (ppc == '\r')) putc('.',sw); if ((c == '\n') && (pc != '\r') && !rawflag) { ppc = pc; pc = '\r'; putc('\r',sw); } putc(c,sw); } if ((pc != '\n') || (ppc != '\r')) { putc('\r',sw); putc('\n',sw); } fclose(sw); } static void protocol(void) { char hn[MAXHOSTNAMELEN]; int i; int ngood; static void rcptchk(char *resp) { switch (resp[0]) { case '2': ngood ++; break; case '4': case '5': if (verbose) { fprintf(stderr,"%s: SMTP error\n",__progname); } else { fprintf(stderr,"%s: recipient rejected:\n",__progname); if (lastsmtpsend) fprint_pref(stderr,lastsmtpsend,">>> "); fprint_pref(stderr,resp,"<<< "); } break; default: if (verbose) { fprintf(stderr,"%s: SMTP error\n",__progname); } else { fprintf(stderr,"%s: SMTP error:\n",__progname); if (lastsmtpsend) fprint_pref(stderr,lastsmtpsend,">>> "); fprint_pref(stderr,resp,"<<< "); } exit(1); break; } } lastsmtpsend = 0; getreply(&mustbe2xx); if (helo_as) { smtpsend("HELO %s",helo_as); } else { gethostname(&hn[0],MAXHOSTNAMELEN); hn[MAXHOSTNAMELEN-1] = '\0'; smtpsend("HELO %s",&hn[0]); } getreply(&mustbe2xx); smtpsend("MAIL From:<%s>",sender); getreply(&mustbe2xx); ngood = 0; for (i=0;i",recips[i]); getreply(&rcptchk); } if (ngood < 1) { fprintf(stderr,"%s: all recipients rejected\n",__progname); } else { smtpsend("DATA"); getreply(&mustbe354); copydata(); smtpsend("."); getreply(&mustbe2xx); } smtpsend("QUIT"); getreply(&mustbe2xx); } int main(int, char **); int main(int ac, char **av) { while (1) { if ((ac > 1) && !strcmp(av[1],"-v")) { ac --; av ++; verbose = 1; continue; } if ((ac > 1) && !strcmp(av[1],"-raw")) { ac --; av ++; rawflag = 1; continue; } if ((ac > 2) && !strcmp(av[1],"-helo")) { ac -= 2; av += 2; helo_as = av[0]; continue; } break; } if (ac < 5) usage(); setserver(av[1]); sender = av[2]; nrecip = ac - 4; /* XXX why is char ** incompatible with const char * const *? */ recips = (const void *)(av+3); setbodyfile(av[ac-1]); makeconn(); protocol(); exit(0); }