/* * Copyright (c) 2003 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 "applefile.h" #include "base64.h" #include "argcargv.h" #include "cksum.h" #include "code.h" #include "pathcmp.h" #include "largefile.h" #include "progress.h" #include "root.h" void (*logger)( char * ) = NULL; int cksum = 0; int verbose = 1; int amode = R_OK | W_OK; int case_sensitive = 1; int checkall = 0; int checkapplefile = 0; int updatetran = 1; char *prefix = NULL; char *radmind_path = _RADMIND_PATH; const EVP_MD *md; extern int showprogress, progress; extern off_t lsize, total; extern char *version, *checksumlist; char prepath[ MAXPATHLEN ] = {0}; /* * exit codes: * 0 No changes found, everything okay * 1 Changes necessary / changes made * 2 System error */ off_t ckapplefile( char *applefile, int afd ) { extern struct as_header as_header; struct as_header header; struct as_entry as_ents[ 3 ]; int rr; off_t size = 0; /* check header */ rr = read( afd, &header, AS_HEADERLEN ); if ( rr < 0 ) { perror( "read" ); exit( 2 ); } if ( rr != AS_HEADERLEN || memcmp( &as_header, &header, AS_HEADERLEN ) != 0 ) { goto invalid_applefile; } size += rr; /* check entries */ rr = read( afd, &as_ents, ( 3 * sizeof( struct as_entry ))); if ( rr < 0 ) { perror( "read" ); exit( 2 ); } if ( rr != ( 3 * sizeof( struct as_entry ))) { goto invalid_applefile; } size += rr; as_entry_netswap( &as_ents[ AS_FIE ] ); as_entry_netswap( &as_ents[ AS_RFE ] ); as_entry_netswap( &as_ents[ AS_DFE ] ); /* check entry IDs */ if ( as_ents[ AS_FIE ].ae_id != ASEID_FINFO || as_ents[ AS_RFE ].ae_id != ASEID_RFORK || as_ents[ AS_DFE ].ae_id != ASEID_DFORK ) { goto invalid_applefile; } /* check offsets */ if ( as_ents[ AS_FIE ].ae_offset != ( AS_HEADERLEN + ( 3 * sizeof( struct as_entry )))) { fprintf( stderr, "%s: invalid finder info offset\n", applefile ); return( -1 ); } if ( as_ents[ AS_RFE ].ae_offset != ( as_ents[ AS_FIE ].ae_offset + as_ents[ AS_FIE ].ae_length )) { fprintf( stderr, "%s: incorrect rsrc fork offset\n", applefile ); return( -1 ); } if ( as_ents[ AS_DFE ].ae_offset != ( as_ents[ AS_RFE ].ae_offset + as_ents[ AS_RFE ].ae_length )) { fprintf( stderr, "%s: incorrect data fork offset\n", applefile ); return( -1 ); } /* total sizes as stored in entries */ size += ( as_ents[ AS_FIE ].ae_length + as_ents[ AS_RFE ].ae_length + as_ents[ AS_DFE ].ae_length ); return( size ); invalid_applefile: fprintf( stderr, "%s: invalid applesingle header\n", applefile ); return( -1 ); } static void cleanup( int clean, char *path ) { if ( ! clean || path == NULL ) { return; } if ( unlink( path ) != 0 ) { fprintf( stderr, "unlink %s: %s\n", path, strerror( errno )); exit( 2 ); } } static int do_lcksum( char *tpath ) { int fd, ufd, updateline = 0; int ucount = 0, len, tac = 0; int prefixfound = 0; int remove = 0; int linenum = 0; int exitval = 0; ssize_t bytes = 0; char *line = NULL; char *d_path = NULL; char **targv; char cwd[ MAXPATHLEN ]; char temp[ MAXPATHLEN ]; char file_root[ MAXPATHLEN ]; char tran_root[ MAXPATHLEN ]; char tran_name[ MAXPATHLEN ]; char tline[ 2 * MAXPATHLEN ]; char path[ 2 * MAXPATHLEN ]; char upath[ 2 * MAXPATHLEN ] = { 0 }; char lcksum[ SZ_BASE64_E( EVP_MAX_MD_SIZE ) ]; FILE *f, *ufs = NULL; struct stat st; off_t cksumsize; if ( getcwd( cwd, MAXPATHLEN ) == NULL ) { perror( "getcwd" ); exit( 2 ); } if ( *tpath == '/' ) { if ( strlen( tpath ) >= MAXPATHLEN ) { fprintf( stderr, "%s: path too long\n", tpath ); exit( 2 ); } strcpy( cwd, tpath ); } else { if ( snprintf( temp, MAXPATHLEN, "%s/%s", cwd, tpath ) >= MAXPATHLEN ) { fprintf( stderr, "%s/%s: path too long\n", cwd, tpath ); exit( 2 ); } strcpy( cwd, temp ); } if ( get_root( radmind_path, cwd, file_root, tran_root, tran_name ) != 0 ) { exit( 2 ); } if ( stat( tpath, &st ) != 0 ) { perror( tpath ); exit( 2 ); } if ( !S_ISREG( st.st_mode )) { fprintf( stderr, "%s: not a regular file\n", tpath ); exit( 2 ); } if ( access( tpath, amode ) != 0 ) { perror( tpath ); exit( 2 ); } if (( f = fopen( tpath, "r" )) == NULL ) { perror( tpath ); exit( 2 ); } if ( updatetran ) { memset( upath, 0, MAXPATHLEN ); if ( snprintf( upath, MAXPATHLEN, "%s.%i", tpath, (int)getpid() ) >= MAXPATHLEN ) { fprintf( stderr, "%s.%i: path too long\n", tpath, (int)getpid() ); } if ( stat( tpath, &st ) != 0 ) { perror( tpath ); exit( 2 ); } /* Open file */ if (( ufd = open( upath, O_WRONLY | O_CREAT | O_EXCL, st.st_mode )) < 0 ) { perror( upath ); exit( 2 ); } if (( ufs = fdopen( ufd, "w" )) == NULL ) { perror( upath ); cleanup( updatetran, upath ); exit( 2 ); } } if ( showprogress ) { /* calculate the loadset size */ lsize = lcksum_loadsetsize( f, prefix ); /* reset progress variables */ total = 0; progress = -1; } memset( prepath, 0, sizeof( prepath )); while ( fgets( tline, MAXPATHLEN, f ) != NULL ) { linenum++; updateline = 0; /* Check line length */ len = strlen( tline ); if (( tline[ len - 1 ] ) != '\n' ) { fprintf( stderr, "%s: %d: line too long\n", tpath, linenum); exitval = 1; goto done; } /* save transcript line -- must free */ if ( ( line = strdup( tline ) ) == NULL ) { perror( "strdup" ); cleanup( updatetran, upath ); exit( 2 ); } tac = acav_parse( NULL, tline, &targv ); /* Skip blank lines and comments */ if (( tac == 0 ) || ( *targv[ 0 ] == '#' )) { if ( updatetran ) { fprintf( ufs, "%s", line ); } goto done; } if ( tac == 1 ) { fprintf( stderr, "line %d: invalid transcript line\n", linenum ); exitval = 1; goto done; } if ( *targv[ 0 ] == '-' ) { remove = 1; targv++; } else { remove = 0; } if (( d_path = decode( targv[ 1 ] )) == NULL ) { fprintf( stderr, "line %d: path too long\n", linenum ); exitval = 1; goto done; } if ( strlen( d_path ) >= MAXPATHLEN ) { fprintf( stderr, "line %d: path too long\n", linenum ); exitval = 1; goto done; } strcpy( path, d_path ); /* check to see if file against prefix */ if ( prefix != NULL ) { if ( strncmp( d_path, prefix, strlen( prefix )) != 0 ) { if ( updatetran ) { fprintf( ufs, "%s", line ); } goto done; } prefixfound = 1; } if ( showprogress && ( tac > 0 && *line != '#' )) { progressupdate( bytes, d_path ); } bytes = 0; /* Check transcript order */ if ( prepath != 0 ) { if ( pathcasecmp( path, prepath, case_sensitive ) < 0 ) { if ( updatetran ) { fprintf( stderr, "line %d: bad sort order\n", linenum ); } else { fprintf( stderr, "line %d: bad sort order. Not continuing.\n", linenum ); } cleanup( updatetran, upath ); exit( 2 ); } } if ( strlen( path ) >= MAXPATHLEN ) { fprintf( stderr, "line %d: path too long\n", linenum ); exitval = 1; goto done; } strcpy( prepath, path ); if ((( *targv[ 0 ] != 'f' ) && ( *targv[ 0 ] != 'a' )) || ( remove )) { if ( updatetran ) { fprintf( ufs, "%s", line ); } bytes += PROGRESSUNIT; goto done; } if ( tac != 8 ) { fprintf( stderr, "line %d: %d arguments should be 8\n", linenum, tac ); exitval = 1; goto done; } if ( snprintf( path, MAXPATHLEN, "%s/%s/%s", file_root, tran_name, d_path ) > MAXPATHLEN - 1 ) { fprintf( stderr, "%d: %s/%s/%s: path too long\n", linenum, file_root, tran_name, d_path ); exitval = 1; goto done; } /* * Since this tool is run on the server, all files can be treated * as regular files. * * HFS+ files saved onto the server are converted to applesingle files. * * fsdiff uses do_acksum( ) to calculate the cksum of HFS+ files. * * do_acksum( ) creates a cksum for the associated applesingle file. */ /* open file here to save us some other open calls */ if (( fd = open( path, O_RDONLY, 0 )) < 0 ) { fprintf( stderr, "open %s: %s\n", d_path, strerror( errno )); cleanup( updatetran, upath ); exit( 2 ); } /* check size */ if ( fstat( fd, &st) != 0 ) { perror( path ); cleanup( updatetran, upath ); exit( 2 ); } if (( cksumsize = do_fcksum( fd, lcksum )) < 0 ) { perror( path ); cleanup( updatetran, upath ); exit( 2 ); } /* check size */ if ( cksumsize != strtoofft( targv[ 6 ], NULL, 10 )) { if ( !updatetran ) { if ( verbose ) printf( "line %d: %s: size wrong\n", linenum, d_path ); exitval = 1; } else { ucount++; if ( verbose ) printf( "%s: size updated\n", d_path ); } updateline = 1; } bytes += cksumsize; bytes += PROGRESSUNIT; /* check cksum */ if ( strcmp( lcksum, targv[ 7 ] ) != 0 ) { if ( !updatetran ) { if ( verbose ) printf( "line %d: %s: " "checksum wrong\n", linenum, d_path ); exitval = 1; } else { ucount++; if ( verbose ) printf( "%s: checksum updated\n", d_path ); } updateline = 1; } if ( *targv[ 0 ] == 'a' && checkapplefile ) { /* rewind the descriptor */ if ( lseek( fd, 0, SEEK_SET ) < 0 ) { perror( "lseek" ); exitval = 1; goto done; } if ( ckapplefile( path, fd ) != st.st_size ) { fprintf( stderr, "%s: corrupted applefile\n", path ); exitval = 1; goto done; } } if ( close( fd ) != 0 ) { perror( "close" ); exitval = 1; goto done; } if ( updatetran ) { if ( updateline ) { /* Line incorrect */ /* Check to see if checksum is listed in transcript */ if ( strcmp( targv[ 7 ], "-" ) != 0) { /* use mtime from server */ fprintf( ufs, "%s %-37s %4s %5s %5s %9ld " "%7" PRIofft "d %s\n", targv[ 0 ], targv[ 1 ], targv[ 2 ], targv[ 3 ], targv[ 4 ], st.st_mtime, st.st_size, lcksum ); } else { /* use mtime from transcript */ fprintf( ufs, "%s %-37s %4s %5s %5s %9s " "%7" PRIofft "d %s\n", targv[ 0 ], targv[ 1 ], targv[ 2 ], targv[ 3 ], targv[ 4 ], targv[ 5 ], st.st_size, lcksum ); } } else { /* Line correct */ fprintf( ufs, "%s", line ); } } done: if ( updatetran && ( exitval != 0 )) { cleanup( updatetran, upath ); exit( 2 ); } free( line ); } if ( showprogress ) { progressupdate( bytes, "" ); } if ( !prefixfound && prefix != NULL ) { if ( verbose ) printf( "warning: prefix \"%s\" not found\n", prefix ); } if ( updatetran ) { if ( ucount ) { if ( rename( upath, tpath ) != 0 ) { fprintf( stderr, "rename %s to %s failed: %s\n", upath, tpath, strerror( errno )); exit( 2 ); } if ( verbose ) printf( "%s: updated\n", tran_name ); return( 1 ); } else { if ( unlink( upath ) != 0 ) { perror( upath ); exit( 2 ); } if ( verbose ) printf( "%s: verified\n", tran_name ); return( 0 ); } } else { if ( exitval == 0 ) { if ( verbose ) printf( "%s: verified\n", tran_name ); return( 0 ); } else { if ( verbose ) printf( "%s: incorrect\n", tran_name ); return( 1 ); } } } int main( int argc, char **argv ) { int c, i, err = 0; extern int optind; char *tpath = NULL; while (( c = getopt( argc, argv, "%Aac:D:iInP:qV" )) != EOF ) { switch( c ) { case 'a': checkall = 1; break; case 'A': checkapplefile = 1; break; case '%': showprogress = 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 'i': setvbuf( stdout, ( char * )NULL, _IOLBF, 0 ); break; case 'I': case_sensitive = 0; break; case 'D': radmind_path = optarg; break; case 'P': prefix = optarg; break; case 'n': amode = R_OK; updatetran = 0; break; case 'q': verbose = 0; break; case 'V': printf( "%s\n", version ); printf( "%s\n", checksumlist ); exit( 0 ); case '?': err++; break; default: err++; break; } } if ( cksum == 0 ) { err++; } if ( checkall && updatetran ) { err++; } if ( err || (( argc - optind ) == 0 )) { fprintf( stderr, "usage: %s [ -%%AiIqV ] ", argv[ 0 ] ); fprintf( stderr, "[ -D path ] " ); fprintf( stderr, "[ -n [ -a ] ] " ); fprintf( stderr, "[ -P prefix ] " ); fprintf( stderr, "-c checksum transcript\n" ); exit( 2 ); } for ( i = optind; i < argc; i++ ) { tpath = argv[ i ]; switch ( do_lcksum( tpath )) { case 2: exit( 2 ); break; case 1: err = 1; if ( !updatetran ) { exit( 1 ); } break; default: break; } } exit( err ); }