/* smtpcli.c * Client routines for Simple Mail Transfer Protocol ala RFC821 * A.D. Barksdale Garbee II, aka Bdale, N3EUA * Copyright 1986 Bdale Garbee, All Rights Reserved. * Permission granted for non-commercial copying and use, provided * this notice is retained. * Modified 14 June 1987 by P. Karn for symbolic target addresses, * also rebuilt locking mechanism * Limit on max simultaneous sessions, reuse of connections - 12/87 NN2Z */ #include #if (!ATARI_ST || LATTICE) /* DG2KK */ #include #endif #include "global.h" #include "netuser.h" #include "mbuf.h" #include "timer.h" #include "tcp.h" #include "smtp.h" #include "trace.h" #include "cmdparse.h" extern int16 lport; /* local port placeholder */ extern int32 resolve(); static struct timer smtpcli_t; int32 gateway; #ifdef SMTPTRACE int16 smtptrace = 0; /* used for trace level */ int dosmtptrace(); #endif int16 smtpmaxcli = MAXSESSIONS; /* the max client connections allowed */ int16 smtpcli = 0; /* number of client connections * currently open */ static struct smtp_cb *cli_session[MAXSESSIONS]; /* queue of client sessions */ int dosmtptick(),dogateway(),dosmtpmaxcli(),mlock(),dotimer(); void quit(),abort_trans(),rejextjob(),sendit(),del_session(),del_job(); void rejectjob(),execjobs(),smtp_transaction(); struct smtp_cb *newcb(),*lookup(); struct smtp_job *setupjob(); struct cmds smtpcmds[] = { "gateway", dogateway, 0, NULLCHAR, NULLCHAR, "kick", dosmtptick, 0, NULLCHAR, NULLCHAR, "maxclients", dosmtpmaxcli, 0, NULLCHAR, NULLCHAR, "timer", dotimer, 0, NULLCHAR, NULLCHAR, #ifdef SMTPTRACE "trace", dosmtptrace, 0, NULLCHAR, NULLCHAR, #endif NULLCHAR, NULLFP, 0, "subcommands: gateway kick maxclients timer trace", NULLCHAR, }; dosmtp(argc,argv) int argc; char *argv[]; { return subcmd(smtpcmds,argc,argv); } static int dosmtpmaxcli(argc,argv) int argc; char *argv[]; { int x; if (argc < 2) printf("%d\n",smtpmaxcli); else { x = atoi(argv[1]); if (x > MAXSESSIONS) printf("max clients must be <= %d\n",MAXSESSIONS); else smtpmaxcli = x; } return 0; } static int dogateway(argc,argv) int argc; char *argv[]; { char *inet_ntoa(); int32 n; extern char badhost[]; if(argc < 2){ printf("%s\n",inet_ntoa(gateway)); } else if((n = resolve(argv[1])) == 0){ printf(badhost,argv[1]); return 1; } else gateway = n; return 0; } #ifdef SMTPTRACE static int dosmtptrace(argc,argv) int argc; char *argv[]; { if (argc < 2) printf("%d\n",smtptrace); else smtptrace = atoi(argv[1]); return 0; } #endif /* Set outbound spool poll interval */ static int dotimer(argc,argv) int argc; char *argv[]; { int dosmtptick(); if(argc < 2){ printf("%d/%d\n",smtpcli_t.start - smtpcli_t.count, smtpcli_t.start); return 0; } smtpcli_t.func = (void (*)())dosmtptick;/* what to call on timeout */ smtpcli_t.arg = NULLCHAR; /* dummy value */ smtpcli_t.start = atoi(argv[1]); /* set timer duration */ start_timer(&smtpcli_t); /* and fire it up */ return 0; } /* this is the routine that gets called every so often to do outgoing mail processing */ int dosmtptick() { register struct smtp_cb *cb; char tmpstring[LINELEN], wfilename[13], prefix[9]; char from[LINELEN], to[LINELEN]; char *cp, *cp1; int32 destaddr; FILE *wfile; #ifdef SMTPTRACE if (smtptrace > 5) { printf("smtp daemon entered\n"); fflush(stdout); } #endif for(filedir(mailqueue,0,wfilename);wfilename[0] != '\0'; filedir(mailqueue,1,wfilename)){ /* save the prefix of the file name which it job id */ cp = wfilename; cp1 = prefix; while (*cp && *cp != '.') *cp1++ = *cp++; *cp1 = '\0'; /* lock this file from the smtp daemon */ if (mlock(mailqdir,prefix)) continue; sprintf(tmpstring,"%s%s",mailqdir,wfilename); if ((wfile = fopen(tmpstring,"r")) == NULLFILE) { /* probably too many open files */ (void) rmlock(mailqdir,prefix); /* continue to next message. The failure * may be temporary */ continue; } fgets(tmpstring,LINELEN,wfile); /* read target host */ rip(tmpstring); fgets(from,LINELEN,wfile); /* read target host */ rip(from); fgets(to,LINELEN,wfile); /* read target user */ rip(to); fclose(wfile); if ((destaddr = mailroute(tmpstring)) == 0) { printf("** smtpcli: Unknown address %s\n",tmpstring); fflush(stdout); (void) rmlock(mailqdir,prefix); continue; } if ((cb = lookup(destaddr)) == NULLCB) { /* there are enough processes running already */ if (smtpcli >= smtpmaxcli) { #ifdef SMTPTRACE if (smtptrace) { printf("smtp daemon: too many processes\n"); fflush(stdout); } #endif (void) rmlock(mailqdir,prefix); break; } if ((cb = newcb()) == NULLCB) { (void) rmlock(mailqdir,prefix); break; } cb->ipaddr = destaddr; } else { /* This system is already is sending mail lets not * interfere with its send queue. */ if (cb->state != CLI_IDLE) { (void) rmlock(mailqdir,prefix); continue; } } #ifdef SMTPTRACE if (smtptrace > 1) { printf("queue job %s To: %s From: %s\n",prefix,to,from); fflush(stdout); } #endif if (setupjob(cb,prefix,to,from) == NULLJOB) { (void) rmlock(mailqdir,prefix); del_session(cb); break; } } /* start sending that mail */ execjobs(); /* Restart timer */ start_timer(&smtpcli_t); #ifdef SMTPTRACE if (smtptrace > 5) { printf("smtp daemon done\n"); fflush(stdout); } #endif } /* this is the master state machine that handles a single SMTP transaction */ void smtp_transaction(cb) struct smtp_cb *cb; { void smtp_cts(); char reply; int rcode; #ifdef SMTPTRACE if (smtptrace > 7) printf("smtp_transaction() enter state=%u\n",cb->state); if (smtptrace) { printf("%s\n",cb->buf); fflush(stdout); } #endif /* Another line follows; ignore this one */ if(cb->buf[0] == '0' || cb->buf[3] == '-') return; reply = cb->buf[0]; rcode = atoi(cb->buf); /* if service shuting down */ if (rcode == 421) { quit(cb); return; } switch(cb->state) { case CLI_OPEN_STATE: if (reply != '2') quit(cb); else { cb->state = CLI_HELO_STATE; sendit(cb,"HELO %s\r\n",hostname); } break; case CLI_HELO_STATE: if (reply != '2') quit(cb); else { cb->state = CLI_MAIL_STATE; /* send both to speed things up */ sendit(cb,"MAIL FROM:<%s>\r\nRCPT TO:<%s>\r\n", cb->jobq->from,cb->jobq->to); } break; case CLI_MAIL_STATE: if (reply != '2') quit(cb); else { cb->state = CLI_RCPT_STATE; /* the RCPT is sent already */ } break; case CLI_RCPT_STATE: if (reply == '5') { rejectjob(cb); abort_trans(cb); } else if (reply != '2') abort_trans(cb); else { /* open text file here because it will be too * late to abort in the data state. */ /* if this file open fails abort */ if ((cb->tfile = fopen(cb->tname,"r")) == NULLFILE) abort_trans(cb); else { cb->state = CLI_DATA_STATE; sendit(cb,"DATA\r\n"); } } break; case CLI_DATA_STATE: if (reply != '3') abort_trans(cb); else { cb->state = CLI_SEND_STATE; /* Kick the data transfer to get it started */ smtp_cts(cb->tcb,cb->tcb->window - cb->tcb->sndcnt); } break; case CLI_SEND_STATE: /* the transmitter upcall routine will advance the state pointer on end of file, so we do nada... */ break; case CLI_UNLK_STATE: if (reply == '5') { rejectjob(cb); abort_trans(cb); } else if (reply != '2') abort_trans(cb); else { unlink(cb->wname); /* unlink workfile */ /* close and unlink the textfile */ if(cb->tfile != NULLFILE) { fclose(cb->tfile); cb->tfile = NULLFILE; } unlink(cb->tname); abort_trans(cb); } break; case CLI_QUIT_STATE: close_tcp(cb->tcb); /* close up connection */ break; } } /* abort the currrent job. Remove the lockfile. * If more work exists set up the next job if * not then shut down. */ static void abort_trans(cb) struct smtp_cb *cb; { if(cb->tfile != NULLFILE) { fclose(cb->tfile); cb->tfile = NULLFILE; } (void) rmlock(mailqdir,cb->jobq->jobname); if (nextjob(cb)) { sendit(cb,"RSET\r\n"); cb->state = CLI_HELO_STATE; } else { sendit(cb,"QUIT\r\n"); /* issue a quit command */ cb->state = CLI_QUIT_STATE; } } /* close down link after a failure */ static void quit(cb) struct smtp_cb *cb; { cb->state = CLI_QUIT_STATE; sendit(cb,"QUIT\r\n"); /* issue a quit command */ } /* smtp receiver upcall routine. fires up the state machine to parse input */ static void smtp_rec(tcb,cnt) struct tcb *tcb; int16 cnt; { register struct smtp_cb *cb; char *inet_ntoa(), c; struct mbuf *bp; #ifdef SMTPTRACE if (smtptrace > 5) { printf("smtp_rec called\n"); fflush(stdout); } #endif cb = (struct smtp_cb *)tcb->user; /* point to our struct */ recv_tcp(tcb,&bp,cnt); /* suck up chars from low level routine */ /* Assemble input line in buffer, return if incomplete */ while(pullup(&bp,&c,1) == 1) { switch(c) { case '\r': /* strip cr's */ continue; case '\n': /* line is finished, go do it! */ cb->buf[cb->cnt] = '\0'; smtp_transaction(cb); cb->cnt = 0; break; default: /* other chars get added to buffer */ if(cb->cnt != LINELEN-1) cb->buf[cb->cnt++] = c; break; } } } /* smtp transmitter ready upcall routine. twiddles cts flag */ static void smtp_cts(tcb,cnt) struct tcb *tcb; int16 cnt; { register struct smtp_cb *cb; struct mbuf *bp; char *cp; int c; #ifdef SMTPTRACE if (smtptrace > 5) { printf("smtp_cts called avail %d\n",cnt); fflush(stdout); } #endif cb = (struct smtp_cb *)tcb->user; /* point to our struct */ /* don't do anything until/unless we're supposed to be sending */ if(cb->state != CLI_SEND_STATE) return; if((bp = alloc_mbuf(cnt)) == NULLBUF){ /* Hard to know what to do here */ return; } cp = bp->data; while(cnt > 1 && (c = getc(cb->tfile)) != EOF){ #if (ATARI_ST) if (c == '\n') { *cp++ = '\r'; bp->cnt++; cnt--; } #endif *cp++ = c; bp->cnt++; cnt--; } if(bp->cnt != 0) send_tcp(tcb,bp); else free_p(bp); if(cnt > 1){ /* EOF seen */ sendit(cb,"\r\n.\r\n"); cb->state = CLI_UNLK_STATE; } } /* smtp state change upcall routine. */ static void smtp_state(tcb,old,new) struct tcb *tcb; char old,new; { register struct smtp_cb *cb; extern char *tcpstates[]; #ifdef SMTPTRACE if (smtptrace > 5) { printf("smtp_state called: %s\n",tcpstates[new]); fflush(stdout); } #endif cb = (struct smtp_cb *)tcb->user; switch(new) { case ESTABLISHED: cb->state = CLI_OPEN_STATE; /* shouldn't be needed */ break; case CLOSE_WAIT: close_tcp(tcb); /* shut things down */ break; case CLOSED: /* if this close was not done by us ie. a RST */ if (cb->state != CLI_QUIT_STATE) { if(cb->tfile != NULLFILE) fclose(cb->tfile); } del_session(cb); del_tcp(tcb); break; } } /* Send message back to server */ /*VARARGS*/ static void sendit(cb,fmt,arg1,arg2) struct smtp_cb *cb; char *fmt,*arg1,*arg2; { struct mbuf *bp,*qdata(); char tmpstring[256]; #ifdef SMTPTRACE if (smtptrace) { printf(">>> "); printf(fmt,arg1,arg2); fflush(stdout); } #endif sprintf(tmpstring,fmt,arg1,arg2); bp = qdata(tmpstring,(int16)strlen(tmpstring)); send_tcp(cb->tcb,bp); } /* create mail lockfile */ int mlock(dir,id) char *dir,*id; { char lockname[LINELEN]; int fd; /* Try to create the lock file in an atomic operation */ sprintf(lockname,"%s%s.lck",dir,id); #if (ATARI_ST && !LATTICE) /* DG2KK */ if(!access(lockname,0) || (fd = creat(lockname, 0666)) == -1) return -1; #else if((fd = open(lockname, O_WRONLY|O_EXCL|O_CREAT, 0666)) == -1) return -1; #endif close(fd); return 0; } /* remove mail lockfile */ int rmlock(dir,id) char *dir,*id; { char lockname[LINELEN]; sprintf(lockname,"%s%s.lck",dir,id); return(unlink(lockname)); } /* free the message struct and data */ static void del_session(cb) register struct smtp_cb *cb; { register int i; register struct smtp_job *jp,*tp; if (cb == NULL) return; for(i=0; iwname != NULLCHAR) free(cb->wname); if(cb->tname != NULLCHAR) free(cb->tname); for (jp = cb->jobq; jp != NULLJOB;jp = tp) { tp = jp->next; (void) rmlock(mailqdir,jp->jobname); del_job(jp); } free((char *)cb); smtpcli--; /* number of connections active */ } void del_job(jp) register struct smtp_job *jp; { if(jp->to != NULLCHAR) free(jp->to); if(jp->from != NULLCHAR) free(jp->from); free((char *)jp); } /* move a bad job out of the send queue into a holding area */ static void rejectjob(cb) struct smtp_cb *cb; { char dest[LINELEN]; #ifdef SMTPTRACE if (smtptrace > 5) { printf("smtp job %s rejected\n",cb->wname); fflush(stdout); } #endif /* remove job from queue and save for admin */ sprintf(dest,"%s%s.txt",baddir,cb->jobq->jobname); (void) rename(cb->tname,dest); strcpy(rindex(dest,'.'),".wrk"); (void) rename(cb->wname,dest); (void) rmlock(mailqdir,cb->jobq->jobname); } /* look to see if a smtp control block exists for this ipaddr */ static struct smtp_cb * lookup(destaddr) int32 destaddr; { register int i; for(i=0; iipaddr == destaddr) return cli_session[i]; } return NULLCB; } /* create a new smtp control block */ static struct smtp_cb * newcb() { register int i; struct smtp_cb *cb; for(i=0; iwname = malloc((unsigned)strlen(mailqdir) + JOBNAME); if (cb->wname == NULLCHAR) { free((char *)cb); return(NULLCB); } cb->tname = malloc((unsigned)strlen(mailqdir) + JOBNAME); if (cb->tname == NULLCHAR) { free(cb->wname); free((char *)cb); return(NULLCB); } cb->state = CLI_IDLE; cli_session[i] = cb; smtpcli++; /* number of connections active */ return(cb); } } return NULLCB; } static void execjobs() { struct socket lsocket, fsocket; void smtp_rec(), smtp_cts(), smtp_state(); register struct smtp_cb *cb; int i; for(i=0; istate != CLI_IDLE) continue; sprintf(cb->tname,"%s%s.txt",mailqdir,cb->jobq->jobname); sprintf(cb->wname,"%s%s.wrk",mailqdir,cb->jobq->jobname); /* setup the socket */ fsocket.address = cb->ipaddr; fsocket.port = SMTP_PORT; lsocket.address = ip_addr; /* our ip address */ lsocket.port = lport++; /* next unused port */ #ifdef SMTPTRACE if (smtptrace) { printf("Trying Connection to %s\n",inet_ntoa(fsocket.address)); fflush(stdout); } #endif /* open smtp connection */ cb->state = CLI_OPEN_STATE; /* init state placeholder */ cb->tcb = open_tcp(&lsocket,&fsocket,TCP_ACTIVE,tcp_window, smtp_rec,smtp_cts,smtp_state,0,(char *)cb); cb->tcb->user = (char *)cb; /* Upward pointer */ } } /* add this job to control block queue */ struct smtp_job * setupjob(cb,id,to,from) struct smtp_cb *cb; char *id,*to,*from; { register struct smtp_job *p1,*p2; p1 = (struct smtp_job *)calloc(1,sizeof(struct smtp_job)); if (p1 == NULLJOB) return NULLJOB; p1->to = malloc((unsigned)strlen(to) + 1); if (p1->to == NULLCHAR) { free((char *)p1); return NULLJOB; } p1->from = malloc((unsigned)strlen(from) + 1); if (p1->from == NULLCHAR) { free(p1->to); free((char *)p1); return NULLJOB; } strcpy(p1->to,to); strcpy(p1->from,from); strcpy(p1->jobname,id); if ((p2 = cb->jobq) == NULLJOB) cb->jobq = p1; else { while(p2->next != NULLJOB) p2 = p2->next; p2->next = p1; } return p1; } /* called to advance to the next job */ static int nextjob(cb) struct smtp_cb *cb; { struct smtp_job *jp; jp = cb->jobq->next; del_job(cb->jobq); if (jp == NULLJOB) { cb->jobq = NULLJOB; return 0; } cb->jobq = jp; sprintf(cb->tname,"%s%s.txt",mailqdir,cb->jobq->jobname); sprintf(cb->wname,"%s%s.wrk",mailqdir,cb->jobq->jobname); #ifdef SMTPTRACE if (smtptrace > 5) { printf("sending %s\n",cb->jobq->jobname); fflush(stdout); } #endif return 1; } /* mail routing funtion. For now just used the hosts file */ int32 mailroute(dest) char *dest; { int32 destaddr; /* look up address or use the gateway */ if ((destaddr = resolve(dest)) == 0) if (gateway != 0) destaddr = gateway; /* Use the gateway */ return destaddr; }