/* * Copyright (c) 2003, 2007 Regents of The University of Michigan. * All Rights Reserved. See COPYRIGHT. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_LIBPAM #ifdef HAVE_PAM_PAM_APPL_H #include #elif HAVE_SECURITY_PAM_APPL_H #include #else /* HAVE_PAM_ not defined */ die die die #endif /* HAVE_PAM_* */ #endif /* HAVE_LIBPAM */ extern SSL_CTX *ctx; #ifdef HAVE_ZLIB #include #endif /* HAVE_ZLIB */ #include #include "applefile.h" #include "base64.h" #include "command.h" #include "argcargv.h" #include "cksum.h" #include "code.h" #include "list.h" #include "wildcard.h" #include "largefile.h" #include "mkdirs.h" #include "connect.h" #define DEFAULT_MODE 0444 #define DEFAULT_UID 0 #define DEFAULT_GID 0 #define K_COMMAND 1 #define K_TRANSCRIPT 2 #define K_SPECIAL 3 #define K_FILE 4 int read_kfile( SNET *sn, char *kfile ); int f_quit( SNET *, int, char *[] ); int f_noop( SNET *, int, char *[] ); int f_help( SNET *, int, char *[] ); int f_stat( SNET *, int, char *[] ); int f_retr( SNET *, int, char *[] ); int f_stor( SNET *, int, char *[] ); int f_noauth( SNET *, int, char *[] ); int f_notls( SNET *, int, char *[] ); int f_starttls( SNET *, int, char *[] ); int f_repo( SNET *, int, char *[] ); #ifdef HAVE_LIBPAM int f_login( SNET *, int, char *[] ); int exchange( int num_msg, struct pam_message **msgm, struct pam_response **response, void *appdata_ptr ); #endif /* HAVE_LIBPAM */ #ifdef HAVE_ZLIB int f_compress( SNET *, int, char *[] ); #endif /* HAVE_ZLIB */ char *user = NULL; char *password = NULL; char *remote_host = NULL; char *remote_addr = NULL; char *remote_cn = NULL; char special_dir[ MAXPATHLEN ]; char command_file[ MAXPATHLEN ]; char upload_xscript[ MAXPATHLEN ]; const EVP_MD *md = NULL; struct list *access_list = NULL; int ncommands = 0; int authorized = 0; int prevstor = 0; int case_sensitive = 1; char hostname[ MAXHOSTNAMELEN ]; #ifdef HAVE_ZLIB int max_zlib_level = 0; #endif /* HAVE_ZLIB */ extern int debug; extern int authlevel; extern int checkuser; struct command notls[] = { { "QUIT", f_quit }, { "NOOP", f_noop }, { "HELP", f_help }, { "STATus", f_notls }, { "RETRieve", f_notls }, { "STORe", f_notls }, { "STARttls", f_starttls }, { "REPOrt", f_notls }, #ifdef HAVE_LIBPAM { "LOGIn", f_notls }, #endif /* HAVE_LIBPAM */ #ifdef HAVE_ZLIB { "COMPress", f_notls }, #endif /* HAVE_ZLIB */ }; struct command noauth[] = { { "QUIT", f_quit }, { "NOOP", f_noop }, { "HELP", f_help }, { "STATus", f_noauth }, { "RETRieve", f_noauth }, { "STORe", f_noauth }, { "REPOrt", f_noauth }, #ifdef HAVE_LIBPAM { "LOGIn", f_noauth }, #endif /* HAVE_LIBPAM */ #ifdef HAVE_ZLIB { "COMPress", f_noauth }, #endif /* HAVE_ZLIB */ }; struct command auth[] = { { "QUIT", f_quit }, { "NOOP", f_noop }, { "HELP", f_help }, { "STATus", f_stat }, { "RETRieve", f_retr }, { "STORe", f_stor }, { "STARttls", f_starttls }, { "REPOrt", f_repo }, #ifdef HAVE_LIBPAM { "LOGIn", f_login }, #endif /* HAVE_LIBPAM */ #ifdef HAVE_ZLIB { "COMPress", f_compress }, #endif /* HAVE_ZLIB */ }; struct command *commands = NULL; int f_quit( SNET *sn, int ac, char **av ) { snet_writef( sn, "%d QUIT OK, closing connection\r\n", 201 ); #ifdef HAVE_ZLIB if ( debug && max_zlib_level > 0 ) print_stats( sn ); #endif /* HAVE_ZLIB */ exit( 0 ); } int f_noop( SNET *sn, int ac, char **av ) { snet_writef( sn, "%d NOOP OK\r\n", 202 ); return( 0 ); } int f_help( SNET *sn, int ac, char **av ) { snet_writef( sn, "%d What is this, SMTP?\r\n", 203 ); return( 0 ); } int f_noauth( SNET *sn, int ac, char **av ) { snet_writef( sn, "%d No access for %s\r\n", 500, remote_host ); exit( 1 ); } int f_notls( SNET *sn, int ac, char **av ) { snet_writef( sn, "%d Must issue a STARTTLS command first\r\n", 530 ); exit( 1 ); } int keyword( int ac, char *av[] ) { /* * For now let's always check the biggest max, * and later we can worry about command specific * checks or no. remote_host is global. +5 is for * "/tmp\0" */ int rc; if ( ac < 2 ) { return( -1 ); } if ( strcasecmp( av[ 1 ], "COMMAND" ) == 0 ) { if ( ac > 3 ) { return( -1 ); } if ( ac == 2 ) { if ( strlen( command_file + 5 ) > MAXPATHLEN ) { syslog( LOG_WARNING, "[tmp]/%s longer than MAXPATHLEN", command_file ); return( -1 ); } } return( K_COMMAND ); } if ( strcasecmp( av[ 1 ], "SPECIAL" ) == 0 ) { if ( ac != 3 ) { return( -1 ); } if ( strlen( av[ 1 ] ) + strlen( av[ 2 ] ) + strlen( remote_host ) + 5 > MAXPATHLEN ) { syslog( LOG_WARNING, "Overflow attempt: %s/%s-%s longer than MAXPATHLEN", av[ 1 ], av[ 2 ], remote_host ); return( -1 ); } rc = K_SPECIAL; } else if ( strcasecmp( av[ 1 ], "TRANSCRIPT" ) == 0 ) { if ( ac != 3 ) { return( -1 ); } if ( strlen( av[ 1 ] ) + strlen( av[ 2 ] ) + 5 > MAXPATHLEN ) { syslog( LOG_WARNING, "[tmp]/%s/%s longer than MAXPATHLEN", av[ 1 ], av [ 2 ] ); return( -1 ); } rc = K_TRANSCRIPT; } else if ( strcasecmp( av[ 1 ], "FILE" ) == 0 ) { if ( ac != 4 ) { return( -1 ); } /* Check for leading ../ */ if ( strncmp( av[ 3 ], "../", strlen( "../" )) == 0 ) { syslog( LOG_WARNING | LOG_AUTH, "attempt to access: %s", av[ 3 ] ); return( -1 ); } /* Check for internal /../ */ if ( strstr( av[ 3 ], "/../" ) != NULL ) { syslog( LOG_WARNING | LOG_AUTH, "attempt to access: %s", av[ 3 ] ); return( -1 ); } if ( strlen( av[ 1 ] ) + strlen( av[ 2 ] ) + strlen( av[ 3 ] ) + 5 > MAXPATHLEN ) { syslog( LOG_WARNING, "Overflow attempt: %s/%s/%s longer than MAXPATHLEN", av[ 1 ], av[ 2 ], av[ 3 ] ); return( -1 ); } rc = K_FILE; } else { return( -1 ); } if ( strstr( av[ 2 ], "../" ) != NULL ) { syslog( LOG_WARNING | LOG_AUTH, "attempt to access: %s", av[ 2 ] ); return( -1 ); } return( rc ); } int f_retr( SNET *sn, int ac, char **av ) { ssize_t readlen; struct stat st; struct timeval tv; char buf[8192]; char path[ MAXPATHLEN ]; char *d_path, *d_tran; int fd; switch ( keyword( ac, av )) { case K_COMMAND: if ( ac == 2 ) { if ( snprintf( path, MAXPATHLEN, "command/%s", command_file ) >= MAXPATHLEN ) { syslog( LOG_ERR, "f_retr: command/%s: path too long", command_file ); snet_writef( sn, "%d Path too long\r\n", 540 ); return( 1 ); } } else { if (( d_path = decode( av[ 2 ] )) == NULL ) { syslog( LOG_ERR, "f_retr: decode: buffer too small" ); snet_writef( sn, "%d Line too long\r\n", 540 ); return( 1 ); } /* Check for access */ if ( !list_check( access_list, d_path )) { syslog( LOG_WARNING | LOG_AUTH, "attempt to access: %s", d_path ); snet_writef( sn, "%d No access for %s\r\n", 540, d_path ); return( 1 ); } if ( snprintf( path, MAXPATHLEN, "command/%s", d_path ) >= MAXPATHLEN ) { syslog( LOG_ERR, "f_retr: command path too long" ); snet_writef( sn, "%d Path too long\r\n", 540 ); return( 1 ); } } break; case K_TRANSCRIPT: if (( d_tran = decode( av[ 2 ] )) == NULL ) { syslog( LOG_ERR, "f_retr: decode: buffer too small" ); snet_writef( sn, "%d Line too long\r\n", 540 ); return( 1 ); } /* Check for access */ if ( !list_check( access_list, d_tran )) { syslog( LOG_WARNING | LOG_AUTH, "attempt to access: %s", d_tran ); snet_writef( sn, "%d No access for %s\r\n", 540, d_tran ); return( 1 ); } if ( snprintf( path, MAXPATHLEN, "transcript/%s", d_tran ) >= MAXPATHLEN ) { syslog( LOG_ERR, "f_retr: transcript path too long" ); snet_writef( sn, "%d Path too long\r\n", 540 ); return( 1 ); } break; case K_SPECIAL: if (( d_path = decode( av[ 2 ] )) == NULL ) { syslog( LOG_ERR, "f_retr: decode: buffer too small" ); snet_writef( sn, "%d Line too long\r\n", 540 ); return( 1 ); } if ( snprintf( path, MAXPATHLEN, "%s/%s", special_dir, d_path ) >= MAXPATHLEN ) { syslog( LOG_ERR, "f_retr: special path too long" ); snet_writef( sn, "%d Path too long\r\n", 540 ); return( 1 ); } break; case K_FILE: if (( d_path = decode( av[ 3 ] )) == NULL ) { syslog( LOG_ERR, "f_retr: decode: buffer too small" ); snet_writef( sn, "%d Line too long\r\n", 540 ); return( 1 ); } if (( d_path = strdup( d_path )) == NULL ) { syslog( LOG_ERR, "f_retr: strdup: %s: %m", d_path ); return( -1 ); } if (( d_tran = decode( av[ 2 ] )) == NULL ) { syslog( LOG_ERR, "f_retr: decode: buffer too small" ); snet_writef( sn, "%d Line too long\r\n", 540 ); return( 1 ); } /* Check for access */ if ( !list_check( access_list, d_tran )) { syslog( LOG_WARNING | LOG_AUTH, "attempt to access: %s", d_tran ); snet_writef( sn, "%d No access for %s:%s\r\n", 540, d_tran, d_path ); return( 1 ); } if ( snprintf( path, MAXPATHLEN, "file/%s/%s", d_tran, d_path ) >= MAXPATHLEN ) { syslog( LOG_ERR, "f_retr: file path too long" ); snet_writef( sn, "%d Path too long\r\n", 540 ); return( 1 ); } free( d_path ); break; default: snet_writef( sn, "%d RETR Syntax error\r\n", 540 ); return( 1 ); } if (( fd = open( path, O_RDONLY, 0 )) < 0 ) { syslog( LOG_ERR, "open: %s: %m", path ); snet_writef( sn, "%d Unable to access %s.\r\n", 543, path ); return( 1 ); } /* dump file info */ if ( fstat( fd, &st ) < 0 ) { syslog( LOG_ERR, "f_retr: fstat: %m" ); snet_writef( sn, "%d Access Error: %s\r\n", 543, path ); if ( close( fd ) < 0 ) { syslog( LOG_ERR, "close: %m" ); return( -1 ); } return( 1 ); } /* * Here's a problem. Do we need to add long long support to * snet_writef? */ snet_writef( sn, "240 Retrieving file\r\n%" PRIofft "d\r\n", st.st_size ); /* dump file */ while (( readlen = read( fd, buf, sizeof( buf ))) > 0 ) { tv.tv_sec = 60 ; tv.tv_usec = 0; if ( snet_write( sn, buf, readlen, &tv ) != readlen ) { syslog( LOG_ERR, "snet_write: %m" ); return( -1 ); } } if ( readlen < 0 ) { syslog( LOG_ERR, "read: %m" ); return( -1 ); } snet_writef( sn, ".\r\n" ); if ( close( fd ) < 0 ) { syslog( LOG_ERR, "close: %m" ); return( -1 ); } syslog( LOG_DEBUG, "f_retr: 'file' %s retrieved", path ); return( 0 ); } /* looks for special file info in transcripts */ char ** special_t( char *transcript, char *epath ) { FILE *fs; int ac, len; char **av; static char line[ MAXPATHLEN ]; if (( fs = fopen( transcript, "r" )) == NULL ) { return( NULL ); } while ( fgets( line, MAXPATHLEN, fs ) != NULL ) { len = strlen( line ); if (( line[ len - 1 ] ) != '\n' ) { syslog( LOG_ERR, "special_t: %s: line too long", transcript ); break; } if (( ac = argcargv( line, &av )) != 8 ) { continue; } if (( *av[ 0 ] != 'f' ) && ( *av[ 0 ] != 'a' )) { continue; } if ( strcmp( av[ 1 ], epath ) == 0 ) { (void)fclose( fs ); return( av ); } } (void)fclose( fs ); return( NULL ); } int f_stat( SNET *sn, int ac, char *av[] ) { char path[ MAXPATHLEN ]; char cksum_b64[ SZ_BASE64_E( EVP_MAX_MD_SIZE ) ]; struct stat st; int key; char *enc_file, *d_tran, *d_path; switch ( key = keyword( ac, av )) { case K_COMMAND: if ( ac == 2 ) { if ( snprintf( path, MAXPATHLEN, "command/%s", command_file ) >= MAXPATHLEN ) { syslog( LOG_ERR, "f_stat: command/%s: path too long", command_file ); snet_writef( sn, "%d Path too long\r\n", 540 ); return( 1 ); } } else { if (( d_path = decode( av[ 2 ] )) == NULL ) { syslog( LOG_ERR, "f_stat: decode: buffer too small" ); snet_writef( sn, "%d Line too long\r\n", 540 ); return( 1 ); } /* Check for access */ if ( !list_check( access_list, d_path )) { syslog( LOG_WARNING | LOG_AUTH, "attempt to access: %s", d_path ); snet_writef( sn, "%d No access for %s\r\n", 540, d_path ); return( 1 ); } if ( snprintf( path, MAXPATHLEN, "command/%s", d_path ) >= MAXPATHLEN ) { syslog( LOG_ERR, "f_stat: command path too long" ); snet_writef( sn, "%d Path too long\r\n", 540 ); return( 1 ); } } break; case K_TRANSCRIPT: if (( d_tran = decode( av[ 2 ] )) == NULL ) { syslog( LOG_ERR, "f_stat: decode: buffer too small" ); snet_writef( sn, "%d Line too long\r\n", 540 ); return( 1 ); } /* Check for access */ if ( !list_check( access_list, d_tran )) { syslog( LOG_WARNING | LOG_AUTH, "attempt to access: %s", d_tran ); snet_writef( sn, "%d No access for %s\r\n", 540, d_tran ); return( 1 ); } if ( snprintf( path, MAXPATHLEN, "transcript/%s", d_tran ) >= MAXPATHLEN ) { syslog( LOG_ERR, "f_stat: transcript path too long" ); snet_writef( sn, "%d Path too long\r\n", 540 ); return( 1 ); } break; case K_SPECIAL: if (( d_path = decode( av[ 2 ] )) == NULL ) { syslog( LOG_ERR, "f_stat: decode: buffer too small" ); snet_writef( sn, "%d Line too long\r\n", 540 ); return( 1 ); } if ( snprintf( path, MAXPATHLEN, "%s/%s", special_dir, d_path) >= MAXPATHLEN ) { syslog( LOG_ERR, "f_stat: special path too long" ); snet_writef( sn, "%d Path too long\r\n", 540 ); return( 1 ); } break; default: snet_writef( sn, "%d STAT Syntax error\r\n", 530 ); return( 1 ); } syslog( LOG_DEBUG, "f_stat: returning infomation for %s", path ); if ( stat( path, &st ) < 0 ) { syslog( LOG_ERR, "f_stat: stat: %m" ); snet_writef( sn, "%d Access Error: %s\r\n", 531, path ); return( 1 ); } /* XXX cksums here, totally the wrong place to do this! */ OpenSSL_add_all_digests(); md = EVP_get_digestbyname( "sha1" ); if ( !md ) { /* XXX */ fprintf( stderr, "%s: unsupported checksum\n", "sha1" ); exit( 1 ); } if ( do_cksum( path, cksum_b64 ) < 0 ) { syslog( LOG_ERR, "do_cksum: %s: %m", path ); snet_writef( sn, "%d Checksum Error: %s: %m\r\n", 500, path ); return( 1 ); } snet_writef( sn, "%d Returning STAT information\r\n", 230 ); switch ( key ) { case K_COMMAND: if ( ac == 2 ) { snet_writef( sn, "%s %s %o %d %d %d %" PRIofft "d %s\r\n", "f", "command", DEFAULT_MODE, DEFAULT_UID, DEFAULT_GID, st.st_mtime, st.st_size, cksum_b64 ); } else { snet_writef( sn, "%s %s %o %d %d %d %" PRIofft "d %s\r\n", "f", av[ 2 ], DEFAULT_MODE, DEFAULT_UID, DEFAULT_GID, st.st_mtime, st.st_size, cksum_b64 ); } return( 0 ); case K_TRANSCRIPT: snet_writef( sn, "%s %s %o %d %d %d %" PRIofft "d %s\r\n", "f", av[ 2 ], DEFAULT_MODE, DEFAULT_UID, DEFAULT_GID, st.st_mtime, st.st_size, cksum_b64 ); return( 0 ); case K_SPECIAL: /* status on a special file comes from 1 of three cases: * 1. A transcript in the special file directory * 2. A transcript in the Transcript dir with .T appended * 3. No transcript is found, and constants are returned */ /* look for transcript containing the information */ if ( ( strlen( path ) + 2 ) > MAXPATHLEN ) { syslog( LOG_WARNING, "f_stat: transcript path longer than MAXPATHLEN" ); /* return constants */ snet_writef( sn, "%s %s %o %d %d %d %" PRIofft "d %s\r\n", "f", av[ 2 ], DEFAULT_MODE, DEFAULT_UID, DEFAULT_GID, st.st_mtime, st.st_size, cksum_b64 ); return( 0 ); } /* if allowable, check for transcript in the special file directory */ strcat( path, ".T" ); /* store value of av[ 2 ], because argcargv will be called * from special_t(), and that will blow away the current values * for av[ 2 ] * * Could just use new argvargc API... XXX Notice how we never free * env_file... */ if (( enc_file = strdup( av[ 2 ] )) == NULL ) { syslog( LOG_ERR, "f_stat: strdup: %s %m", av[ 2 ] ); return( -1 ); } if (( av = special_t( path, enc_file )) == NULL ) { if (( av = special_t( "transcript/special.T", enc_file )) == NULL ) { snet_writef( sn, "%s %s %o %d %d %d %" PRIofft "d %s\r\n", "f", enc_file, DEFAULT_MODE, DEFAULT_UID, DEFAULT_GID, st.st_mtime, st.st_size, cksum_b64 ); free( enc_file ); return( 0 ); } } snet_writef( sn, "%s %s %s %s %s %d %" PRIofft "d %s\r\n", av[ 0 ], enc_file, av[ 2 ], av[ 3 ], av[ 4 ], st.st_mtime, st.st_size, cksum_b64 ); free( enc_file ); return( 0 ); default: return( 1 ); } } int f_stor( SNET *sn, int ac, char *av[] ) { char *sizebuf; char xscriptdir[ MAXPATHLEN ]; char upload[ MAXPATHLEN ]; char buf[ 8192 ]; char *line; char *d_tran, *d_path; int fd; int zero = 0; off_t len; ssize_t rc; struct timeval tv; struct protoent *proto; if ( !prevstor ) { /* Turn off TCP_NODELAY for stores */ if (( proto = getprotobyname( "tcp" )) == NULL ) { syslog( LOG_ERR, "f_stor: getprotobyname: %m" ); return( -1 ); } if ( setsockopt( snet_fd( sn ), proto->p_proto, TCP_NODELAY, &zero, sizeof( zero )) != 0 ) { syslog( LOG_ERR, "f_stor: snet_setopt: %m" ); return( -1 ); } prevstor = 1; } if ( checkuser && ( !authorized )) { snet_writef( sn, "%d Not logged in\r\n", 551 ); exit( 1 ); } /* decode() uses static mem, so strdup() */ if (( d_tran = decode( av[ 2 ] )) == NULL ) { syslog( LOG_ERR, "f_stor: decode: buffer too small" ); snet_writef( sn, "%d Line too long\r\n", 540 ); return( 1 ); } if (( d_tran = strdup( d_tran )) == NULL ) { syslog( LOG_ERR, "f_stor: strdup: %s: %m", d_tran ); return( -1 ); } switch ( keyword( ac, av )) { case K_TRANSCRIPT: if ( snprintf( xscriptdir, MAXPATHLEN, "tmp/file/%s", d_tran ) >= MAXPATHLEN ) { syslog( LOG_ERR, "f_stor: xscriptdir path too long" ); snet_writef( sn, "%d Path too long\r\n", 540 ); return( 1 ); } if ( snprintf( upload, MAXPATHLEN, "tmp/transcript/%s", d_tran ) >= MAXPATHLEN ) { syslog( LOG_ERR, "f_stor: upload path too long" ); snet_writef( sn, "%d Path too long\r\n", 540 ); return( 1 ); } /* keep encoded transcript name, since it will just be * used later to compare in a stor file. */ if ( strlen( av[ 2 ] ) >= MAXPATHLEN ) { syslog( LOG_ERR, "f_stor: upload_xscript path too long" ); snet_writef( sn, "%d Path too long\r\n", 540 ); return( 1 ); } strcpy( upload_xscript, av[ 2 ] ); /* make the directory for the files of this xscript to live in. */ if ( mkdir( xscriptdir, 0777 ) < 0 ) { if ( errno == EEXIST ) { snet_writef( sn, "%d Transcript exists\r\n", 551 ); exit( 1 ); } snet_writef( sn, "%d %s: %s\r\n", 551, xscriptdir, strerror( errno )); exit( 1 ); } break; case K_FILE: /* client must have provided a transcript name before giving * files in that transcript */ if (( strcmp( upload_xscript, av[ 2 ] ) != 0 )) { snet_writef( sn, "%d Incorrect Transcript %s\r\n", 552, av[ 2 ] ); exit( 1 ); } /* decode() uses static mem, so strdup() */ if (( d_path = decode( av[ 3 ] )) == NULL ) { syslog( LOG_ERR, "f_stor: decode: buffer too small" ); snet_writef( sn, "%d Line too long\r\n", 540 ); return( 1 ); } if (( d_path = strdup( d_path )) == NULL ) { syslog( LOG_ERR, "f_stor: strdup: %s: %m", d_path ); return( -1 ); } if ( d_path[ 0 ] == '/' ) { if ( snprintf( upload, MAXPATHLEN, "tmp/file/%s%s", d_tran, d_path ) >= MAXPATHLEN ) { syslog( LOG_ERR, "f_stor: upload path too long" ); snet_writef( sn, "%d Path too long\r\n", 540 ); return( 1 ); } } else { if ( snprintf( upload, MAXPATHLEN, "tmp/file/%s/%s", d_tran, d_path ) >= MAXPATHLEN ) { syslog( LOG_ERR, "f_stor: upload path too long" ); snet_writef( sn, "%d Path too long\r\n", 540 ); return( 1 ); } } free( d_path ); free( d_tran ); break; default: snet_writef( sn, "%d STOR Syntax error\r\n", 550 ); exit( 1 ); } if (( fd = open( upload, O_CREAT|O_EXCL|O_WRONLY, 0666 )) < 0 ) { if ( mkdirs( upload ) < 0 ) { syslog( LOG_ERR, "f_stor: mkdir: %s: %m", upload ); snet_writef( sn, "%d %s: %s\r\n", 555, upload, strerror( errno )); exit( 1 ); } if (( fd = open( upload, O_CREAT|O_EXCL|O_WRONLY, 0666 )) < 0 ) { syslog( LOG_ERR, "f_stor: open: %s: %m", upload ); snet_writef( sn, "%d %s: %s\r\n", 555, upload, strerror( errno )); exit( 1 ); } } snet_writef( sn, "%d Storing file\r\n", 350 ); tv.tv_sec = 60; tv.tv_usec = 0; if ( ( sizebuf = snet_getline( sn, &tv ) ) == NULL ) { syslog( LOG_ERR, "f_stor: snet_getline: %m" ); return( -1 ); } /* Will there be a limit? */ len = strtoofft( sizebuf, NULL, 10 ); for ( ; len > 0; len -= rc ) { tv.tv_sec = 60; tv.tv_usec = 0; if (( rc = snet_read( sn, buf, MIN( len, sizeof( buf )), &tv )) <= 0 ) { if ( snet_eof( sn )) { syslog( LOG_ERR, "f_stor: snet_read: eof" ); } else { syslog( LOG_ERR, "f_stor: snet_read: %m" ); } return( -1 ); } if ( write( fd, buf, rc ) != rc ) { snet_writef( sn, "%d %s: %s\r\n", 555, upload, strerror( errno )); exit( 1 ); } } if ( len != 0 ) { syslog( LOG_ERR, "f_stor: len is %" PRIofft "d", len ); snet_writef( sn, "%d %s: internal error!\r\n", 555, upload ); exit( 1 ); } if ( close( fd ) < 0 ) { snet_writef( sn, "%d %s: %s\r\n", 555, upload, strerror( errno )); exit( 1 ); } syslog( LOG_DEBUG, "f_stor: file %s stored", upload ); tv.tv_sec = 60; tv.tv_usec = 0; if (( line = snet_getline( sn, &tv )) == NULL ) { syslog( LOG_ERR, "f_stor: snet_getline: %m" ); return( -1 ); } /* make sure client agrees we're at the end */ if ( strcmp( line, "." ) != 0 ) { syslog( LOG_ERR, "f_stor: line is: %s", line ); snet_writef( sn, "%d Length doesn't match sent data %s\r\n", 555, upload ); (void)unlink( upload ); exit( 1 ); } snet_writef( sn, "%d File stored\r\n", 250 ); return( 0 ); } int f_repo( SNET *sn, int ac, char **av ) { char *cn = "-"; char *d_msg; if ( ac != 3 ) { snet_writef( sn, "%d Syntax error (invalid parameters)\r\n", 501 ); return( 1 ); } if (( d_msg = decode( av[ 2 ] )) == NULL ) { syslog( LOG_ERR, "f_repo: decode: buffer too small" ); snet_writef( sn, "%d Syntax error (invalid parameter)\r\n", 501 ); return( 1 ); } if ( remote_cn != NULL ) { cn = remote_cn; } syslog( LOG_NOTICE, "report %s %s %s %s %s %s", remote_host, remote_addr, cn, "-", /* reserve for user specified ID, e.g. sasl */ av[ 1 ], d_msg ); snet_writef( sn, "%d Report successful\r\n", 215 ); return( 0 ); } int f_starttls( SNET *sn, int ac, char **av ) { int rc; X509 *peer; char buf[ 1024 ]; if ( ac != 1 ) { snet_writef( sn, "%d Syntax error (no parameters allowed)\r\n", 501 ); return( 1 ); } else { snet_writef( sn, "%d Ready to start TLS\r\n", 220 ); } /* We get here when the client asks for TLS with the STARTTLS verb */ /* * Client MUST NOT attempt to start a TLS session if a TLS * session is already active. No mention of what to do if it does... * * Once STARTTLS has succeeded, the STARTTLS verb is no longer valid */ /* * Begin TLS */ /* This is where the TLS start */ /* At this point the client is also starting TLS */ /* 1 is for server, 0 is client */ if (( rc = snet_starttls( sn, ctx, 1 )) != 1 ) { syslog( LOG_ERR, "f_starttls: snet_starttls: %s", ERR_error_string( ERR_get_error(), NULL ) ); snet_writef( sn, "%d SSL didn't work error! XXX\r\n", 501 ); return( 1 ); } if ( authlevel == 2 ) { if (( peer = SSL_get_peer_certificate( sn->sn_ssl )) == NULL ) { syslog( LOG_ERR, "no peer certificate" ); return( -1 ); } syslog( LOG_INFO, "CERT Subject: %s\n", X509_NAME_oneline( X509_get_subject_name( peer ), buf, sizeof( buf ))); X509_NAME_get_text_by_NID( X509_get_subject_name( peer ), NID_commonName, buf, sizeof( buf )); if (( remote_cn = strdup( buf )) == NULL ) { syslog( LOG_ERR, "strdup: %m" ); X509_free( peer ); return( -1 ); } X509_free( peer ); } /* get command file */ if ( command_k( "config" ) < 0 ) { /* Client not in config */ commands = noauth; ncommands = sizeof( noauth ) / sizeof( noauth[ 0 ] ); } else { /* Client in config */ commands = auth; ncommands = sizeof( auth ) / sizeof( auth[ 0 ] ); if ( read_kfile( sn, command_file ) != 0 ) { /* error message given in list_transcripts */ exit( 1 ); } } return( 0 ); } #ifdef HAVE_LIBPAM int exchange( int num_msg, struct pam_message **msg, struct pam_response **resp, void *appdata_ptr) { int count = 0; struct pam_response *reply= NULL; if ( num_msg <= 0 ) { return( PAM_CONV_ERR ); } /* * From pam_start man page: * * "The storage used by pam_response has to be allocated by the * application and freed by the PAM modules." */ if (( reply = malloc( num_msg * sizeof( struct pam_response ))) == NULL ) { return( PAM_CONV_ERR ); } for ( count = 0; count < num_msg; count++ ) { char *string = NULL; switch( msg[ count ]->msg_style ) { case PAM_PROMPT_ECHO_OFF: case PAM_PROMPT_ECHO_ON: string = strdup( password ); if ( string == NULL ) { goto exchange_failed; } break; case PAM_TEXT_INFO: case PAM_ERROR_MSG: string = NULL; break; default: goto exchange_failed; } reply[ count ].resp = string; reply[ count ].resp_retcode = 0; string = NULL; } *resp = reply; reply = NULL; return( PAM_SUCCESS ); exchange_failed: if ( reply ) { for ( count = 0; count < num_msg; count++ ) { if ( reply[ count ].resp == NULL ) { continue; } free( reply[ count ].resp ); reply[ count ].resp = NULL; } free( reply ); reply = NULL; } return( PAM_CONV_ERR ); } int f_login( SNET *sn, int ac, char **av ) { int retval; pam_handle_t *pamh; struct pam_conv pam_conv = { exchange, NULL }; if ( !checkuser ) { snet_writef( sn, "%d login not enabled\r\n", 502 ); return( 1 ); } /* if ( authlevel < 1 ) { snet_writef( sn, "%d login requires TLS\r\n", 503 ); return( 1 ); } */ if ( ac != 3 ) { snet_writef( sn, "%d Syntax error\r\n", 501 ); return( 1 ); } if ( user != NULL ) { free( user ); user = NULL; } if ( password != NULL ) { free( password ); password = NULL; } if (( user = strdup( av[ 1 ] )) == NULL ) { syslog( LOG_ERR, "f_login: strdup: %m" ); return( -1 ); } if (( password = strdup( av[ 2 ] )) == NULL ) { syslog( LOG_ERR, "f_login: strdup: %m" ); return( -1 ); } if (( retval = pam_start( "radmind", user, &pam_conv, &pamh )) != PAM_SUCCESS ) { syslog( LOG_ERR, "f_login: pam_start: %s\n", pam_strerror( pamh, retval )); snet_writef( sn, "%d Authentication Failed\r\n", 535 ); return( 1 ); } /* is user really user? */ if (( retval = pam_authenticate( pamh, PAM_SILENT )) != PAM_SUCCESS ) { syslog( LOG_ERR, "f_login: pam_authenticate: %s\n", pam_strerror( pamh, retval )); snet_writef( sn, "%d Authentication Failed\r\n", 535 ); return( 1 ); } free( password ); /* permitted access? */ if (( retval = pam_acct_mgmt( pamh, 0 )) != PAM_SUCCESS ) { syslog( LOG_ERR, "f_login: pam_acct_mgmt: %s\n", pam_strerror( pamh, retval )); snet_writef( sn, "%d Authentication Failed\r\n", 535 ); return( 1 ); } if (( retval = pam_end( pamh, retval )) != PAM_SUCCESS ) { syslog( LOG_ERR, "f_login: pam_end: %s\n", pam_strerror( pamh, retval )); snet_writef( sn, "%d Authentication Failed\r\n", 535 ); return( 1 ); } syslog( LOG_INFO, "%s: successfully logged in\n", user ); snet_writef( sn, "%d %s successfully logged in\r\n", 205, user ); authorized = 1; return( 0 ); } #endif /* HAVE_LIBPAM */ #ifdef HAVE_ZLIB int f_compress( SNET *sn, int ac, char **av ) { int level; if ( max_zlib_level <= 0 ) { syslog( LOG_WARNING, "f_compress: compression not enabled" ); snet_writef( sn, "501 Compression not enabled\r\n" ); return( 1 ); } if ( ac != 2 && ac != 3 ) { syslog( LOG_WARNING, "f_compress: syntax error" ); snet_writef( sn, "%d Syntax error\r\n", 501 ); return( 1 ); } if ( snet_flags( sn ) & SNET_ZLIB ) { syslog( LOG_WARNING, "f_compress: compression already enabled" ); snet_writef( sn, "%d Compression already enabled\r\n", 501 ); return( 1 ); } if ( strcasecmp( av[ 1 ], "ZLIB" ) == 0 ) { if( ac == 3 ) { level = atoi( av[2] ); level = MAX( level, 1 ); level = MIN( level, max_zlib_level ); } else { /* If no level given, use max compression */ level = max_zlib_level; } snet_writef( sn, "320 Ready to start ZLIB compression level %d\r\n", level ); if ( snet_setcompression( sn, SNET_ZLIB, level ) != 0 ) { syslog( LOG_ERR, "f_compress: snet_setcompression failed" ); return( -1 ); } snet_writef( sn, "220 ZLIB compression level %d enabled\r\n", level ); } else { syslog( LOG_WARNING, "%s: Unknown compression requested", av[ 1 ] ); snet_writef( sn, "525 %s: unknown compression type\r\n", av[ 1 ] ); } return( 0 ); } #endif /* HAVE_ZLIB */ /* sets command file for connected host */ int command_k( char *path_config ) { SNET *sn; char **av, *line, *p; char temp[ MAXPATHLEN ]; int ac; int linenum = 0; if (( sn = snet_open( path_config, O_RDONLY, 0, 0 )) == NULL ) { syslog( LOG_ERR, "command_k: snet_open: %s: %m", path_config ); return( -1 ); } while (( line = snet_getline( sn, NULL )) != NULL ) { linenum++; if (( ac = argcargv( line, &av )) < 0 ) { syslog( LOG_ERR, "argvargc: %m" ); return( -1 ); } if ( ( ac == 0 ) || ( *av[ 0 ] == '#' ) ) { continue; } if (( ac < 2 ) || (( ac > 2 ) && ( *av[ 2 ] != '#' ))) { syslog( LOG_ERR, "config file: line %d: invalid number of " "arguments", linenum ); continue; } if (( p = strrchr( av[ 1 ], '/' )) == NULL ) { sprintf( special_dir, "special" ); } else { *p = '\0'; if ( snprintf( special_dir, MAXPATHLEN, "special/%s", av[ 1 ] ) >= MAXPATHLEN ) { syslog( LOG_ERR, "config file: line %d: path too long\n", linenum ); continue; } *p = '/'; } if (( remote_cn != NULL ) && wildcard( av[ 0 ], remote_cn, 0 )) { if ( strlen( av[ 1 ] ) >= MAXPATHLEN ) { syslog( LOG_ERR, "config file: line %d: command file too long\n", linenum ); continue; } strcpy( command_file, av[ 1 ] ); if ( snprintf( temp, MAXPATHLEN, "%s/%s", special_dir, remote_cn ) >= MAXPATHLEN ) { syslog( LOG_ERR, "config file: line %d: special dir too long\n", linenum ); continue; } strcpy( special_dir, temp ); return( 0 ); } if ( wildcard( av[ 0 ], remote_host, 0 )) { if ( strlen( av[ 1 ] ) >= MAXPATHLEN ) { syslog( LOG_ERR, "config file: line %d: command file too long\n", linenum ); continue; } strcpy( command_file, av[ 1 ] ); if ( snprintf( temp, MAXPATHLEN, "%s/%s", special_dir, remote_host ) >= MAXPATHLEN ) { syslog( LOG_ERR, "config file: line %d: special dir too long\n", linenum ); continue; } strcpy( special_dir, temp ); return( 0 ); } if ( wildcard( av[ 0 ], remote_addr, 1 )) { if ( strlen( av[ 1 ] ) >= MAXPATHLEN ) { syslog( LOG_ERR, "config file: line %d: command file too long\n", linenum ); continue; } strcpy( command_file, av[ 1 ] ); if ( snprintf( temp, MAXPATHLEN, "%s/%s", special_dir, remote_addr ) >= MAXPATHLEN ) { syslog( LOG_ERR, "config file: line %d: special dir too long\n", linenum ); continue; } strcpy( special_dir, temp ); return( 0 ); } } /* If we get here, the host that connected is not in the config file. So screw him. */ syslog( LOG_ERR, "host not in config file: %s", remote_host ); return( -1 ); } int read_kfile( SNET *sn, char *kfile ) { int ac; int linenum = 0; char **av; char line[ MAXPATHLEN ]; char path[ MAXPATHLEN ]; ACAV *acav; FILE *f; if ( snprintf( path, MAXPATHLEN, "command/%s", kfile ) >= MAXPATHLEN ) { syslog( LOG_ERR, "read_kfile: command/%s: path too long", kfile ); snet_writef( sn, "%d Service not available, closing transmission channel\r\n", 421 ); return( -1 ); } if (( acav = acav_alloc( )) == NULL ) { syslog( LOG_ERR, "acav_alloc: %m" ); snet_writef( sn, "%d Service not available, closing transmission channel\r\n", 421 ); return( -1 ); } if (( f = fopen( path, "r" )) == NULL ) { syslog( LOG_ERR, "fopen: %s: %m", path ); snet_writef( sn, "%d Service not available, closing transmission channel\r\n", 421 ); return( -1 ); } while ( fgets( line, MAXPATHLEN, f ) != NULL ) { linenum++; ac = acav_parse( acav, line, &av ); if (( ac == 0 ) || ( *av[ 0 ] == '#' )) { continue; } /* Skip minus lines in command files for now. Eventually, * the server should not give access to command files, special files * and transcripts that have been ultimately removed with a '-'. * This is difficult as ktcheck reads command files line by line * and will request info on a file that might be removed with a * later '-'. */ if ( *av[ 0 ] == '-' ) { continue; } if ( ac != 2 ) { syslog( LOG_ERR, "%s: line %d: invalid number of arguments", kfile, linenum ); snet_writef( sn, "%d Service not available, closing transmission channel\r\n", 421 ); goto error; } switch( *av[ 0 ] ) { case 'k': if ( !list_check( access_list, av[ 1 ] )) { if ( list_insert( access_list, av[ 1 ] ) != 0 ) { syslog( LOG_ERR, "list_insert: %m" ); snet_writef( sn, "%d Service not available, closing transmission channel\r\n", 421 ); goto error; } if ( read_kfile( sn, av[ 1 ] ) != 0 ) { goto error; } } break; case 'p': case 'n': if ( !list_check( access_list, av[ 1 ] )) { if ( list_insert( access_list, av[ 1 ] ) != 0 ) { syslog( LOG_ERR, "list_insert: %m" ); snet_writef( sn, "%d Service not available, closing transmission channel\r\n", 421 ); goto error; } } break; case 's': break; default: syslog( LOG_ERR, "%s: line %d: %c: unknown file type", kfile, linenum, *av[ 0 ] ); snet_writef( sn, "%d Service not available, closing transmission channel\r\n", 421 ); goto error; } if ( ferror( f )) { syslog( LOG_ERR, "fgets: %m" ); snet_writef( sn, "%d Service not available, closing transmission channel\r\n", 421 ); goto error; } } if ( fclose( f ) != 0 ) { syslog( LOG_ERR, "fclose: %m" ); snet_writef( sn, "%d Service not available, closing transmission channel\r\n", 421 ); goto error; } if ( acav_free( acav ) != 0 ) { syslog( LOG_ERR, "acav_free: %m" ); snet_writef( sn, "%d Service not available, closing transmission channel\r\n", 421 ); return( -1 ); } return( 0 ); error: fclose( f ); acav_free( acav ); return( -1 ); } int cmdloop( int fd, struct sockaddr_in *sin ) { SNET *sn; struct hostent *hp; char *p; int ac, i; int one = 1; unsigned int n; char **av, *line; struct timeval tv; extern char *version; extern int connections; extern int maxconnections; extern int rap_extensions; if ( authlevel == 0 ) { commands = noauth; ncommands = sizeof( noauth ) / sizeof( noauth[ 0 ] ); } else { commands = notls; ncommands = sizeof( notls ) / sizeof( notls[ 0 ] ); } if (( sn = snet_attach( fd, 1024 * 1024 )) == NULL ) { syslog( LOG_ERR, "snet_attach: %m" ); exit( 1 ); } remote_addr = strdup( inet_ntoa( sin->sin_addr )); if (( hp = gethostbyaddr( (char *)&sin->sin_addr, sizeof( struct in_addr ), AF_INET )) == NULL ) { remote_host = strdup( remote_addr ); } else { /* set global remote_host for retr command */ remote_host = strdup( hp->h_name ); for ( p = remote_host; *p != '\0'; p++ ) { *p = tolower( *p ); } } syslog( LOG_INFO, "child for [%s] %s", inet_ntoa( sin->sin_addr ), remote_host ); if ( setsockopt( fd, 6, TCP_NODELAY, &one, sizeof( one )) < 0 ) { syslog( LOG_ERR, "setsockopt: %m" ); } if ( maxconnections != 0 ) { if ( connections > maxconnections ) { syslog( LOG_INFO, "%s: connection refused: server busy\r\n", remote_host ); snet_writef( sn, "%d Server busy\r\n", 420 ); exit( 1 ); } } if (( access_list = list_new( )) == NULL ) { syslog( LOG_ERR, "new_list: %m" ); snet_writef( sn, "%d Service not available, closing transmission channel\r\n", 421 ); return( -1 ); } if ( authlevel == 0 ) { /* lookup proper command file based on the hostname, IP or CN */ if ( command_k( "config" ) < 0 ) { syslog( LOG_INFO, "%s: Access denied: Not in config file", remote_host ); snet_writef( sn, "%d No access for %s\r\n", 500, remote_host ); exit( 1 ); } else { if ( read_kfile( sn, command_file ) != 0 ) { /* error message given in read_kfile */ exit( 1 ); } commands = auth; ncommands = sizeof( auth ) / sizeof( auth[ 0 ] ); } } if ( gethostname( hostname, MAXHOSTNAMELEN ) < 0 ) { syslog( LOG_ERR, "gethostname: %m" ); exit( 1 ); } snet_writef( sn, "200%sRAP 1 %s %s radmind access protocol\r\n", rap_extensions ? "-" : " ", hostname, version ); if ( rap_extensions ) { snet_writef( sn, "200 CAPA" ); #ifdef HAVE_ZLIB if ( max_zlib_level > 0 ) { snet_writef( sn, " ZLIB" ); } #endif /* HAVE_ZLIB */ snet_writef( sn, " REPO" ); snet_writef( sn, "\r\n" ); } /* * 60 minutes * To make fsdiff | lapply work, when fsdiff will take a long time, * we allow the server to wait a long time. */ tv.tv_sec = 60 * 60; tv.tv_usec = 0 ; while (( line = snet_getline( sn, &tv )) != NULL ) { tv.tv_sec = 60 * 60; tv.tv_usec = 0; if ( debug ) { fprintf( stderr, "<<< %s\n", line ); } if (( ac = argcargv( line, &av )) < 0 ) { syslog( LOG_ERR, "argcargv: %m" ); return( 1 ); } if ( ac == 0 ) { snet_writef( sn, "%d Illegal null command\r\n", 501 ); continue; } for ( i = 0; i < ncommands; i++ ) { n = MAX( strlen( av[ 0 ] ), 4 ); if ( strncasecmp( av[ 0 ], commands[ i ].c_name, n ) == 0 ) { break; } } if ( i >= ncommands ) { snet_writef( sn, "%d Command %s unrecognized\r\n", 500, av[ 0 ] ); continue; } if ( (*(commands[ i ].c_func))( sn, ac, av ) < 0 ) { break; } } snet_writef( sn, "%d Server closing connection\r\n", 444 ); if ( line == NULL ) { syslog( LOG_ERR, "snet_getline: %m" ); } return( 0 ); }