/* $Id: ls.c,v 1.3 1996/10/18 13:25:56 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 /* linux rules! */ #include #include #include #include #include "ftpd.h" static char *getname( const uid_t uid ); static char **sreaddir( const char *dirname ); static void addfile( const char *, const char * ); static void outputfiles( FILE * ); static int listfile( const char *name ); static void listdir( FILE * f, const char *name ); char *users = NULL; char *groups = NULL; static int matches; void getnames( void ) { int f; /* uid_t can have 65536 values on linux */ f = open( "/var/adm/ftp/users", O_RDONLY ); if ( f >= 0 ) { users = mmap( 0, (size_t) 9 * 65536, PROT_READ, MAP_FILE | MAP_SHARED, f, 0 ); if ( (signed int) users == -1 ) { users = NULL; close( f ); } } f = open( "/var/adm/ftp/groups", O_RDONLY ); if ( f >= 0 ) { groups = mmap( 0, ( size_t ) 9 * 65536, PROT_READ, MAP_FILE | MAP_SHARED, f, 0 ); if ( (signed int) groups == -1 ) { groups = NULL; close( f ); } } } char *getname( const uid_t uid ) { static char number[9]; if ( users && users[9 * uid] ) { return ( users + ( 9 * uid ) ); } else { sprintf( number, "%-8d", uid ); return ( number ); } } char *getgroup( const gid_t gid ) { static char number[9]; if ( groups && groups[9 * gid] ) { return ( groups + ( 9 * gid ) ); } else { sprintf( number, "%-8d", gid ); return ( number ); } } /* ls options */ int opt_a, opt_C, opt_d, opt_F, opt_l, opt_R; /* listfile returns non-zero if the file is a directory */ int listfile( const char *name ) { int rval = 0; char m[1024]; struct stat st; char months[12][4] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; struct tm *t; char suffix[2]; if ( lstat( name, &st ) == 0 ) { t = localtime( (time_t * ) & st.st_mtime ); if ( !t ) { printf( "421 Bailing out, localtime() is insane\r\n" ); fflush( stdout ); exit( 2 ); } suffix[0] = suffix[1] = '\0'; if ( opt_F ) { if ( S_ISLNK( st.st_mode ) ) suffix[0] = '@'; else if ( S_ISDIR( st.st_mode ) ) { suffix[0] = '/'; rval = 1; } else if ( st.st_mode & 010101 ) suffix[0] = '*'; } if ( opt_l ) { strcpy( m, " ---------" ); switch ( st.st_mode & S_IFMT ) { case S_IFREG: m[0] = '-'; break; case S_IFLNK: m[0] = 'l'; break; /* readlink() here? */ case S_IFDIR: m[0] = 'd'; rval = 1; break; } if ( m[0] != ' ' ) { char nameline[PATH_MAX + PATH_MAX + 128]; char timeline[6]; if ( st.st_mode & 256 ) m[1] = 'r'; if ( st.st_mode & 128 ) m[2] = 'w'; if ( st.st_mode & 64 ) m[3] = 'x'; if ( st.st_mode & 32 ) m[4] = 'r'; if ( st.st_mode & 16 ) m[5] = 'w'; if ( st.st_mode & 8 ) m[6] = 'x'; if ( st.st_mode & 4 ) m[7] = 'r'; if ( st.st_mode & 2 ) m[8] = 'w'; if ( st.st_mode & 1 ) m[9] = 'x'; if ( time( NULL ) - st.st_mtime > 180 * 24 * 60 * 60 ) sprintf( timeline, "%5d", t->tm_year + 1900 ); else sprintf( timeline, "%02d:%02d", t->tm_hour, t->tm_min ); sprintf( nameline, "%s %3d %s %s %8d %s %2d %s %s", m, st.st_nlink, getname( st.st_uid ), getgroup( st.st_gid ), ( unsigned int ) st.st_size, months[t->tm_mon], t->tm_mday, timeline, name ); if ( S_ISLNK( st.st_mode ) ) { char *p = nameline + strlen( nameline ); m[readlink( name, m, 1023 )] = '\0'; suffix[0] = '\0'; if ( opt_F && stat( name, &st ) == 0 ) { if ( S_ISLNK( st.st_mode ) ) suffix[0] = '@'; else if ( S_ISDIR( st.st_mode ) ) suffix[0] = '/'; else if ( st.st_mode & 010101 ) suffix[0] = '*'; } sprintf( p, " -> %s", m ); } addfile( nameline, suffix ); } /* hide non-downloadable files */ } else { if ( S_ISREG( st.st_mode ) || S_ISDIR( st.st_mode ) || S_ISLNK( st.st_mode ) ) { addfile( name, suffix ); } } } return rval; } int colwidth = 0; int filenames = 0; struct filename { struct filename *down; struct filename *right; int top; char line[1]; }; struct filename *head = NULL; struct filename *tail = NULL; void addfile( const char *name, const char *suffix ) { struct filename *p; int l; if ( !name || !suffix ) return; matches++; l = strlen( name ) + strlen( suffix ); if ( l > colwidth ) colwidth = l; p = ( struct filename * ) malloc( sizeof( struct filename ) + l ); if ( !p ) { printf( "421 Out of memory\r\n" ); fflush( stdout ); exit( 7 ); } sprintf( p->line, "%s%s", name, suffix ); if ( tail ) tail->down = p; else head = p; tail = p; filenames++; } void outputfiles( FILE * f ) { int n; struct filename *p; struct filename *q; if ( !head ) return; tail->down = NULL; tail = NULL; colwidth = ( colwidth | 7 ) + 1; if ( opt_l || !opt_C ) colwidth = 75; /* set up first column */ p = head; p->top = 1; n = (filenames + (75 / colwidth)-1) / (75 / colwidth); while ( n && p ) { p = p->down; if ( p ) p->top = 0; n--; } /* while there's a neighbour to the right, point at it */ q = head; while ( p ) { p->top = q->top; q->right = p; q = q->down; p = p->down; } /* some are at the right end */ while ( q ) { q->right = NULL; q = q->down; } /* don't want wraparound, do we? */ p = head; while ( p && p->down && !p->down->top ) p = p->down; if ( p && p->down ) p->down = NULL; /* print each line, which consists of each column */ p = head; while ( p ) { q = p; p = p->down; while ( q ) { char pad[6]; char *tmp = ( char * ) q; if ( q->right ) { strcpy( pad, "\t\t\t\t\t" ); pad[( colwidth + 7 - strlen( q->line ) ) / 8] = '\0'; } else { strcpy( pad, "\r\n" ); } fprintf( f, "%s%s", q->line, pad ); q = q->right; free( tmp ); } } /* reset variables for next time */ head = tail = NULL; colwidth = 0; filenames = 0; } char **sreaddir( const char *dirname ) { DIR *d; struct dirent *de; struct stat st; int i; char **p; char *s; int dsize; static int cmp( const void *a, const void *b ); /* little function to to interface qsort to strcmp */ static int cmp( const void *a, const void *b ) { return strcmp( *( const char ** ) a, *( const char ** ) b ); } if ( stat( dirname, &st ) < 0 ) return NULL; if ( !S_ISDIR( st.st_mode ) ) { errno = ENOTDIR; return NULL; } if ( (d = opendir( dirname ) ) == NULL ) return NULL; /* st_size is enough for any sane fs, but procfs is insane */ dsize = st.st_size + 100; /* okay okay, a little margin is cool */ berkeley: p = (char **)malloc( dsize ); if ( !p ) { closedir( d ); errno = ENOMEM; return NULL; } s = dsize + ( char * ) p; i = 0; while ( (de = readdir( d ) ) != NULL ) { if ( (unsigned int)p + (i+1)*sizeof(char *) + strlen(de->d_name) + 1 > (unsigned int)s ) { dsize = dsize * 2; free( p ); /* should leak some memory too, make it perfect : ) */ rewinddir( d ); goto berkeley; } s -= strlen( de->d_name ) + 1; strcpy( s, de->d_name ); p[i++] = s; } closedir( d ); p[i] = NULL; qsort( p, i, sizeof( char * ), cmp ); return p; } /* have to change to the directory first ( speed hack for -R ) */ void listdir( FILE * f, const char *name ) { char **dir; dir = sreaddir( "." ); if ( dir ) { char **s; char **r; int d; fprintf( f, "total 1\r\n" ); /* so what is total anyway */ s = dir; while ( *s ) { if ( **s != '.' ) { d = listfile( *s ); } else if ( opt_a ) { d = listfile( *s ); if ( ( (*s)[1] == '\0' ) || ( ( (*s)[1] == '.' ) && ( (*s)[2] == '\0' ) ) ) d = 0; } else { d = 0; } if ( !d ) *s = NULL; s++; } outputfiles( f ); r = dir; while ( opt_R && r != s ) { if ( *r && !chdir( *r ) ) { char subdir[MAXPATHLEN]; sprintf( subdir, "%s/%s", name, *r ); fprintf( f, "\r\n%s:\r\n", subdir ); listdir( f, subdir ); if ( chdir( ".." ) ) { /* defensive in the extreme... */ chdir( wd ); if ( chdir( name ) ) { /* someone rmdir()'d it? */ printf( "421 Unrecoverable file system error: %s\r\n", strerror( errno ) ); fflush( stdout ); fclose( f ); exit( 13 ); } } } r++; } free( dir ); } else { addreply( 226, "Out of memory during reading of %s", name ); } } void donlist( const char *arg ) { int c; FILE *f; matches = 0; opt_a = opt_C = opt_d = opt_F = opt_R = 0; while ( isspace( *arg ) ) arg++; while ( arg && *arg == '-' ) { while ( arg++ && isalnum( *arg ) ) { switch ( *arg ) { case 'a': opt_a = 1; break; case 'l': opt_l = 1; opt_C = 0; break; case '1': opt_l = opt_C = 0; break; case 'C': opt_l = 0; opt_C = 1; break; case 'F': opt_F = 1; break; case 'R': opt_R = 1; break; case 'd': opt_d = 1; break; default: } } while ( isspace( *arg ) ) arg++; } c = opendata(); if ( !c ) return; doreply(); f = fdopen( c, "w" ); if ( type == 2 ) addreply( 0, "Binary mode requested, but A (ASCII) used\n" "If this is not what you want, mail agulbra@troll.no" ); if ( setjmp( appropriately ) ) { addreply( 426, "(Directory listings cannot be aborted cleanly, " "please avoid doing it)\nDirectory listing aborted." ); fclose( f ); chdir( wd ); return; } (void)signal( SIGURG, sighandler ); if ( arg && *arg ) { int justone; justone = 1; /* just one argument, so don't print dir name */ while ( arg ) { glob_t g; int a; char buffer[PATH_MAX]; char *endarg = strchr( arg, ' ' ); if ( endarg ) { *endarg++ = '\0'; justone = 0; } if ( debug ) addreply( 226, "Glob argument: %s", arg ); if ( loggedin && !guest && *arg == '~' ) { struct passwd *pw; int i; const char *p; i = 0; p = arg; p++; while ( *p && *p != '/' ) buffer[i++] = *p++; buffer[i] = '\0'; if ( (pw = getpwnam( i ? buffer : account ) ) ) sprintf( buffer, "%s%s", pw->pw_dir, p ); else *buffer = '\0'; } else { *buffer = '\0'; } a = glob( *buffer ? buffer:arg, opt_a ? GLOB_PERIOD : 0, NULL, &g ); if ( !a ) { char **path; path = g.gl_pathv; if ( path && path[0] && path[1] ) justone = 0; while ( path && *path ) { struct stat st; if ( lstat( *path, &st ) == 0 ) { if ( opt_d || !( S_ISDIR( st.st_mode ) ) ) { listfile( *path ); **path = '\0'; } } else { **path = '\0'; } path++; } outputfiles( f ); /* in case of opt_C */ path = g.gl_pathv; while ( path && *path ) { if ( **path ) { if ( !justone ) fprintf( f, "\r\n%s:\r\n", *path ); if ( !chdir( *path ) ) { listdir( f, *path ); chdir( wd ); } } path++; } } else { if ( a == GLOB_NOSPACE ) { addreply( 226, "Out of memory during globbing of %s", arg ); } else if ( a == GLOB_ABORTED ) { addreply( 226, "Read error during globbing of %s", arg ); } else if ( a != GLOB_NOMATCH ) { addreply( 226, "Unknown error during globbing of %s", arg ); } } globfree( &g ); arg = endarg; } } else { if ( opt_d ) listfile( "." ); else listdir( f, "." ); outputfiles( f ); } signal( SIGURG, SIG_IGN ); /* before fclose to avoid race */ fclose( f ); if ( opt_a || opt_C || opt_d || opt_F || opt_l || opt_R ) addreply( 0, "Options: %s%s%s%s%s%s", opt_a ? "-a " : "", opt_C ? "-C " : "", opt_d ? "-d " : "", opt_F ? "-F " : "", opt_l ? "-l " : "", opt_R ? "-R" : "" ); addreply( 226, "%d matches total", matches ); }