/* Internet FTP Server * Copyright 1991 Phil Karn, KA9Q */ #include #include #include #ifdef __TURBOC__ #include #include #endif #include "global.h" #include "mbuf.h" #include "proc.h" #include "socket.h" #include "dirutil.h" #include "commands.h" #include "files.h" #include "ftp.h" #include "ftpserv.h" static void ftpserv __ARGS((int s,void *unused,void *p)); static int pport __ARGS((struct sockaddr_in *sock,char *arg)); static void ftplogin __ARGS((struct ftpserv *ftp,char *pass)); static int sendit __ARGS((struct ftpserv *ftp,char *command,char *file)); static int recvit __ARGS((struct ftpserv *ftp,char *command,char *file)); /* Command table */ static char *commands[] = { "user", "acct", "pass", "type", "list", "cwd", "dele", "name", "quit", "retr", "stor", "port", "nlst", "pwd", "xpwd", /* For compatibility with 4.2BSD */ "mkd ", "xmkd", /* For compatibility with 4.2BSD */ "xrmd", /* For compatibility with 4.2BSD */ "rmd ", "stru", "mode", NULLCHAR }; /* Response messages */ static char banner[] = "220 %s FTP version %s ready at %s\n"; static char badcmd[] = "500 Unknown command\n"; static char binwarn[] = "100 Warning: type is ASCII and %s appears to be binary\n"; static char unsupp[] = "500 Unsupported command or option\n"; static char givepass[] = "331 Enter PASS command\n"; static char logged[] = "230 Logged in\n"; static char typeok[] = "200 Type %s OK\n"; static char only8[] = "501 Only logical bytesize 8 supported\n"; static char deleok[] = "250 File deleted\n"; static char mkdok[] = "200 MKD ok\n"; static char delefail[] = "550 Delete failed: %s\n"; static char pwdmsg[] = "257 \"%s\" is current directory\n"; static char badtype[] = "501 Unknown type \"%s\"\n"; static char badport[] = "501 Bad port syntax\n"; static char unimp[] = "502 Command not yet implemented\n"; static char bye[] = "221 Goodbye!\n"; static char nodir[] = "553 Can't read directory \"%s\": %s\n"; static char cantopen[] = "550 Can't read file \"%s\": %s\n"; static char sending[] = "150 Opening data connection for %s %s\n"; static char cantmake[] = "553 Can't create \"%s\": %s\n"; static char writerr[] = "552 Write error: %s\n"; static char portok[] = "200 Port command okay\n"; static char rxok[] = "226 File received OK\n"; static char txok[] = "226 File sent OK\n"; static char noperm[] = "550 Permission denied\n"; static char noconn[] = "425 Data connection reset\n"; static char lowmem[] = "421 System overloaded, try again later\n"; static char notlog[] = "530 Please log in with USER and PASS\n"; static char userfirst[] = "503 Login with USER first.\n"; static char okay[] = "200 Ok\n"; static int Sftp = -1; /* Prototype socket for service */ /* Start up FTP service */ int ftpstart(argc,argv,p) int argc; char *argv[]; void *p; { struct sockaddr_in lsocket; int s; if(Sftp != -1){ /* Already running! */ return 0; } psignal(Curproc,0); /* Don't keep the parser waiting */ chname(Curproc,"FTP listener"); lsocket.sin_family = AF_INET; lsocket.sin_addr.s_addr = INADDR_ANY; if(argc < 2) lsocket.sin_port = IPPORT_FTP; else lsocket.sin_port = atoi(argv[1]); Sftp = socket(AF_INET,SOCK_STREAM,0); bind(Sftp,(char *)&lsocket,sizeof(lsocket)); listen(Sftp,1); for(;;){ if((s = accept(Sftp,NULLCHAR,(int *)NULL)) == -1) break; /* Service is shutting down */ if(availmem() < Memthresh){ usprintf(s,lowmem); shutdown(s,1); } else { /* Spawn a server */ newproc("ftpserv",2048,ftpserv,s,NULL,NULL,0); } } return 0; } static void ftpserv(s,unused,p) int s; /* Socket with user connection */ void *unused; void *p; { struct ftpserv ftp; char **cmdp,buf[512],*arg,*cp,*cp1,*file,*mode; long t; int cnt,i; struct sockaddr_in socket; sockmode(s,SOCK_ASCII); memset((char *)&ftp,0,sizeof(ftp)); /* Start with clear slate */ ftp.data = -1; sockowner(s,Curproc); /* We own it now */ ftp.control = s; /* Set default data port */ i = SOCKSIZE; getpeername(s,(char *)&socket,&i); socket.sin_port = IPPORT_FTPD; ASSIGN(ftp.port,socket); log(s,"open FTP"); time(&t); cp = ctime(&t); if((cp1 = strchr(cp,'\n')) != NULLCHAR) *cp1 = '\0'; usprintf(s,banner,Hostname,Version,cp); loop: if((cnt = recvline(s,buf,sizeof(buf))) == -1){ /* He closed on us */ goto finish; } if(cnt == 0){ /* Can't be a legal FTP command */ usprintf(ftp.control,badcmd); goto loop; } rip(buf); #ifdef UNIX /* Translate first word to lower case */ for(cp = buf;*cp != ' ' && *cp != '\0';cp++) *cp = tolower(*cp); #else /* Translate entire buffer to lower case */ for(cp = buf;*cp != '\0';cp++) *cp = tolower(*cp); #endif /* Find command in table; if not present, return syntax error */ for(cmdp = commands;*cmdp != NULLCHAR;cmdp++) if(strncmp(*cmdp,buf,strlen(*cmdp)) == 0) break; if(*cmdp == NULLCHAR){ usprintf(ftp.control,badcmd); goto loop; } /* Allow only USER, PASS and QUIT before logging in */ if(ftp.cd == NULLCHAR || ftp.path == NULLCHAR){ switch(cmdp-commands){ case USER_CMD: case PASS_CMD: case QUIT_CMD: break; default: usprintf(ftp.control,notlog); goto loop; } } arg = &buf[strlen(*cmdp)]; while(*arg == ' ') arg++; /* Execute specific command */ switch(cmdp-commands){ case USER_CMD: free(ftp.username); ftp.username = strdup(arg); usprintf(ftp.control,givepass); break; case TYPE_CMD: switch(arg[0]){ case 'A': case 'a': /* Ascii */ ftp.type = ASCII_TYPE; usprintf(ftp.control,typeok,arg); break; case 'l': case 'L': while(*arg != ' ' && *arg != '\0') arg++; if(*arg == '\0' || *++arg != '8'){ usprintf(ftp.control,only8); break; } ftp.type = LOGICAL_TYPE; ftp.logbsize = 8; usprintf(ftp.control,typeok,arg); break; case 'B': case 'b': /* Binary */ case 'I': case 'i': /* Image */ ftp.type = IMAGE_TYPE; usprintf(ftp.control,typeok,arg); break; default: /* Invalid */ usprintf(ftp.control,badtype,arg); break; } break; case QUIT_CMD: usprintf(ftp.control,bye); goto finish; case RETR_CMD: file = pathname(ftp.cd,arg); switch(ftp.type){ case IMAGE_TYPE: case LOGICAL_TYPE: mode = READ_BINARY; break; case ASCII_TYPE: mode = READ_TEXT; break; } if(!permcheck(ftp.path,ftp.perms,RETR_CMD,file)){ usprintf(ftp.control,noperm); } else if((ftp.fp = fopen(file,mode)) == NULLFILE){ usprintf(ftp.control,cantopen,file,sys_errlist[errno]); } else { log(ftp.control,"RETR %s",file); if(ftp.type == ASCII_TYPE && isbinary(ftp.fp)){ usprintf(ftp.control,binwarn,file); } sendit(&ftp,"RETR",file); } free(file); break; case STOR_CMD: file = pathname(ftp.cd,arg); switch(ftp.type){ case IMAGE_TYPE: case LOGICAL_TYPE: mode = WRITE_BINARY; break; case ASCII_TYPE: mode = WRITE_TEXT; break; } if(!permcheck(ftp.path,ftp.perms,STOR_CMD,file)){ usprintf(ftp.control,noperm); } else if((ftp.fp = fopen(file,mode)) == NULLFILE){ usprintf(ftp.control,cantmake,file,sys_errlist[errno]); } else { log(ftp.control,"STOR %s",file); recvit(&ftp,"STOR",file); } free(file); break; case PORT_CMD: if(pport(&ftp.port,arg) == -1){ usprintf(ftp.control,badport); } else { usprintf(ftp.control,portok); } break; #ifndef CPM case LIST_CMD: file = pathname(ftp.cd,arg); if(!permcheck(ftp.path,ftp.perms,RETR_CMD,file)){ usprintf(ftp.control,noperm); } else if((ftp.fp = dir(file,1)) == NULLFILE){ usprintf(ftp.control,nodir,file,sys_errlist[errno]); } else { sendit(&ftp,"LIST",file); } free(file); break; case NLST_CMD: file = pathname(ftp.cd,arg); if(!permcheck(ftp.path,ftp.perms,RETR_CMD,file)){ usprintf(ftp.control,noperm); } else if((ftp.fp = dir(file,0)) == NULLFILE){ usprintf(ftp.control,nodir,file,sys_errlist[errno]); } else { sendit(&ftp,"NLST",file); } free(file); break; case CWD_CMD: file = pathname(ftp.cd,arg); if(!permcheck(ftp.path,ftp.perms,RETR_CMD,file)){ usprintf(ftp.control,noperm); free(file); #ifdef MSDOS /* Don'tcha just LOVE %%$#@!! MS-DOS? */ } else if(strcmp(file,"/") == 0 || access(file,0) == 0){ #else } else if(access(file,0) == 0){ /* See if it exists */ #endif /* Succeeded, record in control block */ free(ftp.cd); ftp.cd = file; usprintf(ftp.control,pwdmsg,file); } else { /* Failed, don't change anything */ usprintf(ftp.control,nodir,file,sys_errlist[errno]); free(file); } break; case XPWD_CMD: case PWD_CMD: usprintf(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: usprintf(ftp.control,unimp); break; case DELE_CMD: file = pathname(ftp.cd,arg); if(!permcheck(ftp.path,ftp.perms,DELE_CMD,file)){ usprintf(ftp.control,noperm); } else if(unlink(file) == 0){ log(ftp.control,"DELE %s",file); usprintf(ftp.control,deleok); } else { usprintf(ftp.control,delefail,sys_errlist[errno]); } free(file); break; case PASS_CMD: if(ftp.username == NULLCHAR) usprintf(ftp.control,userfirst); else ftplogin(&ftp,arg); break; #ifndef CPM case XMKD_CMD: case MKD_CMD: file = pathname(ftp.cd,arg); if(!permcheck(ftp.path,ftp.perms,MKD_CMD,file)){ usprintf(ftp.control,noperm); #ifdef UNIX } else if(mkdir(file,0777) == 0){ #else } else if(mkdir(file) == 0){ #endif log(ftp.control,"MKD %s",file); usprintf(ftp.control,mkdok); } else { usprintf(ftp.control,cantmake,file,sys_errlist[errno]); } free(file); break; case XRMD_CMD: case RMD_CMD: file = pathname(ftp.cd,arg); if(!permcheck(ftp.path,ftp.perms,RMD_CMD,file)){ usprintf(ftp.control,noperm); } else if(rmdir(file) == 0){ log(ftp.control,"RMD %s",file); usprintf(ftp.control,deleok); } else { usprintf(ftp.control,delefail,sys_errlist[errno]); } free(file); break; case STRU_CMD: if(tolower(arg[0]) != 'f') usprintf(ftp.control,unsupp); else usprintf(ftp.control,okay); break; case MODE_CMD: if(tolower(arg[0]) != 's') usprintf(ftp.control,unsupp); else usprintf(ftp.control,okay); break; } #endif goto loop; finish: log(ftp.control,"close FTP"); /* Clean up */ close_s(ftp.control); if(ftp.data != -1) close_s(ftp.data); if(ftp.fp != NULLFILE) fclose(ftp.fp); free(ftp.username); free(ftp.path); free(ftp.cd); } /* Shut down FTP server */ int ftp0(argc,argv,p) int argc; char *argv[]; void *p; { close_s(Sftp); Sftp = -1; return 0; } static int pport(sock,arg) struct sockaddr_in *sock; char *arg; { int32 n; int i; n = 0; for(i=0;i<4;i++){ n = atoi(arg) + (n << 8); if((arg = strchr(arg,',')) == NULLCHAR) return -1; arg++; } sock->sin_addr.s_addr = n; n = atoi(arg); if((arg = strchr(arg,',')) == NULLCHAR) return -1; arg++; n = atoi(arg) + (n << 8); sock->sin_port = n; return 0; } /* Attempt to log in the user whose name is in ftp->username and password * in pass */ static void ftplogin(ftp,pass) struct ftpserv *ftp; char *pass; { char *path; int anony = 0; path = mallocw(200); if((ftp->perms = userlogin(ftp->username,pass,&path,200,&anony)) == -1){ usprintf(ftp->control,noperm); free(path); return; } /* Set up current directory and path prefix */ #if defined(AMIGAGONE) ftp->cd = pathname("", path); ftp->path = strdup(ftp->cd); free(path); #else ftp->cd = path; ftp->path = strdup(path); #endif usprintf(ftp->control,logged); if(!anony) log(ftp->control,"%s logged in",ftp->username); else log(ftp->control,"%s logged in, ID %s",ftp->username,pass); } #ifdef MSDOS /* Illegal characters in a DOS filename */ static char badchars[] = "\"[]:|<>+=;,"; #endif /* Return 1 if the file operation is allowed, 0 otherwise */ int permcheck(path,perms,op,file) char *path; int perms; int op; char *file; { #ifdef MSDOS char *cp; #endif if(file == NULLCHAR || path == NULLCHAR) return 0; /* Probably hasn't logged in yet */ #ifdef MSDOS /* Check for characters illegal in MS-DOS file names */ for(cp = badchars;*cp != '\0';cp++){ if(strchr(file,*cp) != NULLCHAR) return 0; } #endif #ifndef MAC /* The target file must be under the user's allowed search path */ if(strncmp(file,path,strlen(path)) != 0) return 0; #endif switch(op){ case RETR_CMD: /* User must have permission to read files */ if(perms & FTP_READ) return 1; return 0; case DELE_CMD: case RMD_CMD: /* User must have permission to (over)write files */ if(perms & FTP_WRITE) return 1; return 0; case STOR_CMD: case MKD_CMD: /* User must have permission to (over)write files, or permission * to create them if the file doesn't already exist */ if(perms & FTP_WRITE) return 1; if(access(file,2) == -1 && (perms & FTP_CREATE)) return 1; return 0; } return 0; /* "can't happen" -- keep lint happy */ } static int sendit(ftp,command,file) struct ftpserv *ftp; char *command; char *file; { long total; struct sockaddr_in dport; ftp->data = socket(AF_INET,SOCK_STREAM,0); dport.sin_family = AF_INET; dport.sin_addr.s_addr = INADDR_ANY; dport.sin_port = IPPORT_FTPD; bind(ftp->data,(char *)&dport,SOCKSIZE); usprintf(ftp->control,sending,command,file); if(connect(ftp->data,(char *)&ftp->port,SOCKSIZE) == -1){ fclose(ftp->fp); ftp->fp = NULLFILE; close_s(ftp->data); ftp->data = -1; usprintf(ftp->control,noconn); return -1; } /* Do the actual transfer */ total = sendfile(ftp->fp,ftp->data,ftp->type,0); if(total == -1){ /* An error occurred on the data connection */ usprintf(ftp->control,noconn); shutdown(ftp->data,2); /* Blow away data connection */ } else { usprintf(ftp->control,txok); } fclose(ftp->fp); ftp->fp = NULLFILE; close_s(ftp->data); ftp->data = -1; if(total == -1) return -1; else return 0; } static int recvit(ftp,command,file) struct ftpserv *ftp; char *command; char *file; { struct sockaddr_in dport; long total; ftp->data = socket(AF_INET,SOCK_STREAM,0); dport.sin_family = AF_INET; dport.sin_addr.s_addr = INADDR_ANY; dport.sin_port = IPPORT_FTPD; bind(ftp->data,(char *)&dport,SOCKSIZE); usprintf(ftp->control,sending,command,file); if(connect(ftp->data,(char *)&ftp->port,SOCKSIZE) == -1){ fclose(ftp->fp); ftp->fp = NULLFILE; close_s(ftp->data); ftp->data = -1; usprintf(ftp->control,noconn); return -1; } total = recvfile(ftp->fp,ftp->data,ftp->type,0); #ifdef CPM if(ftp->type == ASCII_TYPE) putc(CTLZ,ftp->fp); #endif if(total == -1) { /* An error occurred while writing the file */ usprintf(ftp->control,writerr,sys_errlist[errno]); shutdown(ftp->data,2); /* Blow it away */ } else { usprintf(ftp->control,rxok); close_s(ftp->data); } ftp->data = -1; fclose(ftp->fp); ftp->fp = NULLFILE; if(total == -1) return -1; else return 0; }