/* FTP Server state machine - see RFC 959 */ #define LINELEN 128 /* Length of command buffer */ #include #include "machdep.h" #include "mbuf.h" #include "netuser.h" #include "timer.h" #include "tcp.h" #include "ftp.h" /* Command table */ static char *commands[] = { "user", #define USER_CMD 0 "acct", #define ACCT_CMD 1 "pass", #define PASS_CMD 2 "type", #define TYPE_CMD 3 "list", #define LIST_CMD 4 "cwd", #define CWD_CMD 5 "dele", #define DELE_CMD 6 "name", #define NAME_CMD 7 "quit", #define QUIT_CMD 8 "retr", #define RETR_CMD 9 "stor", #define STOR_CMD 10 "port", #define PORT_CMD 11 "nlst", #define NLST_CMD 12 "pwd", #define PWD_CMD 13 "xpwd", /* For compatibility with 4.2BSD */ #define XPWD_CMD 14 NULLCHAR }; /* Response messages */ static char banner[] = "220 %s FTP Ready\r\n"; static char badcmd[] = "500 Unknown command\r\n"; static char nopass[] = "202 Password not needed\r\n"; static char logged[] = "230 Logged in\r\n"; static char typeok[] = "200 Type OK\r\n"; static char cwdok[] = "250 CWD OK\r\n"; static char pwdmsg[] = "257 \"%s\" is current directory\r\n"; static char badtype[] = "501 Unknown type\r\n"; static char badport[] = "501 Bad port syntax\r\n"; static char unimp[] = "502 Command not yet implemented\r\n"; static char bye[] = "221 Goodbye!\r\n"; static char nodir[] = "553 Can't read directory\r\n"; static char cantopen[] = "550 Can't open file\r\n"; static char sending[] = "150 Opening data connection for %s %s\r\n"; static char cantmake[] = "553 Can't create file\r\n"; static char portok[] = "200 Port command okay\r\n"; static char rxok[] = "226 File received OK\r\n"; static char txok[] = "226 File sent OK\r\n"; static struct tcb *ftp_tcb; /* Start up FTP service */ ftp_start(argc,argv) int argc; char *argv[]; { struct socket lsocket; void r_ftp(),s_ftp(); lsocket.address = ip_addr; if(argc < 2) lsocket.port = FTP_PORT; else lsocket.port = atoi(argv[1]); ftp_tcb = open_tcp(&lsocket,NULLSOCK,TCP_PASSIVE,0,r_ftp,NULLVFP,s_ftp,0,(int *)NULL); } ftp_stop() { if(ftp_tcb != NULLTCB) close_tcp(ftp_tcb); } /* FTP server control channel connection state change upcall handler */ static void s_ftp(tcb,old,new) struct tcb *tcb; char old,new; { extern char hostname[]; struct ftp *ftp,*ftp_create(); void ftp_delete(); char *inet_ntoa(),*pwd(); switch(new){ #ifdef QUICKSTART case SYN_RECEIVED: #else case ESTABLISHED: #endif if((ftp = ftp_create(LINELEN)) == NULLFTP){ /* No space, kill connection */ close_tcp(tcb); return; } ftp->control = tcb; /* Downward link */ tcb->user = (int *)ftp; /* Upward link */ /* Set default data port */ ftp->port.address = tcb->conn.remote.address; ftp->port.port = FTPD_PORT; /* Note current directory */ #ifndef AMIGA ftp->cd = pwd(); #endif log(tcb,"open FTP"); tprintf(ftp->control,banner,hostname); break; case CLOSE_WAIT: close_tcp(tcb); break; case CLOSED: log(tcb,"close FTP"); if((ftp = (struct ftp *)tcb->user) != NULLFTP) ftp_delete(ftp); /* Check if server is being shut down */ if(tcb == ftp_tcb) ftp_tcb = NULLTCB; del_tcp(tcb); break; } } /* FTP control channel receiver upcall handler */ static void r_ftp(tcb,cnt) struct tcb *tcb; int16 cnt; { register struct ftp *ftp; char *index(),c; struct mbuf *bp; void docommand(); if((ftp = (struct ftp *)tcb->user) == NULLFTP){ /* Unknown connection, just kill it */ close_tcp(tcb); return; } switch(ftp->state){ case COMMAND_STATE: /* Assemble an input line in the session buffer. Return if incomplete */ recv_tcp(tcb,&bp,0); while(pullup(&bp,&c,1) == 1){ switch(c){ case '\r': /* Strip cr's */ continue; case '\n': /* Complete line; process it */ ftp->buf[ftp->cnt] = '\0'; docommand(ftp); ftp->cnt = 0; break; default: /* Assemble line */ if(ftp->cnt != LINELEN-1) ftp->buf[ftp->cnt++] = c; break; } } /* else no linefeed present yet to terminate command */ break; case SENDING_STATE: case RECEIVING_STATE: /* Leave commands pending on receive queue until * present command is done */ break; } } /* FTP server data channel connection state change upcall handler */ void s_ftpd(tcb,old,new) struct tcb *tcb; char old,new; { struct ftp *ftp; #ifndef CPM #ifndef AMIGA char *cdsave; #endif #endif if((ftp = (struct ftp *)tcb->user) == NULLFTP){ /* Unknown connection, kill it */ close_tcp(tcb); return; } switch(new){ case FINWAIT2: case TIME_WAIT: if(ftp != NULLFTP && ftp->state == SENDING_STATE){ /* We've received an ack of our FIN, so * send a completion message on the control channel */ ftp->state = COMMAND_STATE; tprintf(ftp->control,txok); /* Kick command parser if something is waiting */ if(ftp->control->rcvcnt != 0) r_ftp(ftp->control,ftp->control->rcvcnt); } break; case CLOSE_WAIT: close_tcp(tcb); if(ftp != NULLFTP && ftp->state == RECEIVING_STATE){ /* End of file received on incoming file */ #ifdef CPM if(ftp->type == ASCII_TYPE) putc(CTLZ,ftp->fp); #endif #ifndef CPM #ifndef AMIGA cdsave = pwd(); /* Save current directory */ chdir(ftp->cd); /* Switch to user's directory*/ #endif #endif fclose(ftp->fp); #ifndef CPM #ifndef AMIGA if(cdsave != NULLCHAR){ chdir(cdsave); /* And back */ free(cdsave); } #endif #endif ftp->fp = NULLFILE; ftp->state = COMMAND_STATE; tprintf(ftp->control,rxok); /* Kick command parser if something is waiting */ if(ftp->control->rcvcnt != 0) r_ftp(ftp->control,ftp->control->rcvcnt); } break; case CLOSED: if(ftp != NULLFTP) ftp->data = NULLTCB; del_tcp(tcb); break; } } /* Parse and execute ftp commands */ static void docommand(ftp) register struct ftp *ftp; { void r_ftpd(),t_ftpd(),s_ftpd(); char *cmd,*arg,*cp,**cmdp; char *index(),*malloc(),*strcpy(); struct socket dport; #ifndef CPM #ifndef AMIGA FILE *dir(); char *cdsave; #endif #endif cmd = ftp->buf; if(ftp->cnt == 0){ /* Can't be a legal FTP command */ tprintf(ftp->control,badcmd); return; } cmd = ftp->buf; /* Translate entire buffer to lower case */ for(cp = cmd;*cp != '\0';cp++) *cp = tolower(*cp); /* Find command in table; if not present, return syntax error */ for(cmdp = commands;*cmdp != NULLCHAR;cmdp++) if(strncmp(*cmdp,cmd,strlen(*cmdp)) == 0) break; if(*cmdp == NULLCHAR){ tprintf(ftp->control,badcmd); return; } arg = &cmd[strlen(*cmdp)]; while(*arg == ' ') arg++; /* Execute specific command */ switch(cmdp-commands){ case USER_CMD: if((ftp->username = malloc((unsigned)strlen(arg)+1)) == NULLCHAR){ close_tcp(ftp->control); break; } strcpy(ftp->username,arg); tprintf(ftp->control,logged); break; case TYPE_CMD: switch(*arg){ case 'a': /* Ascii */ ftp->type = ASCII_TYPE; tprintf(ftp->control,typeok); break; case 'b': /* Binary */ case 'i': /* Image */ ftp->type = IMAGE_TYPE; tprintf(ftp->control,typeok); break; default: /* Invalid */ tprintf(ftp->control,badtype); break; } break; case QUIT_CMD: tprintf(ftp->control,bye); close_tcp(ftp->control); break; case RETR_CMD: /* Disk operation; return ACK now */ tcp_output(ftp->control); #ifndef CPM #ifndef AMIGA cdsave = pwd(); /* Save current directory */ chdir(ftp->cd); /* Switch to user's directory*/ #endif #endif ftp->fp = fopen(arg,"r"); #ifndef CPM #ifndef AMIGA chdir(cdsave); /* And back */ free(cdsave); #endif #endif if(ftp->fp == NULLFILE){ tprintf(ftp->control,cantopen); } else { log(ftp->control,"RETR %s/%s",ftp->cd,arg); dport.address = ip_addr; dport.port = FTPD_PORT; ftp->state = SENDING_STATE; tprintf(ftp->control,sending,"RETR",arg); /* This hack is just so we can talk to ourselves */ ftp->data = open_tcp(&dport,&ftp->port,TCP_PASSIVE, 0,NULLVFP,t_ftpd,s_ftpd,ftp->control->tos,(int *)ftp); ftp->data = open_tcp(&dport,&ftp->port,TCP_ACTIVE, 0,NULLVFP,t_ftpd,s_ftpd,ftp->control->tos,(int *)ftp); } break; case STOR_CMD: /* Disk operation; return ACK now */ tcp_output(ftp->control); #ifndef CPM #ifndef AMIGA cdsave = pwd(); /* Save current directory */ chdir(ftp->cd); /* Switch to user's directory */ #endif #endif ftp->fp = fopen(arg,"w"); #ifndef CPM #ifndef AMIGA chdir(cdsave); /* And back */ free(cdsave); #endif #endif if(ftp->fp == NULLFILE){ tprintf(ftp->control,cantmake); } else { log(ftp->control,"STOR %s/%s",ftp->cd,arg); dport.address = ip_addr; dport.port = FTPD_PORT; ftp->state = RECEIVING_STATE; tprintf(ftp->control,sending,"STOR",arg); /* This hack is just so we can talk to ourselves */ ftp->data = open_tcp(&dport,&ftp->port,TCP_PASSIVE, 0,r_ftpd,NULLVFP,s_ftpd,ftp->control->tos,(int *)ftp); ftp->data = open_tcp(&dport,&ftp->port,TCP_ACTIVE, 0,r_ftpd,NULLVFP,s_ftpd,ftp->control->tos,(int *)ftp); } break; case PORT_CMD: if(pport(&ftp->port,arg) == -1){ tprintf(ftp->control,badport); } else { tprintf(ftp->control,portok); } break; /* #ifndef CPM */ #ifndef AMIGA case LIST_CMD: /* Disk operation; return ACK now */ tcp_output(ftp->control); cdsave = pwd(); /* Save current directory */ chdir(ftp->cd); /* Switch to user's directory */ ftp->fp = dir(arg,1); chdir(cdsave); /* And back */ free(cdsave); if(ftp->fp == NULLFILE){ tprintf(ftp->control,nodir); break; } dport.address = ip_addr; dport.port = FTPD_PORT; ftp->state = SENDING_STATE; tprintf(ftp->control,sending,"LIST",arg); /* This hack is just so we can talk to ourselves */ ftp->data = open_tcp(&dport,&ftp->port,TCP_PASSIVE, 0,NULLVFP,t_ftpd,s_ftpd,ftp->control->tos,(int *)ftp); ftp->data = open_tcp(&dport,&ftp->port,TCP_ACTIVE, 0,NULLVFP,t_ftpd,s_ftpd,ftp->control->tos,(int *)ftp); break; case NLST_CMD: /* Disk operation; return ACK now */ tcp_output(ftp->control); cdsave = pwd(); /* Save current directory */ chdir(ftp->cd); /* Switch to user's directory */ ftp->fp = dir(arg,0); chdir(cdsave); /* And back */ free(cdsave); if(ftp->fp == NULLFILE){ tprintf(ftp->control,nodir); break; } dport.address = ip_addr; dport.port = FTPD_PORT; ftp->state = SENDING_STATE; tprintf(ftp->control,sending,"NLST",arg); /* This hack is just so we can talk to ourselves */ ftp->data = open_tcp(&dport,&ftp->port,TCP_PASSIVE, 0,NULLVFP,t_ftpd,s_ftpd,ftp->control->tos,(int *)ftp); ftp->data = open_tcp(&dport,&ftp->port,TCP_ACTIVE, 0,NULLVFP,t_ftpd,s_ftpd,ftp->control->tos,(int *)ftp); break; case CWD_CMD: tcp_output(ftp->control); /* Disk operation; return ACK now */ cdsave = pwd(); /* Save current directory */ chdir(ftp->cd); /* Go to user's context */ if(chdir(arg) == 0){ /* Attempt switch */ /* Succeeded, record in control block */ free(ftp->cd); ftp->cd = pwd(); tprintf(ftp->control,cwdok); } else { /* Failed, don't change anything */ tprintf(ftp->control,nodir); } chdir(cdsave); /* Go back */ free(cdsave); break; case XPWD_CMD: case PWD_CMD: tprintf(ftp->control,pwdmsg,ftp->cd); break; #else case LIST_CMD: case NLST_CMD: case CWD_CMD: case XPWD_CMD: case PWD_CMD: #endif case ACCT_CMD: case DELE_CMD: tprintf(ftp->control,unimp); break; case PASS_CMD: tprintf(ftp->control,nopass); break; } } static int pport(sock,arg) struct socket *sock; char *arg; { int32 n; int atoi(),i; n = 0; for(i=0;i<4;i++){ n = atoi(arg) + (n << 8); if((arg = index(arg,',')) == NULLCHAR) return -1; arg++; } sock->address = n; n = atoi(arg); if((arg = index(arg,',')) == NULLCHAR) return -1; arg++; n = atoi(arg) + (n << 8); sock->port = n; return 0; }