/* $Id: ftpd.c,v 1.61 1997/09/09 18:43:12 agulbra Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef USE_SHADOW #include #endif #include "ftpd.h" /* include files, *sigh* */ int wildmat( const char *, const char * ); /* then mine */ int sfgets( void ); void *aborttransfer( int ); void parser( void ); void dobanner( void ); void douser( const char *name ); void dopass( const char *password ); void docwd( const char *dir ); void doretr( const char *name ); void dorest (const char *name ); void dodele( const char *name ); void dostor( const char *name ); void domkd( const char *name ); void dormd( const char *name ); void domdtm( const char *name ); void dosize( const char *name ); void doport( unsigned int ip, unsigned int port ); void dopasv( void ); void error( int n, const char *msg ); void domode( const char *arg ); void dostru( const char *arg ); void dotype( const char *arg ); void dornfr( const char *name ); void dornto( const char *name ); jmp_buf appropriately; void sighandler( int sig ) { sig = sig; /* avoid warning */ longjmp( appropriately, 1 ); } const int window = 51200; /* window size */ unsigned int downloaded = 0; /* bytes downloaded */ unsigned int uploaded = 0; /* bytes uploaded */ int passive; int datafd = 0; /* data connection file descriptor */ struct sockaddr_in ctrlconn; /* stdin/stdout, for using the same ip number */ const int cmdsize = PATH_MAX + 32; char cmd[PATH_MAX + 32]; /* command line - about 30 chars for command */ char wd[PATH_MAX + 1]; /* current working directory */ char *rd = NULL; /* root directory, for chroot'd environments */ char cpwd[20]; /* crypted password of the user one logs in as */ int loggedin = 0; char account[9]; char * renamefrom = 0; int warez = 0; /* don't guard against warez */ int debug = 0; /* don't give debug output */ int guest = 0; /* if non-zero, it's a guest user */ uid_t useruid = 0; /* smallest uid which can ftp */ int candownload = 1; /* if non-zero, don't let the user download */ double load; /* for reporting what the load was */ int type = 1; /* type - 0 = error, 1 = ascii, 2 = binary */ int restartat; unsigned long int idletime = 900; struct reply { struct reply *prev; char line[1]; }; struct reply *lastreply = 0; int replycode = 0; void addreply( int code, const char *line,... ) { struct reply *p; struct reply *q; char buf[PATH_MAX + 50]; va_list ap; int offs; if ( code ) replycode = code; q = NULL; va_start( ap, line ); vsnprintf( buf, PATH_MAX + 50, line, ap ); va_end( ap ); offs = strlen( buf ); while ( offs-- && isspace( buf[offs] ) ) buf[offs] = '\0'; if ( !offs && buf[0] ) offs++; while ( offs ) { while ( offs && buf[offs] != '\n' && isspace( buf[offs] ) ) buf[offs--] = '\0'; while ( offs && buf[offs] != '\n' ) offs--; if ( offs ) offs++; p = (struct reply *)malloc( sizeof(struct reply) + strlen(buf+offs) ); if ( !p ) { printf( "421 Out of memory\r\n" ); fflush( stdout ); syslog( LOG_ERR, "out of memory" ); exit( 6 ); } strcpy( p->line, buf + offs ); if ( q ) { p->prev = q->prev; q->prev = p; } else { p->prev = lastreply; lastreply = p; } q = p; if ( offs ) buf[--offs] = '\r'; } } void replycont( struct reply * ); /* ick */ void replycont( struct reply *p ) { if ( p ) { replycont( p->prev ); printf( "%3d-%s\r\n", replycode, p->line ); free( (char * ) p ); } } void doreply( void ) { if ( lastreply ) { replycont( lastreply->prev ); printf( "%3d %s\r\n", replycode, lastreply->line ); free( (char * ) lastreply ); lastreply = NULL; fflush( stdout ); } } int sfgets( void ) { register char *p; if ( setjmp( appropriately ) ) { return 0; } (void)signal( SIGALRM, sighandler ); (void)alarm( idletime ); p = fgets( cmd, 1024, stdin ); /* if you need to debug ftpd, you can (e.g.) connect to it using an ordinary client, connect to the process using gdb or another ptrace()-aware debugger, and set a breakpoint on the next line. ftpd will hit the breakpoint immediately after the next command or whever the timeout hits. */ if ( !p && errno != EINTR ) { syslog( LOG_INFO, "error during command read: %d", errno ); exit( 12 ); } (void)alarm( 0U ); return 1; } void parser( void ) { char *arg; int n; while ( 1 ) { doreply(); if ( !sfgets() ) { addreply( 421, "Timeout - try typing a little faster next time." ); syslog( LOG_INFO, "exiting due to timeout" ); return; } if ( debug ) addreply( 0, "%s", cmd ); n = 0; while ( isalpha( cmd[n] ) && n < 1000 ) { cmd[n] = tolower( cmd[n] ); n++; } if ( n >= cmdsize ) { addreply( 421, "Command line maximum length exceeded." ); syslog( LOG_INFO, "found line over 1000 characters" ); return; } if ( !n ) { addreply( 221, "Goodbye. You uploaded %d and downloaded %d kbytes.", (uploaded+1023)/1024, (downloaded+1023)/1024 ); continue; } while ( isspace( cmd[n] ) && n < cmdsize ) cmd[n++] = '\0'; arg = cmd + n; while ( cmd[n] && n < cmdsize ) n++; n--; while ( isspace( cmd[n] ) ) cmd[n--] = '\0'; if ( !strcasecmp( cmd, "user" ) ) { douser( arg ); } else if ( !strcasecmp( cmd, "pass" ) ) { dopass( arg ); } else if ( !strcasecmp( cmd, "quit" ) ) { addreply( 221, "Goodbye. You uploaded %d and downloaded %d kbytes.", (uploaded+1023)/1024, (downloaded+1023)/1024 ); return; } else if ( !strcasecmp( cmd, "noop" ) ) { addreply( 200, "NOOP command successful" ); } else if ( !strcasecmp( cmd, "syst" ) ) { printf( "215 UNIX Type: L8\r\n" ); fflush( stdout ); } else if ( !strcasecmp( cmd, "port" ) ) { /* don't auto-login for PORT or PASV, but do auto-login for the command which _uses_ the data connection */ unsigned int a1, a2, a3, a4, p1, p2; if ( arg && 6 == sscanf( arg, "%d,%d,%d,%d,%d,%d", &a1, &a2, &a3, &a4, &p1, &p2 ) && a1 < 256 && a2 < 256 && a3 < 256 && a4 < 256 && p1 < 256 && p2 < 256 ) { doport( (a1 << 24) + (a2 << 16) + (a3 << 8) + a4, ( (p1 << 8 ) + p2 ) ); } else { addreply( 501, "Syntax error." ); } } else if ( !strcasecmp( cmd, "pasv" ) ) { dopasv(); } else if ( !strcasecmp( cmd, "pwd" ) ) { if ( loggedin ) addreply( 257, "\"%s\"\r\n", wd ); else addreply( 550, "Not logged in" ); } else { /* from this point, all commands trigger an automatic login */ douser( NULL ); if ( !strcasecmp( cmd, "cwd" ) ) { docwd( arg ); } else if ( !strcasecmp( cmd, "cdup" ) ) { docwd( ".." ); } else if ( !strcasecmp( cmd, "retr" ) ) { if ( arg && *arg ) doretr( arg ); else printf( "501 No file name\r\n" ); } else if ( !strcasecmp( cmd, "rest" ) ) { if (arg && *arg) dorest(arg); else addreply (501, "No restart point"); } else if ( !strcasecmp( cmd, "dele" ) ) { if ( arg && *arg ) dodele( arg ); else addreply( 501, "No file name\r\n" ); } else if ( !strcasecmp( cmd, "stor" ) ) { if ( arg && *arg ) dostor( arg ); else addreply( 501, "No file name." ); } else if ( !strcasecmp( cmd, "mkd" ) ) { if ( arg && *arg ) domkd( arg ); else addreply( 501, "No directory name." ); } else if ( !strcasecmp( cmd, "rmd" ) ) { if ( arg && *arg ) dormd( arg ); else addreply( 550, "No directory name." ); } else if ( !strcasecmp( cmd, "list" ) ) { opt_l = 1; donlist( (arg && *arg ) ? arg : "" ); } else if ( !strcasecmp( cmd, "nlst" ) ) { opt_l = 0; donlist( (arg && *arg ) ? arg : "" ); } else if ( !strcasecmp( cmd, "type" ) ) { dotype( arg ); } else if ( !strcasecmp( cmd, "mode" ) ) { domode( arg ); } else if ( !strcasecmp( cmd, "stru" ) ) { dostru( arg ); } else if ( !strcasecmp( cmd, "abor" ) ) { addreply( 226, "Since you see this ABOR must've succeeded." ); } else if ( !strcasecmp( cmd, "site" ) ) { char *sitearg; sitearg = arg; while ( sitearg && *sitearg && !isspace( *sitearg ) ) sitearg++; if ( sitearg ) *sitearg++ = '\0'; if ( !strcasecmp( arg, "idle" ) ) { if ( !*sitearg ) { addreply( 501, "SITE IDLE: need argument" ); } else { unsigned long int i = 0; i = strtoul( sitearg, &sitearg, 10 ); if ( sitearg && *sitearg ) addreply( 501, "Garbage ( %s ) after value ( %d )", sitearg, i ); else if ( i > 7200 ) addreply( 501, "Too large ( >7200 seconds )" ); else addreply( 200, "Idle time set to %u seconds", ( idletime = i ) ); } } else if ( arg && *arg ) { addreply( 500, "SITE %s unknown", arg ); } else { addreply( 500, "SITE: argument needed" ); } } else if ( !strcasecmp( cmd, "xdbg" ) ) { debug++; addreply( 200, "XDBG command succeeded, debug level is now %d.", debug ); } else if ( !strcasecmp( cmd, "mdtm" ) ) { domdtm( (arg && *arg ) ? arg : "" ); } else if ( !strcasecmp( cmd, "size" ) ) { dosize( (arg && *arg ) ? arg : "" ); } else if ( !strcasecmp( cmd, "rnfr" ) ) { if ( arg && *arg ) dornfr( arg ); else addreply( 550, "No file name given." ); } else if ( !strcasecmp( cmd, "rnto" ) ) { if ( arg && *arg ) dornto( arg ); else addreply( 550, "No file name given." ); } else { addreply( 500, "Unknown command." ); } } } } /* small help routine to display a banner */ void dobanner( void ) { int m; FILE *msg; m = 0; if ( (msg = fopen( ".banner", "r" ) ) != NULL ) { /* if you think this is too small, send me mail. NOTE: It should not be unlimited. Take a look at e.g. ftp.3com.com to see why: at the time of writing the logon banner is 250-odd lines */ char buffer[1025]; int len = fread( (void * ) buffer, 1, 1024, msg ); fclose( msg ); if ( len > 0 && len < 1024 ) { buffer[len] = '\0'; addreply( 0, "%s", buffer ); } } } void douser( const char *username ) { struct passwd *pw; if ( loggedin ) { if ( username ) { if ( guest ) addreply( 230, "Anonymous user logged in, no " "need to bother with a password." ); else addreply( 530, "You're already logged in." ); } return; } if ( username && strcasecmp( username, "ftp" ) && strcasecmp( username, "anonymous" ) ) { /* somebody wants more than generic access */ char *shell; pw = getpwnam( username ); /* get thier user entry */ /* make sure we have a shell available for them */ setusershell(); while ( (shell = getusershell() ) != NULL && pw && strcmp( pw->pw_shell, shell ) ) ; endusershell(); if ( shell && pw && ( pw->pw_uid >= useruid ) && /*if whe have the shell and a user entry and a larger user ID... */ chdir( pw->pw_dir ) == 0 ) { /*then try to cd to thier home directory */ #ifdef USE_SHADOW struct spwd *spw; if ( (spw = getspnam( username ) ) ) strncpy( cpwd, spw->sp_pwdp[0] == '@' ? "*" : spw->sp_pwdp, 19 ); else #endif /* added by James newton and Peter Neumerkel to limit users to thier home directory */ chroot( pw->pw_dir ); chdir( "/" ); /* end change */ strncpy( cpwd, pw->pw_passwd, 19 ); /* sigh */ /*get thier encrypted password */ setfsuid( pw->pw_uid ); /* set the file system user id to the new users id */ setregid( pw->pw_gid, pw->pw_gid ); /* set the group id of this process to that of the new user */ setgroups( sizeof( pw->pw_gid ), &( pw->pw_gid ) ); initgroups( pw->pw_name, pw->pw_gid ); } else { strcpy( cpwd, "*" ); } strncpy( account, username, 8 ); account[8] = '\0'; addreply( 331, "User %s OK. Password required.", account ); loggedin = 0; } else { if ( (pw = getpwnam( "ftp" ) ) == NULL || setregid( pw->pw_gid, pw->pw_gid ) || setgroups( sizeof( pw->pw_gid ), &( pw->pw_gid ) ) || chroot( pw->pw_dir ) || !( rd = strdup( pw->pw_dir ) ) || chdir( "/" ) ) { syslog( LOG_ERR, "unable to set up secure anonymous FTP" ); printf( "421 can't set up secure anonymous FTP, aborting\r\n" ); fflush( stdout ); sleep( 1 ); exit( 4 ); } setfsuid( pw->pw_uid ); dobanner(); /* the 230 will be overwritten if this is an implict login */ addreply( 230, "Anonymous user logged in." ); strcpy( account, "ftp" ); rd = strdup( pw->pw_dir ); loggedin = guest = 1; syslog( LOG_INFO, "guest logged in" ); } (void)getcwd( wd, PATH_MAX ); } void dopass( const char *password ) { if ( loggedin || !strcmp( cpwd, crypt( password, cpwd ) ) ) { gid_t g[NGROUPS_MAX]; int ngroups; /* note that the password stays around for a short while, so root is able to find out what the user's password is */ if ( !loggedin ) candownload = 1; /* real users can always download */ ngroups = getgroups( NGROUPS_MAX, g ); if ( ngroups > 0 ) { char reply[80]; int p; char *q; sprintf( reply, "User %s has group access to:", account ); p = strlen( reply ); do { ngroups--; if ( p > 60 ) { reply[p] = '\0'; addreply( 0, "%s", reply ); *reply = '\0'; p = 0; } reply[p++] = ' '; q = getgroup( g[ngroups] ); while ( q && *q && !isspace( *q ) ) reply[p++] = *q++; } while ( ngroups > 0 ); reply[p] = '\0'; addreply( 0, "%s", reply ); } addreply( 230, "OK" ); syslog( LOG_INFO, "%s logged in", account ); loggedin = 1; } else { addreply( 530, "Sorry" ); } } void docwd( const char *dir ) { char buffer[PATH_MAX + 256]; /* let's hope... */ if ( loggedin && !guest && dir && *dir == '~' ) { /* CWD ~ cd's to the users home directory */ struct passwd *pw; int i; const char *p; i = 0; p = dir; p++; while ( *p && *p != '/' ) buffer[i++] = *p++; buffer[i] = '\0'; /* parse the user name from the path into the buffer array, this is the users name and this allows CWD ~/ to cd to a directory under the users home directory */ if ( (pw = getpwnam( i ? buffer : account ) ) ) /* Get the user data if we found a user name or about the logged in users account if not. */ sprintf( buffer, "%s%s", pw->pw_dir, p ); /* buffer is now the full path (user home dir+relative path) */ else *buffer = '\0'; } else { *buffer = '\0'; } /* if we made a buffer try cding to that other wise, if there was a dir parm and its not empty cd there, it it was empty, cd to / */ if ( chdir( *buffer ? buffer : ( dir && *dir ? dir : "/" ) ) ) { sprintf( buffer, "Can't change directory to %s: %s", dir, strerror( errno ) ); syslog( LOG_INFO, "%s", buffer ); addreply( 530, "%s", buffer ); } else { int m; FILE *msg; m = 0; if ( (msg = fopen( ".message", "r" ) ) != NULL ) { int len = fread( (void *)buffer, 1, 1024, msg ); fclose( msg ); if ( len > 0 && len < 1024 ) { buffer[len] = '\0'; addreply( 0, "%s", buffer ); } } if ( !getcwd( wd, PATH_MAX ) ) { if ( *dir == '/' ) { snprintf( wd, PATH_MAX, "%s", dir ); /* already checked */ } else { if ( snprintf( wd, PATH_MAX, "%s/%s", wd, dir ) < 0 ) { printf( "421 Path too long\r\n" ); fflush( stdout ); syslog( LOG_ERR, "path too long" ); exit( 14 ); } } } addreply( 250, "Changed to %s", wd ); } } void dopasv( void ) { unsigned int fodder; unsigned int a; unsigned int p; unsigned int on; struct sockaddr_in dataconn; /* my data connection endpoint */ if ( datafd ) { /* for buggy clients */ close( datafd ); datafd = 0; } datafd = socket( AF_INET, SOCK_STREAM, 0 ); if ( datafd < 0 ) { error( 425, "Can't open passive connection" ); datafd = 0; return; } on = 1; if ( setsockopt( datafd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) ) < 0 ) { error( 421, "setsockopt" ); return; } dataconn = ctrlconn; dataconn.sin_port = 0; if ( bind( datafd, (struct sockaddr *)&dataconn, sizeof(dataconn) ) < 0 ) { error( 425, "Can't bind to socket" ); close( datafd ); datafd = 0; return; } (void)listen( datafd, 1 ); /* maybe 0 on some unices? */ fodder = sizeof( dataconn ); if ( getsockname( datafd, (struct sockaddr *)&dataconn, &fodder ) < 0 ) { error( 425, "Can't getsockname( dataconn )" ); close( datafd ); datafd = 0; return; } a = ntohl( dataconn.sin_addr.s_addr ); p = ntohs( (unsigned short int ) ( dataconn.sin_port ) ); addreply( 227, "Passive mode OK (%d,%d,%d,%d,%d,%d )", (a >> 24) & 255, (a >> 16) & 255, (a >> 8) & 255, a & 255, (p >> 8) & 255, p & 255 ); passive = 1; return; } void doport( unsigned int a, unsigned int p ) { struct sockaddr_in dataconn; /* his endpoint */ int on; if ( datafd ) { /* for buggy clients saying PORT over and over */ close( datafd ); datafd = 0; } datafd = socket( AF_INET, SOCK_STREAM, 0 ); if ( datafd < 0 ) { error( 425, "Can't make data socket" ); datafd = 0; return; } on = 1; if ( setsockopt( datafd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) ) < 0 ) { error( 421, "setsockopt" ); return; } dataconn = ctrlconn; dataconn.sin_port = htons( (short ) 20 ); /* FTP data connection port */ if ( bind( datafd, (struct sockaddr *)&dataconn, sizeof(dataconn) ) < 0 ) { error( -220, "bind" ); close( datafd ); datafd = 0; return; } if ( debug ) addreply( 0, "My data connection endpoint is %s port %d", inet_ntoa( *(struct in_addr *)&dataconn.sin_addr.s_addr ), ntohs( dataconn.sin_port ) ); a = htonl( a ); dataconn.sin_addr.s_addr = a; dataconn.sin_port = htons( (unsigned short int)p ); dataconn.sin_family = AF_INET; if ( setjmp( appropriately ) ) { addreply( 425, "Timeout connecting to %s port %d: %s", inet_ntoa( *(struct in_addr *)&a ), p, strerror( errno ) ); close( datafd ); datafd = 0; return; } (void)signal( SIGALRM, sighandler ); (void)alarm( idletime ); if ( connect( datafd, (struct sockaddr *)&dataconn, sizeof(dataconn) ) && ( errno != ECONNREFUSED || ( usleep( 500 ), /* try twice */ connect( datafd, (struct sockaddr *)&dataconn, sizeof(dataconn) ) ) ) ) { addreply( 425, "Error connecting to %s port %d: %s", inet_ntoa( *(struct in_addr *)&a ), p, strerror( errno ) ); close( datafd ); datafd = 0; return; } (void)alarm( 0U ); passive = 0; addreply( 200, "Connected to %s port %d", inet_ntoa( *(struct in_addr *)&a ), p ); return; } int opendata( void ) { struct sockaddr_in dataconn; /* his data connection endpoint */ int fd; int fodder; if ( !datafd ) { error( 425, "No data connection" ); return 0; } if ( passive ) { fodder = sizeof( dataconn ); if ( setjmp( appropriately ) ) { syslog( LOG_INFO, "error during passive accept: %s", strerror( errno ) ); exit( 17 ); } (void)signal( SIGALRM, sighandler ); (void)alarm( idletime ); fd = accept( datafd, (struct sockaddr *)&dataconn, &fodder ); if ( fd < 0 ) { error( 421, "accept failed" ); return 0; } (void)alarm( 0U ); } else { fd = datafd; datafd = 0; } addreply( 150, "Opening data connection" ); fodder = IPTOS_THROUGHPUT; if ( setsockopt( fd, SOL_IP, IP_TOS, (char *)&fodder, sizeof(int) ) < 0 ) syslog( LOG_WARNING, "setsockopt( IP_TOS ): %m" ); fodder = window; if ( setsockopt( fd, SOL_SOCKET, SO_SNDBUF, (char *)&fodder, sizeof(int) ) < 0 ) syslog( LOG_WARNING, "setsockopt( SO_SNDBUF, %d ): %m", window ); fodder = window; /* not that important, but... */ if ( setsockopt( fd, SOL_SOCKET, SO_RCVBUF, (char *)&fodder, sizeof(int) ) < 0 ) syslog( LOG_WARNING, "setsockopt( SO_RCVBUF, %d ): %m", window ); return fd; } void dodele( const char *name ) { if ( guest ) addreply( 550, "Anonymous users can not delete files." ); else if ( !name || !*name ) addreply( 501, "No file name supplied." ); else { int successes = 0; glob_t g; int a; a = glob( name, 0, NULL, &g ); if ( a == GLOB_NOSPACE ) { addreply( 550, "Out of memory during globbing of %s", name ); } else if ( a == GLOB_ABORTED ) { addreply( 550, "Read error during globbing of %s", name ); } else if ( a == GLOB_NOMATCH ) { addreply( 550, "No match for %s in %s", name, wd ); } else if ( a == 0 ) { char **path; path = g.gl_pathv; while ( path && *path ) { if ( unlink( *path ) ) { addreply( 0, "Could not delete %s: %s", *path, strerror( errno ) ); } else { addreply( 250, "Deleted %s", *path ); successes++; } path++; } } globfree( &g ); /* set the reply code in case there were no successes */ if ( successes == 0 ) addreply( 550, "No files deleted." ); } } void doretr( const char *name ) { int c, f, o, s, skip; struct stat st; char *p, *buf; int left; struct timeval started, ended; double t; double speed; if ( !candownload ) { addreply( 550, "The load was %3.2f when you connected. We do not " "allow downloads\nby anonymous users when the load is " "that high. Uploads are always\nallowed.", load ); return; } f = open( name, O_RDONLY ); if ( f < 0 ) { char buffer[PATH_MAX + 40]; sprintf( buffer, "Can't open %s", name ); error( 550, buffer ); return; } if ( fstat( f, &st ) ) { close( f ); error( 451, "can't find file size" ); return; } if ( restartat && ( st.st_size <= restartat) ) { restartat = 0; addreply (451, "Restart offset %d is larger than file size %d" "Restart offset reset to 0.", restartat, st.st_size ); return; } if ( !S_ISREG( st.st_mode ) ) { close( f ); addreply( 450, "Not a regular file" ); return; } if ( warez && ( st.st_uid == warez ) && guest ) { close( f ); addreply( 550, "This file has been uploaded by an anonymous user. " "It has not\nyet been approved for downloading by " "the site administrators.\n" ); return; } c = opendata(); if ( !c ) { close( f ); return; } if ( (s = fcntl( c, F_GETFL, 0 ) ) < 0 ) { error( 451, "fcntl failed" ); close( f ); close( c ); return; } s |= FNDELAY; fcntl( c, F_SETFL, s ); if ( type == 1 ) { addreply( 0, "NOTE: ASCII mode requested, but binary mode used" ); if ( (time( 0 ) % 1000000 ) == 0 ) addreply( 0, "The computer is your friend. Trust the computer" ); } doreply(); if ( setjmp( appropriately ) ) { addreply( 426, "Transfer aborted" ); close( f ); close( c ); return; } (void)signal( SIGURG, sighandler ); (void)gettimeofday( &started, NULL ); o = restartat & ~262143; skip = restartat - o; while ( o < st.st_size ) { left = st.st_size - o; if ( left > 262144 ) left = 262144; buf = mmap( 0, left, PROT_READ, MAP_FILE | MAP_SHARED, f, o ); if ( (signed int)buf == -1 ) { error( 451, "mmap of file failed" ); close( f ); close( c ); return; } p = buf; o += left; s = left; while ( left > skip ) { size_t w; w = write( c, p+skip, (size_t) (left - skip) ); if ( (int ) w < 0 ) { if ( errno == EAGAIN ) { /* wait idletime seconds for progress */ fd_set rs; fd_set ws; struct timeval tv; FD_ZERO( &rs ); FD_ZERO( &ws ); FD_SET( 0, &rs ); FD_SET( c, &ws ); tv.tv_sec = idletime; tv.tv_usec = 0; select( c + 1, &rs, &ws, NULL, &tv ); if ( FD_ISSET( 0, &rs ) ) { addreply( 426, "Transfer aborted" ); munmap( buf, s ); close( f ); close( c ); return; } else if ( !( FD_ISSET( c, &ws ) ) ) { /* client presumably gone away */ syslog( LOG_INFO, "died: %ld seconds without download progress", idletime ); exit( 11 ); } w = 0; } else { error( 450, "Error during write to data connection" ); close( f ); close( c ); return; } } left -= w; p += w; } skip = 0; munmap( buf, s ); } signal( SIGURG, SIG_IGN ); (void)gettimeofday( &ended, NULL ); t = ( ended.tv_sec + ended.tv_usec / 1000000.0 ) - ( started.tv_sec + started.tv_usec / 1000000.0 ); addreply( 226, "File written successfully" ); if ( t && ( st.st_size - restartat ) > 2*window ) speed = ( st.st_size - restartat - window ) / t; else speed = 0.0; if ( speed > 524288 ) addreply( 0, "%.3f seconds (measured here), %.2f Mbytes per second", t, speed / 1048576 ); else if ( speed > 512 ) addreply( 0, "%.3f seconds (measured here), %.2f Kbytes per second", t, speed / 1024 ); else if ( speed > 0.1 ) addreply( 0, "%.3f seconds (measured here), %.2f bytes per second", t, speed ); close( f ); close( c ); if ( restartat ) { addreply( 0, "Restart offset reset to 0." ); restartat = 0; } downloaded += st.st_size; syslog( LOG_INFO, "%s%s%s%s downloaded", rd ? rd : "", *name == '/' ? "" : wd, ( *name != '/' && ( !*wd || wd[strlen( wd ) - 1] != '/' ) ) ? "/" : "", name ); return; } void dorest (const char *name) { char *endptr; restartat = strtoul( name, &endptr, 10 ); if ( *endptr ) { restartat = 0; addreply(501, "RESTART needs numeric parameter."); } else { syslog(LOG_NOTICE, "info: restart %d", restartat); addreply(350, "Restarting at %ld. Send STOR or RETR to initiate transfer.", restartat); } } /* next two functions contributed by Patrick Michael Kane */ void domkd( const char *name ) { if ( guest ) addreply( 550, "Sorry, anonymous users are not allowed to " "make directories." ); else if ( (mkdir( name, 0755 ) ) < 0 ) error( 550, "Can't create directory" ); else addreply( 257, "MKD command successful." ); return; } void dormd( const char *name ) { if ( guest ) addreply( 550, "Sorry, anonymous users are not allowed to " "remove directories." ); else if ( (rmdir( name ) ) < 0 ) error( 550, "Can't remove directory" ); else addreply( 250, "RMD command successful." ); return; } void dostor( const char *name ) { int c, f; char *p; char buf[16384]; int r; static int filesize; /* static to avoid longjmp warning */ struct statfs statfsbuf; struct stat st; filesize = 0; if ( type < 2 ) { addreply( 503, "Only binary mode (TYPE L8 or I) is supported" ); return; } if ( !stat( name, &st ) ) { if ( guest ) { addreply( 553, "Anonymous users may not overwrite existing files" ); return; } } else if ( errno != ENOENT ) { error( 553, "Can't check for file" ); return; } f = open( name, O_CREAT | O_TRUNC | O_WRONLY, 0600 ); if ( f < 0 ) { error( 553, "Can't open file" ); return; } if ( restartat && lseek(f, restartat, SEEK_SET) < 0) { error (451, "can't seek" ); return; } restartat = 0; c = opendata(); if ( !c ) { close( f ); return; } doreply(); if ( setjmp( appropriately ) ) { close( f ); close( c ); addreply( 426, "Transfer aborted, %s %s", name, unlink( name ) ? "partially uploaded" : "removed" ); return; } (void)signal( SIGURG, sighandler ); do { r = read( c, &buf, 16384 ); if ( r > 0 ) { p = buf; filesize += r; while ( r ) { size_t w; w = write( f, p, ( size_t ) r ); if ( (signed int)w < 0 ) { error( -450, "Error during write to file" ); close( f ); close( c ); addreply( 450, "%s %s", name, unlink( name ) ? "partially uploaded" : "removed" ); return; } r -= w; p += w; } r = 1; } else if ( r < 0 ) { error( -451, "Error during read from data connection" ); close( f ); close( c ); addreply( 451, "%s %s", name, unlink( name ) ? "partially uploaded" : "removed" ); return; } } while ( r > 0 ); signal( SIGURG, SIG_IGN ); fchmod( f, 0644 ); addreply( 226, "File written successfully" ); if ( fstatfs( f, &statfsbuf ) == 0 ) { double space; space = (double)statfsbuf.f_bsize * (double)statfsbuf.f_bavail; if ( space > 524288 ) addreply( 0, "%.1f Mbytes free disk space", space / 1048576 ); else addreply( 0, "%f Kbytes free disk space", space / 1024 ); } close( f ); close( c ); uploaded += filesize; syslog( LOG_INFO, "%s%s%s%s uploaded", rd ? rd : "", *name == '/' ? "" : wd, ( *name != '/' && ( !*wd || wd[strlen( wd ) - 1] != '/' ) ) ? "/" : "", name ); return; } void domdtm( const char *name ) { struct stat st; struct tm *t; if ( !name || !*name ) { addreply( 500, "Command not understood" ); } else if ( lstat( name, &st ) ) { if ( debug ) addreply( 0, "arg is %s, wd is %s", name, wd ); addreply( 550, "Unable to stat()" ); } else if ( !S_ISREG( st.st_mode ) ) { addreply( 550, "Not a regular file" ); } else { t = gmtime( (time_t * ) & st.st_mtime ); if ( !t ) { addreply( 550, "gmtime() returned NULL" ); } else { addreply( 213, "%04d%02d%02d%02d%02d%02d", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec ); } } return; } void dosize( const char *name ) { struct stat st; if ( !name || !*name ) { addreply( 500, "Command not understood" ); } else if ( lstat( name, &st ) ) { if ( debug ) addreply( 0, "arg is %s, wd is %s", name, wd ); addreply( 550, "Unable to stat()" ); } else if ( !S_ISREG( st.st_mode ) ) { addreply( 550, "Not a regular file" ); } else { addreply( 213, "%ld ", (long)st.st_size ); } return; } void dotype( const char *arg ) { replycode = 200; /* bloody awful hack */ if ( !arg || !*arg ) { addreply( 501, "TYPE needs an argument\n" "Only A(scii), I(mage) and L(ocal) are supported" ); } else if ( *arg == 'A' ) type = 1; else if ( *arg == 'I' ) type = 2; else if ( *arg == 'L' ) { if ( arg[1] == '8' ) { type = 2; } else if ( isdigit( arg[1] ) ) { addreply( 504, "Only 8-bit bytes are supported" ); } else { addreply( 0, "Byte size not specified" ); type = 2; } } else { addreply( 504, "Unknown TYPE: %s", arg ); } addreply( 0, "TYPE is now %s", ( type > 1 ) ? "8-bit binary" : "ASCII" ); } void dostru( const char *arg ) { if ( !arg || !*arg ) addreply( 500, "No arguments\n" "Not that it matters, only STRU F is supported" ); else if ( strcasecmp( arg, "F" ) ) addreply( 504, "STRU %s is not supported\nOnly F(ile) is supported", arg ); else addreply( 200, "F OK" ); } void domode( const char *arg ) { if ( !arg || !*arg ) addreply( 500, "No arguments\n" "Not that it matters, only MODE S is supported" ); else if ( strcasecmp( arg, "S" ) ) addreply( 504, "MODE %s is not supported\n" "Only S(tream) is supported", arg ); else addreply( 200, "S OK" ); } void dornfr( const char *name ) { struct stat st; if ( guest ) { addreply( 550, "Sorry, anonymous users are not allowed to rename files." ); } else if ( ( stat( name, &st ) ) == 0 ) { if ( renamefrom ) { addreply( 0, "Aborting previous rename operation." ); (void) free( renamefrom ); } renamefrom = strdup( name ); addreply( 350, "RNFR accepted - file exists, ready for destination." ); } else { addreply( 550, "File does not exist!" ); } return; } /* rnto can return 550, which is not strictly allowed */ void dornto( const char *name ) { struct stat st; if ( guest ) addreply( 550, "Sorry, anonymous users are not allowed to rename files." ); else if ( ( stat( name, &st ) ) == 0 ) addreply( 550, "RENAME Failed - destination file already exists." ); else if ( !renamefrom ) addreply( 503, "Need RNFR before RNTO" ); else if ( (rename( renamefrom, name ) ) < 0 ) addreply( 550, "Rename failed: %s", strerror( errno ) ); else addreply( 250, "File renamed." ); if ( renamefrom ) (void) free( renamefrom ); renamefrom = 0; return; } // end of addition void error( int n, const char *msg ) { syslog( LOG_ERR, "%s: %m", msg ); addreply( n, "%s: %s", msg, strerror( errno ) ); } int main( int argc, char **argv ) { int fodder; struct hostent *he; struct passwd *pw; struct sockaddr_in peer; FILE *f; double maxload; time_t tmt; struct tm *t; struct rusage ru; signal( SIGURG, SIG_IGN ); maxload = 0.0; openlog( "in.ftpd", LOG_CONS | LOG_PID, LOG_LOCAL2 ); while ( (fodder = getopt( argc, argv, "sc:m:u:" ) ) != -1 ) { if ( fodder == 's' ) { if ( (pw = getpwnam( "ftp" ) ) ) warez = pw->pw_uid; else syslog( LOG_ERR, "can't find ftp uid" ); } else if ( fodder == 'c' ) { struct rlimit limit; char *nptr, *endptr; nptr = optarg; endptr = NULL; limit.rlim_max = strtol( nptr, &endptr, 0 ); limit.rlim_cur = limit.rlim_max; if ( !nptr || !*nptr || !endptr || *endptr || !limit.rlim_max ) { printf( "421 Configuration error: Illegal CPU limit: %s\r\n", optarg ); syslog( LOG_ERR, "Illegal CPU limit: %s", optarg ); sleep( 1 ); exit( 8 ); } else if ( setrlimit( RLIMIT_CPU, &limit ) ) { printf( "421 Cannot set CPU limit, errno=%d\r\n", errno ); syslog( LOG_ERR, "Cannot set CPU limit: %s", strerror(errno) ); sleep( 1 ); exit( 5 ); } else { addreply( 0, "This session is limited to %u seconds CPU time.", limit.rlim_max ); } } else if ( fodder == 'm' ) { char *nptr, *endptr; nptr = optarg; endptr = NULL; maxload = strtod( nptr, &endptr ); if ( !nptr || !*nptr || !endptr || *endptr || maxload <= 0.0 ) { printf( "421 Configuration error: Illegal load limit: %s\r\n", optarg ); syslog( LOG_ERR, "Illegal load limit: %s", optarg ); sleep( 1 ); exit( 15 ); } } else if ( fodder == 'u' ) { char *nptr, *endptr; long tmp; nptr = optarg; endptr = NULL; tmp = strtol( nptr, &endptr, 10 ); if ( !nptr || !*nptr || !endptr || *endptr || tmp > 65535 || tmp < 0 ) { printf( "421 Configuration error: Illegal uid limit: %s\r\n", optarg ); syslog( LOG_ERR, "Illegal uid limit: %s", optarg ); sleep( 1 ); exit( 16 ); } useruid = ( uid_t ) tmp; } else if ( fodder == '?' ) { syslog( LOG_WARNING, "unknown option" ); } } load = -1.0; if ( (f = fopen( "/proc/loadavg", "r" ) ) != NULL && fscanf( f, "%lf", &load ) == 1 ) fclose( f ); fodder = sizeof(struct sockaddr_in); if ( getsockname( 0, (struct sockaddr *)&ctrlconn, &fodder ) ) { printf( "421 Cannot getsockname( STDIN ), errno=%d\r\n", errno ); syslog( LOG_ERR, "Cannot getsockname( STDIN ): %s", strerror(errno) ); sleep( 1 ); exit( 3 ); } getnames(); loggedin = 0; if ( getpeername( 0, (struct sockaddr *)&peer, &fodder ) ) { printf( "421 Cannot getpeername( STDIN ), errno=%d\r\n", errno ); syslog( LOG_ERR, "Cannot getpeername( STDIN ): %s", strerror(errno) ); sleep( 1 ); exit( 9 ); } he = gethostbyaddr( (char *)&peer.sin_addr.s_addr, sizeof(peer.sin_addr.s_addr), AF_INET ); syslog( LOG_INFO, "connection from %s", he && strlen( he->h_name ) ? he->h_name : inet_ntoa( peer.sin_addr ) ); he = gethostbyaddr( (char *)&ctrlconn.sin_addr.s_addr, sizeof(ctrlconn.sin_addr.s_addr), AF_INET ); if ( he && he->h_name ) { char name[PATH_MAX]; FILE * config; if ( snprintf( name, PATH_MAX, "/var/adm/ftp/%s/.", he->h_name ) < 0 ){ syslog( LOG_ERR, "host name far too long for %s", inet_ntoa( ctrlconn.sin_addr ) ); exit( 15 ); } if ( !chdir( name ) ) { rd = malloc( strlen( he->h_name ) + 3 ); /* it'd be much better with a separate name here */ if ( !rd || ( pw = getpwnam( "ftp" ) ) == NULL || setregid( pw->pw_gid, pw->pw_gid ) || setgroups( sizeof( pw->pw_gid ), &( pw->pw_gid ) ) || chroot( name ) || chdir( "/" ) ) { printf( "421 Unable to cage in anonymous user, aborting\r\n" ); syslog( LOG_INFO, "Unable to cage in anonymous user" ); fflush( stdout ); sleep( 1 ); exit( 10 ); } sprintf( rd, "%s:", he->h_name ); setfsuid( pw->pw_uid ); dobanner(); loggedin = guest = 1; syslog( LOG_INFO, "logged in guest of %s", he->h_name ); } else if ( (config=fopen( name, "r" )) != NULL ) { char line[1024]; do { fgets( line, 1024, config ); } while( 0 ); printf( "421 %s: configuration error\r\n", name ); syslog( LOG_ERR, "%s: configuration error", name ); fflush( stdout ); sleep( 1 ); exit( 19 ); } strcpy( cpwd, "*" ); } chdir( "/" ); strcpy( wd, "/" ); fodder = IPTOS_LOWDELAY; if ( setsockopt( 0, SOL_IP, IP_TOS, (char *)&fodder, sizeof(int) ) < 0 ) syslog( LOG_WARNING, "setsockopt ( IP_TOS ): %m" ); fodder = 1; if ( setsockopt( 0, SOL_SOCKET, SO_OOBINLINE, (char *)&fodder, sizeof(int) ) < 0 ) syslog( LOG_WARNING, "setsockopt: %m" ); tmt = time( NULL ); t = localtime( (time_t * ) & tmt ); if ( t != 0 && load >= 0.0 ) addreply( 220, "Local time is now %02d:%02d and the load is %3.2f.", t->tm_hour, t->tm_min, load ); else syslog( LOG_ERR, "unable to read load average or current time" ); if ( loggedin ) addreply( 220, "Only anonymous FTP is allowed at %s.", he->h_name ); addreply( 220, "You will be thrown out after %d seconds of inactivity.", idletime ); fflush( stdout ); if ( maxload > 0.0 && load >= maxload ) candownload = 0; /* can't download - dopass may negate this */ parser(); if ( getrusage( RUSAGE_SELF, &ru ) == 0 ) { unsigned long s, u; u = ( ru.ru_utime.tv_usec + ru.ru_stime.tv_usec + 500 ) / 1000; s = ru.ru_utime.tv_sec + ru.ru_stime.tv_sec; if ( u > 999 ) { u -= 1000; s++; } addreply( 0, "CPU time spent on you: %ld.%03ld seconds.", s, u ); } doreply(); fflush( stdout ); sleep( 1 ); exit( 0 ); }