/* * Copyright (c) 2004-2007, OpenFWTK Development Group * All rights reserved. See LICENSE. */ /* * FTP gateway * * (C) Copyright 2004 ArkanoiD * (C) Copyright 2004 Alexei Kravchuk * (C) Copyright 2001 Matthew Kirkwood * based on ftp proxy from "fk" toolkit * * $Id: ftp.c,v 1.32 2007/10/10 19:11:10 arkenoi Exp $ */ /* * Does this impose far too simplistic a state machine? * * Only time will tell... */ /* XXX - search for 999 to find missing response numbers */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "line.h" #include "telnet.h" #include "smftp.h" #include #include "firewall2.h" #include "fwfunc.h" #include "auth.h" #include "milters.h" #include "c-type.h" #include "transaction.h" #define TEMPFILE_TEMPL "/tmp/ftp-gw.XXXXXX" #define FAILED -1 #define REFUSED -2 static int authenticated = 0; static int authopened = 0; static int established = 0; extern magic_t proxy_magic; static Cfg *confp; static char authuser[AUTH_USIZ+1]; static char separator[2] = "@"; static int user_limit = AUTH_USIZ - 1; static int pass_limit = AUTH_PWSIZ - 1; static int ccdscp = 0; static int cddscp = IPTOS_THROUGHPUT; static int scdscp = 0; static int sddscp = IPTOS_THROUGHPUT; static int transparent = 0; static int extendperm = 0; static char **validests = (char **)0; static char **validusers = (char **)0; static int authallflg = 0; static char username[AUTH_USIZ+1]; static int nocheck_data = 0; static char helpfile [MAX_STR] = ""; static char denydestfile[MAX_STR] = ""; static char denyfile [MAX_STR] = ""; static char welcomefile [MAX_STR] = ""; unsigned int buf_limit = 256; int server_fd = -1; time_t last_keepalive_notification_time = 0; int keepalive_timeout_val = 10; char *tmpfilename = NULL; typedef struct { int reversed_streams; int *ftpstate; char ftp_msg[MAX_STR]; int *need_noop_resp; int client_cntl_fd; int server_cntl_fd; } keepalive_args; typedef void (*progress_callback)(void *); void keepalive_progress_callback(keepalive_args *keepalive_args); static fwparm options[] = { { FWPARM_BOOL, "-transparent", (char*) &transparent }, { FWPARM_BOOL, "-authall", (char*) &authallflg }, { FWPARM_BOOL, "-extnd", (char*) &extendperm }, { FWPARM_CHAR, "-separator", separator }, { FWPARM_LIST, "-dest", (char*) &validests }, { FWPARM_LIST, "-user", (char*) &validusers }, { FWPARM_STRING, "-plug-to", proxy_stats.dst }, { FWPARM_PORT, "-port", (char *)&proxy_stats.port }, { FWPARM_STRING, "-authuser", proxy_stats.authuser }, { FWPARM_OPLOG, "-log", (char*) &transaction_descr }, { FWPARM_OPDENY, "-deny", (char*) &transaction_descr }, { FWPARM_DSCP, "-clientcmd-dscp", (char*) &ccdscp }, { FWPARM_DSCP, "-clientdata-dscp", (char*) &cddscp }, { FWPARM_DSCP, "-servercmd-dscp", (char*) &scdscp }, { FWPARM_DSCP, "-serverdata-dscp", (char*) &sddscp }, { FWPARM_INT, "-keepalive-timeout", (char*) &keepalive_timeout_val }, { FWPARM_BOOL, "-nocheck-data", (char*) &nocheck_data }, { 0, 0, 0 } }; /* globals */ static struct sockaddr_in clientaddr, servaddr; /* data connection handles */ static struct connstate { /* client/NEEDCONN => PORT connections * client/LISTEN => PASV * server/NEEDCONN => PASV * server/LISTEN => PORT * (but we don't have to worry about these details * hugely) */ enum { OFF, NEEDCONN, LISTEN, CONNECTED, READ, WRITE } state; /* -1 => no connection/listening socket made */ int fd; /* Address to connect to (only interesting for NEEDCONN) */ struct sockaddr_in addr; } clientconn = { OFF, -1, }, serverconn = { OFF, -1, }; /* Deal with data connections */ static int make_data_conn(struct connstate *conn); static void cleanup_conn(struct connstate *conn); static int rdwr(struct connstate *c1, struct connstate *c2, int client_cntl_fd, int server_cntl_fd, int *alldone, int *ftpstate, char **ftp_msg); static int authsrv_auth(char *resp); static int authsrv_resp(char *resp); int parse_user_cmd(char *buffer,char *user,char *server, unsigned int *port); static int negotiate_server_connection(int fd, int *res, struct connstate *conn); static void port_snprintf(char *buf, size_t len, const char *fmt, struct sockaddr_in *sin); static int parse_port_string(const char *ln, struct sockaddr_in *sin); int strwritev(int ofd, int count, ...); void puts_fd(int fd,char *line); int send_file_msg(int fd,char *fn,int code); void relay_ftp_response(int ifd, int ofd); int getline(int fd,unsigned char *buf,int siz); static int net_send(int s, char *buf, int len, int flags); int send_file_chunk(int o_fd, int tmp_fd); void start_keepalive_timeout(); int keepalive_timeout(); void end_keepalive_timeout(); enum direction { DOWNLOAD, UPLOAD }; enum state { USER, PASS, ACCT, ESTABLISHED, TRANSFER, TXFRDONE, QUIT, QUITONLY }; enum state state = USER; int keepalive_notification(int server_fd, int client_fd, int reversed_streams, int *need_noop_resp); /* general gunk */ static ssize_t tn_recvline(int i, char *line, int maxsize, int fl); static int __read_ftp_response(int ifd, char *ln, int maxsize); static int read_ftp_response(int ifd); /* option parsing */ char *hostname = NULL, *servname = NULL, *bindaddr = NULL; int idletimeout = 0, linger = -1, transp = 0; char *file = NULL; int readonly = 0; static int prefer_server_port = 0; static int refuse_server_port = 0, refuse_server_pasv = 0; static struct portrange { int min, max; } server_port_range = { IPPORT_RESERVED, (2<<16)-1 }, client_pasv_range = { IPPORT_RESERVED, (2<<16)-1 }; static int allocate_listening_socket(int ifd, struct sockaddr_in *psin, struct portrange *ran); int tmp_fd = -1; char *fname2stor; /* General FTP things */ static ssize_t tn_recvline(int i, char *line, int maxsize, int fl) { int res; res = recvline(i, line, maxsize, fl); if(res > 0) unparse_telnet_string(line, i, fl); return res; } static int __read_ftp_response(int ifd, char *ln, int maxsize) { return __read_smftp_response(ifd, ln, maxsize, &tn_recvline); } static int read_ftp_response(int ifd) { return read_smftp_response(ifd, &tn_recvline); } /* Specific FTP commands */ /*static int __handle_noarg(int server_fd, const char *cmd) { return ____handle_noarg(server_fd, cmd, read_ftp_response); }*/ static int __handle_noarg_resp(int server_fd, const char *cmd, char *ftp_resp, int max_len) { return ____handle_noarg_resp(server_fd, cmd, __read_ftp_response, ftp_resp, max_len); } static int __handle_1str(int server_fd, const char *cmd, const char *arg) { return ____handle_1str(server_fd, cmd, arg, read_ftp_response); } static int __handle_1str_resp(int server_fd, const char *cmd, const char *arg, char *ftp_resp, int max_len) { return ____handle_1str_resp(server_fd, cmd, arg, __read_ftp_response, ftp_resp, max_len); } static int __handle_txfr(int ifd, int server_fd, int cmdres, char *server_resp, enum state *state, enum direction dir) { struct connstate *up, *down; /* XXX -- check that we have an OK client data setup */ if(cmdres != 150) goto err_out; server_resp = NULL; if(dir == DOWNLOAD) { up = &serverconn; down = &clientconn; } else { down = &serverconn; up = &clientconn; } if(make_data_conn(up)) goto err_out; /* To confuse you further, these are from the client's PoV */ up->state = WRITE; shutdown(up->fd, SHUT_WR); *state = TRANSFER; send_msg(0, 150, dir == DOWNLOAD, dir == DOWNLOAD ? "Data connection for reading setup OK" : "Data connection setup OK"); return 0; err_out: puts_fd(0, server_resp ? server_resp : "426 Connection setup failed"); return -1; } int do_quit(command,flags,argc,argv) char* command; int flags; int argc; char **argv; { proxy_exit(); return(0); } int do_n_args(command,flags,argc,argv, nargs) char* command; int flags; int argc; char **argv; int nargs; { if (argc != nargs + 1) { syslog(LLEV,"%.100s command syntax error %d",command,argc); send_msg(0, 500, 0, "syntax error"); return(0); } else { if(!established) { send_msg(0, 530, 0, "Login first with USER and PASS"); return 1; } puts_fd(server_fd, command); relay_ftp_response(server_fd, 0); } return(0); } int do_noargs(command,flags,argc,argv) char* command; int flags; int argc; char **argv; { return do_n_args(command,flags,argc,argv, 0); } int do_1arg(command,flags,argc,argv) char* command; int flags; int argc; char **argv; { return do_n_args(command,flags,argc,argv, 1); } int do_1_noargs(command,flags,argc,argv) char* command; int flags; int argc; char **argv; { if (argc > 2) { syslog(LLEV,"%.100s command syntax error %d",command,argc); send_msg(0, 500, 0, "syntax error"); return(0); } else return do_n_args(command,flags,argc,argv, argc - 1); } int do_type(command,flags,argc,argv) char* command; int flags; int argc; char **argv; { if (argc < 2 || argc > 3) { syslog(LLEV,"%.100s command syntax error %d",command,argc); send_msg(0, 500, 0, "syntax error"); return(0); } else return do_n_args(command,flags,argc,argv, argc - 1); } int do_mode(command,flags,argc,argv) char* command; int flags; int argc; char **argv; { if (argc != 2) { syslog(LLEV,"%.100s command syntax error %d",command,argc); send_msg(0, 500, 0, "syntax error"); return(0); } else if(strcasecmp(argv[1], "s")) { syslog(LLEV,"command MODE: Unimplemeted MODE type"); send_msg(0, 502, 0, "Unimplemeted MODE type"); return(0); } else { return do_1arg(command,flags,argc,argv); } } int do_help(command,flags,argc,argv) char* command; int flags; int argc; char **argv; { if(server_fd == -1) { if(*helpfile != '\0') { if(send_file_msg(0,helpfile,220)) { syslog(LLEV,"fwtksyserr: cannot display help %.512s: %s",helpfile,strerror(errno)); send_msg(0, 500, 0, "cannot display help"); return(0); } return(0); } else { send_msg(0,214, 0, "no help available"); return 0; } } else { return do_1_noargs(command, flags, argc, argv); } } int do_rein(command,flags,argc,argv) char* command; int flags; int argc; char **argv; { int res; char server_resp[MAX_STR]; if (argc != 1) { syslog(LLEV,"%.100s command syntax error %d",command,argc); send_msg(0, 500, 0, "syntax error"); return(0); } else { if(!established) { send_msg(0, 530, 0, "Login first with USER and PASS"); return 1; } if((res = __read_ftp_response(server_fd, server_resp, buf_limit)) == 120) { res = __read_ftp_response(server_fd, server_resp, buf_limit); } if(res == 220) { send_msg(0, 230, 0, "REIN OK"); state = USER; established = 0; } else { puts_fd(0, server_resp); } } return 0; } int do_generic(command,flags,argc,argv) char* command; int flags; int argc; char **argv; { if(!established) { send_msg(0, 530, 0, "Login first with USER and PASS"); return 1; } puts_fd(server_fd, command); relay_ftp_response(server_fd, 0); return(0); } int do_stat_abor(command,flags,argc,argv) char* command; int flags; int argc; char **argv; { unsigned char kbuf[4]; kbuf[0] = IAC; kbuf[1] = IP; kbuf[2] = IAC; if(net_send(server_fd,(char *)kbuf,3,MSG_OOB) != 3) return(0); kbuf[0] = DM; if(net_send(server_fd,(char *)kbuf,1,0) != 1) return(0); puts_fd(server_fd, command); /* handle STAT or ABOR */ relay_ftp_response(server_fd, 0); return(0); } int do_user(command,flags,argc,argv) char* command; int flags; int argc; char **argv; { int res; socklen_t slen; int ftpstate; char msg[MAX_STR]; char server_resp[MAX_STR]; if(!authenticated && authallflg) { authsrv_auth(argv[1]); state = PASS; return 0; } else authenticated = 1; if (parse_user_cmd(argv[1],username,proxy_stats.dst, &proxy_stats.port) != 0) { send_msg(0,501, 0, "Use USER user@site to connect via proxy"); return 1; } if(server_fd <= 0) { char err_msg[MAX_STR]; if (proxy_check_dest(validests,extendperm) || searchlist(username,validusers)) { char mbuf[MAX_STR]; if(*denydestfile != '\0') { if(send_file_msg(0,denydestfile,500)) { syslog(LLEV,"fwtksyserr: cannot display denydest-msg %.512s: %s",denydestfile,strerror(errno)); send_msg(0,500, 0, "Can't display denydest-msg file."); proxy_exit(); } else { return(1); } } snprintf(mbuf,sizeof(mbuf),"Permission denied for user %.100s to connect to %.512s",proxy_stats.authuser,proxy_stats.dst); send_msg(0, 500, 0, mbuf); return(1); } proxy_update_operation("CONNECTING"); if((server_fd = conn_server(proxy_stats.dst,proxy_stats.port,0,err_msg)) < 0) { syslog(LLEV,"cannot connect to server %s/%d: %s", proxy_stats.dst,proxy_stats.port, err_msg); send_msg(0,500, 0, "FTP gateway destination connect failed"); proxy_exit(); } /* Remember who we're talking to.. */ if(getpeername(0, (struct sockaddr*)&clientaddr, (slen=sizeof(clientaddr),&slen)) < 0) { syslog(LLEV, "fwtksyserr: Can't get client address: %s", strerror(errno)); send_msg(0, 500, 0, "Can't get client address."); proxy_exit(); } if((server_fd>=0) && getpeername(server_fd, (struct sockaddr*)&servaddr, (slen=sizeof(servaddr),&slen)) < 0) { syslog(LLEV, "fwtksyserr: Can't get server address: %s", strerror(errno)); send_msg(0,500, 0, "Can't get server address."); proxy_exit(); } /* on connection, the server must say hello */ ftpstate = (server_fd < 0) ? 500 : __read_ftp_response(server_fd, server_resp, buf_limit); if(DIG1(ftpstate) != 2) { state = QUITONLY; switch(server_fd) { case FAILED: send_msg(0, 500, 0, "Connection to server failed"); syslog(LLEV, "Connection to server failed"); break; case REFUSED: send_msg(0, 500, 0, "Use of proxy refused"); syslog(LLEV, "Use of proxy refused"); break; default: send_msg(0, ftpstate, 0, "Server denied access"); syslog(LLEV, "Server denied access"); } return 1; } else { proxy_update_operation("AUTHENTICATION"); } } snprintf(msg, sizeof(msg), "Connected to ftp server (%s)", server_resp); res = __handle_1str(server_fd, "USER", argv[1]); if((res == 331) || (res == 332)) { send_msg(0, 331, 1, msg); send_msg(0, 331, 0, "Username OK, send password"); state = PASS; } else if(res == 230) { send_msg(0, 230, 1, msg); send_msg(0, 230, 0, "Logged in OK, no password needed"); state = ESTABLISHED; established = 1; if (scdscp) proxy_set_dscp(server_fd,scdscp); } else { send_msg(0, res, 0, "Apparently not"); /* XXX */ return 1; } return 0; } int do_pass(command,flags,argc,argv) char* command; int flags; int argc; char **argv; { int res; if(state != PASS) { send_msg(0, 530, 0, "Login first with USER"); return 1; } if(authenticated || !authallflg) { if(argv[1] && strlen(argv[1]) > pass_limit) buffer_overrun("do_pass", NULL, pass_limit); res = __handle_1str(server_fd, "PASS", argv[1]); if(res == 230) { send_msg(0, 230, 0, "Logged in OK"); state = ESTABLISHED; established = 1; if (scdscp) proxy_set_dscp(server_fd,scdscp); } else if(res == 202) { send_msg(0, 202, 0, "Not neccessary, never mind"); state = ESTABLISHED; established = 1; if (scdscp) proxy_set_dscp(server_fd,scdscp); } else send_msg(0, res, 0, "Apparently not"); /* XXX */ return 0; } else if (strlen(command) < 6) { syslog(LLEV,"%.100s command syntax error %d",command,argc); send_msg(0, 500, 0, "syntax error"); return(0); } else { authsrv_resp(&command[5]); state = USER; return 0; } } int do_pasv(command,flags,argc,argv) char* command; int flags; int argc; char **argv; { int sock = -1; #define PASVLEN 1000 /* XXX - ick */ char pasvbuf[PASVLEN+1]; pasvbuf[PASVLEN] = 0; if((sock = allocate_listening_socket(server_fd, &clientconn.addr, &client_pasv_range)) < 0) { send_msg(0, 999, 0, "System error"); if(sock != -1) close(sock); return 1; } clientconn.state = LISTEN; if(clientconn.fd != -1) close(clientconn.fd); clientconn.fd = sock; /* XXX - ick */ port_snprintf(pasvbuf, PASVLEN, "Passive OK (%d,%d,%d,%d,%d,%d)", &clientconn.addr); send_msg(0, 227, 0, pasvbuf); return 0; } int do_list(command,flags,argc,argv) char* command; int flags; int argc; char **argv; { int res; char server_resp[MAX_STR]; if(!established) { send_msg(0, 530, 0, "Login first with USER and PASS"); return 1; } if(negotiate_server_connection(server_fd, &res, &serverconn)) { send_msg(0, res, 0, "Server connection failed"); return -1; } res = argc >= 2 ? __handle_1str_resp(server_fd,argv[0] , argv[1], server_resp, buf_limit) : __handle_noarg_resp(server_fd, argv[0], server_resp, buf_limit); return __handle_txfr(0, server_fd, res, server_resp, &state, DOWNLOAD); } int do_retr(command,flags,argc,argv) char* command; int flags; int argc; char **argv; { int res; char server_resp[MAX_STR]; if(!established) { send_msg(0, 530, 0, "Login first with USER and PASS"); return 1; } if(negotiate_server_connection(server_fd, &res, &serverconn)) { send_msg(0, res, 0, "Server connection failed"); return -1; } start_keepalive_timeout(); return __handle_txfr(0, server_fd, __handle_1str_resp(server_fd, "RETR", argv[1], server_resp, buf_limit), server_resp, &state, DOWNLOAD); } int do_stor(command,flags,argc,argv) char* command; int flags; int argc; char **argv; { int res; if(!established) { send_msg(0, 530, 0, "Login first with USER and PASS"); return 1; } if(negotiate_server_connection(server_fd, &res, &serverconn)) { send_msg(0, res, 0, "Server connection failed"); return -1; } start_keepalive_timeout(); /* Postpone STOR till after (possible) milter data check * because the server can close the data connection due to timeout expiration */ fname2stor = xstrdup(argv[1]); return __handle_txfr(0, server_fd, 150, NULL, &state, UPLOAD); } int do_port(command,flags,argc,argv) char* command; int flags; int argc; char **argv; { struct sockaddr_in sin; if(!established) { send_msg(0, 530, 0, "Login first with USER and PASS"); return 1; } if(parse_port_string(argv[1], &sin)) return -1; if(sin.sin_addr.s_addr != clientaddr.sin_addr.s_addr) { send_msg(0, 999, 0, "Connection to wrong address refused"); return -1; } /* cool, got address */ cleanup_conn(&clientconn); clientconn.state = NEEDCONN; clientconn.addr = sin; send_msg(0, 200, 0, "OK"); return 0; } int main(int argc, char **argv) { size_t bytes = 0; char *line = NULL; int ftpstate; char *tmp; char buffer[MAX_STR]; char pass[AUTH_PWSIZ]; char tokbuf[MAX_STR]; char *tokav[MAX_ARG]; int tokac; proxy_init(argc,argv); proxy_chroot_setugid(); if (ccdscp) proxy_set_dscp(0,ccdscp); if((tmp = proxy_conf_string(proxy_confp, "help-msg"))) { strncpy(helpfile, tmp, sizeof(helpfile) - 1); helpfile[sizeof(helpfile) - 1] = '\0'; } if((tmp = proxy_conf_string(proxy_confp, "denial-msg"))) { strncpy(denyfile, tmp, sizeof(denyfile) - 1); denyfile[sizeof(denyfile) - 1] = '\0'; } if((tmp = proxy_conf_string(proxy_confp, "denydest-msg"))) { strncpy(denydestfile, tmp, sizeof(denydestfile) - 1); denydestfile[sizeof(denydestfile) - 1] = '\0'; } if((tmp = proxy_conf_string(proxy_confp, "welcome-msg"))) { strncpy(welcomefile, tmp, sizeof(welcomefile) - 1); welcomefile[sizeof(welcomefile) - 1] = '\0'; } if ((confp=proxy_conf_hosts(proxy_confp,proxy_stats.rladdr, proxy_stats.riaddr))) proxy_parse_options(confp,options); else { if(*denyfile != '\0') { if(send_file_msg(0,denyfile,500)) { syslog(LLEV,"fwtksyserr: cannot display denial-msg %.512s: %s",denyfile,strerror(errno)); } } else { snprintf(buffer,sizeof(buffer),"500 %s/%s not authorized to use FTP proxy",proxy_stats.rladdr,proxy_stats.riaddr); send_msg(0, 500, 0, buffer); } proxy_exit(); } user_limit = proxy_conf_int(proxy_confp,"user-limit",1,sizeof(username)-1,user_limit); pass_limit = proxy_conf_int(proxy_confp,"pass-limit",1,sizeof(pass)-1,pass_limit); buf_limit = proxy_conf_int(proxy_confp,"buf-limit",1,sizeof(buffer)-1,buf_limit); sotimeout(proxy_timeout); memset(buffer, 0, sizeof(buffer)); memset(username, 0, sizeof(username)); memset(pass, 0, sizeof(pass)); if (transparent) { /* * We do not check if port number is permitted because it comes * from redirection rule we defined manually anyways. */ proxy_get_transparent_dst(proxy_stats.dst,&proxy_stats.port); if (proxy_check_dest(validests,extendperm)) { char mbuf[MAX_STR]; if(*denydestfile != '\0') { if(send_file_msg(0,denydestfile,500)) { syslog(LLEV,"fwtksyserr: cannot display denydest-msg %.512s: %s",denydestfile,strerror(errno)); } else { return(1); } } snprintf(mbuf,sizeof(mbuf),"Permission denied to connect to %.512s",proxy_stats.dst); send_msg(0, 500, 0, mbuf); proxy_exit(); } } if(authallflg) send_msg(0, 220, 1, "Proxy first requires authentication"); if(*welcomefile != '\0') { if(send_file_msg(0,welcomefile,220)) { syslog(LLEV,"fwtksyserr: cannot display welcome %.512s: %s",welcomefile,strerror(errno)); proxy_exit(); } } else { send_msg(0, 220, 0, "FTP Proxy ready"); } while(1) { int msg_int; const struct hash_entry *he; int (*handler)(char*,int,int,char**); char *ftp_msg = NULL; int txfrdone = 0; if(state == TRANSFER) { if(idletimeout > 0) alarm(0); bytes += rdwr(&clientconn, &serverconn, 0, server_fd, &txfrdone, &ftpstate, &ftp_msg); if(idletimeout > 0) alarm(idletimeout); if(txfrdone) state = TXFRDONE; } if(state == TXFRDONE) { int ftp_resp; char server_resp[MAX_STR]; ftp_resp = __read_ftp_response(server_fd, server_resp, buf_limit); if(ftpstate == -1) puts_fd(0, server_resp); else send_msg(0, ftpstate, 0, ftp_msg ? ftp_msg : DIG1(ftpstate) == 2 && txfrdone == 1 ? "OK, done, I think" : "Error occured"); cleanup_conn(&clientconn); cleanup_conn(&serverconn); if(tmpfilename != NULL) { unlink(tmpfilename); free(tmpfilename); tmpfilename = NULL; } state = ESTABLISHED; if(ftp_msg) { free(ftp_msg); ftp_msg = NULL; } } if(established) proxy_update_operation("IDLE"); /* read command */ if(getline(0, (unsigned char*) buffer, buf_limit) < 0) { syslog(LLEV,"error reading client data: %s",strerror(errno)); proxy_exit(); } tokac = enargv(buffer,tokav,56,tokbuf,sizeof(tokbuf)); if(!tokav[0]) { send_msg(0, 502, 0, "unimplemented"); if(state != QUITONLY) syslog(LLEV, "read non-command from client; line = %s", line); continue; } if((state == USER || state == PASS) && strcasecmp(tokav[0], "USER") && strcasecmp(tokav[0], "PASS") && strcasecmp(tokav[0], "QUIT") && strcasecmp(tokav[0], "HELP")) { send_msg(0, 530, 0, "Login first with USER and PASS"); continue; } if (!(he = find_he2(&transaction_descr,tokav[0],strlen(tokav[0])))) { syslog(LLEV,"unknown ftp command %.100s",buffer); send_msg(0, 502, 0, "command unknown or out of state"); continue; } else { if(established) proxy_update_operation(buffer); if (he->flags & OP_LOG) syslog(LLEV,"%.512s/%.20s: %.512s",proxy_stats.rladdr,proxy_stats.riaddr,buffer); if (he->flags & OP_DENY) { syslog(LLEV,"deny host=%.512s/%.20s operation %.100s",proxy_stats.rladdr,proxy_stats.riaddr,buffer); send_msg(0, 530, 0, "operation denied"); continue; } if(extendperm && (he->flags & OP_XTND)) { msg_int = auth_perm(proxy_confp,proxy_stats.authuser,"ftp-gw",proxy_stats.riaddr,tokav); if(msg_int == 1 || msg_int == -1) { syslog(LLEV,"deny host=%.512s/%.20s operation %.100s - extended permissions",proxy_stats.rladdr,proxy_stats.riaddr,buffer); send_msg(0, 530, 0, "operation denied"); continue; } } handler = he->handler; assert (TRANSACTION_HANDLER (handler)); handler(buffer,he->flags,tokac,tokav); continue; } } /* * Unreached */ proxy_exit(); return 0; } /* Misc stuff */ static int rdwr(struct connstate *c1, struct connstate *c2, int client_cntl_fd, int server_cntl_fd, int *done, int *ftpstate, char **ftp_msg) { int c, mfd = 0; int i, o; fd_set ifds, ofds, exfds; #define BUFLEN 4096 /* Is this adequate? */ char buf[BUFLEN]; ssize_t r, w; int reversed_streams = 0; int need_noop_resp = 0; static int sent_chunk_bytes = 0, sent_bytes = 0, rcvd_bytes = 0; int w_total, w_chunk; int ftp_resp; char** milters = NULL; char* magic_ctype = NULL; keepalive_args *keepalive_args; *ftpstate = -1; /* before reading NOOP responses (server keepalive notification) a final RETR command response should * be read. It is stored in this variable */ *ftp_msg = NULL; /* final RETR command response is stored in this variable */ if(c2->state != WRITE) { struct connstate *t; t = c1; c1 = c2; c2 = t; reversed_streams = 1; } if((c1->state != READ) && (c2->state != WRITE)) { syslog(LOG_ERR,"securityalert: wrong state of peers in rdwr: (%d, %d)", c1->state, c2->state); send_msg(0,500, 0, "wrong state of peers"); proxy_exit(); } o = c1->fd; i = c2->fd; *done = 0; c = 0; for(;;) { FD_ZERO(&ifds); FD_SET(client_cntl_fd, &ifds); if(need_noop_resp) FD_SET(server_cntl_fd, &ifds); /* check for input from the ftp server only if NOOP has been sent * as a keepalive notification, so in this case a response should be read out */ FD_ZERO(&ofds); if(c2->state != WRITE && tmp_fd != -1) { /* check output data connection status only if input */ FD_SET(o, &ofds); /* is closed and there is open tmp file */ mfd = o; } else if(c2->state == WRITE) { /* check input data connection if it has an appropriate state */ FD_SET(i, &ifds); mfd = i; } else { syslog(LLEV, "fwtkerr: Server data connection is incorrect"); proxy_exit(); } /* quit if either of intfds becomes exceptional */ FD_ZERO(&exfds); FD_SET(client_cntl_fd, &exfds); FD_SET(server_cntl_fd, &exfds); if(client_cntl_fd > mfd) mfd = client_cntl_fd; if(server_cntl_fd > mfd) mfd = server_cntl_fd; if(select(mfd+1, &ifds, &ofds, &exfds, NULL) < 0) { syslog(LLEV, "fwtkerrsys: select() failed: %s", strerror(errno)); proxy_exit(); } if(need_noop_resp && FD_ISSET(server_cntl_fd, &ifds)) { /* read out a response to the NOOP command sent to the ftp server * as a keepalive notification */ if(*ftpstate == -1 && !reversed_streams) { /* first response read is a final RETR response */ char server_resp[MAX_STR]; ftp_resp = __read_ftp_response(server_cntl_fd, server_resp, buf_limit); *ftp_msg = xstrdup(server_resp + 4); *ftpstate = ftp_resp; } else { ftp_resp = read_ftp_response(server_cntl_fd); } need_noop_resp = 0; continue; } /* Control data awaits -- we can go */ if(FD_ISSET(client_cntl_fd, &ifds)) /* && !FD_ISSET(i, &ifds)) */ break; if(FD_ISSET(client_cntl_fd, &exfds)) { /* telnet_response(00, MSG_OOB); */ break; } if(FD_ISSET(server_cntl_fd, &exfds)) { /* telnet_response(01, MSG_OOB); */ break; } if(tmp_fd != -1 && c1->state == READ && FD_ISSET(o, &ofds)) { /* data from the previously stored tmp file * could be sent to the client */ if((sent_chunk_bytes = send_file_chunk(o, tmp_fd)) > 0) { /* chunk sent ok */ if(reversed_streams) { proxy_stats.outbytes += sent_chunk_bytes; } else { proxy_stats.inbytes += sent_chunk_bytes; } proxy_update_status(); sent_bytes += sent_chunk_bytes; if(keepalive_notification(reversed_streams ? -1 : server_cntl_fd, reversed_streams ? client_cntl_fd : -1 , reversed_streams, &need_noop_resp) && reversed_streams) *ftpstate = 226; /* HACK!!! Client keepalive notification has been sent while uploading file. * It can only be sent as a multiline final response * to the STOR command having initiated data connection. * It is assumed here that everithing will be OK and a response * code will be 226. Since multiline response has been started here * with code 226, an actual final response code should be coerced to be 226 too. * Don't see any good way to handle error conditions in such a case! */ continue; } else { /* tmp file sending is complete or error has occured */ *done = 1; syslog(LOG_DEBUG, "SEND DONE"); cleanup_conn(c1); cleanup_conn(c2); if(sent_chunk_bytes < 0) { syslog(LLEV, "can't send data from tmp file: %s", strerror(errno)); } else if(sent_bytes != rcvd_bytes) { syslog(LLEV, "truncated file sent (rcvd: %d; sent: %d)", rcvd_bytes, sent_bytes); if(*ftpstate == -1 || !reversed_streams){ *ftpstate = 426; } *ftp_msg = xstrdup("Error occured"); send_msg(0, *ftpstate, 1, "Truncated file sent"); } rcvd_bytes = sent_bytes = 0; if(tmp_fd != -1) close(tmp_fd); tmp_fd = -1; break; } } if(!FD_ISSET(i, &ifds)) continue; /* Read data from sender */ r = recv(i, buf, BUFLEN, 0); if(r < 0) syslog(LLEV, "fwtkerrsys: recv(%d): %s", i, strerror(errno)); if(!r || (r < 0)) { /* zero-length read => connection closed */ cleanup_conn(c2); /* cleanup input data connection */ if(tmp_fd != -1) { /* if data have been stored in the tmp file, it's time now for creating * output data connection to send data from the tmp file */ syslog(LOG_DEBUG, "RECV DONE"); if(r < 0) { if(reversed_streams) { puts_fd(server_cntl_fd, "NOOP"); /* After data transfer has beeen completed * a final ftp response should be read. * It is necessary to send this stub command * so that there is something to read afterward. */ } else { send_msg(client_cntl_fd, 150, 0, "Data reading failed."); *ftpstate = 426; } if(*ftpstate == -1) { *ftpstate = 426; } *ftp_msg = xstrdup("Error occured"); send_msg(client_cntl_fd, *ftpstate, 1, "Data reading failed"); syslog(LLEV, "Data reading failed: %s", strerror(errno)); *done = -1; close(tmp_fd); tmp_fd = -1; rcvd_bytes = sent_bytes = 0; break; } start_keepalive_timeout(); /* start keepalive timeout to notify ftp server that we are up by sending NOOP */ keepalive_args = xmalloc(sizeof(*keepalive_args)); keepalive_args->reversed_streams = reversed_streams; keepalive_args->ftpstate = ftpstate; keepalive_args->ftp_msg[0] = '\0'; keepalive_args->need_noop_resp = &need_noop_resp; keepalive_args->client_cntl_fd = client_cntl_fd; keepalive_args->server_cntl_fd = server_cntl_fd; /* set keepalive_progress_callback and keepalive_args as args container for it */ /* for(i = 0; i < 150; i++) { keepalive_progress_callback(keepalive_args); sleep(10); } */ fsync(tmp_fd); /* process data */ if(milters) { char* err_msg = NULL; if(inspect_with_milters(tmpfilename, magic_ctype, milters, &err_msg) == -1) { /* Content is BAD! */ if(!err_msg) err_msg = "Error occurred"; if(reversed_streams) { puts_fd(server_cntl_fd, "NOOP"); /* After data transfer has beeen completed * a final ftp response should be read. * It is necessary to send this stub command * so that there is something to read afterward. */ } else { send_msg(client_cntl_fd, 150, 0, err_msg); *ftpstate = 426; } if(*ftpstate == -1) { *ftpstate = 426; } *ftp_msg = xstrdup("Error occurred"); send_msg(client_cntl_fd, *ftpstate, 1, err_msg); *done = -1; close(tmp_fd); tmp_fd = -1; rcvd_bytes = sent_bytes = 0; break; } } if(strlen(keepalive_args->ftp_msg) > 4) { *ftp_msg = xstrdup(keepalive_args->ftp_msg + 4); } if(keepalive_args) { free(keepalive_args); keepalive_args = NULL; } if(reversed_streams) { int ftp_resp = 0; char server_resp[MAX_STR]; if(need_noop_resp) { ftp_resp = read_ftp_response(server_cntl_fd); need_noop_resp = 0; } ftp_resp = __handle_1str_resp(server_cntl_fd, "STOR", fname2stor, server_resp, buf_limit); free(fname2stor); if(ftp_resp != 150 || make_data_conn(c1)) { if(*ftpstate == -1) { *ftpstate = 426; } if(ftp_resp != 150) *ftp_msg = xstrdup(server_resp + 4); else *ftp_msg = xstrdup("Error occured"); send_msg(client_cntl_fd, *ftpstate, 1, "Connection for writing setup failed"); syslog(LLEV, "fwtkerr: Connection for writing setup failed"); puts_fd(server_cntl_fd, "NOOP"); /* After data transfer has beeen completed * a final ftp response should be read. * It is necessary to send this stub command * so that there is something to read afterward. */ *done = -1; close(tmp_fd); tmp_fd = -1; rcvd_bytes = sent_bytes = 0; break; } } else { if(make_data_conn(c1)) { send_msg(client_cntl_fd, 150, 0, "File processing complete."); *ftpstate = 426; *ftp_msg = xstrdup("Error occured"); send_msg(client_cntl_fd, *ftpstate, 1, "Connection for writing setup failed"); syslog(LLEV, "fwtkerr: Connection for writing setup failed"); *done = -1; close(tmp_fd); tmp_fd = -1; rcvd_bytes = sent_bytes = 0; break; } send_msg(client_cntl_fd, 150, 1, "File processing complete."); send_msg(client_cntl_fd, 150, 0, "Data connection for writing setup OK"); } /* output data connection created correctly */ c1->state = READ; o = c1->fd; shutdown(o, SHUT_RD); if(lseek(tmp_fd, 0, SEEK_SET) < 0) { /* rewind the tmp file to the beginning */ syslog(LLEV, "fwtksyserr: can't lseek tmp file: %s", strerror(errno)); proxy_exit(); } continue; } else { /* data are processed without tmp file using, so just cleanup both connections here */ *done = r ? 1 : -1; rcvd_bytes = sent_bytes = 0; if(r < 0 && *ftpstate == -1) { *ftpstate = 426; } *ftp_msg = xstrdup("Error occured"); cleanup_conn(c1); break; } } if(rcvd_bytes == 0) { /* Look for magic. If there is no magic in config, don't even bother */ if (config_havemagic()) magic_ctype = xstrdup(magic_buffer(proxy_magic, buf, r)); else magic_ctype = "unknown"; if(config_ctype(magic_ctype, &milters)) { /* MIME type is denied */ if(reversed_streams) { puts_fd(server_cntl_fd, "NOOP"); /* After data transfer has beeen completed * a final ftp response should be read. * It is necessary to send this stub command * so that there is something to read afterward. */ } else { send_msg(client_cntl_fd, 150, 0, "Data MIME type is denied."); *ftpstate = 426; } if(*ftpstate == -1) { *ftpstate = 426; } *ftp_msg = xstrdup("prohibited"); send_msg(client_cntl_fd, *ftpstate, 1, "Data MIME type is denied"); *done = -1; rcvd_bytes = sent_bytes = 0; break; } else if(milters) { /* MIME type will be inspected with the 'milters' list */ char tmpfile_templ[] = TEMPFILE_TEMPL; if(( tmp_fd = mkstemp(tmpfile_templ)) == -1) { syslog(LLEV, "fwtksyserr: mkstemp: can't open tmp file for writing data stream: %s", strerror(errno)); proxy_exit(); } else tmpfilename = xstrdup(tmpfile_templ); } else { /* MIME type is permitted to be relayed unchecked */ if(make_data_conn(c1)) { syslog(LLEV, "fwtksyserr: Can't create data connection for writing: %s", strerror(errno)); cleanup_conn(c2); if(reversed_streams) { puts_fd(server_cntl_fd, "NOOP"); /* After data transfer has beeen completed * a final ftp response should be read. * It is necessary to send this stub command * so that there is something to read afterward. */ } else { send_msg(client_cntl_fd, 150, 0, "Can't create data connection for writing."); *ftpstate = 426; } if(*ftpstate == -1) { *ftpstate = 426; } *ftp_msg = xstrdup("Error occurred"); send_msg(client_cntl_fd, *ftpstate, 1, "Can't create data connection for writing"); *done = -1; rcvd_bytes = sent_bytes = 0; break; } c1->state = READ; shutdown(c1->fd, SHUT_RD); o = c1->fd; send_msg(client_cntl_fd, 150, 0, "Data connection setup OK"); *ftpstate = -1; } } rcvd_bytes += r; if(tmp_fd == -1) { /* send data right through, without intermediate storing/processing if there is no open tmp file */ w = sowrite(o, buf, r); if(w >= 0) { c += w; sent_bytes += w; } if(w < r) { syslog(LLEV, "error: short send(%d): %s", o, strerror(errno)); proxy_exit(); } if(reversed_streams) proxy_stats.outbytes += w; else proxy_stats.inbytes += w; proxy_update_status(); } else { /* there is an open tmp file, store data in this file first */ keepalive_notification(reversed_streams ? server_cntl_fd: -1, reversed_streams ? -1 : client_cntl_fd , reversed_streams, &need_noop_resp); for(w_total = 0; w_total < r; w_total += w_chunk) { if((w_chunk = write(tmp_fd, buf + w_total, r - w_total)) <=0) { syslog(LLEV, "fwtksyserr: error while writing data stream into tmp file: %s", strerror(errno)); proxy_exit(); } } } } if(need_noop_resp) { if(*ftpstate == -1 && !reversed_streams) { /* first response read is a final RETR response */ char server_resp[MAX_STR]; ftp_resp = __read_ftp_response(server_cntl_fd, server_resp, buf_limit); *ftp_msg = xstrdup(server_resp + 4); *ftpstate = ftp_resp; } else { ftp_resp = read_ftp_response(server_cntl_fd); } } if(milters) freelist(&milters); return c; } static int make_data_conn(struct connstate *conn) { int as; switch(conn->state) { case CONNECTED: return 0; case LISTEN: if((as=accept(conn->fd, NULL, NULL)) < 0) { syslog(LLEV, "fwtksyserr: accept(%d) failed: %s", conn->fd, strerror(errno)); return -1; } close(conn->fd); proxy_set_dscp(as,sddscp); conn->fd = as; break; case NEEDCONN: if((as = socket(AF_INET, SOCK_STREAM, 0)) < 0) { syslog(LLEV, "fwtksyserr: Can't make socket for connection: %s", strerror(errno)); return -1; } conn->addr.sin_family = AF_INET; /* XXX? */ if(connect(as, (struct sockaddr *) &conn->addr, sizeof(conn->addr)) < 0) { syslog(LLEV, "Can't connect socket: %s", strerror(errno)); close(as); return -1; } proxy_set_dscp(as,cddscp); conn->fd = as; break; default: syslog(LOG_ERR,"securityalert: wrong state trying to make a data connection: %d", conn->state); send_msg(0,500, 0, "wrong state trying to make a data connection"); proxy_exit(); } conn->state = CONNECTED; return 0; } static void cleanup_conn(struct connstate *conn) { conn->state = OFF; if(conn->fd != -1) { close(conn->fd); conn->fd = -1; } } static int parse_port_string(const char *ln, struct sockaddr_in *sin) { int res = -1; #define NARGS 6 unsigned i, argt[NARGS]; char *tmp_str; char *ptok, *pendtok; tmp_str = xstrdup(ln); ptok = tmp_str; for(i=0; i 255)) goto done; sin->sin_addr.s_addr = htonl((argt[0]<<24) + (argt[1]<<16) + (argt[2]<<8) + argt[3]); sin->sin_port = htons((argt[4]<<8) + argt[5]); res = 0; done: free(tmp_str); return res; } /* Listen on a local socket (address taken from fd's local address) */ static int allocate_listening_socket(int ifd, struct sockaddr_in *psin, struct portrange *ran) { struct sockaddr_in sin; socklen_t slen; int sock, res; if(!psin) psin = &sin; if((res = sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { syslog(LLEV, "fwtksyserr: Can't make listening socket for client: %s", strerror(errno)); goto error; } /* global */ if((res = getsockname(0, (struct sockaddr *) psin, (slen=sizeof(*psin),&slen))) < 0) { syslog(LLEV, "fwtksyserr: Can't getsockname(%d): %s", ifd, strerror(errno)); goto error; } /* XXX - fuck me this is foul */ #define PASV_TRIES 100 for(res=0; ressin_port = htons((rand() % (ran->max - ran->min)) + ran->min); if(!bind(sock, (struct sockaddr *) psin, sizeof(sin))) break; } if(res == PASV_TRIES) { syslog(LLEV, "fwtksyserr: Can't find a port for client PASV: %s", strerror(errno)); res = -1; goto error; } if((res = listen(sock, 1)) < 0) { syslog(LLEV, "fwtksyserr: Can't listen on client PASV socket: %s", strerror(errno)); goto error; } error: if(res < 0) { close(sock); return res; } proxy_set_dscp(sock,cddscp); return sock; } static int negotiate_pasv_connection(int fd, int *cmdres, struct connstate *conn) { int res = -1; char *cp, *cp2; struct sockaddr_in sin; int sock; char server_resp[MAX_STR]; *cmdres = 999; if(strwritev(fd, 1, "PASV\r\n") < 0) { syslog(LLEV, "strwritev(%d) failed: %s", fd, strerror(errno)); return -1; } *cmdres = __read_ftp_response(fd, server_resp, buf_limit); if(DIG1(*cmdres) != 2) { /* PASV failed -- at least we don't have to parse the response :) */ goto done; } /* bollocks, must now parse the response */ for(cp = server_resp+3; *cp; cp++) { if(isdigit(*cp)) break; } if(!*cp) { syslog(LLEV, "Address not found in server PASV response"); goto done; } for(cp2 = cp; *cp2; cp2++) { if((',' != *cp2) && !isdigit(*cp2)) break; } if(!*cp2) { syslog(LLEV, "PASV response broken"); return -1; } *cp2 = 0; *cmdres = 504; /* 999 */ if(parse_port_string(cp, &sin)) goto done; if(sin.sin_addr.s_addr != servaddr.sin_addr.s_addr) goto done; /* so all is now probably well... */ if((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { syslog(LLEV, "fwtksyserr: Can't make socket for PASV to server: %s", strerror(errno)); goto done; } sin.sin_family = AF_INET; if(connect(sock, (struct sockaddr *) &sin, sizeof(sin)) < 0) { syslog(LLEV, "Can't connect PASV socket to server: %s", strerror(errno)); close(sock); goto done; } proxy_set_dscp(sock,sddscp); conn->addr = sin; conn->state = CONNECTED; conn->fd = sock; *cmdres = 200; res = 0; done: return res; } static void port_snprintf(char *buf, size_t len, const char *fmt, struct sockaddr_in *sin) { #define SA(n) ((ntohl(sin->sin_addr.s_addr)>>(n)) & 0xff) #define SP(n) ((ntohs(sin->sin_port)>>(n)) & 0xff) snprintf(buf, len, fmt, SA(24), SA(16), SA(8), SA(0), SP(8), SP(0)); } static int negotiate_port_connection(int fd, int *cmdres, struct connstate *conn) { int sock, res; char pstr[MAX_STR+1]; pstr[MAX_STR] = 0; *cmdres = 999; if((res = sock = allocate_listening_socket(fd, &serverconn.addr, &server_port_range)) < 0) return res; port_snprintf(pstr, MAX_STR, "%d,%d,%d,%d,%d,%d", &serverconn.addr); if(strwritev(fd, 3, "PORT ", pstr, "\r\n") < 0) { syslog(LLEV, "strwritev(%d) failed: %s", fd, strerror(errno)); close(sock); return -1; } *cmdres = read_ftp_response(fd); if(DIG1(*cmdres) != 2) { /* PASV failed -- at least we don't have to parse the response :) */ close(sock); conn->state = OFF; } else { conn->state = LISTEN; conn->fd = sock; *cmdres = 200; res = 0; } return res; } static int negotiate_server_connection(int fd, int *res, struct connstate *conn) { if(prefer_server_port && !refuse_server_port && !negotiate_port_connection(fd, res, conn)) /* PORT OK -- we have a listening socket */ return 0; /* Try passive mode first */ if(!refuse_server_pasv && !negotiate_pasv_connection(fd, res, conn)) /* PASV accepted, data sock is setup */ return 0; if(!prefer_server_port && !refuse_server_port && !negotiate_port_connection(fd, res, conn)) /* PORT OK -- we have a listening socket */ return 0; return 1; } static int authsrv_resp(char *resp) { char buf[512]; if(!authopened) { send_msg(0, 501, 0, "Use \"authorize\" command first"); return 1; } if(strlen(resp) > 512) { send_msg(0, 501, 0, "Response too long"); return 1; } /* send response */ if(index(resp,'\'') == (char *)0 && index(resp,'\"') == (char *)0) snprintf(buf,sizeof(buf),"response '%s'",resp); else snprintf(buf,sizeof(buf),"response %s",resp); if(auth_send(buf)) goto lostconn; if(auth_recv(buf,sizeof(buf))) goto lostconn; if(!strncmp(buf,"challenge ",10) || !strncmp(buf,"chalnecho ",10)) { send_msg(0, 331, 0, &buf[10]); return 0; } else if(!strncmp(buf,"display ",8)) { send_msg(0, 331, 1, &buf[8]); return 0; } else if(strncmp(buf,"ok",2)) { auth_close(); authopened = 0; send_msg(0, 501, 0, buf); return 1; } authenticated = 1; syslog(LLEV,"authenticate user=%.100s",authuser); strlcpy(proxy_stats.authuser,authuser,sizeof(proxy_stats.authuser)); proxy_update_status(); auth_close(); authopened = 0; if(buf[2] != '\0') { char ebuf[MAX_STR]; snprintf(ebuf, sizeof(ebuf), "User authenticated to proxy: %s",&buf[2]); send_msg(0, 230, 0, ebuf); return 0; } send_msg(0, 230, 0, "User authenticated to proxy"); return 0; lostconn: auth_close(); authopened = 0; send_msg(0, 501 , 0, "Cannot connect to authentication server"); return 1; } static int authsrv_auth(char *username) { char lbuf[512]; char rbuf[512]; int mline = 0; authenticated = 0; if(strlen(username) > 24) { send_msg(0, 501, 0, "User name too large"); return 1; } if(strlen(username) == 0) { send_msg(0, 501, 0, "Missing or extra username"); return 1; } if(!authopened) if(auth_open(confp)) { send_msg(0, 501, 0, "Cannot connect to authentication server"); return 1; } /* get welcome message from auth server */ if(!authopened) { if(auth_recv(rbuf,sizeof(rbuf))) goto lostconn; if(strncmp(rbuf,"Authsrv ready",13)) { send_msg(0, 551, 0, rbuf); auth_close(); return 1; } } authopened++; /* send username */ if(strlen(proxy_stats.rladdr) + strlen(proxy_stats.riaddr) + strlen(username) + 100 > MAX_STR) { syslog(LLEV,"securityalert: buffer overflow on ftp authentication"); send_msg(0, 501, 0, "client data exceed buffer size"); return 1; } else snprintf(lbuf, sizeof(lbuf), "authorize %s 'ftp-gw %s/%s'",username,proxy_stats.rladdr,proxy_stats.riaddr); if(auth_send(lbuf)) goto lostconn; if(auth_recv(rbuf,sizeof(rbuf))) goto lostconn; if(!strncmp(rbuf,"challenge ",10) || !strncmp(rbuf,"chalnecho ",10)) snprintf(lbuf, sizeof(lbuf), "%s",&rbuf[10]); else if(!strncmp(rbuf,"display ",8)) { snprintf(lbuf, sizeof(lbuf), "%s",&rbuf[8]); mline = 1; } else if(!strncmp(rbuf,"password",8)) snprintf(lbuf, sizeof(lbuf), "Enter authentication password for %s", username); else { send_msg(0, 551, 0, rbuf); return 1; } strlcpy(authuser, username, sizeof(authuser)); send_msg(0, 331, mline, lbuf); return 0; lostconn: auth_close(); authopened = 0; send_msg(0, 501, 0, "Cannot connect to authentication server"); return 1; } int parse_user_cmd(buffer,user,server, port) char *buffer; char *user; char *server; unsigned int *port; { char *p; if ((p = strtok(buffer,separator)) != NULL) { if(strlen(p) > ((user_limit < AUTH_USIZ) ? user_limit : AUTH_USIZ)) { buffer_overrun("user parse", p, (user_limit < AUTH_USIZ) ? user_limit : 31); /* This function never returns */ } strlcpy(user,p,AUTH_USIZ); } if ((p = strtok(NULL,":")) != NULL && *server == '\0') if(strlen(p) > 63) { buffer_overrun("user parse", p, 63); /* This function never returns */ } strncpy(server,p,64); if ((p = strtok(NULL,"\r\n")) != NULL && *port <= 0) { while(isspace(*p)) p++; *port = atoi(p); } if (!*port) *port = str_to_port("ftp"); if ((strlen(user)==0) || (strlen(server)==0) || (strlen(user) > user_limit)) return(1); return(0); } static int net_send(s, buf, len, flags) int s; char *buf; int len; int flags; { int nbytes = 0; int j; while (nbytes < len) { j = send(s, buf, len-nbytes, flags); if (j <= 0) return (nbytes ? nbytes : j); buf += j; nbytes += j; } return nbytes; } int strwritev(int ofd, int count, ...) { va_list ap; int i; int copied_len = 0; char line[MAX_STR] = ""; va_start(ap, count); for(i = 0; i < count; i++) { char *t; if(!(t = va_arg(ap, char*))) continue; if(!*t) continue; strncat(line, t, buf_limit - copied_len - 1); copied_len += strlen(t); if(copied_len > buf_limit) { buffer_overrun("strwritev", NULL, buf_limit); /* This function never returns */ } } va_end(ap); if (ofd) proxy_stats.outbytes += copied_len; else proxy_stats.inbytes += copied_len; if(sowrite(ofd, line, copied_len) != copied_len) return -1; return count; } void puts_fd(fd,line) int fd; char *line; { int x; x = strlen(line); if (strwritev(fd, 2, line, "\r\n") < 0) { close(fd); if (fd) { syslog(LLEV,"error writing server data, %s", strerror(errno)); send_msg(0, 500, 0, "error writing server data"); } else syslog(LLEV,"error writing client data, %s", strerror(errno)); proxy_exit(); } } int send_file_msg(fd,fn,code) int fd; char *fn; int code; { FILE *f; char buf[MAX_STR]; char *c; int x; int sentsomething = 0; if((f = fopen(fn,"r")) == (FILE *)0) return(1); while(fgets(buf,buf_limit,f) != (char *)0) { if((c = index(buf,'\n')) != (char *)0) *c = '\0'; x = fgetc(f); send_msg(fd, code, x != EOF, buf); ungetc(x,f); sentsomething++; } fclose(f); if (!sentsomething) { syslog(LLEV,"fwtkcfgerr: send_file_msg for %d is empty",code); send_msg(fd, code, 0, "The file to display is empty"); return(1); } return(0); } void read_raw_ftp_response(int ifd, int *cont, char *ln, int maxsize, read_actor *reader) { int len; if((len = reader(ifd, ln, maxsize,0))) { if( len > 3 && isdigit((ln)[0]) && isdigit((ln)[1]) && isdigit((ln)[2])) { *cont = ((ln)[3] != ' '); } trim_eol(ln); } else { /* error checking */ } } void relay_ftp_response(int ifd, int ofd) { int cont = 0; char line[MAX_STR]; do { read_raw_ftp_response(ifd, &cont, line, buf_limit, &tn_recvline); puts_fd(ofd, line); } while(cont); } int getline(fd,buf,siz) int fd; unsigned char *buf; int siz; { int x = 0; while(1) { if(soread(fd,(char *)&buf[x],1) != 1) return(-1); /* get \r\n - read a char and throw it away */ if(buf[x] == '\r') { if(soread(fd,(char *)&buf[x],1) != 1) return(-1); buf[x] = '\0'; return(x); } if(buf[x] == '\n') { buf[x] = '\0'; return(x); } if(buf[x] == IAC) { unsigned char j; unsigned char jbuf[4]; if(soread(fd,(char*)&j,1) != 1) return(-1); switch(j) { case WILL: case WONT: if(soread(fd,(char *)&j,1) != 1) return(-1); jbuf[0] = IAC; jbuf[1] = DONT; jbuf[2] = j; if(net_send(fd,(char *)jbuf,3,0) != 3) return(-1); continue; case DO: case DONT: if(soread(fd,(char *)&j,1) != 1) return(-1); jbuf[0] = IAC; jbuf[1] = WONT; jbuf[2] = j; if(net_send(fd,(char *)jbuf,3, 0) != 3) return(-1); continue; case IAC: break; case IP: if(soread(fd,(char *)&j,1) != 1) return(-1); if(j == DM || j == IAC) x = 0; continue; default: continue; } } if(++x >= siz) { buffer_overrun("getline", NULL, siz); /* This function never returns */ } } } int send_file_chunk(int o_fd, int tmp_fd) { int r, w; char buf[MAX_STR]; if((r = read(tmp_fd, buf, sizeof(buf))) > 0) { w = sowrite(o_fd, buf, r); if(w < r) { syslog(LLEV, "short send(%d): %s", o_fd, strerror(errno)); return -1; } } if(r < 0) { syslog(LLEV, "fwtksyserr: can't read tmp file: %s", strerror(errno)); return -1; } return r; } void start_keepalive_timeout() { last_keepalive_notification_time = time(NULL); } int keepalive_timeout() { int ret_val = (last_keepalive_notification_time && keepalive_timeout_val && difftime(time(NULL), last_keepalive_notification_time) > keepalive_timeout_val) ? 1 : 0; return ret_val; } void end_keepalive_timeout() { last_keepalive_notification_time = 0; } int keepalive_notification(int server_fd, int client_fd, int reversed_streams, int *need_noop_resp) { int notify_fd_fl; int ret_val = 0; if(tmp_fd == -1) return 0; if(server_fd != -1) { /* send keepalives to the server */ if(!*need_noop_resp && keepalive_timeout()) { /* keepalive timeout has expired and there is no * previous keepalive notification (NOOP command) * without response recieved */ /* set fd into the nonblocking mode so that sending keepalive notification could not block processing */ if((notify_fd_fl = fcntl(server_fd, F_GETFL)) != -1 && fcntl(server_fd, F_SETFL, notify_fd_fl | O_NONBLOCK) != -1) { if(strwritev(server_fd, 1, "PWD\r\n") != -1) { /* send NOOP command to notify ftp server that we still are up */ *need_noop_resp = 1; /* set flag that response to the NOOP command is waited for */ ret_val = 1; syslog(LLEV, "keepalive_notification: send noop"); } fcntl(server_fd, F_SETFL, notify_fd_fl); /* restore fd mode */ } } } if(client_fd != -1) { /* send keepalives to the client */ /* a message that a file downloading/processing is still in progress should be displayed periodically * in order for a user not to disconnect */ if(keepalive_timeout()) { /* set fd into the nonblocking mode, so that sending keepalive notification to the user could not block processing */ if((notify_fd_fl = fcntl(client_fd, F_GETFL)) != -1 && fcntl(client_fd, F_SETFL, notify_fd_fl | O_NONBLOCK) != -1) { send_msg(client_fd, reversed_streams ? 226 /* attach notification to the "2xx OK ..." message while uploading */ : 150, /* attach notification to the "1xx Data connection ... " while downloading */ 1, "File is being processed. Please wait."); ret_val = 1; fcntl(client_fd, F_SETFL, notify_fd_fl); /* restore fd mode */ } } } start_keepalive_timeout(); return ret_val; } void keepalive_progress_callback(keepalive_args *keepalive_args) { int reversed_streams = keepalive_args->reversed_streams; int *need_noop_resp = keepalive_args->need_noop_resp; int ftp_resp; int *ftpstate = keepalive_args->ftpstate; char *ftp_msg = keepalive_args->ftp_msg; int server_cntl_fd = keepalive_args->server_cntl_fd; if(*need_noop_resp) { fd_set ifds; struct timeval tv = {0,0}; FD_ZERO(&ifds); FD_SET(server_cntl_fd, &ifds); syslog(LLEV, "keepalive: need noop resp"); if(select(server_cntl_fd+1, &ifds, NULL, NULL, &tv) < 0) { syslog(LLEV, "fwtksyserr: select() failed: %s", strerror(errno)); proxy_exit(); } if(FD_ISSET(server_cntl_fd, &ifds)) { /* read out a response to the NOOP command sent to the ftp server * as a keepalive notification */ if(*ftpstate == -1 && !reversed_streams) { /* first response read is a final RETR response */ ftp_resp = __read_ftp_response(server_cntl_fd, ftp_msg, buf_limit); *ftpstate = ftp_resp; } else { ftp_resp = read_ftp_response(server_cntl_fd); } *need_noop_resp = 0; } } if( keepalive_notification(keepalive_args->server_cntl_fd, keepalive_args->client_cntl_fd, reversed_streams, need_noop_resp) && reversed_streams) *ftpstate = 226; /* HACK!!! Client keepalive notification has been sent while uploading file. * It can only be sent as a multiline final response * to the STOR command having initiated data connection. * It is assumed here that everithing will be OK and a response * code will be 226. Since multiline response has been started here * with code 226, an actual final response code should be coerced to be 226 too. * Don't see any good way to handle error conditions in such a case! */ }