aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/attach.c9
-rw-r--r--src/auth.c14
-rw-r--r--src/build.c40
-rw-r--r--src/ctime.c3
-rw-r--r--src/func.c3
-rw-r--r--src/legacy.c2
-rw-r--r--src/main.c5
-rw-r--r--src/pragma.c6
-rw-r--r--src/prepare.c7
-rw-r--r--src/shell.c68
-rw-r--r--src/sqlite.h.in1
-rw-r--r--src/sqliteInt.h46
-rw-r--r--src/tclsqlite.c11
-rw-r--r--src/test1.c133
-rw-r--r--src/test_config.c6
15 files changed, 341 insertions, 13 deletions
diff --git a/src/attach.c b/src/attach.c
index 89050fd9d..cf52bb24b 100644
--- a/src/attach.c
+++ b/src/attach.c
@@ -207,6 +207,15 @@ static void attachFunc(
rc = sqlite3Init(db, &zErrDyn);
sqlite3BtreeLeaveAll(db);
}
+#ifdef SQLITE_USER_AUTHENTICATION
+ if( rc==SQLITE_OK ){
+ u8 newAuth = 0;
+ rc = sqlite3UserAuthCheckLogin(db, zName, &newAuth);
+ if( newAuth<db->auth.authLevel ){
+ rc = SQLITE_AUTH_USER;
+ }
+ }
+#endif
if( rc ){
int iDb = db->nDb - 1;
assert( iDb>=2 );
diff --git a/src/auth.c b/src/auth.c
index d38bb836a..1680c9a7c 100644
--- a/src/auth.c
+++ b/src/auth.c
@@ -73,7 +73,7 @@ int sqlite3_set_authorizer(
void *pArg
){
sqlite3_mutex_enter(db->mutex);
- db->xAuth = xAuth;
+ db->xAuth = (sqlite3_xauth)xAuth;
db->pAuthArg = pArg;
sqlite3ExpirePreparedStatements(db);
sqlite3_mutex_leave(db->mutex);
@@ -108,7 +108,11 @@ int sqlite3AuthReadCol(
char *zDb = db->aDb[iDb].zName; /* Name of attached database */
int rc; /* Auth callback return code */
- rc = db->xAuth(db->pAuthArg, SQLITE_READ, zTab,zCol,zDb,pParse->zAuthContext);
+ rc = db->xAuth(db->pAuthArg, SQLITE_READ, zTab,zCol,zDb,pParse->zAuthContext
+#ifdef SQLITE_USER_AUTHENTICATION
+ ,db->auth.zAuthUser
+#endif
+ );
if( rc==SQLITE_DENY ){
if( db->nDb>2 || iDb!=0 ){
sqlite3ErrorMsg(pParse, "access to %s.%s.%s is prohibited",zDb,zTab,zCol);
@@ -208,7 +212,11 @@ int sqlite3AuthCheck(
if( db->xAuth==0 ){
return SQLITE_OK;
}
- rc = db->xAuth(db->pAuthArg, code, zArg1, zArg2, zArg3, pParse->zAuthContext);
+ rc = db->xAuth(db->pAuthArg, code, zArg1, zArg2, zArg3, pParse->zAuthContext
+#ifdef SQLITE_USER_AUTHENTICATION
+ ,db->auth.zAuthUser
+#endif
+ );
if( rc==SQLITE_DENY ){
sqlite3ErrorMsg(pParse, "not authorized");
pParse->rc = SQLITE_AUTH;
diff --git a/src/build.c b/src/build.c
index 6d54befbc..791f6f203 100644
--- a/src/build.c
+++ b/src/build.c
@@ -156,6 +156,17 @@ void sqlite3FinishCoding(Parse *pParse){
while( sqlite3VdbeDeletePriorOpcode(v, OP_Close) ){}
sqlite3VdbeAddOp0(v, OP_Halt);
+#if SQLITE_USER_AUTHENTICATION
+ if( pParse->nTableLock>0 && db->init.busy==0 ){
+ sqlite3UserAuthInit(db);
+ if( db->auth.authLevel<UAUTH_User ){
+ pParse->rc = SQLITE_AUTH_USER;
+ sqlite3ErrorMsg(pParse, "user not authenticated");
+ return;
+ }
+ }
+#endif
+
/* The cookie mask contains one bit for each database file open.
** (Bit 0 is for main, bit 1 is for temp, and so forth.) Bits are
** set for each database that is used. Generate code to start a
@@ -271,6 +282,16 @@ void sqlite3NestedParse(Parse *pParse, const char *zFormat, ...){
pParse->nested--;
}
+#if SQLITE_USER_AUTHENTICATION
+/*
+** Return TRUE if zTable is the name of the system table that stores the
+** list of users and their access credentials.
+*/
+int sqlite3UserAuthTable(const char *zTable){
+ return sqlite3_stricmp(zTable, "sqlite_user")==0;
+}
+#endif
+
/*
** Locate the in-memory structure that describes a particular database
** table given the name of that table and (optionally) the name of the
@@ -289,6 +310,13 @@ Table *sqlite3FindTable(sqlite3 *db, const char *zName, const char *zDatabase){
assert( zName!=0 );
/* All mutexes are required for schema access. Make sure we hold them. */
assert( zDatabase!=0 || sqlite3BtreeHoldsAllMutexes(db) );
+#if SQLITE_USER_AUTHENTICATION
+ /* Only the admin user is allowed to know that the sqlite_user table
+ ** exists */
+ if( db->auth.authLevel<UAUTH_Admin && sqlite3UserAuthTable(zName)!=0 ){
+ return 0;
+ }
+#endif
for(i=OMIT_TEMPDB; i<db->nDb; i++){
int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */
if( zDatabase!=0 && sqlite3StrICmp(zDatabase, db->aDb[j].zName) ) continue;
@@ -333,6 +361,12 @@ Table *sqlite3LocateTable(
}
pParse->checkSchema = 1;
}
+#if SQLITE_USER_AUTHENICATION
+ else if( pParse->db->auth.authLevel<UAUTH_User ){
+ sqlite3ErrorMsg(pParse, "user not authenticated");
+ p = 0;
+ }
+#endif
return p;
}
@@ -2052,7 +2086,7 @@ int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){
int nErr = 0; /* Number of errors encountered */
int n; /* Temporarily holds the number of cursors assigned */
sqlite3 *db = pParse->db; /* Database connection for malloc errors */
- int (*xAuth)(void*,int,const char*,const char*,const char*,const char*);
+ sqlite3_xauth xAuth; /* Saved xAuth pointer */
assert( pTable );
@@ -2867,6 +2901,10 @@ Index *sqlite3CreateIndex(
assert( pTab!=0 );
assert( pParse->nErr==0 );
if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0
+ && db->init.busy==0
+#if SQLITE_USER_AUTHENTICATION
+ && sqlite3UserAuthTable(pTab->zName)==0
+#endif
&& sqlite3StrNICmp(&pTab->zName[7],"altertab_",9)!=0 ){
sqlite3ErrorMsg(pParse, "table %s may not be indexed", pTab->zName);
goto exit_create_index;
diff --git a/src/ctime.c b/src/ctime.c
index 286f66e06..6f7ac8fcb 100644
--- a/src/ctime.c
+++ b/src/ctime.c
@@ -368,6 +368,9 @@ static const char * const azCompileOpt[] = {
#ifdef SQLITE_USE_ALLOCA
"USE_ALLOCA",
#endif
+#ifdef SQLITE_USER_AUTHENTICATION
+ "USER_AUTHENTICATION",
+#endif
#ifdef SQLITE_WIN32_MALLOC
"WIN32_MALLOC",
#endif
diff --git a/src/func.c b/src/func.c
index e338ab842..94ad1b62d 100644
--- a/src/func.c
+++ b/src/func.c
@@ -1695,6 +1695,9 @@ void sqlite3RegisterGlobalFunctions(void){
FUNCTION(sqlite_version, 0, 0, 0, versionFunc ),
FUNCTION(sqlite_source_id, 0, 0, 0, sourceidFunc ),
FUNCTION(sqlite_log, 2, 0, 0, errlogFunc ),
+#if SQLITE_USER_AUTHENTICATION
+ FUNCTION(sqlite_crypt, 2, 0, 0, sqlite3CryptFunc ),
+#endif
#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS
FUNCTION(sqlite_compileoption_used,1, 0, 0, compileoptionusedFunc ),
FUNCTION(sqlite_compileoption_get, 1, 0, 0, compileoptiongetFunc ),
diff --git a/src/legacy.c b/src/legacy.c
index b8cb90d70..a10006e55 100644
--- a/src/legacy.c
+++ b/src/legacy.c
@@ -125,7 +125,7 @@ exec_out:
sqlite3DbFree(db, azCols);
rc = sqlite3ApiExit(db, rc);
- if( rc!=SQLITE_OK && ALWAYS(rc==sqlite3_errcode(db)) && pzErrMsg ){
+ if( rc!=SQLITE_OK && pzErrMsg ){
int nErrMsg = 1 + sqlite3Strlen30(sqlite3_errmsg(db));
*pzErrMsg = sqlite3Malloc(nErrMsg);
if( *pzErrMsg ){
diff --git a/src/main.c b/src/main.c
index 977c786f3..231890de4 100644
--- a/src/main.c
+++ b/src/main.c
@@ -985,6 +985,10 @@ void sqlite3LeaveMutexAndCloseZombie(sqlite3 *db){
sqlite3Error(db, SQLITE_OK); /* Deallocates any cached error strings. */
sqlite3ValueFree(db->pErr);
sqlite3CloseExtensions(db);
+#if SQLITE_USER_AUTHENTICATION
+ sqlite3_free(db->auth.zAuthUser);
+ sqlite3_free(db->auth.zAuthPW);
+#endif
db->magic = SQLITE_MAGIC_ERROR;
@@ -2566,7 +2570,6 @@ static int openDatabase(
db->aDb[0].pSchema = sqlite3SchemaGet(db, db->aDb[0].pBt);
db->aDb[1].pSchema = sqlite3SchemaGet(db, 0);
-
/* The default safety_level for the main database is 'full'; for the temp
** database it is 'NONE'. This matches the pager layer defaults.
*/
diff --git a/src/pragma.c b/src/pragma.c
index 12446125f..543f265ba 100644
--- a/src/pragma.c
+++ b/src/pragma.c
@@ -1397,6 +1397,12 @@ void sqlite3Pragma(
** in auto-commit mode. */
mask &= ~(SQLITE_ForeignKeys);
}
+#if SQLITE_USER_AUTHENTICATION
+ if( db->auth.authLevel==UAUTH_User ){
+ /* Do not allow non-admin users to modify the schema arbitrarily */
+ mask &= ~(SQLITE_WriteSchema);
+ }
+#endif
if( sqlite3GetBoolean(zRight, 0) ){
db->flags |= mask;
diff --git a/src/prepare.c b/src/prepare.c
index 5b92e8851..a05e619f3 100644
--- a/src/prepare.c
+++ b/src/prepare.c
@@ -328,7 +328,7 @@ static int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg){
db->aDb[iDb].zName, zMasterName);
#ifndef SQLITE_OMIT_AUTHORIZATION
{
- int (*xAuth)(void*,int,const char*,const char*,const char*,const char*);
+ sqlite3_xauth xAuth;
xAuth = db->xAuth;
db->xAuth = 0;
#endif
@@ -394,6 +394,7 @@ int sqlite3Init(sqlite3 *db, char **pzErrMsg){
int commit_internal = !(db->flags&SQLITE_InternChanges);
assert( sqlite3_mutex_held(db->mutex) );
+ assert( db->init.busy==0 );
rc = SQLITE_OK;
db->init.busy = 1;
for(i=0; rc==SQLITE_OK && i<db->nDb; i++){
@@ -409,8 +410,8 @@ int sqlite3Init(sqlite3 *db, char **pzErrMsg){
** schema may contain references to objects in other databases.
*/
#ifndef SQLITE_OMIT_TEMPDB
- if( rc==SQLITE_OK && ALWAYS(db->nDb>1)
- && !DbHasProperty(db, 1, DB_SchemaLoaded) ){
+ assert( db->nDb>1 );
+ if( rc==SQLITE_OK && !DbHasProperty(db, 1, DB_SchemaLoaded) ){
rc = sqlite3InitOne(db, 1, pzErrMsg);
if( rc ){
sqlite3ResetOneSchema(db, 1);
diff --git a/src/shell.c b/src/shell.c
index afe01ef1a..ec83b1391 100644
--- a/src/shell.c
+++ b/src/shell.c
@@ -33,6 +33,9 @@
#include <stdio.h>
#include <assert.h>
#include "sqlite3.h"
+#if SQLITE_USER_AUTHENTICATION
+# include "sqlite3userauth.h"
+#endif
#include <ctype.h>
#include <stdarg.h>
@@ -3435,6 +3438,71 @@ static int do_meta_command(char *zLine, ShellState *p){
#endif
}else
+#if SQLITE_USER_AUTHENTICATION
+ if( c=='u' && strncmp(azArg[0], "user", n)==0 ){
+ if( nArg<2 ){
+ fprintf(stderr, "Usage: .user SUBCOMMAND ...\n");
+ rc = 1;
+ goto meta_command_exit;
+ }
+ open_db(p, 0);
+ if( strcmp(azArg[1],"login")==0 ){
+ if( nArg!=4 ){
+ fprintf(stderr, "Usage: .user login USER PASSWORD\n");
+ rc = 1;
+ goto meta_command_exit;
+ }
+ rc = sqlite3_user_authenticate(p->db, azArg[2], azArg[3],
+ (int)strlen(azArg[3]));
+ if( rc ){
+ fprintf(stderr, "Authentication failed for user %s\n", azArg[2]);
+ rc = 1;
+ }
+ }else if( strcmp(azArg[1],"add")==0 ){
+ if( nArg!=5 ){
+ fprintf(stderr, "Usage: .user add USER PASSWORD ISADMIN\n");
+ rc = 1;
+ goto meta_command_exit;
+ }
+ rc = sqlite3_user_add(p->db, azArg[2],
+ azArg[3], (int)strlen(azArg[3]),
+ booleanValue(azArg[4]));
+ if( rc ){
+ fprintf(stderr, "User-Add failed: %d\n", rc);
+ rc = 1;
+ }
+ }else if( strcmp(azArg[1],"edit")==0 ){
+ if( nArg!=5 ){
+ fprintf(stderr, "Usage: .user edit USER PASSWORD ISADMIN\n");
+ rc = 1;
+ goto meta_command_exit;
+ }
+ rc = sqlite3_user_change(p->db, azArg[2],
+ azArg[3], (int)strlen(azArg[3]),
+ booleanValue(azArg[4]));
+ if( rc ){
+ fprintf(stderr, "User-Edit failed: %d\n", rc);
+ rc = 1;
+ }
+ }else if( strcmp(azArg[1],"delete")==0 ){
+ if( nArg!=3 ){
+ fprintf(stderr, "Usage: .user delete USER\n");
+ rc = 1;
+ goto meta_command_exit;
+ }
+ rc = sqlite3_user_delete(p->db, azArg[2]);
+ if( rc ){
+ fprintf(stderr, "User-Delete failed: %d\n", rc);
+ rc = 1;
+ }
+ }else{
+ fprintf(stderr, "Usage: .user login|add|edit|delete ...\n");
+ rc = 1;
+ goto meta_command_exit;
+ }
+ }else
+#endif /* SQLITE_USER_AUTHENTICATION */
+
if( c=='v' && strncmp(azArg[0], "version", n)==0 ){
fprintf(p->out, "SQLite %s %s\n" /*extra-version-info*/,
sqlite3_libversion(), sqlite3_sourceid());
diff --git a/src/sqlite.h.in b/src/sqlite.h.in
index 56dede8ba..e947c3e19 100644
--- a/src/sqlite.h.in
+++ b/src/sqlite.h.in
@@ -492,6 +492,7 @@ int sqlite3_exec(
#define SQLITE_NOTICE_RECOVER_WAL (SQLITE_NOTICE | (1<<8))
#define SQLITE_NOTICE_RECOVER_ROLLBACK (SQLITE_NOTICE | (2<<8))
#define SQLITE_WARNING_AUTOINDEX (SQLITE_WARNING | (1<<8))
+#define SQLITE_AUTH_USER (SQLITE_AUTH | (1<<8))
/*
** CAPI3REF: Flags For File Open Operations
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index ceacdbb33..805c925f7 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -988,6 +988,45 @@ struct FuncDefHash {
FuncDef *a[23]; /* Hash table for functions */
};
+#ifdef SQLITE_USER_AUTHENTICATION
+/*
+** Information held in the "sqlite3" database connection object and used
+** to manage user authentication.
+*/
+typedef struct sqlite3_userauth sqlite3_userauth;
+struct sqlite3_userauth {
+ u8 authLevel; /* Current authentication level */
+ int nAuthPW; /* Size of the zAuthPW in bytes */
+ char *zAuthPW; /* Password used to authenticate */
+ char *zAuthUser; /* User name used to authenticate */
+};
+
+/* Allowed values for sqlite3_userauth.authLevel */
+#define UAUTH_Unknown 0 /* Authentication not yet checked */
+#define UAUTH_Fail 1 /* User authentication failed */
+#define UAUTH_User 2 /* Authenticated as a normal user */
+#define UAUTH_Admin 3 /* Authenticated as an administrator */
+
+/* Functions used only by user authorization logic */
+int sqlite3UserAuthTable(const char*);
+int sqlite3UserAuthCheckLogin(sqlite3*,const char*,u8*);
+void sqlite3UserAuthInit(sqlite3*);
+void sqlite3CryptFunc(sqlite3_context*,int,sqlite3_value**);
+
+#endif /* SQLITE_USER_AUTHENTICATION */
+
+/*
+** typedef for the authorization callback function.
+*/
+#ifdef SQLITE_USER_AUTHENTICATION
+ typedef int (*sqlite3_xauth)(void*,int,const char*,const char*,const char*,
+ const char*, const char*);
+#else
+ typedef int (*sqlite3_xauth)(void*,int,const char*,const char*,const char*,
+ const char*);
+#endif
+
+
/*
** Each database connection is an instance of the following structure.
*/
@@ -1055,8 +1094,7 @@ struct sqlite3 {
} u1;
Lookaside lookaside; /* Lookaside malloc configuration */
#ifndef SQLITE_OMIT_AUTHORIZATION
- int (*xAuth)(void*,int,const char*,const char*,const char*,const char*);
- /* Access authorization function */
+ sqlite3_xauth xAuth; /* Access authorization function */
void *pAuthArg; /* 1st argument to the access auth function */
#endif
#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
@@ -1082,7 +1120,6 @@ struct sqlite3 {
i64 nDeferredCons; /* Net deferred constraints this transaction. */
i64 nDeferredImmCons; /* Net deferred immediate constraints */
int *pnBytesFreed; /* If not NULL, increment this in DbFree() */
-
#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
/* The following variables are all protected by the STATIC_MASTER
** mutex, not by sqlite3.mutex. They are used by code in notify.c.
@@ -1100,6 +1137,9 @@ struct sqlite3 {
void (*xUnlockNotify)(void **, int); /* Unlock notify callback */
sqlite3 *pNextBlocked; /* Next in list of all blocked connections */
#endif
+#ifdef SQLITE_USER_AUTHENTICATION
+ sqlite3_userauth auth; /* User authentication information */
+#endif
};
/*
diff --git a/src/tclsqlite.c b/src/tclsqlite.c
index 945fd9598..756d0daa5 100644
--- a/src/tclsqlite.c
+++ b/src/tclsqlite.c
@@ -872,6 +872,9 @@ static int auth_callback(
const char *zArg2,
const char *zArg3,
const char *zArg4
+#ifdef SQLITE_USER_AUTHENTICATION
+ ,const char *zArg5
+#endif
){
const char *zCode;
Tcl_DString str;
@@ -924,6 +927,9 @@ static int auth_callback(
Tcl_DStringAppendElement(&str, zArg2 ? zArg2 : "");
Tcl_DStringAppendElement(&str, zArg3 ? zArg3 : "");
Tcl_DStringAppendElement(&str, zArg4 ? zArg4 : "");
+#ifdef SQLITE_USER_AUTHENTICATION
+ Tcl_DStringAppendElement(&str, zArg5 ? zArg5 : "");
+#endif
rc = Tcl_GlobalEval(pDb->interp, Tcl_DStringValue(&str));
Tcl_DStringFree(&str);
zReply = rc==TCL_OK ? Tcl_GetStringResult(pDb->interp) : "SQLITE_DENY";
@@ -1700,8 +1706,11 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
pDb->zAuth = 0;
}
if( pDb->zAuth ){
+ typedef int (*sqlite3_auth_cb)(
+ void*,int,const char*,const char*,
+ const char*,const char*);
pDb->interp = interp;
- sqlite3_set_authorizer(pDb->db, auth_callback, pDb);
+ sqlite3_set_authorizer(pDb->db,(sqlite3_auth_cb)auth_callback,pDb);
}else{
sqlite3_set_authorizer(pDb->db, 0, 0);
}
diff --git a/src/test1.c b/src/test1.c
index d050e683f..62b575989 100644
--- a/src/test1.c
+++ b/src/test1.c
@@ -6497,6 +6497,132 @@ static int sorter_test_sort4_helper(
}
+#ifdef SQLITE_USER_AUTHENTICATION
+#include "sqlite3userauth.h"
+/*
+** tclcmd: sqlite3_user_authenticate DB USERNAME PASSWORD
+*/
+static int test_user_authenticate(
+ ClientData clientData, /* Unused */
+ Tcl_Interp *interp, /* The TCL interpreter that invoked this command */
+ int objc, /* Number of arguments */
+ Tcl_Obj *CONST objv[] /* Command arguments */
+){
+ char *zUser = 0;
+ char *zPasswd = 0;
+ int nPasswd = 0;
+ sqlite3 *db;
+ int rc;
+
+ if( objc!=4 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "DB USERNAME PASSWORD");
+ return TCL_ERROR;
+ }
+ if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){
+ return TCL_ERROR;
+ }
+ zUser = Tcl_GetString(objv[2]);
+ zPasswd = Tcl_GetStringFromObj(objv[3], &nPasswd);
+ rc = sqlite3_user_authenticate(db, zUser, zPasswd, nPasswd);
+ Tcl_SetResult(interp, (char *)t1ErrorName(rc), TCL_STATIC);
+ return TCL_OK;
+}
+#endif /* SQLITE_USER_AUTHENTICATION */
+
+#ifdef SQLITE_USER_AUTHENTICATION
+/*
+** tclcmd: sqlite3_user_add DB USERNAME PASSWORD ISADMIN
+*/
+static int test_user_add(
+ ClientData clientData, /* Unused */
+ Tcl_Interp *interp, /* The TCL interpreter that invoked this command */
+ int objc, /* Number of arguments */
+ Tcl_Obj *CONST objv[] /* Command arguments */
+){
+ char *zUser = 0;
+ char *zPasswd = 0;
+ int nPasswd = 0;
+ int isAdmin = 0;
+ sqlite3 *db;
+ int rc;
+
+ if( objc!=5 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "DB USERNAME PASSWORD ISADMIN");
+ return TCL_ERROR;
+ }
+ if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){
+ return TCL_ERROR;
+ }
+ zUser = Tcl_GetString(objv[2]);
+ zPasswd = Tcl_GetStringFromObj(objv[3], &nPasswd);
+ Tcl_GetBooleanFromObj(interp, objv[4], &isAdmin);
+ rc = sqlite3_user_add(db, zUser, zPasswd, nPasswd, isAdmin);
+ Tcl_SetResult(interp, (char *)t1ErrorName(rc), TCL_STATIC);
+ return TCL_OK;
+}
+#endif /* SQLITE_USER_AUTHENTICATION */
+
+#ifdef SQLITE_USER_AUTHENTICATION
+/*
+** tclcmd: sqlite3_user_change DB USERNAME PASSWORD ISADMIN
+*/
+static int test_user_change(
+ ClientData clientData, /* Unused */
+ Tcl_Interp *interp, /* The TCL interpreter that invoked this command */
+ int objc, /* Number of arguments */
+ Tcl_Obj *CONST objv[] /* Command arguments */
+){
+ char *zUser = 0;
+ char *zPasswd = 0;
+ int nPasswd = 0;
+ int isAdmin = 0;
+ sqlite3 *db;
+ int rc;
+
+ if( objc!=5 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "DB USERNAME PASSWORD ISADMIN");
+ return TCL_ERROR;
+ }
+ if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){
+ return TCL_ERROR;
+ }
+ zUser = Tcl_GetString(objv[2]);
+ zPasswd = Tcl_GetStringFromObj(objv[3], &nPasswd);
+ Tcl_GetBooleanFromObj(interp, objv[4], &isAdmin);
+ rc = sqlite3_user_change(db, zUser, zPasswd, nPasswd, isAdmin);
+ Tcl_SetResult(interp, (char *)t1ErrorName(rc), TCL_STATIC);
+ return TCL_OK;
+}
+#endif /* SQLITE_USER_AUTHENTICATION */
+
+#ifdef SQLITE_USER_AUTHENTICATION
+/*
+** tclcmd: sqlite3_user_delete DB USERNAME
+*/
+static int test_user_delete(
+ ClientData clientData, /* Unused */
+ Tcl_Interp *interp, /* The TCL interpreter that invoked this command */
+ int objc, /* Number of arguments */
+ Tcl_Obj *CONST objv[] /* Command arguments */
+){
+ char *zUser = 0;
+ sqlite3 *db;
+ int rc;
+
+ if( objc!=3 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "DB USERNAME");
+ return TCL_ERROR;
+ }
+ if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){
+ return TCL_ERROR;
+ }
+ zUser = Tcl_GetString(objv[2]);
+ rc = sqlite3_user_delete(db, zUser);
+ Tcl_SetResult(interp, (char *)t1ErrorName(rc), TCL_STATIC);
+ return TCL_OK;
+}
+#endif /* SQLITE_USER_AUTHENTICATION */
+
/*
** Register commands with the TCL interpreter.
*/
@@ -6734,6 +6860,13 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
{ "load_static_extension", tclLoadStaticExtensionCmd },
{ "sorter_test_fakeheap", sorter_test_fakeheap },
{ "sorter_test_sort4_helper", sorter_test_sort4_helper },
+#ifdef SQLITE_USER_AUTHENTICATION
+ { "sqlite3_user_authenticate", test_user_authenticate, 0 },
+ { "sqlite3_user_add", test_user_add, 0 },
+ { "sqlite3_user_change", test_user_change, 0 },
+ { "sqlite3_user_delete", test_user_delete, 0 },
+#endif
+
};
static int bitmask_size = sizeof(Bitmask)*8;
int i;
diff --git a/src/test_config.c b/src/test_config.c
index 78c65a8e5..074faf211 100644
--- a/src/test_config.c
+++ b/src/test_config.c
@@ -603,6 +603,12 @@ Tcl_SetVar2(interp, "sqlite_options", "mergesort", "1", TCL_GLOBAL_ONLY);
Tcl_SetVar2(interp, "sqlite_options", "secure_delete", "0", TCL_GLOBAL_ONLY);
#endif
+#ifdef SQLITE_USER_AUTHENTICATION
+ Tcl_SetVar2(interp, "sqlite_options", "userauth", "1", TCL_GLOBAL_ONLY);
+#else
+ Tcl_SetVar2(interp, "sqlite_options", "userauth", "0", TCL_GLOBAL_ONLY);
+#endif
+
#ifdef SQLITE_MULTIPLEX_EXT_OVWR
Tcl_SetVar2(interp, "sqlite_options", "multiplex_ext_overwrite", "1", TCL_GLOBAL_ONLY);
#else