Index: cmds.c =================================================================== --- cmds.c.orig +++ cmds.c @@ -136,69 +136,63 @@ extern struct conn *first_conn; extern struct dcache *first_dcache; #endif -#if HAVE_POLL -extern struct pollfd fds[]; -#else -extern fd_set master_fds, master_send_fds; -#endif - struct handler { char cmd_name[6]; char add_cmlen; /* =1 if the command takes an argument */ int (*callback)(struct conn * const); - char min_auth; + enum conn_state min_auth; #if !WANT_NONROOT char do_setuid; /* =1 if root is not *really* needed */ #endif }; static const struct handler handler_table[] = { - { "user ", 1, cmd_user, 0 NO_SETUID }, - { "pass ", 1, cmd_pass, 1 NO_SETUID }, - { "retr ", 1, cmd_retr, 3 DO_SETUID }, - { "acct ", 1, cmd_acct, 0 NO_SETUID }, - { "port ", 1, cmd_port, 3 DO_SETUID }, - { "pasv" , 0, cmd_pasv, 3 DO_SETUID }, - { "pwd" , 0, cmd_pwd, 3 DO_SETUID }, - { "cwd " , 1, cmd_cwd, 3 DO_SETUID }, - { "cdup" , 0, cmd_cdup, 3 DO_SETUID }, - { "rest ", 1, cmd_rest, 3 DO_SETUID }, - { "list" , 0, cmd_list, 3 DO_SETUID }, - { "nlst" , 0, cmd_nlst, 3 DO_SETUID }, - { "type ", 1, cmd_type, 3 DO_SETUID }, - { "mode ", 1, cmd_mode, 3 DO_SETUID }, - { "stru ", 1, cmd_stru, 3 DO_SETUID }, - { "size ", 1, cmd_size, 3 DO_SETUID }, - { "mdtm ", 1, cmd_mdtm, 3 DO_SETUID }, - { "abor" , 0, cmd_abor, 3 DO_SETUID }, - { "dele ", 1, cmd_dele, 3 DO_SETUID }, - { "rnfr ", 1, cmd_rnfr, 3 DO_SETUID }, - { "rnto ", 1, cmd_rnto, 3 DO_SETUID }, - { "mkd " , 1, cmd_mkd, 3 DO_SETUID }, - { "rmd " , 1, cmd_rmd, 3 DO_SETUID }, - { "allo ", 1, cmd_allo, 3 DO_SETUID }, - { "stat" , 0, cmd_stat, 0 NO_SETUID }, - { "noop" , 0, cmd_noop, 0 DO_SETUID }, - { "syst" , 0, cmd_syst, 0 DO_SETUID }, - { "help" , 0, cmd_help, 0 NO_SETUID }, - { "quit" , 0, cmd_quit, 0 DO_SETUID }, - { "rein" , 0, cmd_rein, 0 DO_SETUID }, + { "user ", 1, cmd_user, CONN_STATE_INIT NO_SETUID }, + { "pass ", 1, cmd_pass, CONN_STATE_WAIT_USER NO_SETUID }, + { "retr ", 1, cmd_retr, CONN_STATE_READY DO_SETUID }, + { "acct ", 1, cmd_acct, CONN_STATE_INIT NO_SETUID }, + { "port ", 1, cmd_port, CONN_STATE_READY DO_SETUID }, + { "pasv" , 0, cmd_pasv, CONN_STATE_READY DO_SETUID }, + { "pwd" , 0, cmd_pwd, CONN_STATE_READY DO_SETUID }, + { "cwd " , 1, cmd_cwd, CONN_STATE_READY DO_SETUID }, + { "cdup" , 0, cmd_cdup, CONN_STATE_READY DO_SETUID }, + { "rest ", 1, cmd_rest, CONN_STATE_READY DO_SETUID }, + { "list" , 0, cmd_list, CONN_STATE_READY DO_SETUID }, + { "nlst" , 0, cmd_nlst, CONN_STATE_READY DO_SETUID }, + { "type ", 1, cmd_type, CONN_STATE_READY DO_SETUID }, + { "mode ", 1, cmd_mode, CONN_STATE_READY DO_SETUID }, + { "stru ", 1, cmd_stru, CONN_STATE_READY DO_SETUID }, + { "size ", 1, cmd_size, CONN_STATE_READY DO_SETUID }, + { "mdtm ", 1, cmd_mdtm, CONN_STATE_READY DO_SETUID }, + { "abor" , 0, cmd_abor, CONN_STATE_READY DO_SETUID }, + { "dele ", 1, cmd_dele, CONN_STATE_READY DO_SETUID }, + { "rnfr ", 1, cmd_rnfr, CONN_STATE_READY DO_SETUID }, + { "rnto ", 1, cmd_rnto, CONN_STATE_READY DO_SETUID }, + { "mkd " , 1, cmd_mkd, CONN_STATE_READY DO_SETUID }, + { "rmd " , 1, cmd_rmd, CONN_STATE_READY DO_SETUID }, + { "allo ", 1, cmd_allo, CONN_STATE_READY DO_SETUID }, + { "stat" , 0, cmd_stat, CONN_STATE_INIT NO_SETUID }, + { "noop" , 0, cmd_noop, CONN_STATE_INIT DO_SETUID }, + { "syst" , 0, cmd_syst, CONN_STATE_INIT DO_SETUID }, + { "help" , 0, cmd_help, CONN_STATE_INIT NO_SETUID }, + { "quit" , 0, cmd_quit, CONN_STATE_INIT DO_SETUID }, + { "rein" , 0, cmd_rein, CONN_STATE_INIT DO_SETUID }, /* deprecated forms */ - { "xcup" , 0, cmd_cdup, 3 DO_SETUID }, - { "xcwd ", 1, cmd_cwd, 3 DO_SETUID }, - { "xpwd" , 0, cmd_pwd, 3 DO_SETUID }, - { "xmkd ", 1, cmd_mkd, 3 DO_SETUID }, - { "xrmd ", 1, cmd_rmd, 3 DO_SETUID }, + { "xcup" , 0, cmd_cdup, CONN_STATE_READY DO_SETUID }, + { "xcwd ", 1, cmd_cwd, CONN_STATE_READY DO_SETUID }, + { "xpwd" , 0, cmd_pwd, CONN_STATE_READY DO_SETUID }, + { "xmkd ", 1, cmd_mkd, CONN_STATE_READY DO_SETUID }, + { "xrmd ", 1, cmd_rmd, CONN_STATE_READY DO_SETUID }, #if WANT_UPLOAD - { "stor ", 1, cmd_stor, 3 DO_SETUID }, - { "appe ", 1, cmd_appe, 3 DO_SETUID }, + { "stor ", 1, cmd_stor, CONN_STATE_READY DO_SETUID }, + { "appe ", 1, cmd_appe, CONN_STATE_READY DO_SETUID }, #endif #if DOING_PROFILING #warning Use DOING_PROFILING with caution, and NEVER on a production server! :-) - { "exit", 0, cmd_exit, 0 NO_SETUID }, + { "exit", 0, cmd_exit, CONN_STATE_INIT NO_SETUID }, #endif - { "" , 0, NULL, 0 NO_SETUID } + { "" , 0, NULL, CONN_STATE_INIT NO_SETUID } }; /* @@ -266,10 +260,10 @@ int cmd_user(struct conn * const c) strcpy(c->username, "ftp"); } if (strcasecmp(c->username, "ftp") == 0) { - c->auth = 1; + conn_GOTO(c,CONN_STATE_WAIT_USER); return numeric(c, 331, "Login OK, send password (your e-mail)."); } else { - c->auth = 2; + conn_GOTO(c,CONN_STATE_WAIT_PASS); return numeric(c, 331, "Password required for %s.", c->username); } /*notreached*/ @@ -301,38 +295,38 @@ int cmd_pass(struct conn * const c) #endif if (p == NULL) { - c->auth = 0; + conn_GOTO(c,CONN_STATE_INIT); } else { c->uid = p->pw_uid; strncpy(c->curr_dir, p->pw_dir, 254); c->curr_dir[254] = 0; } - if (c->auth == 1) { + if (c->auth == CONN_STATE_WAIT_USER) { if (c->curr_dir[strlen(c->curr_dir) - 1] != '/') { strcat(c->curr_dir, "/"); } strcpy(c->root_dir, c->curr_dir); - c->auth = 3; - } else if (c->auth != 0) { + conn_GOTO(c,CONN_STATE_READY); + } else if (c->auth != CONN_STATE_INIT) { strcpy(c->root_dir, "/"); if (strcmp(crypt(c->recv_buf, p->pw_passwd), p->pw_passwd) != 0 #if WANT_SHADOW && HAVE_SHADOW_H && (s == NULL || strcmp(crypt(c->recv_buf, s->sp_pwdp), s->sp_pwdp) != 0) #endif ) { - c->auth = 0; + conn_GOTO(c,CONN_STATE_INIT); } else { - c->auth = 3; + conn_GOTO(c,CONN_STATE_READY); } } #endif /* !WANT_NONROOT */ /* root should not be allowed to FTP */ if (c->uid == 0) { - c->auth = 0; + conn_GOTO(c,CONN_STATE_INIT); } - if (c->auth == 0) { + if (c->auth == CONN_STATE_INIT) { return numeric(c, 530, "Login incorrect."); } else { #if WANT_MESSAGE @@ -375,7 +369,7 @@ int cmd_port(struct conn * const c) struct ftran *f; struct sockaddr_in sin; - if ((c->transfer != NULL) && (c->transfer->state >= 4)) { + if ((c->transfer != NULL) && (c->transfer->state == FTRAN_STATE_TRANSFERING)) { return numeric(c, 500, "Sorry, only one transfer at a time."); } @@ -384,6 +378,8 @@ int cmd_port(struct conn * const c) destroy_ftran(c->transfer); c->transfer = f = alloc_new_ftran(sock, c); + if (f == NULL) + return 1; i = sscanf(c->recv_buf, "%3hu,%3hu,%3hu,%3hu,%3hu,%3hu", &a0, &a1, &a2, &a3, &p0, &p1); if (i < 6) { @@ -421,10 +417,7 @@ int cmd_port(struct conn * const c) ((unsigned char)(p0) << 8) + ((unsigned char)(p1) )); f->sock = sock; - f->state = 3; - - i = 1; - ioctl(f->sock, FIONBIO, &one); + ftran_GOTO(f,FTRAN_STATE_GOTPORT); } return 1; } @@ -440,19 +433,26 @@ int cmd_pasv(struct conn * const c) unsigned int one = 1; struct sockaddr_in addr; - if ((c->transfer != NULL) && (c->transfer->state >= 4)) { + if ((c->transfer != NULL) && (c->transfer->state == FTRAN_STATE_TRANSFERING)) { return numeric(c, 503, "Sorry, only one transfer at once."); } destroy_ftran(c->transfer); sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); TRAP_ERROR(sock == -1, 500, return 1); - err = add_fd(sock, POLLIN); - TRAP_ERROR(err != 0, 501, return 1); - c->transfer = f = alloc_new_ftran(sock, c); + if (f == NULL) + return 1; - ioctl(sock, FIONBIO, &one); + ftran_GOTO(f,FTRAN_STATE_LISTENING); + rn_prepare_fd_for_add(f->sock,own_pid); + err = rn_add(&rns,f->sock,ftran_listening_ioready,f); + if (err != 0) { + close(f->sock); + f->sock = ILLEGAL_FD; + destroy_ftran(f); + TRAP_ERROR(err != 0, 501, return 1); + } /* setup socket */ tmp = sizeof(addr); @@ -469,7 +469,6 @@ int cmd_pasv(struct conn * const c) err = listen(f->sock, 1); TRAP_ERROR(err == -1, 500, return 1); - f->state = 1; return numeric(c, 227, "Entering passive mode (%u,%u,%u,%u,%u,%u)", (htonl(addr.sin_addr.s_addr) & 0xff000000) >> 24, @@ -604,7 +603,7 @@ int cmd_retr(struct conn * const c) int fd; struct ftran *f = c->transfer; - if ((f == NULL) || ((f->state != 1) && (f->state != 3))) { + if ((f == NULL) || ((f->state != FTRAN_STATE_LISTENING) && (f->state != FTRAN_STATE_GOTPORT) && (f->state != FTRAN_STATE_CONNECTED_NO_CMD))) { return numeric(c, 425, "No data connection set up; please use PASV or PORT."); } @@ -670,7 +669,7 @@ int do_store(struct conn * const c, cons struct ftran *f = c->transfer; int fd; - if ((f == NULL) || ((f->state != 1) && (f->state != 3))) { + if ((f == NULL) || ((f->state != FTRAN_STATE_LISTENING) && (f->state != FTRAN_STATE_GOTPORT) && (f->state != FTRAN_STATE_CONNECTED_NO_CMD))) { return numeric(c, 425, "No data connection set up; please use PASV or PORT."); } @@ -912,24 +911,6 @@ int cmd_allo(struct conn * const c) * Note that we need to bypass numeric(), to get a multi-line * reply. */ -#if WANT_STAT -char conn_state[5][27] = { - "Not logged in", - "Waiting for e-mail address", - "Waiting for password", - "Logged in", - "Waiting for password", /* actually non-existant user */ -}; - -char ftran_state[6][42] = { - "Not initialized", - "Decided PASV address/port", - "Waiting on PASV socket", - "Got PORT address/port", - "Connecting on PORT address/port", - "Transferring file (or connecting on PORT)" -}; -#endif int cmd_stat(struct conn * const c) { @@ -950,11 +931,11 @@ int cmd_stat(struct conn * const c) " Data connection state: %s\r\n" "211 End of status\r\n", inet_ntoa(((struct sockaddr_in *)(&(c->addr)))->sin_addr), - conn_state[c->auth], + conn_convert_to_string(c->auth), #if WANT_ASCII (c->ascii_mode == 1) ? "ASCII, FORM: Nonprint" : "Image", #endif - (f) ? ftran_state[f->state] : ftran_state[0]); + (f) ? ftran_convert_to_string(f->state) : convert_to_string(FTRAN_STATE_NONE)); i = strlen(buf); @@ -1174,7 +1155,7 @@ int do_listing(struct conn * const c, st if (status == -1) destroy_ftran(c->transfer); if (status != 1) - return status; + return 1; #if WANT_DCACHE getcwd(cwd, 256); @@ -1204,9 +1185,11 @@ int do_listing(struct conn * const c, st #if HAVE_MMAP { int num_files = get_num_files(c, ptr, lo, &status); - if (status != 1) - return status; - + if (status != 1) { + numeric(f->owner, 226, "Transfer complete"); + destroy_ftran(c->transfer); + return 1; + } size = num_files * 160; f->file_data = malloc(size + 1); TRAP_ERROR(f->file_data == NULL, 550, return -1); @@ -1219,7 +1202,7 @@ int do_listing(struct conn * const c, st if (status == -1) destroy_ftran(c->transfer); if (status != 1) - return status; + return 1; #if WANT_DCACHE populate_dcache(f, cwd, ptr, lo); @@ -1419,10 +1402,14 @@ int list_core(struct conn * const c, con } chdir(temp); - pos = list_core(c, "*", tmp2, lo, pstatus, #if HAVE_MMAP - size, pos); + pos = +#endif + list_core(c, "*", tmp2, lo, pstatus +#if HAVE_MMAP + ,size, pos #endif + ); chdir(".."); if (*pstatus != 1) return 0; /* caller will destroy_ftran if *pstatus is -1 */ @@ -1556,8 +1543,8 @@ int cmd_quit(struct conn * const c) int cmd_rein(struct conn * const c) { destroy_ftran(c->transfer); - c->buf_len = c->auth = c->rest_pos = 0; - + c->buf_len = c->rest_pos = 0; + conn_GOTO(c,CONN_STATE_INIT); /* equals: strcpy(c->curr_dir, "/") ; strcpy(c->last_cmd, ""); */ c->curr_dir[0] = '/'; #if WANT_FULLSCREEN @@ -1585,6 +1572,7 @@ void cmd_exit(struct conn * const c) { while (first_conn->next_conn) destroy_conn(first_conn->next_conn); + rn_shutdown(&rns); exit(0); } #endif @@ -1597,13 +1585,17 @@ void cmd_exit(struct conn * const c) * * To me, this command seems optimizable, but I'm not really * sure where :-) + * This function returns 0 if connection has been destroyed, + * 1 otherwise. */ -void parse_command(struct conn *c) + +int parse_command(struct conn *c) { - int cmlen; + int cmlen,status; const struct handler *h = handler_table; /* first entry */ - if (c == NULL) return; + if (c == NULL) + return 0; /* strip any leading non-ASCII characters (including CR/LFs) */ while (c->buf_len > 0 && (c->recv_buf[0] < 'a' || c->recv_buf[0] > 'z') @@ -1613,7 +1605,8 @@ void parse_command(struct conn *c) /* scan, searching for CR or LF */ cmlen = strcspn(c->recv_buf, "\r\n"); - if (cmlen >= c->buf_len) return; + if (cmlen >= c->buf_len) + return 1; #if WANT_FULLSCREEN strncpy(c->last_cmd, c->recv_buf, cmlen); @@ -1625,7 +1618,7 @@ void parse_command(struct conn *c) (strncasecmp(c->recv_buf, h->cmd_name, strlen(h->cmd_name)) == 0)) { if (c->auth < h->min_auth) { if (!numeric(c, 503, "Please login with USER and PASS.")) - return; + return 1; while (c->recv_buf[0] != '\n') remove_bytes(c, 1); } else { char schar; @@ -1649,7 +1642,8 @@ void parse_command(struct conn *c) c->recv_buf[cmlen] = 0; /* result of zero means the connection is freed */ - if (h->callback(c)) { + status = h->callback(c); + if (status) { c->recv_buf[cmlen] = schar; #if !WANT_NONROOT if (h->do_setuid) seteuid(getuid()); @@ -1657,7 +1651,7 @@ void parse_command(struct conn *c) remove_bytes(c, cmlen); } } - return; + return status; } } while ((++h)->callback != NULL); @@ -1673,8 +1667,10 @@ void parse_command(struct conn *c) * nonroot notice: prepare_for_transfer() assumes all access * checks are already done. */ -void prepare_for_transfer(struct ftran *f) +int prepare_for_transfer(struct ftran *f) { + int err; + #if WANT_NONROOT #warning No nonroot checking for prepare_for_transfer() yet #endif @@ -1694,14 +1690,29 @@ void prepare_for_transfer(struct ftran * } #endif - if (f->state == 1) { /* PASV connection */ - f->state = 2; /* waiting */ - } else if (f->state == 3) { /* PORT connection */ - f->state = 4; + if (f->state == FTRAN_STATE_LISTENING) { /* PASV connection */ + ftran_GOTO(f,FTRAN_STATE_LISTENING_GOT_CMD); + } + else if (f->state == FTRAN_STATE_CONNECTED_NO_CMD) + { + ftran_GOTO(f,FTRAN_STATE_TRANSFERING); + init_file_transfer(f); + } + else if (f->state == FTRAN_STATE_GOTPORT) { /* PORT connection */ + struct conn * const c = f->owner; + ftran_GOTO(f,FTRAN_STATE_CONNECTING); + rn_prepare_fd_for_add(f->sock,own_pid); + err = rn_add(&rns,f->sock, ftran_data_ioready,f); + if (err != 0) { + close(f->sock); + f->sock = ILLEGAL_FD; + destroy_ftran(f); + TRAP_ERROR(err != 0, 501, return 1); + } connect(f->sock, (struct sockaddr *)&f->sin, sizeof(f->sin)); - add_fd(f->sock, POLLOUT); } time(&(f->tran_start)); + return 1; } /* @@ -1850,8 +1861,9 @@ int prepare_for_listing(struct conn * co #warning No nonroot checking for prepare_for_listing() yet #endif - if ((f == NULL) || ((f->state != 1) && (f->state != 3))) { - return numeric(c, 425, "No data connection set up; please use PASV or PORT."); + if ((f == NULL) || ((f->state != FTRAN_STATE_LISTENING) && (f->state != FTRAN_STATE_GOTPORT) && (f->state != FTRAN_STATE_CONNECTED_NO_CMD))) { + numeric(c, 425, "No data connection set up; please use PASV or PORT."); + return -1; } /* Index: cmds.h =================================================================== --- cmds.h.orig +++ cmds.h @@ -35,6 +35,7 @@ * If TRAP_ERROR_DEBUG is defined, some extra debugging info is * sent. Don't enable this for a normal server, it could be a * security risk. + * FIXME: return value of numeric must be propagated to caller! */ #undef TRAP_ERROR_DEBUG /* #define TRAP_ERROR_DEBUG 1 */ @@ -98,8 +99,8 @@ CMD_PROTO(exit); #endif int cmd_cwd_internal(struct conn * const c, const char * const newd); -void parse_command(struct conn *c); -void prepare_for_transfer(struct ftran *f); +int parse_command(struct conn *c); +int prepare_for_transfer(struct ftran *f); char decode_mode(mode_t mode); char *translate_path(struct conn * const c, char * const path, int *pstatus); int do_openfile(struct conn * const c, char * const path, @@ -121,7 +122,7 @@ int list_core(struct conn * const c, con #endif ); char classify(const mode_t mode); -void do_store(struct conn * const c, int append); +int do_store(struct conn * const c, int append); char *do_pwd(struct conn * const c, char * const retbuf, const char * const dir); #ifndef HAVE_POLL Index: ftpd.c =================================================================== --- ftpd.c.orig +++ ftpd.c @@ -13,6 +13,7 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + Portions Copyright (C) Ixia Communications 2003 Dan Kegel,Rohan Chitradurga */ /* @@ -148,6 +149,13 @@ #include #endif +#include + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + /* * does not export this to glibc2 systems, and it isn't * always defined anywhere else. @@ -177,18 +185,12 @@ struct ftran *first_ftran = NULL; struct dcache *first_dcache = NULL; #endif -#if HAVE_POLL -unsigned int highest_fds = 0; +rn_t rns; +pid_t own_pid; -#define FD_MAX 1024 -#define fds_send fds -struct pollfd fds[FD_MAX]; +struct list_element *destroyed_list_header = NULL; -#define MAXCLIENTS FD_MAX -#else -fd_set master_fds, master_send_fds; -#define MAXCLIENTS FD_SETSIZE -#endif +long int FD_MAX; #if WANT_XFERLOG FILE *xferlog = NULL; @@ -198,6 +200,7 @@ FILE *xferlog = NULL; int sendfile_supported = 1; #endif + /* * This variable specifies if it's soon time to check for timed out * clients, and timed out directory listing cache entries. It is @@ -249,56 +252,50 @@ int vsnprintf(char *str, size_t n, const } #endif -/* - * add_fd(): Add an fd to the set we monitor. Return 0 on success. - * This code is shared between poll() and select() versions. - */ -int add_fd(const int fd, const int events) -{ -#if HAVE_POLL - if (fd >= FD_MAX) { - printf("add_fd(%d, %x): failed\n", fd, events); - return E2BIG; - } - - fds[fd].fd = fd; - fds[fd].events = events; - if (highest_fds < fd) - highest_fds = fd; -#else - if (fd >= FD_SETSIZE) - return E2BIG; - if (events & POLLIN) - FD_SET(fd, &master_fds); - if (events & POLLOUT) - FD_SET(fd, &master_send_fds); -#endif - return 0; -} - -/* - * del_fd(): Close and remove an fd from the set(s) we monitor. (See also add_fd().) - */ -void del_fd(const int fd) +const char *ftran_convert_to_string(enum ftran_state state) { -#if HAVE_POLL - if (fd >= FD_MAX) - return; - - fds[fd].fd = -1; - fds[fd].events = 0; - - /* Reduce poll()'s workload by not making it watch past end of array */ - while ((highest_fds > 0) && (fds[highest_fds].fd == -1)) - highest_fds--; -#else - if (fd >= FD_SETSIZE) - return; - FD_CLR(fd, &master_fds); - FD_CLR(fd, &master_send_fds); -#endif - - close(fd); + switch (state) { + case FTRAN_STATE_NONE: + return "FTRAN_STATE_NONE"; + case FTRAN_STATE_LISTENING: + return "FTRAN_STATE_LISTENING"; + case FTRAN_STATE_CONNECTED_NO_CMD: + return "FTRAN_STATE_CONNECTED_NO_CMD"; + case FTRAN_STATE_LISTENING_GOT_CMD: + return "FTRAN_STATE_LISTENING_GOT_CMD"; + case FTRAN_STATE_GOTPORT: + return "FTRAN_STATE_GOTPORT"; + case FTRAN_STATE_CONNECTING: + return "FTRAN_STATE_CONNECTING"; + case FTRAN_STATE_TRANSFERING: + return "FTRAN_STATE_TRANSFERING"; + case FTRAN_STATE_DESTROYED: + return "FTRAN_STATE_DESTROYED"; + default: + ; + } + assert(FALSE); + return "UNKNOWN"; +} + +const char *conn_convert_to_string(enum conn_state cstate) +{ + switch (cstate) { + case CONN_STATE_INIT: + return "CONN_STATE_INIT"; + case CONN_STATE_WAIT_USER: + return "CONN_STATE_WAIT_USER"; + case CONN_STATE_WAIT_PASS: + return "CONN_STATE_WAIT_PASS"; + case CONN_STATE_READY: + return "CONN_STATE_READY"; + case CONN_STATE_DESTROYED: + return "CONN_STATE_DESTROYED"; + default: + ; + } + assert(FALSE); + return "UNKNOWN"; } #if 0 @@ -334,6 +331,18 @@ void add_to_linked_list(struct list_elem } } +void purge_destroyed_list() { + + struct list_element *elem = destroyed_list_header->next; + destroyed_list_header->next = NULL; + while (elem != NULL) + { + struct list_element *next = elem->next; + free(elem); + elem=next; + } +} + /* * remove_from_linked_list(): * Removes an element (conn, ftran or dcache) from its linked list, @@ -343,7 +352,7 @@ void remove_from_linked_list(struct list { if (elem->prev != NULL) elem->prev->next = elem->next; if (elem->next != NULL) elem->next->prev = elem->prev; - free(elem); + add_to_linked_list(destroyed_list_header,elem); } /* @@ -360,14 +369,20 @@ struct conn *alloc_new_conn(const int so if (c == NULL) return c; c->prev_conn = NULL; + c->transfer = NULL; + c->sock = sock; + c->buf_len = c->rest_pos = 0; + c->auth = CONN_STATE_INIT; + conn_GOTO(c,CONN_STATE_INIT); c->next_conn = NULL; if (sock != -1) { - ioctl(sock, FIONBIO, &one); - if (add_fd(sock, POLLIN) != 0) { + rn_prepare_fd_for_add(sock,own_pid); + if (rn_add(&rns,sock,client_eventhandler,c) != 0) { /* temp unavail */ send(sock, "230 Server too busy, please try again later.\r\n", 46, 0); close(sock); + c->sock = ILLEGAL_FD; return NULL; } @@ -379,9 +394,6 @@ struct conn *alloc_new_conn(const int so c->prev_conn = NULL; } - c->transfer = NULL; - c->sock = sock; - c->buf_len = c->auth = c->rest_pos = 0; #if WANT_ASCII c->ascii_mode = 0; #endif @@ -429,9 +441,12 @@ struct ftran *alloc_new_ftran(const int #endif f->owner = (struct conn * const)c; f->sock = sock; - f->state = 0; - f->local_file = -1; - + f->state = FTRAN_STATE_NONE; + ftran_GOTO(f,FTRAN_STATE_NONE); /* For logging */ + f->local_file = f->size = f->block_size = -1; + f->pos = 0; + f->tran_start=0; + #if WANT_DCACHE f->dir_cache = NULL; #endif @@ -445,12 +460,16 @@ struct ftran *alloc_new_ftran(const int * Destroy a control connection, remove it from the linked * list, and clean up after it. */ -void destroy_conn(struct conn * const c) +void _destroy_conn(struct conn * const c) { if (c == NULL) return; - del_fd(c->sock); + if (c->sock == ILLEGAL_FD) return; + rn_del(&rns,c->sock); + close(c->sock); + c->sock = ILLEGAL_FD; destroy_ftran(c->transfer); + conn_GOTO(c,CONN_STATE_DESTROYED); remove_from_linked_list((struct list_element *)c); } @@ -467,15 +486,22 @@ void destroy_conn(struct conn * const c) * If you wonder why I check for `defined(SOL_TCP)' and don't * provide an alternative, see the comments on init_file_transfer(). */ -void destroy_ftran(struct ftran * const f) +void _destroy_ftran(struct ftran * const f) { const unsigned int zero = 0; + int err; if (f == NULL) return; + if (f->state == FTRAN_STATE_DESTROYED) return; + + if (f->sock != ILLEGAL_FD) { #if defined(TCP_CORK) && defined(SOL_TCP) - setsockopt(f->sock, SOL_TCP, TCP_CORK, (void *)&zero, sizeof(zero)); + err=setsockopt(f->sock, SOL_TCP, TCP_CORK, (void *)&zero, sizeof(zero)); #endif - del_fd(f->sock); + rn_del(&rns,f->sock); + close(f->sock); + f->sock = ILLEGAL_FD; + } #if WANT_DCACHE if (f->dir_cache) { @@ -504,188 +530,255 @@ void destroy_ftran(struct ftran * const f->owner->transfer = NULL; + #if WANT_DCACHE if (f->dir_cache != NULL) f->dir_cache->use_count--; #endif + ftran_GOTO(f,FTRAN_STATE_DESTROYED); remove_from_linked_list((struct list_element *)f); } /* - * process_all_clients(): - * Processes all the _control_ connections in active_clients - * (normally returned from a select(), there are at max - * NUM_AC active connections in the set), sending them - * through to the command parser if a command has been - * entered. + *client_eventhandler(): + rn_WaitAndDispatch calls this function as the event handler for + any event occuring on a client connection. */ -#if HAVE_POLL -int process_all_clients(const int num_ac) -#else -int process_all_clients(const fd_set * const active_clients, const int num_ac) -#endif + +int client_eventhandler(rn_pollevent_t * e) { - struct conn *c = NULL, *next = first_conn->next_conn; - int checked_through = 0; + int bytes_avail,status,loop_cnt; + struct conn *c = e->client.data; - /* run through the linked list */ - while (next != NULL && checked_through < num_ac) { - int bytes_avail; + DPRINT("c->auth = %s\n",conn_convert_to_string(c->auth)); - c = next; - next = c->next_conn; -#if HAVE_POLL - if ((fds[c->sock].revents & (POLLIN|POLLERR|POLLHUP|POLLNVAL)) == 0) { - continue; - } -#else - if (!FD_ISSET(c->sock, active_clients)) { - continue; - } -#endif + if (c->auth == CONN_STATE_DESTROYED) + return 0; - checked_through++; +/* POLLHUP just means "writes would fail, and some future read will return EOF"; + * we still need to finish reading even after POLLHUP, so ignore it. + */ - bytes_avail = recv(c->sock, c->recv_buf + c->buf_len, - 255 - c->buf_len, 0); - if (bytes_avail <= 0) { - /* - * select() has already told us there's something about - * this socket, so if we get a return value of zero, the - * client has closed the socket. If we get a return value - * of -1 (error), we close the socket ourselves. - * - * We do the same for poll(), even though we actually have - * bits that tell us what is happening (in case of new - * input AND error/hangup at the same time, we do an - * explicit check at the bottom of the loop as well). - */ - destroy_conn(c); - continue; - } + if (e->revents & (rn_POLLERR /* | rn_POLLHUP */)) { + DPRINT("revents %x\n", e->revents); + destroy_conn(c); + return 1; + } - /* overrun = disconnect */ - if (c->buf_len + bytes_avail > 254) { - if (numeric(c, 503, "Buffer overrun; disconnecting.")) - destroy_conn(c); - continue; + loop_cnt = 0; + /* Loop until EWOULDBLOCK or error */ + while (1) { + bytes_avail = + recv(c->sock, c->recv_buf + c->buf_len, 255 - c->buf_len, 0); + + loop_cnt++; + DPRINT("loop count = %d, recv returns %d\n",loop_cnt,bytes_avail); + if (bytes_avail > 0) { + /* overrun = disconnect */ + if (c->buf_len + bytes_avail > 254) { + if (numeric(c, 503, "Buffer overrun; disconnecting.")) { + /* numeric didn't destroy the connection, so we get to. */ + destroy_conn(c); } + /* connection is destroyed by here */ + return 1; + } + + /* normal case */ + c->buf_len += bytes_avail; + status = parse_command(c); - c->buf_len += bytes_avail; - parse_command(c); + /* a return of 0 means connection is freed. */ + if (!status) break; - /* FIXME: c could be invalid here, as parse_command can destroy it - if (fds[c->sock].revents & (POLLERR|POLLHUP|POLLNVAL)) { - destroy_conn(c); - } - */ + } else if (bytes_avail < 0) { + if (errno == EWOULDBLOCK) + break; + if (errno != EINTR) { + DPRINT("errno %d, destroying connection\n", errno); + destroy_conn(c); + return 1; + } + } else /* if (bytes_avail == 0) */ { + /* + * select() has already told us there's something about + * this socket, so if we get a return value of zero, the + * client has closed the socket. If we get a return value + * of -1 (error), we close the socket ourselves. + * + * We do the same for poll(), even though we actually have + * bits that tell us what is happening (in case of new + * input AND error/hangup at the same time, we do an + * explicit check at the bottom of the loop as well). + */ + if (loop_cnt == 1) { + DPRINT("EOF destroying connection,cnt = %d\n",loop_cnt); + destroy_conn(c); + return 1; + } else + break; } - return checked_through; + } + return 0; } -/* - * process_all_sendfiles(): - * Sends data to all clients that are ready to receive it. - * Also checks for data connections that are newly-connected, - * and handler xferlog entries for the files that are finished. +/* ftran_listening_ioready(): + * This eventhandler is instantiated when a PASV command is received. The + * state of ftran when this is invoked can be one of FTRAN_STATE_LISTENING + * or FTRAN_STATE_LISTENING_GOT_CMD depending on whether another command is + * received before this eventhandler can be called. */ -#if HAVE_POLL -int process_all_sendfiles(const int num_ac) -#else -int process_all_sendfiles(fd_set * const active_clients, const int num_ac) -#endif + +int ftran_listening_ioready(rn_pollevent_t *e) { - struct ftran *f = NULL, *next = first_ftran->next_ftran; - int checked_through = 0; - struct sockaddr tempaddr; - int tempaddr_len = sizeof(tempaddr); - - while (next != NULL && checked_through < num_ac) { - f = next; - next = f->next_ftran; + struct ftran *f = e->client.data; + + if (f->state == FTRAN_STATE_DESTROYED) + return 0; + + assert((f->state == FTRAN_STATE_LISTENING) || (f->state == FTRAN_STATE_LISTENING_GOT_CMD)); + + assert(f->sock == e->fd); + + DPRINT("fd %d, state %d\n", f->sock, f->state); + +/* POLLHUP just means "writes would fail, and some future read will return EOF"; + * we still need to finish reading even after POLLHUP, so ignore it. + */ + if (e->revents & (rn_POLLERR /* | rn_POLLHUP */ )) { + destroy_ftran(f); + return 0; + } -#if HAVE_POLL - if (fds[f->sock].revents & (POLLHUP|POLLERR|POLLNVAL)) { + /* We only expect one connect, but looping needs to be done to take care of interrupted system calls */ + for(;;){ + struct sockaddr tempaddr; + int tempaddr_len = sizeof(tempaddr); + const unsigned int one = 1; + + int tempsock = accept(f->sock, (struct sockaddr *)&tempaddr,&tempaddr_len); + DPRINT("accept returned fd %d\n", tempsock); + + if (tempsock == -1) { + if (errno == EWOULDBLOCK) + break; + else if ((errno == EINTR) || (errno == EPROTO) || (errno == ECONNABORTED)) + continue; destroy_ftran(f); - continue; - } -#endif + break; + } + /* done accepting */ + rn_del(&rns,f->sock); + close(f->sock); + + f->sock = tempsock; - /* state = 2: incoming PASV, state >3: send file */ -#if HAVE_POLL - if ((f->state < 2) || (f->state == 3) || (fds[f->sock].revents & (POLLIN|POLLOUT)) == 0) { -#else - if ((f->state < 2) || (f->state == 3) || !FD_ISSET(f->sock, active_clients)) { -#endif - continue; + if (f->state == FTRAN_STATE_LISTENING) + { + f->state = FTRAN_STATE_CONNECTED_NO_CMD; + } + else if (f->state == FTRAN_STATE_LISTENING_GOT_CMD) + { + ftran_GOTO(f,FTRAN_STATE_TRANSFERING); + init_file_transfer(f); /* this function will fake the first event + by calling ftran_data_ioready that might + destroy ftran + */ + } + break; } + + assert((f->state == FTRAN_STATE_CONNECTED_NO_CMD) || (f->state == FTRAN_STATE_TRANSFERING) || (f->state == FTRAN_STATE_DESTROYED) || (f->state == FTRAN_STATE_LISTENING) || (f->state == FTRAN_STATE_LISTENING_GOT_CMD)); + return 0; +} - checked_through++; +/* ftran_data_ioready(): + * Sends data to client that are ready to receive it, + * and handle xferlog entries for the files that are finished. + * + */ +int ftran_data_ioready(rn_pollevent_t * e) +{ + enum ftran_ioresult transfer_status; + struct ftran *f = e->client.data; -#if HAVE_POLL - /* Nothing is needed for the poll() version? */ -#else - FD_CLR(f->sock, active_clients); -#endif + if (f->state == FTRAN_STATE_DESTROYED) + return 0; - if (f->state == 2) { /* incoming PASV */ - const unsigned int one = 1; - const int tempsock = accept(f->sock, (struct sockaddr *)&tempaddr, - &tempaddr_len); + DPRINT("fd %d, state %d\n", f->sock, f->state); - del_fd(f->sock); + assert((f->state == FTRAN_STATE_CONNECTING) || (f->state == FTRAN_STATE_TRANSFERING)); - if (tempsock == -1) { - destroy_ftran(f); - continue; - } + assert(f->sock == e->fd); + +/* POLLHUP just means "writes would fail, and some future read will return EOF"; + * we still need to finish reading even after POLLHUP, so ignore it. + */ + if (e->revents & (rn_POLLERR /* | rn_POLLHUP */ )) { + destroy_ftran(f); + return 0; + } + + if ((f->state == FTRAN_STATE_CONNECTING)) { + /* FIXME: should retrieve the status of the non blocking connect before initiating + * transfer + */ + init_file_transfer(f); - f->sock = tempsock; - ioctl(f->sock, FIONBIO, &one); - init_file_transfer(f); -#if WANT_UPLOAD - if (f->upload) continue; -#endif - } - if (f->state < 5) { - init_file_transfer(f); #if WANT_UPLOAD - if (f->upload) continue; + if (f->upload) + return 0; /* FIXME */ #endif - } + } + + assert(f->state == FTRAN_STATE_TRANSFERING); + + do { - /* for download, we send the first packets right away */ #if WANT_UPLOAD - if (f->upload) { - if (do_upload(f)) continue; - } else + if (f->upload) { + transfer_status = do_upload(f); + } else #endif - if (do_download(f)) continue; + transfer_status = do_download(f); - /* do_{upload,download} returned 0, the transfer is complete */ - if (!numeric(f->owner, 226, "Transfer complete.")) - continue; - time(&(f->owner->last_transfer)); + if (transfer_status == FTRAN_IORESULT_BLOCKED) + return; + } + while (transfer_status == FTRAN_IORESULT_PARTIAL); + + assert((transfer_status == FTRAN_IORESULT_COMPLETE) || (transfer_status == FTRAN_IORESULT_FAILED)); + + if (transfer_status == FTRAN_IORESULT_COMPLETE) { + /* do_{upload,download} returned TRANSFER_STATUS_COMPLETE, the transfer is complete */ + if (!numeric(f->owner, 226, "Transfer complete")) + return 1; + time(&(f->owner->last_transfer)); + } + else { + if (!numeric(f->owner, 550, "Transfer Failed")) + return 1; + } #if WANT_XFERLOG - if (!f->dir_listing) { - write_xferlog(f); - } + if (!f->dir_listing) + write_xferlog(f); #endif - destroy_ftran(f); + destroy_ftran(f); + #if WANT_FULLSCREEN - update_display(first_conn); + update_display(first_conn); #endif - } - return checked_through; + return 0; } #if WANT_UPLOAD -int do_upload(struct ftran *f) +enum ftran_ioready do_upload(struct ftran *f) { char upload_buf[16384]; + enum ftran_ioresult upload_status; int size; #if WANT_ASCII /* keep buffer size small in ascii transfers @@ -714,26 +807,39 @@ int do_upload(struct ftran *f) size = ascii_uploadfilter(upload_buf, size); } #endif - if (size > 0 && (write(f->local_file, upload_buf, size) == size)) { - return 1; - } else if (size == -1) { - /* don't write xferlog... or? */ - numeric(f->owner, 426, strerror(errno)); - destroy_ftran(f); - return 1; - } - return 0; + if ((size == -1) && (errno == EWOULDBLOCK)) + upload_status = FTRAN_IORESULT_BLOCKED; + else if ((size == -1) && (errno != EWOULDBLOCK)) { + upload_status = FTRAN_IORESULT_FAILED; + /* don't write xferlog... or? */ + numeric(f->owner, 426, strerror(errno)); + } + else if (size > 0) { + if (write(f->local_file, upload_buf, size) == size) + upload_status = FTRAN_IORESULT_PARTIAL; + else + upload_status = FTRAN_IORESULT_FAILED; + } + else if (size == 0) + upload_status = FTRAN_IORESULT_COMPLETE; + + DPRINT("f->sock %d, size %d, upload status %d\n",f->sock,size,f->pos,upload_status); + return upload_status; } #endif -int do_download(struct ftran *f) +/* Return TRUE if there is more to send, FALSE if the caller + * can destroy the ftran. + * On exit, the ftran is never destroyed, that's the caller's job. + */ +enum ftran_ioresult do_download(struct ftran *f) { #if defined(TCP_CORK) && defined(SOL_TCP) unsigned int zero = 0; #endif char *sendfrom_buf; - int bytes_to_send; - int more_to_send = 0; + int bytes_to_send,nbytes; + enum ftran_ioresult download_status; #if !HAVE_MMAP char buf[MAX_BLOCK_SIZE]; @@ -749,8 +855,8 @@ int do_download(struct ftran *f) * Here we use a rather simplified sending `algorithm', * leaving most of the quirks to the system calls. */ + /* FIXME: don't activate this if WANT_ASCII && (f->ascii_mode == 1) */ if (sendfile_supported == 1 && f->dir_listing == 0) { - int err; size = f->size - f->pos; if (size > f->block_size) size = f->block_size; @@ -758,30 +864,39 @@ int do_download(struct ftran *f) #ifdef TCP_CORK if (size != f->block_size) { - setsockopt(f->sock, SOL_TCP, TCP_CORK, (void *)&zero, sizeof(zero)); + err=setsockopt(f->sock, SOL_TCP, TCP_CORK, (void *)&zero, sizeof(zero)); } #endif - err = sendfile(f->sock, f->local_file, &f->pos, size); - return (f->pos < f->size) && (err > -1); + nbytes = sendfile(f->sock, f->local_file, &f->pos, size); + if (nbytes == -1) assert(errno != EINTR); + if (f->pos == f->size) + download_status = FTRAN_IORESULT_COMPLETE; + else if (nbytes > -1) + download_status = FTRAN_IORESULT_PARTIAL; + else if (errno == EWOULDBLOCK) + download_status = FTRAN_IORESULT_BLOCKED; + else + download_status = FTRAN_IORESULT_FAILED; + + DPRINT("f->sock %d, f->local_file %d, f->pos %d, size %d, bytes sent %d download_status %d\n",f->sock,f->local_file,f->pos,size,nbytes,download_status); + + return download_status; } #endif #if HAVE_MMAP size = f->size - f->pos; - if (size > f->block_size) size = f->block_size; if (size < 0) size = 0; bytes_to_send = size; sendfrom_buf = f->file_data + f->pos; #else - bytes_to_send = read(f->local_file, buf, f->block_size); + bytes_to_send = pread(f->local_file, buf, f->block_size,f->pos); sendfrom_buf = buf; #endif - if (bytes_to_send == f->block_size) more_to_send = 1; - #if WANT_ASCII if (f->ascii_mode == 1) { bytes_to_send = ascii_downloadfilter(sendfrom_buf, @@ -790,27 +905,35 @@ int do_download(struct ftran *f) } #endif /* WANT_ASCII */ -#if defined(TCP_CORK) && defined(SOL_TCP) - /* if we believe this is the last packet, unset TCP_CORK */ - if (more_to_send == 0) { - setsockopt(f->sock, SOL_TCP, TCP_CORK, (void *)&zero, sizeof(zero)); - } -#endif - - size = send(f->sock, sendfrom_buf, bytes_to_send, 0); - if (size < bytes_to_send) more_to_send = 1; + nbytes = send(f->sock, sendfrom_buf, bytes_to_send, 0); #if WANT_ASCII - if (f->ascii_mode == 1 && size < bytes_to_send && size > 0) { - size = ascii_findlength(sendfrom_buf, size); + if (f->ascii_mode == 1 && nbytes < bytes_to_send && nbytes > 0) { + size = ascii_findlength(sendfrom_buf, nbytes); } #endif -#if HAVE_MMAP if (size > 0) f->pos += size; + + if (f->pos == f->size) + download_status = FTRAN_IORESULT_COMPLETE; + else if ( ((nbytes == bytes_to_send) && (f->pos < f->size)) || ((nbytes < bytes_to_send) && (nbytes > -1)) ) + download_status = FTRAN_IORESULT_PARTIAL; + else if (errno == EWOULDBLOCK) + download_status = FTRAN_IORESULT_BLOCKED; + else + download_status = FTRAN_IORESULT_FAILED; + + DPRINT("send returns %d, bytes to send %d, with errno %d, transfer status = %d\n",nbytes,bytes_to_send,errno,download_status); + +#if defined(TCP_CORK) && defined(SOL_TCP) + /* if we believe this is the last packet, unset TCP_CORK */ + if (download_status == FTRAN_IORESULT_COMPLETE) { + int err=setsockopt(f->sock, SOL_TCP, TCP_CORK, (void *)&zero, sizeof(zero)); + } #endif - return more_to_send; + return download_status; } #if WANT_XFERLOG @@ -875,40 +998,31 @@ void screw_clients() int main(void) { int server_sock; + struct rlimit rlimit; -#if HAVE_POLL - /* the sets are declared globally if we use poll() */ -#else - fd_set fds, fds_send; -#endif + getrlimit(RLIMIT_NOFILE,&rlimit); + FD_MAX = rlimit.rlim_max; + printf("FD_MAX set to %d\n",FD_MAX); /*setlinebuf(stdout);*/ setvbuf(stdout, (char *)NULL, _IOLBF, 0); signal(SIGPIPE, SIG_IGN); - printf("BetaFTPD version %s, Copyright (C) 1999-2000 Steinar H. Gunderson\n", VERSION); + + printf("BetaFTPD version %s, Copyright (C) 1999-2000 Steinar H. Gunderson, portions Copyright 2003 Ixia Communications\n", VERSION); puts("BetaFTPD comes with ABSOLUTELY NO WARRANTY; for details see the file"); puts("COPYING. This is free software, and you are welcome to redistribute it"); puts("under certain conditions; again see the file COPYING for details."); puts(""); + /* we don't need stdin */ close(0); -#if HAVE_POLL - { - int i; - for (i = 0; i < FD_MAX; i++) { - fds[i].fd = -1; - fds[i].events = 0; - } - } -#else - FD_ZERO(&master_fds); - FD_ZERO(&master_send_fds); -#endif - + own_pid=getpid(); + rn_init(&rns,FD_MAX); + rn_setSignum(&rns,SIGRTMIN); server_sock = create_server_socket(); #if WANT_FULLSCREEN @@ -916,6 +1030,7 @@ int main(void) #endif /* init dummy first connection */ + first_conn = alloc_new_conn(-1); first_ftran = alloc_new_ftran(0, NULL); #if WANT_DCACHE @@ -968,102 +1083,42 @@ int main(void) if (errno == ENOSYS) sendfile_supported = 0; } #endif + /* create the header of the list containing all the deleted objects + * (both conn and ftran). + */ - for ( ;; ) { - int i; -#ifndef HAVE_POLL - struct timeval timeout; -#endif - - /*screw_clients(); //look for memory errors */ + destroyed_list_header = (struct list_element *) calloc(1,sizeof(struct list_element)); + for ( ;; ) { + int i,err; + #if WANT_FULLSCREEN update_display(first_conn); #endif - -#if HAVE_POLL - i = poll(fds, highest_fds + 1, 60000); -#if 0 - { - int j; - for (j=0; j<=highest_fds; j++) { - if (fds[j].revents) printf("fds[%d].fd %d, .revents %x\n", j, fds[j].fd, fds[j].revents); + + err=rn_waitAndDispatchEvents(&rns,100000); + if (err) { + if (err == EBADF) { + /* fill in here later */ } - } -#endif -#else - /* reset fds (gets changed by select()) */ - fds = master_fds; - fds_send = master_send_fds; - - /* - * wait up to 60 secs for any activity - */ - timeout.tv_sec = 60; - timeout.tv_usec = 0; - - i = select(FD_SETSIZE, &fds, &fds_send, NULL, &timeout); -#endif - - if (i == -1) { - if (errno == EBADF) { -#if !HAVE_POLL - /* don't like this, but we have to */ - clear_bad_fds(&server_sock); -#endif - } else if (errno != EINTR) { -#if HAVE_POLL - perror("poll()"); -#else - perror("select()"); -#endif + else if (err == EWOULDBLOCK) { continue; } } + + /* once waitAndDispatchEvents has returned, we can delete + * the list of destroyed objects. + */ -#if HAVE_POLL - /* fix an invalid server socket */ - if (fds[server_sock].revents & POLLERR) { - del_fd(server_sock); - server_sock = create_server_socket(); - } -#endif - + purge_destroyed_list(); + /* remove any timed out sockets */ if (time_to_check) { time_out_sockets(); #if WANT_DCACHE - time_out_dcache(); + time_out_dcache(); #endif - time_to_check = 0; - } - - if (i <= 0) continue; - -#if HAVE_POLL - i -= process_all_sendfiles(i); - process_all_clients(i); -#else - /* sends are given highest `priority' */ - i -= process_all_sendfiles(&fds_send, i); - - /* incoming PASV connections and uploads */ - i -= process_all_sendfiles(&fds, i); - - /* - * check the incoming PASV connections first, so - * process_all_clients() won't be confused. - */ - process_all_clients(&fds, i); -#endif - -#if HAVE_POLL - if (fds[server_sock].revents & POLLIN) { -#else - if (FD_ISSET(server_sock, &fds)) { -#endif - accept_new_client(&server_sock); - i--; + time_to_check = 0; } } } @@ -1073,32 +1128,42 @@ int main(void) * Open a socket for the new client, say hello and put it in * among the others. */ -void accept_new_client(int * const server_sock) +int accept_new_client(rn_pollevent_t *e) { struct sockaddr_in tempaddr; + rn_pollevent_t e1; int tempaddr_len = sizeof(tempaddr); - const int tempsock = accept(*server_sock, (struct sockaddr *)&tempaddr, &tempaddr_len); - static int num_err = 0; + for(;;) + { + const int tempsock = accept(e->fd, (struct sockaddr *)&tempaddr, &tempaddr_len); + DPRINT("accept returned fd %d\n", tempsock); - if (tempsock < 0) { + if (tempsock < 0) { + if (errno == EWOULDBLOCK) break; /* handle other errno's due to which this might loop infinitely. See stevens UNPv1, page 424*/ + else if ((errno == EINTR) || (errno == EPROTO) || (errno == ECONNABORTED)) + continue; + else + { #ifndef WANT_FORK - perror("accept()"); + perror("accept()"); #endif - close(tempsock); - if ((errno == EBADF || errno == EPIPE) && ++num_err >= 3) { - del_fd(*server_sock); - *server_sock = create_server_socket(); - } - } else { - struct conn * const c = alloc_new_conn(tempsock); - num_err = 0; - if (c != NULL) { - if (numeric(c, 220, "BetaFTPD " VERSION " ready.")) { + break; + } + } else { + /* done accepting */ + struct conn * const c = alloc_new_conn(tempsock); + if (c != NULL) { + if (numeric(c, 220, "BetaFTPD " VERSION " ready.")) { #if WANT_STAT - memcpy(&(c->addr), &tempaddr, sizeof(struct sockaddr)); + memcpy(&(c->addr), &tempaddr, sizeof(struct sockaddr)); #endif - ; + } + DPRINT("Fake the first control event in case the client sends USER command early\n"); + e1.fd = c->sock; + e1.revents = rn_POLLIN; + e1.client.data = c; + client_eventhandler(&e1); } } } @@ -1139,7 +1204,7 @@ void time_out_sockets() c = next; next = c->next_conn; - if ((c->transfer == NULL || c->transfer->state != 5) && + if ((c->transfer == NULL || c->transfer->state != FTRAN_STATE_TRANSFERING) && (now - c->last_transfer > TIMEOUT_SECS)) { /* RFC violation? */ if (numeric(c, 421, "Timeout (%u minutes): Closing control connection.", TIMEOUT_SECS/60)) @@ -1188,6 +1253,7 @@ int numeric(struct conn * const c, const va_end(args); err = send(c->sock, buf, i, 0); + DPRINT("fd %d sending %s, err = %d\n",c->sock,buf,err); if (err == -1 && errno == EPIPE) { destroy_conn(c); return 0; @@ -1220,14 +1286,16 @@ void init_file_transfer(struct ftran * c struct conn * const c = f->owner; const int mode = IPTOS_THROUGHPUT, zero = 0, one = 1; struct stat buf; - int events; + int err; + rn_pollevent_t e; + #ifdef SOL_TCP /* we want max throughput */ setsockopt(f->sock, SOL_IP, IP_TOS, (void *)&mode, sizeof(mode)); setsockopt(f->sock, SOL_TCP, TCP_NODELAY, (void *)&zero, sizeof(zero)); #ifdef TCP_CORK - setsockopt(f->sock, SOL_TCP, TCP_CORK, (void *)&one, sizeof(one)); + err=setsockopt(f->sock, SOL_TCP, TCP_CORK, (void *)&one, sizeof(one)); #endif #else /* should these pointers be freed afterwards? */ @@ -1249,7 +1317,6 @@ void init_file_transfer(struct ftran * c #if WANT_ASCII f->ascii_mode = f->owner->ascii_mode; #endif - /* find the preferred block size */ f->block_size = MAX_BLOCK_SIZE; if (fstat(f->local_file, &buf) != -1 && @@ -1258,38 +1325,17 @@ void init_file_transfer(struct ftran * c } } - f->state = 5; - - events = POLLOUT; -#if WANT_UPLOAD - if (f->upload) { - events = POLLIN; - } -#endif /* WANT_UPLOAD */ - - TRAP_ERROR(add_fd(f->sock, events), 500, return); - + ling.l_onoff = 0; ling.l_linger = 0; setsockopt(f->sock, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling)); -#if !HAVE_POLL && WANT_UPLOAD - /* - * if we let an upload socket stay in master_send_fds, we would - * get data that would fool us into closing the socket... (sigh) - */ - if (f->upload) { - FD_CLR(f->sock, &master_send_fds); - FD_SET(f->sock, &master_fds); - } -#endif - time(&(f->owner->last_transfer)); if (f->dir_listing) { /* include size? */ if (!numeric(f->owner, 150, "Opening ASCII mode data connection for directory listing.")) - return; + return; /* FIXME caller will crash */ } else { /* * slightly kludged -- perhaps we should kill the second arm, @@ -1356,6 +1402,29 @@ void init_file_transfer(struct ftran * c #else /* !HAVE_MMAP */ lseek(f->local_file, f->owner->rest_pos, SEEK_SET); #endif + + /* if init_file_transfer is called in this state, then fake the first event */ + + switch (f->state) { + case FTRAN_STATE_TRANSFERING: + rn_prepare_fd_for_add(f->sock,own_pid); + err = rn_add(&rns,f->sock,ftran_data_ioready,f); + if (err != 0) { + close(f->sock); + f->sock = ILLEGAL_FD; + destroy_ftran(f); + TRAP_ERROR(err != 0, 501, return ) ; + } + DPRINT("Fake the first event to get the ball rolling\n"); + e.fd = f->sock; + e.revents = rn_POLLOUT; + e.client.data = f; + ftran_data_ioready(&e); + break; + case FTRAN_STATE_CONNECTING: + ftran_GOTO(f,FTRAN_STATE_TRANSFERING); + break; + } } /* @@ -1369,7 +1438,7 @@ int create_server_socket() const unsigned int one = 1; struct sockaddr_in addr; int err; - + /* * In the `perfect' world, if an address was in use, we could * just wait for the kernel to clear everything up, and everybody @@ -1379,7 +1448,6 @@ int create_server_socket() * up right away... hence this option. */ setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - ioctl(server_sock, FIONBIO, &one); /* just in case */ addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; @@ -1402,11 +1470,12 @@ int create_server_socket() } } while (err == -1); - listen(server_sock, 20); - - err = add_fd(server_sock, POLLIN); + listen(server_sock, 40); + + rn_prepare_fd_for_add(server_sock,own_pid); + err = rn_add(&rns,server_sock, accept_new_client,NULL); if (err) { - perror("add_fd"); + perror("rn_add"); return -1; } Index: ftpd.h =================================================================== --- ftpd.h.orig +++ ftpd.h @@ -101,6 +101,16 @@ struct list_element { /* structure specific data here */ }; +#define ILLEGAL_FD -1 + +enum conn_state { + CONN_STATE_DESTROYED, + CONN_STATE_INIT, + CONN_STATE_WAIT_USER, + CONN_STATE_WAIT_PASS, + CONN_STATE_READY, +}; + /* doubly linked list of active connections */ struct conn { struct conn *prev_conn; @@ -117,7 +127,7 @@ struct conn { char rename_from[256]; int buf_len; - int auth; + enum conn_state auth; char username[17]; @@ -135,19 +145,40 @@ struct conn { time_t last_transfer; }; +const char *conn_convert_to_string(enum conn_state); + +#define conn_GOTO(c,s) do { DPRINT("c->sock = %d, oldstate = %s, newstate = %s\n",c->sock,conn_convert_to_string(c->auth),conn_convert_to_string(s)); c->auth = s; } while(0) + +enum ftran_ioresult { + FTRAN_IORESULT_COMPLETE, + FTRAN_IORESULT_PARTIAL, + FTRAN_IORESULT_BLOCKED, + FTRAN_IORESULT_FAILED +}; + +enum ftran_state { + FTRAN_STATE_NONE, + FTRAN_STATE_LISTENING, + FTRAN_STATE_CONNECTED_NO_CMD, + FTRAN_STATE_LISTENING_GOT_CMD, + FTRAN_STATE_GOTPORT, + FTRAN_STATE_CONNECTING, + FTRAN_STATE_TRANSFERING, + FTRAN_STATE_DESTROYED +}; + /* doubly linked list of file transfers */ struct ftran { struct ftran *prev_ftran; struct ftran *next_ftran; struct conn *owner; - int state; /* + enum ftran_state state; /* * 0 = none, 1 = got PASV addr, - * 2 = waiting on PASV socket, - * 3 = got PORT addr, 4 = waiting for - * PORT connect, - * 5 = transferring file (or waiting - * for PORT connect) + * 2 = accepted data connection on PASV socket,not got cmd + * yet, 3 = got cmd (followed by pasv cmd), 4 = got PORT + * cmd, 5 = waiting for cmd following PORT cmd, + * 6 = transfering, 7 = destroyed */ struct sockaddr_in sin; int sock; @@ -176,6 +207,11 @@ struct ftran { #endif }; + +const char *ftran_convert_to_string(enum ftran_state); + +#define ftran_GOTO(f,s) do { DPRINT("f->sock = %d, oldstate = %s, newstate = %s\n",f->sock,ftran_convert_to_string(f->state),ftran_convert_to_string(s)); f->state = s; } while(0) + void add_to_linked_list(struct list_element * const first, struct list_element * const elem); void remove_from_linked_list(struct list_element * const elem); @@ -184,31 +220,31 @@ struct conn *alloc_new_conn(const int so struct ftran *alloc_new_ftran(const int sock, const struct conn * const c); int add_fd(const int fd, const int events); -void del_fd(const int fd); -void destroy_conn(struct conn * const c); -void destroy_ftran(struct ftran * const f); +void _destroy_conn(struct conn * const c); +#define destroy_conn(c) do { if (c) DPRINT("destroy_conn(c %p, fd %d)\n",c,c->sock); _destroy_conn(c); } while(0) +void _destroy_ftran(struct ftran * const f); +#define destroy_ftran(f) do { if (f) DPRINT("destroy_ftran(f %p, fd %d)\n", f, f->sock); _destroy_ftran(f); } while (0) + +/* The three functions to handle the three types of events that can occur */ + +int client_eventhandler(rn_pollevent_t *); +int ftran_listening_ioready(rn_pollevent_t *); +int ftran_data_ioready(rn_pollevent_t *); +int accept_new_client(rn_pollevent_t *); -#if HAVE_POLL -int process_all_clients(const int num_ac); -int process_all_sendfiles(const int num_ac); -#else -int process_all_clients(const fd_set * const active_clients, const int num_ac); -int process_all_sendfiles(fd_set * const active_clients, const int num_ac); -#endif - -int do_upload(struct ftran *f); -int do_download(struct ftran *f); +enum ftran_ioresult do_upload(struct ftran *f); +enum ftran_ioresult do_download(struct ftran *f); void write_xferlog(struct ftran *f); int main(void); RETSIGTYPE handle_alarm(int signum); -void accept_new_client(int * const server_sock); void time_out_sockets(); void remove_bytes(struct conn * const c, const int i); int numeric(struct conn * const c, const int numeric, const char * const format, ...); + void init_file_transfer(struct ftran * const f); int create_server_socket(); @@ -221,4 +257,6 @@ void dump_file(struct conn * const c, co void list_readmes(struct conn * const c); #endif +extern rn_t rns; +extern pid_t own_pid; #endif Index: nonroot.c =================================================================== --- nonroot.c.orig +++ nonroot.c @@ -67,7 +67,7 @@ struct users { /* we will add cacheing of both users and rights LATER :-) */ -int nr_userinfo(const char * const username, int * const uid, +enum conn_state nr_userinfo(const char * const username, int * const uid, char * const homedir, char * const rootdir, const char * const password) { @@ -75,7 +75,7 @@ int nr_userinfo(const char * const usern char this_username[256]; char real_password[256]; - if (users_file == NULL) return 0; /* panic, reject all users */ + if (users_file == NULL) return CONN_STATE_INIT; /* panic, reject all users */ /* * ignores gids atm, we may want to change that in the future @@ -93,14 +93,14 @@ int nr_userinfo(const char * const usern printf("rdir = %s\nEND\n", rootdir); if (strcmp(real_password, crypt(password, real_password)) == 0) { - return 3; + return CONN_STATE_READY; } else { - return 0; + return CONN_STATE_INIT; } } fclose(users_file); - return 0; /* no such user */ + return CONN_STATE_INIT; /* no such user */ } /* Index: nonroot.h =================================================================== --- nonroot.h.orig +++ nonroot.h @@ -18,7 +18,7 @@ #ifndef _NONROOT_H #define _NONROOT_H 1 -int nr_userinfo(const char * const username, int * const uid, +enum conn_state nr_userinfo(const char * const username, int * const uid, char * const homedir, char * const rootdir, const char * const password); int nr_check_permission(const uid_t uid, const char * const object,