/* * 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_ZLIB #include #endif /* HAVE_ZLIB */ #include #include "applefile.h" #include "base64.h" #include "cksum.h" #include "connect.h" #include "argcargv.h" #include "list.h" #include "llist.h" #include "pathcmp.h" #include "tls.h" #include "largefile.h" #include "mkdirs.h" #include "rmdirs.h" #include "report.h" #include "mkprefix.h" int cleandirs( char *path, struct llist *khead ); int clean_client_dir( void ); int check( SNET *sn, char *type, char *path); int createspecial( SNET *sn, struct list *special_list ); int getstat( SNET *sn, char *description, char *stats ); int read_kfile( char * ); SNET *sn; void (*logger)( char * ) = NULL; int linenum = 0; int cksum = 0; int verbose = 0; int dodots= 0; int quiet = 0; int update = 1; int change = 0; int case_sensitive = 1; int report = 1; int create_prefix = 0; char *base_kfile= _RADMIND_COMMANDFILE; char *radmind_path = _RADMIND_PATH; char *kdir= ""; const EVP_MD *md; SSL_CTX *ctx; struct list *special_list, *kfile_seen; extern struct timeval timeout; extern char *version, *checksumlist; extern char *caFile, *caDir, *cert, *privatekey; static void expand_kfile( struct llist **khead, char *kfile ) { struct llist *new; FILE *kf; char path[ MAXPATHLEN ]; char buf[ MAXPATHLEN ]; char **tav; int tac; size_t len; unsigned int line = 0; if (( kf = fopen( kfile, "r" )) == NULL ) { perror( kfile ); exit( 2 ); } while ( fgets( buf, MAXPATHLEN, kf ) != NULL ) { line++; len = strlen( buf ); if ( buf[ len - 1 ] != '\n' ) { fprintf( stderr, "%s line %d: line too long\n", base_kfile, line ); fclose( kf ); exit( 2 ); } /* skip comments, special and minus lines */ if ( *buf == '#' || *buf == 's' || *buf == '-' ) { continue; } /* skip blank lines */ if (( tac = argcargv( buf, &tav )) == 0 ) { continue; } if ( snprintf( path, MAXPATHLEN, "%s%s", kdir, tav[ 1 ] ) >= MAXPATHLEN ) { fprintf( stderr, "%s%s: path too long\n", kdir, tav[ 1 ] ); fclose( kf ); exit( 2 ); } new = ll_allocate( path ); ll_insert( khead, new ); } if ( fclose( kf ) != 0 ) { perror( "fclose" ); exit( 2 ); } } int cleandirs( char *path, struct llist *khead ) { DIR *d; struct dirent *de; struct llist *head = NULL, *kcur; struct llist *cur, *new; struct stat st; char fsitem[ MAXPATHLEN ]; int match = 0; if (( d = opendir( path )) == NULL ) { perror( path ); return( -1 ); } while (( de = readdir( d )) != NULL ) { /* skip dotfiles and the special transcript */ if ( de->d_name[ 0 ] == '.' || strcmp( de->d_name, "special.T" ) == 0 ) { continue; } if ( snprintf( fsitem, MAXPATHLEN, "%s/%s", path, de->d_name ) >= MAXPATHLEN ) { fprintf( stderr, "%s/%s: path too long\n", path, de->d_name ); return( -1 ); } /* * also skip the base command file. second case * handles "-K kfile.K", where kfile path is * same as "./kfile.K", but is passed as "kfile.K" */ if ( strcmp( fsitem, base_kfile ) == 0 || ( strncmp( kdir, path, strlen( path )) == 0 && strcmp( base_kfile, de->d_name )) == 0 ) { continue; } new = ll_allocate( fsitem ); ll_insert( &head, new ); } if ( closedir( d ) != 0 ) { perror( "closedir" ); return( -1 ); } for ( cur = head; cur != NULL; cur = cur->ll_next ) { if ( lstat( cur->ll_name, &st ) != 0 ) { perror( cur->ll_name ); return( -1 ); } for ( kcur = khead; kcur != NULL; kcur = kcur->ll_next ) { if ( strcmp( cur->ll_name, kcur->ll_name ) == 0 || ischild( kcur->ll_name, cur->ll_name )) { match = 1; break; } } if ( !match ) { if ( S_ISDIR( st.st_mode )) { rmdirs( cur->ll_name ); if ( verbose ) { printf( "unused directory %s deleted\n", cur->ll_name ); } } else { if ( unlink( cur->ll_name ) != 0 ) { perror( cur->ll_name ); return( -1 ); } if ( verbose ) { printf( "unused file %s deleted\n", cur->ll_name ); } } } else if ( S_ISDIR( st.st_mode )) { cleandirs( cur->ll_name, khead ); } match = 0; } ll_free( head ); return( 0 ); } int clean_client_dir( void ) { struct llist *khead = NULL; struct node *node; char dir[ MAXPATHLEN ]; char *p; expand_kfile( &khead, base_kfile ); while (( node = list_pop_head( kfile_seen )) != NULL ) { expand_kfile( &khead, node->n_path ); } /* * can't pass in kdir, since it has a trailing slash. * bounds checking done when creating kdir in main(). */ strcpy( dir, kdir ); if (( p = strrchr( dir, '/' )) != NULL ) { *p = '\0'; } cleandirs( dir, khead ); ll_free( khead ); return( 0 ); } int getstat( SNET *sn, char *description, char *stats ) { struct timeval tv; char *line; if( snet_writef( sn, "STAT %s\n", description ) < 0 ) { perror( "snet_writef" ); return( -1 ); } if ( verbose ) printf( ">>> STAT %s\n", description ); tv = timeout; if (( line = snet_getline_multi( sn, logger, &tv )) == NULL ) { perror( "snet_getline_multi" ); return( -1 ); } if ( *line != '2' ) { fprintf( stderr, "%s\n", line ); exit( 2 ); } tv = timeout; if (( line = snet_getline( sn, &tv )) == NULL ) { perror( "snet_getline 1" ); return( -1 ); } if ( strlen( line ) >= MAXPATHLEN ) { fprintf( stderr, "%s: line too long\n", line ); return( -1 ); } strcpy( stats, line ); if ( verbose ) printf( "<<< %s\n", stats ); return( 0 ); } int createspecial( SNET *sn, struct list *special_list ) { FILE *fs; struct node *node; char filedesc[ MAXPATHLEN * 2 ]; char path[ MAXPATHLEN ]; char stats[ MAXPATHLEN ]; /* Open file */ if ( snprintf( path, MAXPATHLEN, "%sspecial.T.%i", kdir, getpid()) >= MAXPATHLEN ) { fprintf( stderr, "path too long: %sspecial.T.%i\n", kdir, (int)getpid()); exit( 2 ); } if (( fs = fopen( path, "w" )) == NULL ) { perror( path ); return( 1 ); } for ( node = list_pop_head( special_list ); node != NULL; node = list_pop_head( special_list )) { if ( snprintf( filedesc, MAXPATHLEN * 2, "SPECIAL %s", node->n_path) >= ( MAXPATHLEN * 2 )) { fprintf( stderr, "SPECIAL %s: too long\n", node->n_path ); return( 1 ); } if ( getstat( sn, (char *)&filedesc, stats ) != 0 ) { return( 1 ); } if ( fputs( stats, fs) == EOF ) { fprintf( stderr, "fputs" ); return( 1 ); } if ( fputs( "\n", fs) == EOF ) { fprintf( stderr, "fputs" ); return( 1 ); } free( node ); } if ( fclose( fs ) != 0 ) { perror( path ); return( 1 ); } return( 0 ); } /* * return codes: * 0 okay * 1 update made * 2 system error */ int check( SNET *sn, char *type, char *file ) { int needupdate = 0; char **targv; char stats[ MAXPATHLEN ]; char pathdesc[ 2 * MAXPATHLEN ]; char tempfile[ 2 * MAXPATHLEN ]; char ccksum[ SZ_BASE64_E( EVP_MAX_MD_SIZE ) ]; char path[ MAXPATHLEN ]; char *p; int tac; struct stat st; struct utimbuf times; if ( file != NULL ) { if ( snprintf( pathdesc, MAXPATHLEN * 2, "%s %s", type, file ) >= ( MAXPATHLEN * 2 )) { fprintf( stderr, "%s %s: too long", type, file ); return( 2 ); } /* create full path */ if ( snprintf( path, MAXPATHLEN, "%s%s", kdir, file ) >= MAXPATHLEN ) { fprintf( stderr, "%s%s: path too long\n", kdir, file ); return( 2 ); } /* Check for transcript with directories */ for ( p = strchr( file, '/' ); p != NULL; p = strchr( p, '/' )) { *p = '\0'; /* Check to see if path exists as a directory */ if ( snprintf( tempfile, MAXPATHLEN, "%s%s", kdir, file ) >= MAXPATHLEN ) { fprintf( stderr, "%s%s: path too long\n", kdir, file ); return( 2 ); } if ( stat( tempfile, &st ) != 0 ) { if ( errno != ENOENT ) { perror( tempfile ); return( 2 ); } else { if ( mkdir( tempfile, 0777 ) != 0 ) { perror( tempfile ); return( 2 ); } } } else { /* Make sure it is a directory */ if ( !S_ISDIR( st.st_mode )) { if ( unlink( tempfile ) != 0 ) { perror( tempfile ); return( 2 ); } if ( mkdir( tempfile, 0777 )) { perror( tempfile ); return( 2 ); } } } *p++ = '/'; } if ( stat( path, &st ) != 0 ) { if ( errno != ENOENT ) { perror( path ); return( 2 ); } } else { if ( S_ISDIR( st.st_mode )) { if ( rmdirs( path ) != 0 ) { perror( path ); return( 2 ); } } } } else { if ( strlen( type ) >= ( MAXPATHLEN * 2 )) { fprintf( stderr, "%s: too long\n", type ); return( 2 ); } strcpy( pathdesc, type ); file = base_kfile; /* create full path */ if ( strlen( base_kfile ) >= MAXPATHLEN ) { fprintf( stderr, "%s: path too long\n", base_kfile ); return( 2 ); } strcpy( path, base_kfile ); } if ( getstat( sn, (char *)&pathdesc, stats ) != 0 ) { return( 2 ); } tac = acav_parse( NULL, stats, &targv ); if ( tac != 8 ) { perror( "Incorrect number of arguments\n" ); return( 2 ); } times.modtime = atoi( targv[ 5 ] ); times.actime = time( NULL ); if (( stat( path, &st )) != 0 ) { if ( errno != ENOENT ) { perror( path ); return( 2 ); } else { /* Local file is missing */ if ( update ) { if ( !quiet ) { printf( "%s:", path ); fflush( stdout ); } if ( retr( sn, pathdesc, path, (char *)&tempfile, 0666, strtoofft( targv[ 6 ], NULL, 10 ), targv[ 7 ] ) != 0 ) { return( 2 ); } if ( utime( tempfile, × ) != 0 ) { perror( path ); return( 1 ); } if ( rename( tempfile, path ) != 0 ) { perror( tempfile ); return( 2 ); } if ( !quiet ) printf( " updated\n" ); } else { if ( !quiet ) printf ( "%s: missing\n", path ); } return( 1 ); } } /* * With cksum we only use cksum and size. * Without cksum we only use mtime and size. */ if ( strtoofft( targv[ 6 ], NULL, 10 ) != st.st_size ) { needupdate = 1; } else { if ( cksum ) { if (( do_cksum( path, ccksum )) < 0 ) { perror( path ); return( 2 ); } if ( strcmp( targv[ 7 ], ccksum ) != 0 ) { needupdate = 1; } } else { if ( atoi( targv[ 5 ] ) != (int)st.st_mtime ) { needupdate = 1; } } } if ( needupdate ) { if ( update ) { if ( !quiet ) { printf( "%s:", path ); fflush( stdout ); } if ( unlink( path ) != 0 ) { perror( path ); return( 2 ); } if ( retr( sn, pathdesc, path, (char *)&tempfile, 0666, strtoofft( targv[ 6 ], NULL, 10 ), targv[ 7 ] ) != 0 ) { return( 2 ); } if ( utime( tempfile, × ) != 0 ) { perror( path ); return( 1 ); } if ( rename( tempfile, path ) != 0 ) { perror( path ); return( 2 ); } if ( !quiet ) printf( " updated\n" ); } else { if ( !quiet ) printf( "%s: out of date\n", path ); } return( 1 ); } else { return( 0 ); } } /* * exit codes: * 0 No changes found, everything okay * 1 Changes necessary / changes made * 2 System error */ int main( int argc, char **argv ) { int c, port = htons( 6662 ), err = 0; int authlevel = _RADMIND_AUTHLEVEL; int use_randfile = 0; int clean = 0; char lcksum[ SZ_BASE64_E( EVP_MAX_MD_SIZE ) ]; char tcksum[ SZ_BASE64_E( EVP_MAX_MD_SIZE ) ]; struct stat tst, lst; extern int optind; char *host = _RADMIND_HOST, *p; char path[ MAXPATHLEN ]; char tempfile[ MAXPATHLEN ]; struct servent *se; char **capa = NULL; /* capabilities */ while (( c = getopt( argc, argv, "Cc:D:h:iK:np:P:qrvVw:x:y:z:Z:" )) != EOF ) { switch( c ) { case 'C': /* clean up dir containing command.K */ clean = 1; break; case 'c': OpenSSL_add_all_digests(); md = EVP_get_digestbyname( optarg ); if ( !md ) { fprintf( stderr, "%s: unsupported checksum\n", optarg ); exit( 2 ); } cksum = 1; break; case 'D': radmind_path = optarg; break; case 'h': host = optarg; break; case 'i': setvbuf( stdout, ( char * )NULL, _IOLBF, 0 ); break; case 'K': base_kfile = optarg; break; case 'n': update = 0; break; case 'p': if (( port = htons ( atoi( optarg )) ) == 0 ) { if (( se = getservbyname( optarg, "tcp" )) == NULL ) { fprintf( stderr, "%s: service unknown\n", optarg ); exit( 2 ); } port = se->s_port; } break; case 'P' : /* ca dir */ caDir = optarg; break; case 'q': quiet = 1; break; case 'r': use_randfile = 1; break; case 'v': verbose = 1; logger = v_logger; if ( isatty( fileno( stdout ))) { dodots = 1; } break; case 'V': printf( "%s\n", version ); printf( "%s\n", checksumlist ); exit( 0 ); case 'w' : /* authlevel 0:none, 1:serv, 2:client & serv */ authlevel = atoi( optarg ); if (( authlevel < 0 ) || ( authlevel > 2 )) { fprintf( stderr, "%s: invalid authorization level\n", optarg ); exit( 2 ); } break; case 'x' : /* ca file */ caFile = optarg; break; case 'y' : /* cert file */ cert = optarg; break; case 'z' : /* private key */ privatekey = optarg; break; case 'Z': #ifdef HAVE_ZLIB zlib_level = atoi(optarg); if (( zlib_level < 0 ) || ( zlib_level > 9 )) { fprintf( stderr, "Invalid compression level\n" ); exit( 1 ); } break; #else /* HAVE_ZLIB */ fprintf( stderr, "Zlib not supported.\n" ); exit( 1 ); #endif /* HAVE_ZLIB */ default: err++; break; } } if ( verbose && quiet ) { err++; } if ( err || ( argc - optind != 0 )) { fprintf( stderr, "usage: %s ", argv[ 0 ] ); fprintf( stderr, "[ -CinrV ] [ -q | -v ] " ); fprintf( stderr, "[ -c checksum ] [ -D radmind_path ] " ); fprintf( stderr, "[ -K command file ] " ); fprintf( stderr, "[ -h host ] [ -p port ] [ -P ca-pem-directory ] " ); fprintf( stderr, "[ -w auth-level ] [ -x ca-pem-file ] " ); fprintf( stderr, "[ -y cert-pem-file] [ -z key-pem-file ] " ); fprintf( stderr, "[ -Z compression-level ]\n" ); exit( 2 ); } if (( special_list = list_new( )) == NULL ) { perror( "list_new" ); exit( 2 ); } if (( kfile_seen = list_new( )) == NULL ) { perror( "list_new" ); exit( 2 ); } if ( strlen( base_kfile ) >= MAXPATHLEN ) { fprintf( stderr, "%s: path too long\n", base_kfile ); exit( 2 ); } if (( kdir = strdup( base_kfile )) == NULL ) { perror( "strdup failed" ); exit( 2 ); } if (( p = strrchr( kdir, '/' )) == NULL ) { /* No '/' in kfile - use working directory */ kdir = "./"; } else { p++; *p = (char)'\0'; } strcpy( path, base_kfile ); if (( sn = connectsn( host, port )) == NULL ) { exit( 2 ); } if (( capa = get_capabilities( sn )) == NULL ) { exit( 2 ); } if ( authlevel != 0 ) { if ( tls_client_setup( use_randfile, authlevel, caFile, caDir, cert, privatekey ) != 0 ) { /* error message printed in tls_setup */ exit( 2 ); } if ( tls_client_start( sn, host, authlevel ) != 0 ) { /* error message printed in tls_cleint_starttls */ exit( 2 ); } } #ifdef HAVE_ZLIB /* Enable compression */ if ( zlib_level > 0 ) { if ( negotiate_compression( sn, capa ) != 0 ) { exit( 2 ); } } #endif /* HAVE_ZLIB */ /* Turn off reporting if server doesn't support it */ if ( check_capability( "REPO", capa ) == 0 ) { report = 0; } /* Check/get correct base command file */ switch( check( sn, "COMMAND", NULL )) { case 0: break; case 1: change++; if ( !update ) { goto done; } break; case 2: if ( report ) report_event( sn, "ktcheck", "Error" ); exit( 2 ); } if ( read_kfile( base_kfile ) != 0 ) { exit( 2 ); } /* Exit here if there's already been a change to avoid processing * the special transcript. */ if ( !update && change ) { exit( 1 ); } if ( special_list->l_count > 0 ) { if ( createspecial( sn, special_list ) != 0 ) { exit( 2 ); } if ( snprintf( path, MAXPATHLEN, "%sspecial.T", kdir ) >= MAXPATHLEN ) { fprintf( stderr, "path too long: %sspecial.T\n", kdir ); exit( 2 ); } if ( snprintf( tempfile, MAXPATHLEN, "%sspecial.T.%i", kdir, getpid()) >= MAXPATHLEN ) { fprintf( stderr, "path too long: %sspecial.T.%i\n", kdir, (int)getpid()); exit( 2 ); } /* get file sizes */ if ( stat( path, &lst ) != 0 ) { if ( errno == ENOENT ) { /* special.T did not exist */ if ( update ) { if ( rename( tempfile, path ) != 0 ) { fprintf( stderr, "rename failed: %s %s\n", tempfile, path ); exit( 2 ); } if ( !quiet ) printf( "%s: created\n", path ); change++; } else { /* special.T not updated */ if ( unlink( tempfile ) !=0 ) { perror( tempfile ); exit( 2 ); } } goto done; } perror( path ); exit( 2 ); } if ( stat( tempfile, &tst ) != 0 ) { perror( tempfile ); exit( 2 ); } /* get checksums */ if ( cksum ) { if ( do_cksum( path, lcksum ) < 0 ) { perror( path ); exit( 2 ); } if ( do_cksum( tempfile, tcksum ) < 0 ) { perror( tempfile ); exit( 2 ); } } /* * Without checksums we must assume that the special * transcript has changed since there is no way to * verify its contents */ /* Special exists */ if ( !cksum || (( tst.st_size != lst.st_size ) || ( strcmp( tcksum, lcksum) != 0 ))) { change++; if ( update ) { if ( rename( tempfile, path ) != 0 ) { fprintf( stderr, "rename failed: %s %s\n", tempfile, path ); exit( 2 ); } if ( !quiet ) printf( "%s: updated\n", path ); } else { if ( unlink( tempfile ) !=0 ) { perror( tempfile ); exit( 2 ); } } } else { /* special.T not updated */ if ( unlink( tempfile ) !=0 ) { perror( tempfile ); exit( 2 ); } } } done: #ifdef HAVE_ZLIB if ( verbose && zlib_level > 0 ) print_stats( sn ); #endif /* HAVE_ZLIB */ if ( clean && update ) { clean_client_dir(); } if ( change ) { if ( update ) { if ( report ) { if ( report_event( sn, "ktcheck", "Updates retrieved" ) != 0 ) { fprintf( stderr, "warning: could not report event\n" ); } } } else { if ( report ) { if ( report_event( sn, "ktcheck", "Updates available" ) != 0 ) { fprintf( stderr, "warning: could not report event\n" ); } } } } else { if ( report ) { if ( report_event( sn, "ktcheck", "No updates needed" ) != 0 ) { fprintf( stderr, "warning: could not report event\n" ); } } } if (( closesn( sn )) != 0 ) { fprintf( stderr, "cannot close sn\n" ); exit( 2 ); } if ( change ) { exit( 1 ); } if ( !quiet ) printf( "No updates needed\n" ); exit( 0 ); } int read_kfile( char * kfile ) { int ac, minus = 0; char **av; char line[ MAXPATHLEN ]; char path[ MAXPATHLEN ]; ACAV *acav; FILE *f; if (( acav = acav_alloc( )) == NULL ) { perror( "acav_alloc" ); return( -1 ); } if (( f = fopen( kfile, "r" )) == NULL ) { perror( kfile ); return( -1 ); } while ( fgets( line, MAXPATHLEN, f ) != NULL ) { linenum++; ac = acav_parse( acav, line, &av ); if (( ac == 0 ) || ( *av[ 0 ] == '#' )) { continue; } /* Skip non-special minus lines */ if ( *av[ 0 ] == '-' ) { if ( *av[ 1 ] == 's' ) { minus = 1; av++; ac--; } else { continue; } } else { /* Set incase previous line was a minus */ minus = 0; } if ( ac != 2 ) { fprintf( stderr, "%s: %d: invalid command line\n", kfile, linenum ); goto error; } switch( *av[ 0 ] ) { case 'k': if ( snprintf( path, MAXPATHLEN, "%s%s", kdir, av[ 1 ] ) >= MAXPATHLEN ) { fprintf( stderr, "path too long: %s%s\n", kdir, av[ 1 ] ); goto error; } if ( !list_check( kfile_seen, path )) { if ( list_insert_tail( kfile_seen, path ) != 0 ) { perror( "list_insert_tail" ); goto error; } } switch( check( sn, "COMMAND", av[ ac - 1] )) { case 0: break; case 1: change++; if ( !update ) { goto done; } break; case 2: if ( report ) report_event( sn, "ktcheck", "Error" ); goto error; } if ( read_kfile( path ) != 0 ) { exit( 2 ); } break; case 's': /* Added special file if it's not already in the list */ if ( minus ) { if ( list_check( special_list, av[ 1 ] )) { list_remove( special_list, av[ 1 ] ); } } else { if ( !list_check( special_list, av[ 1 ] )) { if ( list_insert( special_list, av[ 1 ] ) != 0 ) { perror( "list_insert" ); exit( 2 ); } } } continue; case 'p': case 'n': switch( check( sn, "TRANSCRIPT", av[ ac - 1] )) { case 0: break; case 1: change++; if ( !update ) { goto done; } break; case 2: if ( report ) report_event( sn, "ktcheck", "Error" ); exit( 2 ); } break; } } if ( ferror( f )) { perror( "fgets" ); return( -1 ); } done: if ( fclose( f ) != 0 ) { perror( "fclose" ); return( -1 ); } if ( acav_free( acav ) != 0 ) { perror( "acav_free" ); return( -1 ); } if ( !update && change ) { if ( report ) report_event( sn, "ktcheck", "Updates available" ); exit( 1 ); } return( 0 ); error: fclose( f ); acav_free( acav ); return( -1 ); }