/* ** Copyright (c) 2014 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the Simplified BSD License (also ** known as the "2-Clause License" or "FreeBSD License".) ** This program is distributed in the hope that it will be useful, ** but without any warranty; without even the implied warranty of ** merchantability or fitness for a particular purpose. ** ** Author contact information: ** drh@sqlite.org */ #include "sqlite3.h" #include <stdio.h> #include <stdarg.h> #include <stdlib.h> #include <zlib.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <dirent.h> #include <string.h> #include <assert.h> #include <ctype.h> /* Maximum length of a pass-phrase */ #define MX_PASSPHRASE 120 /* ** Show a help message and quit. */ static void showHelp(const char *argv0){ fprintf(stderr, "Usage: %s [options] archive [files...]\n", argv0); fprintf(stderr, "Options:\n" " -d Delete files from the archive\n" " -e Prompt for passphrase. -ee to scramble the prompt\n" " -l List files in archive\n" " -n Do not compress files\n" " -x Extract files from archive\n" " -v Verbose output\n" ); exit(1); } /* ** The database schema. Each file, directory or symlink in an archive ** is represented by a single row in this table. ** ** name: Path to file-system entry (text). ** mode: Value of stat.st_mode returned by stat() call (integer). ** mtime: Value of stat.st_mtime returned by stat() call (integer). ** sz: Size of file in bytes. Always 0 for a directory. -1 for symlink. ** data: Blob containing file contents. NULL for a directory. Text value ** containing linked path for a symlink. */ static const char zSchema[] = "CREATE TABLE IF NOT EXISTS sqlar(\n" " name TEXT PRIMARY KEY,\n" " mode INT,\n" " mtime INT,\n" " sz INT,\n" " data BLOB\n" ");" ; /* ** Prepared statement that needs finalizing before sqlite3_close(). */ static sqlite3_stmt *pStmt = 0; /* ** Open database connection */ static sqlite3 *db = 0; /* ** Close the database */ static void db_close(int commitFlag){ if( pStmt ){ sqlite3_finalize(pStmt); pStmt = 0; } if( db ){ if( commitFlag ){ sqlite3_exec(db, "COMMIT", 0, 0, 0); }else{ sqlite3_exec(db, "ROLLBACK", 0, 0, 0); } sqlite3_close(db); db = 0; } } /* ** Panic message */ static void errorMsg(const char *zFormat, ...){ va_list ap; va_start(ap, zFormat); vfprintf(stderr, zFormat, ap); va_end(ap); db_close(0); exit(1); } /* ** Scramble substitution matrix: */ static char aSubst[256]; /* ** Descramble the password */ static void descramble(char *z){ int i; for(i=0; z[i]; i++) z[i] = aSubst[(unsigned char)z[i]]; } /* Print a string in 5-letter groups */ static void printFive(const unsigned char *z){ int i; for(i=0; z[i]; i++){ if( i>0 && (i%5)==0 ) putchar(' '); putchar(z[i]); } putchar('\n'); } /* Return a pseudo-random integer between 0 and N-1 */ static int randint(int N){ unsigned char x; assert( N<256 ); sqlite3_randomness(1, &x); return x % N; } /* ** Generate and print a random scrambling of letters a through z (omitting x) ** and set up the aSubst[] matrix to descramble. */ static void generateScrambleCode(void){ unsigned char zOrig[30]; unsigned char zA[30]; unsigned char zB[30]; int nA = 25; int nB = 0; int i; memcpy(zOrig, "abcdefghijklmnopqrstuvwyz", nA+1); memcpy(zA, zOrig, nA+1); assert( nA==(int)strlen((char*)zA) ); for(i=0; i<sizeof(aSubst); i++) aSubst[i] = i; printFive(zA); while( nA>0 ){ int x = randint(nA); zB[nB++] = zA[x]; zA[x] = zA[--nA]; } assert( nB==25 ); zB[nB] = 0; printFive(zB); for(i=0; i<nB; i++) aSubst[zB[i]] = zOrig[i]; } /* ** Do a single prompt for a passphrase. Store the results in the blob. ** ** If the FOSSIL_PWREADER environment variable is set, then it will ** be the name of a program that prompts the user for their password/ ** passphrase in a secure manner. The program should take one or more ** arguments which are the prompts and should output the acquired ** passphrase as a single line on stdout. This function will read the ** output using popen(). ** ** If FOSSIL_PWREADER is not set, or if it is not the name of an ** executable, then use the C-library getpass() routine. ** ** The return value is a pointer to a static buffer that is overwritten ** on subsequent calls to this same routine. */ static void prompt_for_passphrase( const char *zPrompt, /* Passphrase prompt */ int doScramble, /* Scramble the input if true */ char *zPassphrase /* Write result here */ ){ char *z; int i; if( doScramble ){ generateScrambleCode(); z = getpass(zPrompt); if( z ) descramble(z); printf("\033[3A\033[J"); /* Erase previous three lines */ fflush(stdout); }else{ z = getpass(zPrompt); } while( isspace(z[0]) ) z++; for(i=0; i<MX_PASSPHRASE-1; i++){ zPassphrase[i] = z[i]; } while( i>0 && isspace(z[i-1]) ){ i--; } zPassphrase[i] = 0; } /* ** List of command-line arguments */ typedef struct NameList NameList; struct NameList { const char **azName; /* List of names */ int nName; /* Number of names on the list */ }; /* ** Inplementation of SQL function "name_on_list(X)". Return ** true if X is on the list of GLOB patterns given on the command-line. */ static void name_on_list( sqlite3_context *context, int argc, sqlite3_value **argv ){ NameList *pList = (NameList*)sqlite3_user_data(context); int i; int rc = 0; const char *z = (const char*)sqlite3_value_text(argv[0]); if( z!=0 ){ for(i=0; i<pList->nName; i++){ if( sqlite3_strglob(pList->azName[i], z)==0 ){ rc = 1; break; } } } sqlite3_result_int(context, rc); } /* ** SQL functions that always true. This is used in place of ** name_on_list() when no command-line arguments are given. */ static void alwaysTrue(sqlite3_context *ctx, int n, sqlite3_value **v){ sqlite3_result_int(ctx, 1); } /* ** Open the database. */ static void db_open( const char *zArchive, int writeFlag, int seeFlag, const char **azFiles, int nFiles ){ int rc; int fg; NameList *x = 0; if( writeFlag ){ fg = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; }else{ fg = SQLITE_OPEN_READONLY; } rc = sqlite3_open_v2(zArchive, &db, fg, 0); if( rc ) errorMsg("Cannot open archive [%s]: %s\n", zArchive, sqlite3_errmsg(db)); if( azFiles!=0 && nFiles>0 ){ x = sqlite3_malloc( sizeof(NameList) ); if( x==0 ) errorMsg("Out of memory"); x->azName = azFiles; x->nName = nFiles; sqlite3_create_function(db, "name_on_list", 1, SQLITE_UTF8, (char*)x, name_on_list, 0, 0); }else{ sqlite3_create_function(db, "name_on_list", 1, SQLITE_UTF8, 0, alwaysTrue, 0, 0); } if( seeFlag ){ char zPassPhrase[MX_PASSPHRASE+1]; #ifndef SQLITE_HAS_CODEC printf("WARNING: The passphrase is a no-op because this build of\n" "sqlar is compiled without encryption capabilities.\n"); #endif memset(zPassPhrase, 0, sizeof(zPassPhrase)); prompt_for_passphrase("passphrase: ", seeFlag>1, zPassPhrase); #ifdef SQLITE_HAS_CODEC sqlite3_key_v2(db, "main", zPassPhrase, -1); #endif } sqlite3_exec(db, "BEGIN", 0, 0, 0); sqlite3_exec(db, zSchema, 0, 0, 0); rc = sqlite3_exec(db, "SELECT 1 FROM sqlar LIMIT 1", 0, 0, 0); if( rc!=SQLITE_OK ){ fprintf(stderr, "File [%s] is not an SQLite archive\n", zArchive); exit(1); } } /* ** Prepare the pStmt statement. */ static void db_prepare(const char *zSql){ int rc; sqlite3_finalize(pStmt); rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); if( rc ){ errorMsg("Error: %s\nwhile preparing: %s\n", sqlite3_errmsg(db), zSql); } } /* ** Read a file from disk into memory obtained from sqlite3_malloc(). ** Compress the file as it is read in if doing so reduces the file ** size and if the noCompress flag is false. ** ** Return the original size and the compressed size of the file in ** *pSizeOrig and *pSizeCompr, respectively. If these two values are ** equal, that means the file was not compressed. */ static char *read_file( const char *zFilename, /* Name of file to read */ int *pSizeOrig, /* Write original file size here */ int *pSizeCompr, /* Write compressed file size here */ int noCompress /* Do not compress if true */ ){ FILE *in; char *zIn; long int nIn; char *zCompr; unsigned long int nCompr; int rc; in = fopen(zFilename, "rb"); if( in==0 ) errorMsg("cannot open \"%s\" for reading\n", zFilename); fseek(in, 0, SEEK_END); nIn = ftell(in); rewind(in); zIn = sqlite3_malloc( nIn+1 ); if( zIn==0 ) errorMsg("cannot malloc for %d bytes\n", nIn+1); if( nIn>0 && fread(zIn, nIn, 1, in)!=1 ){ errorMsg("unable to read %d bytes of file %s\n", nIn, zFilename); } fclose(in); if( noCompress ){ *pSizeOrig = *pSizeCompr = nIn; return zIn; } nCompr = 13 + nIn + (nIn+999)/1000; zCompr = sqlite3_malloc( nCompr+1 ); if( zCompr==0 ) errorMsg("cannot malloc for %d bytes\n", nCompr+1); rc = compress((Bytef*)zCompr, &nCompr, (const Bytef*)zIn, nIn); if( rc!=Z_OK ) errorMsg("Cannot compress %s\n", zFilename); if( nIn>nCompr ){ sqlite3_free(zIn); *pSizeOrig = nIn; *pSizeCompr = (int)nCompr; return zCompr; }else{ sqlite3_free(zCompr); *pSizeOrig = *pSizeCompr = nIn; return zIn; } } /* ** Make sure the parent directory for zName exists. Create it if it does ** not exist. */ static void make_parent_directory(const char *zName){ char *zParent; int i, j, rc; for(i=j=0; zName[i]; i++) if( zName[i]=='/' ) j = i; if( j>0 ){ zParent = sqlite3_mprintf("%.*s", j, zName); if( zParent==0 ) errorMsg("mprintf failed\n"); while( j>0 && zParent[j]=='/' ) j--; zParent[j] = 0; if( j>0 && access(zParent,F_OK)!=0 ){ make_parent_directory(zParent); rc = mkdir(zParent, 0777); if( rc ) errorMsg("cannot create directory: %s\n", zParent); } sqlite3_free(zParent); } } /* ** Write a file or a directory. ** ** Create any missing directories leading up to the given file or directory. ** Also set the access mode and the modification time. ** ** If sz>nCompr that means that the content is compressed and needs to be ** decompressed before writing. */ static void write_file( const char *zFilename, /* Store content in this file */ int iMode, /* The unix-style access mode */ sqlite3_int64 mtime, /* Modification time */ int sz, /* Size of file as stored on disk */ const char *pCompr, /* Content (usually compressed) */ int nCompr /* Size of content (prior to decompression) */ ){ char *pOut; unsigned long int nOut; int rc; FILE *out; make_parent_directory(zFilename); if( pCompr==0 ){ rc = mkdir(zFilename, iMode); if( rc ) errorMsg("cannot make directory: %s\n", zFilename); return; } if( sz<0 ){ /* This is a symlink. */ if( symlink(pCompr, zFilename)<0 ){ errorMsg("failed to create symlink: %s -> %s\n", zFilename, pCompr); } }else{ out = fopen(zFilename, "wb"); if( out==0 ) errorMsg("cannot open for writing: %s\n", zFilename); if( sz==nCompr ){ if( sz>0 && fwrite(pCompr, sz, 1, out)!=1 ){ errorMsg("failed to write: %s\n", zFilename); } }else{ pOut = sqlite3_malloc( sz+1 ); if( pOut==0 ) errorMsg("cannot allocate %d bytes\n", sz+1); nOut = sz; rc = uncompress((Bytef*)pOut, &nOut, (const Bytef*)pCompr, nCompr); if( rc!=Z_OK ) errorMsg("uncompress failed for %s\n", zFilename); if( nOut>0 && fwrite(pOut, nOut, 1, out)!=1 ){ errorMsg("failed to write: %s\n", zFilename); } sqlite3_free(pOut); } fclose(out); rc = chmod(zFilename, iMode&0777); if( rc ) errorMsg("cannot change mode to %03o: %s\n", iMode&0777,zFilename); } } /* ** Error out if there are any issues with the given filename */ static void check_filename(const char *z){ if( strncmp(z, "../", 3)==0 || sqlite3_strglob("*/../*", z)==0 ){ errorMsg("Filename with '..' in its path: %s\n", z); } if( sqlite3_strglob("*\\*", z)==0 ){ errorMsg("Filename with '\\' in its name: %s\n", z); } } /* ** File zSymlink is a symbolic link. Figure out the path that the link ** points to and bind it (as text) to parameter iVar of statement pStmt. */ static void bind_symlink_path( sqlite3_stmt *pStmt, /* Statement to bind to */ int iVar, /* Variable within pStmt to bind to */ const char *zSymlink, /* Path to symlink */ int verboseFlag /* If true, show file added */ ){ char buf[4096]; int n = readlink(zSymlink, buf, sizeof(buf)); if( n<0 ){ errorMsg("readlink(%s) failed\n", zSymlink); } if( n>sizeof(buf) ){ errorMsg("symlinked path is too long (max %d bytes)\n", sizeof(buf)); } sqlite3_bind_text(pStmt, iVar, buf, n, SQLITE_TRANSIENT); if( verboseFlag ) printf(" added: %s -> %.*s\n", zSymlink, n, buf); } /* ** Add a file to the database. */ static void add_file( const char *zFilename, /* Name of file to add */ int verboseFlag, /* If true, show each file added */ int noCompress /* If true, always omit compression */ ){ int rc; struct stat x; int szOrig; int szCompr; const char *zName; check_filename(zFilename); rc = lstat(zFilename, &x); if( rc ) errorMsg("no such file or directory: %s\n", zFilename); if( x.st_size>1000000000 ){ errorMsg("file too big: %s\n", zFilename); } if( pStmt==0 ){ db_prepare("REPLACE INTO sqlar(name,mode,mtime,sz,data)" " VALUES(?1,?2,?3,?4,?5)"); } zName = zFilename; while( zName[0]=='/' ) zName++; sqlite3_bind_text(pStmt, 1, zName, -1, SQLITE_STATIC); sqlite3_bind_int(pStmt, 2, x.st_mode); sqlite3_bind_int64(pStmt, 3, x.st_mtime); if( S_ISLNK(x.st_mode) ){ sqlite3_bind_int(pStmt, 4, -1); bind_symlink_path(pStmt, 5, zFilename, verboseFlag); }else if( S_ISREG(x.st_mode) ){ char *zContent = read_file(zFilename, &szOrig, &szCompr, noCompress); sqlite3_bind_int(pStmt, 4, szOrig); sqlite3_bind_blob(pStmt, 5, zContent, szCompr, sqlite3_free); if( verboseFlag ){ if( szCompr<szOrig ){ int pct = szOrig ? (100*(sqlite3_int64)szCompr)/szOrig : 0; printf(" added: %s (deflate %d%%)\n", zFilename, 100-pct); }else{ printf(" added: %s\n", zFilename); } } }else{ sqlite3_bind_int(pStmt, 4, 0); sqlite3_bind_null(pStmt, 5); if( verboseFlag ) printf(" added: %s\n", zFilename); } rc = sqlite3_step(pStmt); if( rc!=SQLITE_DONE ){ errorMsg("Insert failed for %s: %s\n", zFilename, sqlite3_errmsg(db)); } sqlite3_reset(pStmt); if( S_ISDIR(x.st_mode) ){ DIR *d; struct dirent *pEntry; char *zSubpath; d = opendir(zFilename); if( d ){ while( (pEntry = readdir(d))!=0 ){ if( strcmp(pEntry->d_name,".")==0 || strcmp(pEntry->d_name,"..")==0 ){ continue; } zSubpath = sqlite3_mprintf("%s/%s", zFilename, pEntry->d_name); add_file(zSubpath, verboseFlag, noCompress); sqlite3_free(zSubpath); } closedir(d); } } } int main(int argc, char **argv){ const char *zArchive = 0; const char **azFiles = 0; int nFiles = 0; int listFlag = 0; int extractFlag = 0; int verboseFlag = 0; int noCompress = 0; int seeFlag = 0; int deleteFlag = 0; int i, j; if( sqlite3_strglob("*/unsqlar", argv[0])==0 ){ extractFlag = 1; } for(i=1; i<argc; i++){ if( argv[i][0]=='-' ){ for(j=1; argv[i][j]; j++){ switch( argv[i][j] ){ case 'l': listFlag = 1; break; case 'n': noCompress = 1; break; case 'v': verboseFlag = 1; break; case 'x': extractFlag = 1; break; case 'e': seeFlag++; break; case 'd': deleteFlag = 1; break; case '-': break; default: showHelp(argv[0]); } } }else if( zArchive==0 ){ zArchive = argv[i]; }else{ azFiles = (const char**)&argv[i]; nFiles = argc - i; break; } } if( zArchive==0 ) showHelp(argv[0]); if( listFlag || deleteFlag ){ if( deleteFlag && nFiles==0 ){ errorMsg("Specify one or more files to delete on the command-line"); } db_open(zArchive, deleteFlag, seeFlag, azFiles, nFiles); if( verboseFlag ){ db_prepare( "SELECT name, sz, length(data), mode, datetime(mtime,'unixepoch')" " FROM sqlar WHERE name_on_list(name) ORDER BY name" ); while( sqlite3_step(pStmt)==SQLITE_ROW ){ if( deleteFlag ) printf("DELETE "); printf("%10d %10d %03o %s %s\n", sqlite3_column_int(pStmt, 1), sqlite3_column_int(pStmt, 2), sqlite3_column_int(pStmt, 3)&0777, sqlite3_column_text(pStmt, 4), sqlite3_column_text(pStmt, 0)); } }else{ db_prepare( "SELECT name FROM sqlar WHERE name_on_list(name) ORDER BY name" ); while( sqlite3_step(pStmt)==SQLITE_ROW ){ if( deleteFlag ) printf("DELETE "); printf("%s\n", sqlite3_column_text(pStmt,0)); } } if( deleteFlag ){ sqlite3_exec(db, "DELETE FROM sqlar WHERE name_on_list(name)", 0, 0, 0); } db_close(1); }else if( extractFlag ){ const char *zSql; db_open(zArchive, 0, seeFlag, azFiles, nFiles); zSql = "SELECT name, mode, mtime, sz, data FROM sqlar" " WHERE name_on_list(name)"; db_prepare(zSql); while( sqlite3_step(pStmt)==SQLITE_ROW ){ int sz = sqlite3_column_int(pStmt, 3); /* Size of file on disk */ const char *pData; /* Data for this file */ const char *zFN = (const char*)sqlite3_column_text(pStmt, 0); check_filename(zFN); if( zFN[0]=='/' ){ errorMsg("absolute pathname: %s\n", zFN); } if( sqlite3_column_type(pStmt,4)==SQLITE_BLOB && access(zFN, F_OK)==0 ){ errorMsg("file already exists: %s\n", zFN); } if( verboseFlag ) printf("%s\n", zFN); if( sz<0 ){ /* symlink */ pData = (const char*)sqlite3_column_text(pStmt, 4); }else{ pData = (const char*)sqlite3_column_blob(pStmt,4); if( pData==0 && sqlite3_column_type(pStmt, 4)!=SQLITE_NULL ){ /* If the file is zero bytes in size, then column "data" will ** contain a zero-byte blob value. In this case, column_blob() ** returns NULL, which confuses the write_file() call into ** thinking this is a directory, not a zero-byte file. */ pData = ""; } } write_file(zFN, sqlite3_column_int(pStmt,1), sqlite3_column_int64(pStmt,2), sz, pData, sqlite3_column_bytes(pStmt,4)); } db_close(1); }else{ if( azFiles==0 ){ errorMsg("Specify one or more files to add on the command-line"); } db_open(zArchive, 1, seeFlag, 0, 0); for(i=0; i<nFiles; i++){ add_file(azFiles[i], verboseFlag, noCompress); } db_close(1); } return 0; }