/* ** Copyright (c) 2010 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@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** ** This module contains the code that initializes the "sqlite3" command-line ** shell against the repository database. The command-line shell itself ** is a copy of the "shell.c" code from SQLite. This file contains logic ** to initialize the code in shell.c. */ #include "config.h" #include "sqlcmd.h" #include /* atexit() */ #include #ifndef _WIN32 # include "linenoise.h" #endif /* ** True if the "fossil sql" command has the --test flag. False otherwise. */ static int local_bSqlCmdTest = 0; /* ** Implementation of the "content(X)" SQL function. Return the complete ** content of artifact identified by X as a blob. */ static void sqlcmd_content( sqlite3_context *context, int argc, sqlite3_value **argv ){ int rid; Blob cx; const char *zName; assert( argc==1 ); zName = (const char*)sqlite3_value_text(argv[0]); if( zName==0 ) return; g.db = sqlite3_context_db_handle(context); g.repositoryOpen = 1; rid = name_to_rid(zName); if( rid==0 ) return; if( content_get(rid, &cx) ){ sqlite3_result_blob(context, blob_buffer(&cx), blob_size(&cx), SQLITE_TRANSIENT); blob_reset(&cx); } } /* ** Implementation of the "compress(X)" SQL function. The input X is ** compressed using zLib and the output is returned. */ static void sqlcmd_compress( sqlite3_context *context, int argc, sqlite3_value **argv ){ const unsigned char *pIn; unsigned char *pOut; unsigned int nIn; unsigned long int nOut; int rc; pIn = sqlite3_value_blob(argv[0]); nIn = sqlite3_value_bytes(argv[0]); nOut = 13 + nIn + (nIn+999)/1000; pOut = sqlite3_malloc( nOut+4 ); pOut[0] = nIn>>24 & 0xff; pOut[1] = nIn>>16 & 0xff; pOut[2] = nIn>>8 & 0xff; pOut[3] = nIn & 0xff; rc = compress(&pOut[4], &nOut, pIn, nIn); if( rc==Z_OK ){ sqlite3_result_blob(context, pOut, nOut+4, sqlite3_free); }else if( rc==Z_MEM_ERROR ){ sqlite3_free(pOut); sqlite3_result_error_nomem(context); }else{ sqlite3_free(pOut); sqlite3_result_error(context, "input cannot be zlib compressed", -1); } } /* ** Implementation of the "decompress(X)" SQL function. The argument X ** is a blob which was obtained from compress(Y). The output will be ** the value Y. */ static void sqlcmd_decompress( sqlite3_context *context, int argc, sqlite3_value **argv ){ const unsigned char *pIn; unsigned char *pOut; unsigned int nIn; unsigned long int nOut; int rc; pIn = sqlite3_value_blob(argv[0]); if( pIn==0 ) return; nIn = sqlite3_value_bytes(argv[0]); if( nIn<4 ) return; nOut = (pIn[0]<<24) + (pIn[1]<<16) + (pIn[2]<<8) + pIn[3]; pOut = sqlite3_malloc( nOut+1 ); rc = uncompress(pOut, &nOut, &pIn[4], nIn-4); if( rc==Z_OK ){ sqlite3_result_blob(context, pOut, nOut, sqlite3_free); }else if( rc==Z_MEM_ERROR ){ sqlite3_free(pOut); sqlite3_result_error_nomem(context); }else{ sqlite3_free(pOut); sqlite3_result_error(context, "input is not zlib compressed", -1); } } /* ** Implementation of the "gather_artifact_stats(X)" SQL function. ** That function merely calls the gather_artifact_stats() function ** in stat.c to populate the ARTSTAT temporary table. */ static void sqlcmd_gather_artifact_stats( sqlite3_context *context, int argc, sqlite3_value **argv ){ gather_artifact_stats(1); } /* ** Add the content(), compress(), decompress(), and ** gather_artifact_stats() SQL functions to database connection db. */ int add_content_sql_commands(sqlite3 *db){ sqlite3_create_function(db, "content", 1, SQLITE_UTF8, 0, sqlcmd_content, 0, 0); sqlite3_create_function(db, "compress", 1, SQLITE_UTF8, 0, sqlcmd_compress, 0, 0); sqlite3_create_function(db, "decompress", 1, SQLITE_UTF8, 0, sqlcmd_decompress, 0, 0); sqlite3_create_function(db, "gather_artifact_stats", 0, SQLITE_UTF8, 0, sqlcmd_gather_artifact_stats, 0, 0); return SQLITE_OK; } /* ** Undocumented test SQL functions: ** ** db_protect(X) ** db_protect_pop(X) ** ** These invoke the corresponding C routines. ** ** WARNING: ** Do not instantiate these functions for any Fossil webpage or command ** method other than the "fossil sql" command. If an attacker gains access ** to these functions, he will be able to disable other defense mechanisms. ** ** This routines are for interactiving testing only. They are experimental ** and undocumented (apart from this comments) and might go away or change ** in future releases. ** ** 2020-11-29: These functions are now only available if the "fossil sql" ** command is started with the --test option. */ static void sqlcmd_db_protect( sqlite3_context *context, int argc, sqlite3_value **argv ){ unsigned mask = 0; const char *z = (const char*)sqlite3_value_text(argv[0]); if( z!=0 && local_bSqlCmdTest ){ if( sqlite3_stricmp(z,"user")==0 ) mask |= PROTECT_USER; if( sqlite3_stricmp(z,"config")==0 ) mask |= PROTECT_CONFIG; if( sqlite3_stricmp(z,"sensitive")==0 ) mask |= PROTECT_SENSITIVE; if( sqlite3_stricmp(z,"readonly")==0 ) mask |= PROTECT_READONLY; if( sqlite3_stricmp(z,"all")==0 ) mask |= PROTECT_ALL; db_protect(mask); } } static void sqlcmd_db_protect_pop( sqlite3_context *context, int argc, sqlite3_value **argv ){ if( !local_bSqlCmdTest ) db_protect_pop(); } /* ** This is the "automatic extension" initializer that runs right after ** the connection to the repository database is opened. Set up the ** database connection to be more useful to the human operator. */ static int sqlcmd_autoinit( sqlite3 *db, const char **pzErrMsg, const void *notUsed ){ int mTrace = SQLITE_TRACE_CLOSE; add_content_sql_commands(db); db_add_aux_functions(db); re_add_sql_func(db); search_sql_setup(db); foci_register(db); deltafunc_init(db); helptext_vtab_register(db); builtin_vtab_register(db); g.repositoryOpen = 1; g.db = db; sqlite3_busy_timeout(db, 10000); sqlite3_db_config(db, SQLITE_DBCONFIG_MAINDBNAME, "repository"); db_maybe_set_encryption_key(db, g.zRepositoryName); if( g.zLocalDbName ){ char *zSql = sqlite3_mprintf("ATTACH %Q AS 'localdb' KEY ''", g.zLocalDbName); sqlite3_exec(db, zSql, 0, 0, 0); sqlite3_free(zSql); } if( g.zConfigDbName ){ char *zSql = sqlite3_mprintf("ATTACH %Q AS 'configdb' KEY ''", g.zConfigDbName); sqlite3_exec(db, zSql, 0, 0, 0); sqlite3_free(zSql); } /* Arrange to trace close operations so that static prepared statements ** will get cleaned up when the shell closes the database connection */ if( g.fSqlTrace ) mTrace |= SQLITE_TRACE_PROFILE; sqlite3_trace_v2(db, mTrace, db_sql_trace, 0); db_protect_only(PROTECT_NONE); sqlite3_set_authorizer(db, db_top_authorizer, db); if( local_bSqlCmdTest ){ sqlite3_create_function(db, "db_protect", 1, SQLITE_UTF8, 0, sqlcmd_db_protect, 0, 0); sqlite3_create_function(db, "db_protect_pop", 0, SQLITE_UTF8, 0, sqlcmd_db_protect_pop, 0, 0); sqlite3_create_function(db, "shared_secret", 2, SQLITE_UTF8, 0, sha1_shared_secret_sql_function, 0, 0); } return SQLITE_OK; } /* ** atexit() handler that cleans up global state modified by this module. */ static void sqlcmd_atexit(void) { g.zConfigDbName = 0; /* prevent panic */ } /* ** This routine is called by the sqlite3 command-line shell to ** to load the name the Fossil repository database. */ void sqlcmd_get_dbname(const char **pzRepoName){ *pzRepoName = g.zRepositoryName; } /* ** This routine is called by the sqlite3 command-line shell to do ** extra initialization prior to starting up the shell. */ void sqlcmd_init_proc(void){ sqlite3_initialize(); sqlite3_auto_extension((void(*)(void))sqlcmd_autoinit); } #if USE_SEE /* ** This routine is called by the patched sqlite3 command-line shell in order ** to load the encryption key for the open Fossil database. The memory that ** is pointed to by the value placed in pzKey must be obtained from malloc. */ void fossil_key(const char **pzKey, int *pnKey){ char *zSavedKey = db_get_saved_encryption_key(); char *zKey; size_t savedKeySize = db_get_saved_encryption_key_size(); if( !db_is_valid_saved_encryption_key(zSavedKey, savedKeySize) ) return; zKey = (char*)malloc( savedKeySize ); if( zKey ){ memcpy(zKey, zSavedKey, savedKeySize); *pzKey = zKey; if( fossil_getenv("FOSSIL_USE_SEE_TEXTKEY")==0 ){ *pnKey = (int)strlen(zKey); }else{ *pnKey = -1; } }else{ fossil_fatal("failed to allocate %u bytes for key", savedKeySize); } } #endif /* ** This routine closes the Fossil databases and/or invalidates the global ** state variables that keep track of them. */ static void fossil_close(int bDb, int noRepository){ if( bDb ) db_close(1); if( noRepository ) g.zRepositoryName = 0; g.db = 0; g.repositoryOpen = 0; g.localOpen = 0; } /* ** COMMAND: sql ** COMMAND: sqlite3* ** ** Usage: %fossil sql ?OPTIONS? ** ** Run the sqlite3 command-line shell on the Fossil repository ** identified by the -R option, or on the current repository. ** See https://www.sqlite.org/cli.html for additional information about ** the sqlite3 command-line shell. ** ** WARNING: Careless use of this command can corrupt a Fossil repository ** in ways that are unrecoverable. Be sure you know what you are doing before ** running any SQL commands that modify the repository database. Use the ** --readonly option to prevent accidental damage to the repository. ** ** Options: ** --no-repository Skip opening the repository database ** --readonly Open the repository read-only. No changes ** are allowed. This is a recommended safety ** precaution to prevent repository damage. ** -R REPOSITORY Use REPOSITORY as the repository database ** --test Enable some testing and analysis features ** that are normally disabled. ** ** All of the standard sqlite3 command-line shell options should also ** work. ** ** The following SQL extensions are provided with this Fossil-enhanced ** version of the sqlite3 command-line shell: ** ** builtin A virtual table that contains one row for ** each datafile that is built into the Fossil ** binary. ** ** checkin_mtime(X,Y) Return the mtime for the file Y (a BLOB.RID) ** found in check-in X (another BLOB.RID value). ** ** compress(X) Compress text X with the same algorithm used ** to compress artifacts in the BLOB table. ** ** content(X) Return the content of artifact X. X can be an ** artifact hash or hash prefix or a tag. Artifacts ** are stored compressed and deltaed. This function ** does all necessary decompression and undeltaing. ** ** decompress(X) Decompress text X. Undoes the work of ** compress(X). ** ** delta_apply(X,D) Apply delta D to source blob X and return ** the result. ** ** delta_create(X,Y) Create and return a delta that will convert ** X into Y. ** ** delta_output_size(D) Return the number of bytes of output to expect ** when applying delta D ** ** delta_parse(D) A table-valued function that deconstructs ** delta D and returns rows for each element of ** that delta. ** ** files_of_checkin(X) A table-valued function that returns info on ** all files contained in check-in X. Example: ** ** SELECT * FROM files_of_checkin('trunk'); ** ** helptext A virtual table with one row for each command, ** webpage, and setting together with the built-in ** help text. ** ** now() Return the number of seconds since 1970. ** ** obscure(T) Obfuscate the text password T so that its ** original value is not readily visible. Fossil ** uses this same algorithm when storing passwords ** of remote URLs. ** ** regexp The REGEXP operator works, unlike in ** standard SQLite. ** ** symbolic_name_to_rid(X) Return the BLOB.RID corresponding to symbolic ** name X. */ void cmd_sqlite3(void){ int noRepository; char *zConfigDb; extern int sqlite3_shell(int, char**); #ifdef FOSSIL_ENABLE_TH1_HOOKS g.fNoThHook = 1; #endif noRepository = find_option("no-repository", 0, 0)!=0; local_bSqlCmdTest = find_option("test",0,0)!=0; if( !noRepository ){ db_find_and_open_repository(OPEN_ANY_SCHEMA, 0); } db_open_config(1,0); zConfigDb = fossil_strdup(g.zConfigDbName); fossil_close(1, noRepository); sqlite3_shutdown(); #ifndef _WIN32 linenoiseSetMultiLine(1); #endif atexit(sqlcmd_atexit); g.zConfigDbName = zConfigDb; g.argv[1] = "-quote"; sqlite3_shell(g.argc, g.argv); sqlite3_cancel_auto_extension((void(*)(void))sqlcmd_autoinit); fossil_close(0, noRepository); }