--- src/client.c 2004-01-21 04:35:13.000000000 -0500 +++ src/client.c 2004-01-21 04:37:19.000000000 -0500 @@ -202,3 +202,5 @@ static int init_remote(CLI *c) { /* Setup c->remote_fd, now */ - if(c->opt->option.remote) { + if(c->opt->option.fdforward) { + fd=c->opt->use_fd; + } else if(c->opt->option.remote) { c->resolved_addresses=NULL; --- src/options.c 2004-01-21 04:35:13.000000000 -0500 +++ src/options.c 2004-01-21 04:35:30.000000000 -0500 @@ -618,2 +618,25 @@ static char *service_options(CMD cmd, LO + /* fdforward */ + switch(cmd) { + case CMD_INIT: + section->option.fdforward=0; + section->use_fd=-1; + break; + case CMD_EXEC: + if(strcasecmp(opt, "fdforward")) + break; + section->option.fdforward=1; + if(atoi(arg)>0 || !strcmp(arg, "0")) + section->use_fd=atoi(arg); + else + return "Illegal fdforward value"; + return NULL; /* OK */ + case CMD_DEFAULT: + break; + case CMD_HELP: + log_raw("%-15s = an already open socket file descriptor", + "fdforward"); + break; + } + /* delay */ @@ -734,3 +757,26 @@ static char *service_options(CMD cmd, LO "protocol"); - log_raw("%18scurrently supported: smtp, pop3, nntp", ""); + log_raw("%18scurrently supported: smtp, pop3, imap, nntp", ""); + break; + } + + /* revertible */ + switch(cmd) { + case CMD_INIT: + section->option.revertible=0; + break; + case CMD_EXEC: + if(strcasecmp(opt, "revertible")) + break; + if(!strcasecmp(arg, "yes")) + section->option.revertible=1; + else if(!strcasecmp(arg, "no")) + section->option.revertible=0; + else + return "argument should be either 'yes' or 'no'"; + return NULL; /* OK */ + case CMD_DEFAULT: + break; + case CMD_HELP: + log_raw("%-15s = yes|no allow starttls enabled protocols to revert back to plaintext", + "revertible"); break; @@ -989,3 +1035,4 @@ static char *section_validate(LOCAL_OPTI (unsigned int)section->option.program + - (unsigned int)section->option.remote != 2) + (unsigned int)section->option.remote + + (unsigned int)section->option.fdforward != 2) #endif --- src/protocol.c 2004-01-21 04:35:13.000000000 -0500 +++ src/protocol.c 2004-01-21 04:35:30.000000000 -0500 @@ -40,2 +40,4 @@ static int pop3_client(CLI *); static int pop3_server(CLI *); +static int imap_client(CLI *); +static int imap_server(CLI *); static int nntp_client(CLI *); @@ -69,2 +71,8 @@ int negotiate(CLI *c) { } + if(!strcmp(c->opt->protocol, "imap")) { + if(options.option.client) + return imap_client(c); + else + return imap_server(c); + } if(!strcmp(c->opt->protocol, "nntp")) { @@ -130,4 +138,114 @@ static int smtp_client(CLI *c) { +#define PP_BUFSIZE 8192 +static int plaintext_proxy(CLI *c) +{ + fd_set rd_set, wr_set; + char rbuf[PP_BUFSIZE]; + int rbufend,rdone; + char lbuf[PP_BUFSIZE]; + int lbufend,ldone; + struct timeval tv; + int ready; + int fdmax; + int howmuch, sofar; + + fdmax = c->remote_fd.fd; + if (c->local_wfd.fd > fdmax) + fdmax = c->local_wfd.fd; + if (c->local_rfd.fd > fdmax) + fdmax = c->local_rfd.fd; + fdmax++; + + lbufend = rbufend = 0; + rdone = ldone = 0; + + tv.tv_sec = 86400; + tv.tv_usec = 0; + while( !( (rdone || ldone) && (!rbufend && !lbufend) ) ) { + log(LOG_DEBUG,"rdone=%d, ldone=%d, rbufend=%d, lbufend=%d",rdone,ldone,rbufend,lbufend); + FD_ZERO(&rd_set); + FD_ZERO(&wr_set); + if (rbufend) + FD_SET((c->local_wfd.fd),&wr_set); + else if (!rdone) + FD_SET(c->remote_fd.fd,&rd_set); + + if (lbufend) + FD_SET(c->remote_fd.fd,&wr_set); + else if (!ldone) + FD_SET(c->local_rfd.fd,&rd_set); + + do { /* Skip "Interrupted system call" errors */ + ready=select(fdmax, &rd_set, &wr_set, NULL, &tv); + } while(ready<0 && get_last_socket_error()==EINTR); + + if(ready<0) { /* Break the connection for others */ + sockerror("select"); + return 1; + } + if(!ready) { /* Timeout */ + log(LOG_DEBUG, "select timeout - connection reset"); + return 1; + } + + if (lbufend > 0) { + if (FD_ISSET(c->remote_fd.fd,&wr_set)) + { + sofar = 0; + while (sofar < lbufend) { + if ((howmuch=write(c->remote_fd.fd,lbuf+sofar,lbufend-sofar)) < 0) { + sockerror("write"); + return 1; + } + sofar += howmuch; + } + lbufend = 0; + } + } else { + if (FD_ISSET(c->local_rfd.fd,&rd_set)) { + if ( (lbufend = read(c->local_rfd.fd,lbuf,PP_BUFSIZE)) < 0) { + sockerror("read"); + return 1; + } + if (lbufend == 0) /* EOF */ + { + log(LOG_DEBUG,"EOF reading from local"); + ldone=1; + } + } + } + + if (rbufend > 0) { + if (FD_ISSET(c->local_wfd.fd, &wr_set)) { + sofar = 0; + while (sofar < rbufend) { + if ( (howmuch = write(c->local_wfd.fd, rbuf + sofar, rbufend - sofar)) < 0) { + sockerror("write"); + return 1; + } + sofar += howmuch; + } + rbufend = 0; + } + } else { + if (FD_ISSET(c->remote_fd.fd,&rd_set)) { + if ( (rbufend = read(c->remote_fd.fd,rbuf,PP_BUFSIZE)) < 0) { + sockerror("read"); + return 1; + } + if (rbufend == 0) /* EOF */ { + log(LOG_DEBUG,"EOF reading from remote"); + rdone=1; + } + } + } + } + + log(LOG_DEBUG, "plaintext_proxy finished normally."); + return 0; +} + static int smtp_server(CLI *c) { char line[STRLEN]; + int read_ret; @@ -136,3 +254,3 @@ static int smtp_server(CLI *c) { - if(fdscanf(c, c->remote_fd.fd, "220%[^\n]", line)!=1) { + if(fdscanf(c, c->remote_fd.fd, "220%[^\r\n]", line)!=1) { log(LOG_ERR, "Unknown server welcome"); @@ -142,13 +260,48 @@ static int smtp_server(CLI *c) { return -1; - if(fdscanf(c, c->local_rfd.fd, "EHLO %[^\n]", line)!=1) { - log(LOG_ERR, "Unknown client EHLO"); - return -1; + + /* See if we get an EHLO command */ + if (((read_ret=fdgets(c, c->local_rfd.fd, line)) >= 0) && + (strlen(line) >= 4) && + (!line[4] || isspace(line[4])) && + (strncasecmp(line,"ehlo",4) == 0) ) { + if (fdputs(c,c->remote_fd.fd,line) < 0) + return -1; + while ( (fdgets(c,c->remote_fd.fd, line) >= 0) + && (line[3] == '-') ) { + if (fdputs(c,c->local_wfd.fd,line) < 0) + return -1; + } + line[3] = '-'; + if (fdputs(c,c->local_wfd.fd,line) < 0) + return -1; + if (fdputs(c,c->local_wfd.fd,"250 STARTTLS") < 0) + return -1; + + /* See if we get a STARTTLS command */ + if (((read_ret=fdgets(c,c->local_rfd.fd, line)) >= 0) && + (strlen(line) >= 8) && + (!line[8] || isspace(line[8])) && + (strncasecmp(line,"starttls",8) == 0) ) { + if (!line[8]) { + /* Technically, we should shut down the SMTP connection and get + * a new one, but screw it for now. */ + if(fdputs(c,c->local_wfd.fd, "220 Go ahead") >= 0) + return 0; + } else { + fdputs(c,c->local_wfd.fd,"501 Syntax error (no parameters allowed; STARTTLS disabled) (#5.5.4)"); + return -1; + } + + } } - if(fdprintf(c, c->local_wfd.fd, "250-%s Welcome", line)<0) - return -1; - if(fdprintf(c, c->local_wfd.fd, "250 STARTTLS")<0) - return -1; - if(fdscanf(c, c->local_rfd.fd, "STARTTLS", line)<0) { - log(LOG_ERR, "STARTTLS expected"); - return -1; + if (read_ret < 0) + return -1; + if (c->opt->option.revertible) { + if (fdputs(c,c->remote_fd.fd,line) < 0) + return -1; + exit(plaintext_proxy(c)); + } + else { + fdputs(c,c->local_wfd.fd,"421 Encryption required; must use STARTTLS"); + return -1; } @@ -184,22 +337,145 @@ static int pop3_server(CLI *c) { - if(fdscanf(c, c->remote_fd.fd, "%[^\n]", line)<0) + if(fdscanf(c, c->remote_fd.fd, "%[^\r\n]", line)<0) return -1; + if(fdprintf(c, c->local_wfd.fd, "%s + stunnel", line)<0) return -1; - if(fdscanf(c, c->local_rfd.fd, "%[^\n]", line)<0) + + while(1) { + if(fdgets(c,c->local_rfd.fd, line)<0) + return -1; + if(strncasecmp(line, "CAPA", 4) == 0) { /* Client wants RFC 2449 extensions */ + fdputs(c,c->remote_fd.fd,line); + if (fdgets(c,c->remote_fd.fd,line) < 0) + return -1; + if (line[0] == '+') { + if (fdputs(c,c->local_wfd.fd,line) < 0) + return -1; + while ( (fdgets(c,c->remote_fd.fd,line) >= 0) + && ( (strlen(line) > 1) || (line[0] != '.') ) ) { + if (fdputs(c,c->local_wfd.fd,line) < 0) + return -1; + } + } else { + if (fdputs(c,c->local_wfd.fd,"+OK Stunnel capability list follows") < 0) + return -1; + } + if (fdputs(c,c->local_wfd.fd,"STLS") < 0) + return -1; + if (fdputs(c,c->local_wfd.fd,".") < 0) + return -1; + } else if (strncasecmp(line, "STLS", 4) == 0) { + if(fdputs(c,c->local_wfd.fd, "+OK stunnel starting TLS negotiation")<0) + return -1; + return 0; + } + else { + if (fdputs(c,c->remote_fd.fd,line) < 0) + return -1; + if (c->opt->option.revertible) + exit(plaintext_proxy(c)); + else { + fdputs(c,c->local_wfd.fd,"-Encryption required; must use STLS"); + return -1; + } + } + } + return -1; +} + +static int imap_client(CLI *c) { + char line[STRLEN]; + + if(fdgets(c,c->remote_fd.fd, line)<0) return -1; - if(!strncmp(line, "CAPA", 4)) { /* Client wants RFC 2449 extensions */ - if(fdprintf(c, c->local_wfd.fd, "-ERR Stunnel does not support capabilities")<0) - return -1; - if(fdscanf(c, c->local_rfd.fd, "%[^\n]", line)<0) - return -1; + if(strncasecmp(line,"* OK ",4)) { + log(LOG_ERR, "Unknown server welcome"); } - if(strncmp(line, "STLS", 4)) { - log(LOG_ERR, "Client does not want TLS"); + if(fdputs(c,c->local_wfd.fd, line)<0) + if(fdprintf(c,c->remote_fd.fd, "a STARTTLS")<0) + return -1; + if(fdgets(c,c->remote_fd.fd, line)<0) + return -1; + if(strncasecmp(line,"a OK ",5)) { + log(LOG_ERR, "Server does not support TLS"); return -1; } - if(fdprintf(c, c->local_wfd.fd, "+OK Stunnel starts TLS negotiation")<0) + return 0; +} + +static int imap_server(CLI *c) { + char line[STRLEN]; + char procline[STRLEN]; + char *tag, *cmd; + int do_if_fail = 0; + + if (fdgets(c,c->remote_fd.fd,line) < 0) + return -1; + + if(fdprintf(c,c->local_wfd.fd, "%s + stunnel", line)<0) return -1; - return 0; + while(1) { + if(fdgets(c,c->local_rfd.fd, line)<0) + return -1; + safecopy(procline, line); + tag = procline; + if (!(cmd=strchr(procline,' '))) { + do_if_fail = 1; + break; + } + *cmd='\0'; + cmd++; + if(strncasecmp(cmd, "CAPABILITY", 11) == 0) { + fdputs(c,c->remote_fd.fd,line); + if (fdgets(c,c->remote_fd.fd,line) < 0) + return -1; + if (strncasecmp(line,"* CAPABILITY",12) == 0) { + safeconcat(line," STARTTLS"); + fdputs(c,c->local_wfd.fd,line); + } else { + do_if_fail = 2; + break; + } + if (fdgets(c,c->remote_fd.fd,line) < 0) + return -1; + if ( (strncasecmp(line,tag,strlen(tag)) != 0) || + (line[strlen(tag)] != ' ') || + (strncasecmp(line+strlen(tag)+1,"OK",2) != 0) ) + { + do_if_fail = 2; + break; + } + if (fdputs(c,c->local_wfd.fd,line) < 0) + return -1; + } + else if (strncasecmp(cmd, "STARTTLS", 8) == 0) { + if (fdwrite(c,c->local_wfd.fd,tag,strlen(tag)) < 0) + return -1; + if (fdwrite(c,c->local_wfd.fd," ",1) < 0) + return -1; + if (fdputs(c,c->local_wfd.fd,"OK Begin TLS negotiation now") < 0) + return -1; + + return 0; + } else { + do_if_fail = 1; + break; + } + } + + if (c->opt->option.revertible) { + if (do_if_fail == 1) { + if (fdputs(c,c->remote_fd.fd,line) < 0) + return -1; + } else if (do_if_fail == 2) { + if (fdputs(c,c->local_wfd.fd,line) < 0) + return -1; + } + exit(plaintext_proxy(c)); + } + else { + fdputs(c,c->local_wfd.fd,"-Encryption required; must use STLS"); + return -1; + } } --- src/prototypes.h 2004-01-21 04:35:13.000000000 -0500 +++ src/prototypes.h 2004-01-21 04:35:30.000000000 -0500 @@ -159,2 +159,3 @@ typedef struct local_options { char *remote_address; + int use_fd; int timeout_busy; /* Maximum waiting for data time */ @@ -171,2 +172,4 @@ typedef struct local_options { unsigned int remote:1; + unsigned int fdforward:1; + unsigned int revertible:1; #ifndef USE_WIN32 @@ -215,2 +218,3 @@ typedef struct { LOCAL_OPTIONS *opt; + int allow_downgrade; char accepting_address[16], connecting_address[16]; /* Dotted-decimal */ @@ -272,2 +276,5 @@ int fdscanf(CLI *, int, const char *, ch #endif +int fdwrite(CLI *, int, char *, int); +int fdgets(CLI *, int, char *); +int fdputs(CLI *, int, char *); --- src/sselect.c 2004-01-21 04:35:13.000000000 -0500 +++ src/sselect.c 2004-01-21 04:35:30.000000000 -0500 @@ -183,7 +183,34 @@ void exec_status(void) { /* dead local ( +int fdwrite(CLI *c, int fd, char *buffer, int len) { + char logline[STRLEN]; + int ptr, written, towrite; + + for(ptr=0, towrite=len; towrite>0; ptr+=written, towrite-=written) { + if(waitforsocket(fd, 1 /* write */, c->opt->timeout_busy)<1) + return -1; + written=writesocket(fd, buffer+ptr, towrite); + if(written<0) { + sockerror("writesocket (fdwrite)"); + return -1; + } + } + safecopy(logline, buffer); + safestring(logline); + log(LOG_DEBUG, " -> %s", logline); + return 0; +} + +int fdputs(CLI *c, int fd, char *line) { + char crlf[]="\r\n"; + + if (fdwrite(c, fd, line, strlen(line)) < 0) + return -1; + return fdwrite(c,fd,crlf,strlen(crlf)); +} + int fdprintf(CLI *c, int fd, const char *format, ...) { va_list arglist; - char line[STRLEN], logline[STRLEN]; + char line[STRLEN]; char crlf[]="\r\n"; - int len, ptr, written, towrite; + int len; @@ -198,44 +225,46 @@ int fdprintf(CLI *c, int fd, const char len+=2; - for(ptr=0, towrite=len; towrite>0; ptr+=written, towrite-=written) { - if(waitforsocket(fd, 1 /* write */, c->opt->timeout_busy)<1) - return -1; - written=writesocket(fd, line+ptr, towrite); - if(written<0) { - sockerror("writesocket (fdprintf)"); - return -1; - } - } - safecopy(logline, line); - safestring(logline); - log(LOG_DEBUG, " -> %s", logline); - return len; + + if (fdwrite(c,fd,line,len)<0) + return -1; + else + return len; } -int fdscanf(CLI *c, int fd, const char *format, char *buffer) { - char line[STRLEN], logline[STRLEN]; +int fdgets(CLI *c, int fd, char *buffer) { + char logline[STRLEN]; int ptr; - for(ptr=0; ptropt->timeout_busy)<1) return -1; - switch(readsocket(fd, line+ptr, 1)) { + switch(readsocket(fd, buffer+ptr, 1)) { case -1: /* error */ - sockerror("readsocket (fdscanf)"); + sockerror("readsocket (fdgets)"); return -1; case 0: /* EOF */ - log(LOG_ERR, "Unexpected socket close (fdscanf)"); + log(LOG_ERR, "Unexpected socket close (fdgets)"); return -1; } - if(line[ptr]=='\r') + if(buffer[ptr]=='\r') continue; - if(line[ptr]=='\n') + if(buffer[ptr]=='\n') break; + ptr++; } - line[ptr]='\0'; - safecopy(logline, line); + buffer[ptr]='\0'; + safecopy(logline, buffer); safestring(logline); log(LOG_DEBUG, " <- %s", logline); - return sscanf(line, format, buffer); + return 0; } +int fdscanf(CLI *c, int fd, const char *format, char *buffer) { + char line[STRLEN]; + + if (fdgets(c, fd, line) < 0) + return -1; + + return sscanf(line, format, buffer); +} + /* End of select.c */