#include <cassert>
#include <cstdio>
#include <cstring>
#include <new>
#include "FTPAccessFile.h"
#include "ftpmunge.h"
using namespace std;
#pragma mark *** File Constants
static const size_t kMaxInt32Digits = 11; static const size_t kMaxLogKindLen = 10; static const int32_t kDefaultLoginFails = 5;
static const char *kDefaultVirtualRoot = "/Library/FTPServer/FTPRoot";
static const char *kDefaultShutdownFilename = "/Library/FTPServer/Messages/shutdown.txt";
#pragma mark *** File Macros
#define NEWMEMBER( name, word ) \
aclmember *name = new aclmember; \
memset( name, 0, sizeof( aclmember ) ); \
strncpy( name->keyword, word, MAXKWLEN ); \
name->keyword[MAXKWLEN-1] = 0
#pragma mark *** FTPAccess Member functions
FTPAccessFile::FTPAccessFile( const char *filename )
throw( std::bad_alloc, std::runtime_error )
: ConfigurationFile( filename),
aclmembers( NULL )
{
parseacl( readacl( GetFileForReading() ) );
}
FTPAccessFile::~FTPAccessFile() throw()
{
FlushFile();
for( aclmember *member = aclmembers; member != NULL; ) {
aclmember *next = member->next;
RemoveAndDeallocMember( member );
member = next;
}
}
void FTPAccessFile::FlushFile()
{
FILE *file = StartWrite();
for( aclmember *member = aclmembers; member; member = member->next )
fprintf( file, "%s\n", member->rawText );
StopWrite();
}
uint32_t FTPAccessFile::GetAnonymousStatus() throw()
{
char *str = GetSingleton( "anonFTP" );
if( str != NULL && 0 == strncasecmp( str, "YES", strlen( "YES" ) ) )
return 1;
return 0;
}
void FTPAccessFile::SetAnonymousStatus( uint32_t status )
throw( std::bad_alloc )
{
SetSingleton( "anonFTP", status == 0 ? "no" : "yes" );
}
uint32_t FTPAccessFile::GetMacBinAndDmgAutoConversion() throw()
{
char *str = GetSingleton( "influence_listings" );
if( str != NULL && 0 == strncasecmp( str, "NO", strlen( "NO" ) ) )
return 0;
return 1; }
void FTPAccessFile::SetMacBinAndDmgAutoConversion( uint32_t status )
throw( std::bad_alloc )
{
SetSingleton( "influence_listings", status == 0 ? "no" : "yes" );
}
int32_t FTPAccessFile::GetAnonymousLimit() throw()
{
return GetLimitForClass( "anonusers" );
}
int32_t FTPAccessFile::GetRealLimit() throw()
{
return GetLimitForClass( "realusers" );
}
char *FTPAccessFile::GetShutdownFilename() throw()
{
const char *filename = GetSingleton( "shutdown" );
if( !filename )
SetShutdownFilename( kDefaultShutdownFilename );
return ftp2rawstr( GetSingleton( "shutdown" ) );
}
void FTPAccessFile::SetShutdownFilename( const char *filename )
throw( std::bad_alloc )
{
char *ftpFilename = raw2ftpstr( filename );
SetSingleton( "shutdown", ftpFilename );
delete[] ftpFilename;
}
char *FTPAccessFile::GetBannerFilename() throw()
{
return ftp2rawstr( GetSingleton( "banner" ) );
}
void FTPAccessFile::SetBannerFilename( const char *filename )
throw( std::bad_alloc )
{
char *ftpFilename = raw2ftpstr( filename );
SetSingleton( "banner", ftpFilename );
delete [] ftpFilename;
}
char *FTPAccessFile::GetLoginMessageFilename() throw()
{
for( aclmember *member = GetNextKeywordEntry( "message", NULL );
member != NULL;
member = GetNextKeywordEntry( "message", member ) ) {
if( 0 == strncasecmp( "login", member->arg[1], strlen( "login" ) )
&& !member->arg[2] )
return ftp2rawstr( member->arg[0] );
}
return NULL;
}
void FTPAccessFile::SetLoginMessageFilename( const char *filename )
throw( std::bad_alloc )
{
for( aclmember *member = aclmembers; member; ) {
aclmember *next = member->next;
if( 0 == strncasecmp( "message", member->keyword, MAXKWLEN )
&& 0 == strncasecmp( "login", member->arg[1], strlen( "login" ) ) ) {
RemoveAndDeallocMember( member );
}
member = next;
}
if( filename == NULL )
return;
NEWMEMBER( member, "message" );
member->arg[0] = raw2ftpstr( filename );
member->arg[1] = NewStr( "login" );
RedoRawText( member );
AddMember( member );
}
int32_t FTPAccessFile::GetLoginFails() throw()
{
aclmember *member = GetNextKeywordEntry( "loginfails", NULL );
if( member == NULL )
return kDefaultLoginFails;
return atoi( member->arg[0] );
}
const char *FTPAccessFile::GetEmail() throw()
{
return ftp2rawstr( GetSingleton( "email" ) );
}
void FTPAccessFile::SetEmail( const char *email ) throw( std::bad_alloc )
{
const char *ftpEmail = raw2ftpstr( email );
SetSingleton( "email", ftpEmail );
delete[] ftpEmail;
}
const char *FTPAccessFile::GetKerberosPrincipal() throw()
{
return ftp2rawstr( GetSingleton( "krb5_principal" ) );
}
void FTPAccessFile::SetKerberosPrincipal( const char *principal ) throw( std::bad_alloc )
{
const char *ftpPrincipal = raw2ftpstr( principal );
SetSingleton( "krb5_principal", ftpPrincipal );
delete[] ftpPrincipal;
}
const char *FTPAccessFile::GetAnonRoot() throw()
{
const char *root = GetSingleton( "anonymous-root" );
if( !root )
SetAnonRoot( kDefaultVirtualRoot );
return ftp2rawstr( GetSingleton( "anonymous-root" ) );
}
void FTPAccessFile::SetAnonRoot( const char *anonRootPath ) throw( std::bad_alloc )
{
const char *ftpAnonRoot = raw2ftpstr( anonRootPath );
SetSingleton( "anonymous-root", ftpAnonRoot );
delete[] ftpAnonRoot;
}
const char *FTPAccessFile::GetVirtualRoot() throw()
{
const char *root = GetSingleton( "defrootdir" );
if( !root )
SetVirtualRoot( kDefaultVirtualRoot );
return ftp2rawstr( GetSingleton( "defrootdir" ) );
}
void FTPAccessFile::SetVirtualRoot( const char *root ) throw( std::bad_alloc )
{
char *ftpRoot = raw2ftpstr( root ? root : kDefaultVirtualRoot );
SetSingleton( "defrootdir", ftpRoot );
SetUploadDirectives( ftpRoot );
delete[] ftpRoot;
}
void FTPAccessFile::SetUploadDirectives( const char *path ) throw(std::bad_alloc)
{
DeleteKeywordEntries( "upload" );
if( !path )
return;
NEWMEMBER( first, "upload" );
first->arg[0] = NewStr( path );
first->arg[1] = raw2ftpstr( "/uploads" );
first->arg[2] = NewStr( "yes" );
first->arg[3] = NewStr( "ftp" );
first->arg[4] = NewStr( "daemon" );
first->arg[5] = NewStr( "0666" );
first->arg[6] = NewStr( "nodirs" );
RedoRawText( first );
AddMember( first );
NEWMEMBER( second, "upload" );
second->arg[0] = NewStr( path );
second->arg[1] = raw2ftpstr( "/uploads/mkdirs" );
second->arg[2] = NewStr( "yes" );
second->arg[3] = NewStr( "ftp" );
second->arg[4] = NewStr( "daemon" );
second->arg[5] = NewStr( "0666" );
second->arg[6] = NewStr( "dirs" );
second->arg[7] = NewStr( "0777" );
RedoRawText( second );
AddMember( second );
}
uint32_t FTPAccessFile::GetAuthLevel() throw( std::bad_alloc )
{
char *str = GetSingleton( "auth_level" );
if( str == NULL || 0 == strcasecmp( str, "standard" ) )
return kAuthLevelStandard;
else if( 0 == strcasecmp( str, "gssapi" ) )
return kAuthLevelGSSAPI;
else if( 0 == strcasecmp( str, "both" ) )
return kAuthLevelBoth;
else {
SetSingleton( "auth_level", "standard" );
}
return kAuthLevelStandard;
}
void FTPAccessFile::SetAuthLevel( uint32_t level ) throw( std::bad_alloc )
{
const char *str = "standard";
if( level == kAuthLevelGSSAPI )
str = "gssapi";
else if( level == kAuthLevelBoth )
str = "both";
SetSingleton( "auth_level", str );
}
uint32_t FTPAccessFile::GetChrootType() throw( std::bad_alloc )
{
const char *str = GetSingleton( "chroot_type" );
if( str == NULL || 0 == strcasecmp( str, "standard" ) )
return kChrootTypeStandard;
else if( 0 == strcasecmp( str, "homedir" ) )
return kChrootTypeHomedir;
else if( 0 == strcasecmp( str, "restricted" ) )
return kChrootTypeRestricted;
else {
SetSingleton( "chroot_type", "standard" );
}
return kChrootTypeStandard;
}
void FTPAccessFile::SetChrootType( uint32_t type ) throw( std::bad_alloc )
{
const char *str = "standard";
if( type == kChrootTypeHomedir )
str = "homedir";
else if( type == kChrootTypeRestricted )
str = "restricted";
SetSingleton( "chroot_type", str );
}
#pragma mark ** Singleton functions
char *FTPAccessFile::GetSingleton( const char *kind ) throw()
{
assert( kind != NULL );
aclmember *member = GetNextKeywordEntry( kind, NULL );
if( member == NULL )
return NULL;
return member->arg[0];
}
void FTPAccessFile::SetSingleton( const char *kind, const char *arg0 )
throw( std::bad_alloc )
{
assert( kind != NULL );
DeleteKeywordEntries( kind );
if( arg0 == NULL )
return;
NEWMEMBER( member, kind );
member->arg[0] = NewStr( arg0 );
RedoRawText( member );
AddMember( member );
}
#pragma mark ** Limit helper functions
int32_t FTPAccessFile::GetLimitForClass( const char *limitClass ) throw()
{
for( aclmember *member = GetNextKeywordEntry( "limit", NULL );
member;
member = GetNextKeywordEntry( "limit", member ) ) {
if( 0 == strncasecmp( limitClass, member->arg[0], strlen(limitClass) ) )
return atoi( member->arg[1] );
}
return -1;
}
#pragma mark ** Functions for dealing with log directives
LogCommands FTPAccessFile::GetLogCommands() throw()
{
LogCommands commands = { 0, 0, 0 };
for( aclmember *member = GetNextKeywordEntry( "log", NULL );
member; member = GetNextKeywordEntry( "log", member ) ) {
if( 0 == strncasecmp("commands",member->arg[0],kMaxLogKindLen))
ExtractTypeList( member->arg[1], commands );
}
return commands;
}
void FTPAccessFile::SetLogCommands( LogCommands commands )
throw( std::bad_alloc )
{
DeleteLogKind( "commands" );
if( !commands.real && !commands.guest && !commands.anonymous )
return;
NEWMEMBER( member, "log" );
member->arg[0] = NewStr( "commands" );
member->arg[1] = TypeListToStr( commands );
RedoRawText( member );
AddMember( member );
}
LogSecurity FTPAccessFile::GetLogSecurity() throw()
{
LogSecurity security = { 0, 0, 0 };
for( aclmember *member = GetNextKeywordEntry( "log", NULL );
member; member = GetNextKeywordEntry( "log", member ) ) {
if( 0 == strncasecmp( "security", member->arg[0], MAXKWLEN ) )
ExtractTypeList( member->arg[1], security );
}
return security;
}
void FTPAccessFile::SetLogSecurity( LogSecurity security )
throw( std::bad_alloc )
{
DeleteLogKind( "security" );
if( !security.real && !security.guest && !security.anonymous )
return;
NEWMEMBER( member, "log" );
member->arg[0] = NewStr( "security" );
member->arg[1] = TypeListToStr( security );
RedoRawText( member );
AddMember( member );
}
uint32_t FTPAccessFile::GetLogSyslog() throw()
{
for( aclmember *member = GetNextKeywordEntry( "log", NULL );
member; member = GetNextKeywordEntry( "log", member ) ) {
if( 0 == strncasecmp( "syslog", member->arg[0], MAXKWLEN ) )
return 1;
}
return 0;
}
void FTPAccessFile::SetLogSyslog( uint32_t syslog ) throw( std::bad_alloc )
{
DeleteLogKind( "syslog" );
if( !syslog )
return;
NEWMEMBER( member, "log" );
member->arg[0] = NewStr( "syslog" );
RedoRawText( member );
AddMember( member );
}
LogTransfers FTPAccessFile::GetLogTransfers() throw()
{
TypeList in = { 0, 0, 0 };
TypeList out = { 0, 0, 0 };
for( aclmember *member = GetNextKeywordEntry( "log", NULL );
member; member = GetNextKeywordEntry( "log", member ) ) {
if( 0 == strncasecmp( "transfers", member->arg[0],
strlen( "transfers" ) )
&& NULL != strcasestr( member->arg[2], "inbound" ) )
ExtractTypeList( member->arg[1], in );
if( 0 == strncasecmp( "transfers", member->arg[0],
strlen( "transfers" ) )
&& NULL != strcasestr( member->arg[2], "outbound" ) )
ExtractTypeList( member->arg[1], out );
}
LogTransfers transfers;
transfers.real.inbound = in.real;
transfers.real.outbound = out.real;
transfers.guest.inbound = in.guest;
transfers.guest.outbound = out.guest;
transfers.anonymous.inbound = in.anonymous;
transfers.anonymous.outbound = out.anonymous;
return transfers;
}
void FTPAccessFile::SetLogTransfers( LogTransfers transfers )
throw( std::bad_alloc )
{
DeleteLogKind( "transfers" );
LogTransfers c = transfers;
if( transfers.real.inbound || transfers.anonymous.inbound
|| transfers.guest.inbound ) {
NEWMEMBER( member, "log" );
member->arg[0] = NewStr( "transfers" );
TypeList inbound;
inbound.real = transfers.real.inbound;
inbound.anonymous = transfers.anonymous.inbound;
inbound.guest = transfers.guest.inbound;
member->arg[1] = TypeListToStr( inbound );
member->arg[2] = NewStr( "inbound" );
RedoRawText( member );
AddMember( member );
}
if( transfers.real.outbound || transfers.anonymous.outbound
|| transfers.guest.outbound ) {
NEWMEMBER( member, "log" );
member->arg[0] = NewStr( "transfers" );
TypeList outbound;
outbound.real = transfers.real.outbound;
outbound.anonymous = transfers.anonymous.outbound;
outbound.guest = transfers.guest.outbound;
member->arg[1] = TypeListToStr( outbound );
member->arg[2] = NewStr( "outbound" );
RedoRawText( member );
AddMember( member );
}
}
void FTPAccessFile::DeleteLogKind( const char *kind ) throw()
{
assert( kind );
for( aclmember *member = aclmembers; member; ) {
aclmember *next = member->next;
if( 0 == strncasecmp( "log", member->keyword, MAXKWLEN )
&& 0 == strncasecmp(kind, member->arg[0],kMaxLogKindLen)) {
RemoveAndDeallocMember( member );
}
member = next;
}
}
#pragma mark ** General utility functions
char *FTPAccessFile::TypeListToStr( TypeList typelist ) throw( std::bad_alloc )
{
if( typelist.real && typelist.guest && typelist.anonymous )
return NewStr( "real,guest,anonymous" );
else if( typelist.real && typelist.guest )
return NewStr( "real,guest" );
else if( typelist.real && typelist.anonymous )
return NewStr( "real,anonymous" );
else if( typelist.guest && typelist.anonymous )
return NewStr( "guest,anonymous" );
else if( typelist.real )
return NewStr( "real" );
else if( typelist.guest )
return NewStr( "guest" );
else if( typelist.anonymous )
return NewStr( "anonymous" );
assert( "not reached" == NULL );
return NULL;
}
void FTPAccessFile::ExtractTypeList( const char *string, TypeList &typelist )
throw()
{
assert( string );
if( strcasestr( string, "real" ) != NULL )
typelist.real = true;
if( strcasestr( string, "anonymous" ) != NULL )
typelist.anonymous = true;
if( strcasestr( string, "guest" ) != NULL )
typelist.guest = true;
}
char *FTPAccessFile::NewStr( const char *str)
throw( bad_alloc )
{
if( str == NULL )
return NULL;
char *tmp = new char[ strlen( str ) + 1 ];
strcpy( tmp, str );
return tmp;
}
void FTPAccessFile::RedoRawText( aclmember *member ) throw( std::bad_alloc )
{
assert( member );
size_t len = strlen( member->keyword );
for( int i = 0; i < MAXKWLEN && member->arg[i]; i++ ) {
len += strlen( "\t" ) + strlen( member->arg[i] ) ;
}
len += 1;
delete [] member->rawText;
member->rawText = new char[ len ];
strcpy( member->rawText, member->keyword );
for( int i = 0; i < MAXKWLEN && member->arg[i]; i++ ) {
strcat( member->rawText, "\t" );
strcat( member->rawText, member->arg[i] );
}
}
#pragma mark ** ACL member manipulation functions
aclmember *FTPAccessFile::GetNextKeywordEntry( const char *keyword,
aclmember *previous ) throw()
{
assert( keyword );
if( previous == NULL )
previous = aclmembers;
else
previous = previous->next;
for( ; previous && 0!= strncasecmp(keyword,previous->keyword,MAXKWLEN);
previous = previous->next );
return previous;
}
void FTPAccessFile::Description()
{
aclmember* member = aclmembers;
while (member != NULL)
{
int n = 0;
printf("rawText=%s\n keyword=%s\n", member->rawText, member->keyword);
while ( member->arg[n] )
{
printf(" arg[%d]=%s\n", n, member->arg[n] );
n++;
}
member = member->next;
}
}
void FTPAccessFile::DeleteKeywordEntries( const char *keyword ) throw()
{
for( aclmember *member = aclmembers; member != NULL; ) {
aclmember *next = member->next;
if( 0 == strncasecmp( keyword, member->keyword, MAXKWLEN ) )
RemoveAndDeallocMember( member );
member = next;
}
}
void FTPAccessFile::RemoveAndDeallocMember( aclmember *member ) throw()
{
assert( member );
if( member->next )
member->next->prev = member->prev;
if( member->prev )
member->prev->next = member->next;
else
aclmembers = NULL;
for( uint8_t i = 0; i < MAXKWLEN; i++ ) {
delete[] member->arg[i];
member->arg[i] = NULL;
}
delete[] member->rawText;
delete member;
}
void FTPAccessFile::AddMember( aclmember *member ) throw()
{
assert( member != NULL );
member->next = member->prev = NULL;
if( !aclmembers ) {
aclmembers = member;
return;
}
aclmember *end = aclmembers;
for( ; end->next; end = end->next )
;
assert( end->next == NULL );
end->next = member;
member->prev = end;
}
#pragma mark ** wu-ftpd derived configuration parsing functions
char *FTPAccessFile::readacl( std::FILE *aclfile )
throw( std::bad_alloc, std::runtime_error )
{
assert( aclfile != NULL );
if( fseek( aclfile, 0, SEEK_END ) == -1 ) {
rewind( aclfile );
throw std::runtime_error( "cannot seek to the end of ftpaccess file" );
}
long size = ftell( aclfile );
rewind( aclfile );
if( size < 0 )
throw std::runtime_error( "size of ftpaccess < 0" );
char *aclbuf = NULL;
if( size == 0 ) {
aclbuf = new char[1] ;
memset( aclbuf, 0, 1 ) ;
}
else {
aclbuf = new char[ size + 1 ];
size_t bytes = fread( aclbuf, (size_t)size, 1, aclfile );
if( bytes == 0 && ferror( aclfile ) ) {
delete[] aclbuf;
aclbuf = NULL;
rewind( aclfile );
throw std::runtime_error( "unable to read in ftpaccess file" );
}
*(aclbuf + size) = '\0';
}
rewind( aclfile );
return aclbuf;
}
void FTPAccessFile::parseacl( char *aclbuf ) throw( std::bad_alloc )
try {
assert( aclbuf != NULL );
char *aclptr = aclbuf;
while( *aclptr != '\0' ) {
char *line = aclptr;
while( *aclptr && *aclptr != '\n' )
aclptr++;
*aclptr++ = (char) NULL;
aclmember *member = new aclmember;
memset( member, 0, sizeof(aclmember) );
member->rawText = new char[ strlen(line) + 1 ];
strcpy( member->rawText, line );
char *ptr = NULL;
if( (ptr = strchr(line, '#')) != NULL ) {
if( ((ptr > aclbuf) && (*(ptr-1) != '\\'))
|| ptr == aclbuf )
*ptr++ = '\0';
}
while( (ptr = strsep( &line, " \t" )) != NULL && *ptr == '\0')
;
if( ptr ) {
strncpy( member->keyword, ptr, MAXKWLEN );
member->keyword[ MAXKWLEN - 1 ] = '\0';
int cnt = 0;
while( (ptr = strsep( &line, " \t" )) != NULL ) {
if( *ptr == '\0' )
continue;
if( cnt >= MAXARGS )
break;
member->arg[cnt] = NewStr( ptr );
cnt++ ;
}
}
AddMember( member );
}
delete[] aclbuf;
}
catch(...) {
delete[] aclbuf;
throw;
}