Merge pull request #586 from mattn/feature/userauth

Feature/userauth
This commit is contained in:
mattn 2018-06-08 10:05:37 +09:00 committed by GitHub
commit 4218441e44
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 2955 additions and 115 deletions

7
.gitignore vendored
View File

@ -5,3 +5,10 @@
# VSCode
.vscode
# Exclude from upgrade
upgrade/*.c
upgrade/*.h
# Exclude upgrade binary
upgrade/upgrade

View File

@ -87,6 +87,7 @@ before_install:
brew upgrade icu4c
fi
- |
go get github.com/smartystreets/goconvey
if [[ "${GOOS}" != "windows" ]]; then
go get github.com/mattn/goveralls
go get golang.org/x/tools/cmd/cover

121
README.md
View File

@ -33,6 +33,10 @@ Supported Golang version:
- [Mac OSX](#mac-osx)
- [Windows](#windows)
- [Errors](#errors)
- [User Authentication](#user-authentication)
- [Compile](#compile)
- [Usage](#usage)
-
- [Extensions](#extensions)
- [Spatialite](#spatialite)
- [FAQ](#faq)
@ -76,6 +80,11 @@ Boolean values can be one of:
| Name | Key | Value(s) | Description |
|------|-----|----------|-------------|
| UA - Create | `_auth` | - | Create User Authentication, for more information see [User Authentication](#user-authentication) |
| UA - Username | `_auth_user` | `string` | Username for User Authentication, for more information see [User Authentication](#user-authentication) |
| UA - Password | `_auth_pass` | `string` | Password for User Authentication, for more information see [User Authentication](#user-authentication) |
| UA - Crypt | `_auth_crypt` | <ul><li>SHA1</li><li>SSHA1</li><li>SHA256</li><li>SSHA256</li><li>SHA384</li><li>SSHA384</li><li>SHA512</li><li>SSHA512</li></ul> | Password encoder to use for User Authentication, for more information see [User Authentication](#user-authentication) |
| UA - Salt | `_auth_salt` | `string` | Salt to use if the configure password encoder requires a salt, for User Authentication, for more information see [User Authentication](#user-authentication) |
| Auto Vacuum | `_auto_vacuum` \| `_vacuum` | <ul><li>`0` \| `none`</li><li>`1` \| `full`</li><li>`2` \| `incremental`</li></ul> | For more information see [PRAGMA auto_vacuum](https://www.sqlite.org/pragma.html#pragma_auto_vacuum) |
| Busy Timeout | `_busy_timeout` \| `_timeout` | `int` | Specify value for sqlite3_busy_timeout. For more information see [PRAGMA busy_timeout](https://www.sqlite.org/pragma.html#pragma_busy_timeout) |
| Case Sensitive LIKE | `_case_sensitive_like` \| `_cslike` | `boolean` | For more information see [PRAGMA case_sensitive_like](https://www.sqlite.org/pragma.html#pragma_case_sensitive_like) |
@ -144,6 +153,7 @@ go build --tags "icu json1 fts5 secure_delete"
| Secure Delete | sqlite_secure_delete | This compile-time option changes the default setting of the secure_delete pragma.<br><br>When this option is not used, secure_delete defaults to off. When this option is present, secure_delete defaults to on.<br><br>The secure_delete setting causes deleted content to be overwritten with zeros. There is a small performance penalty since additional I/O must occur.<br><br>On the other hand, secure_delete can prevent fragments of sensitive information from lingering in unused parts of the database file after it has been deleted. See the documentation on the secure_delete pragma for additional information |
| Secure Delete (FAST) | sqlite_secure_delete_fast | For more information see [PRAGMA secure_delete](https://www.sqlite.org/pragma.html#pragma_secure_delete) |
| Tracing / Debug | sqlite_trace | Activate trace functions |
| User Authentication | sqlite_userauth | SQLite User Authentication see [User Authentication](#user-authentication) for more information. |
# Compilation
@ -303,6 +313,117 @@ For example the TDM-GCC Toolchain can be found [here](ttps://sourceforge.net/pro
go install github.com/mattn/go-sqlite3
```
# User Authentication
This package supports the SQLite User Authentication module.
## Compile
To use the User authentication module the package has to be compiled with the tag `sqlite_userauth`. See [Features](#features).
## Usage
### Create protected database
To create a database protected by user authentication provide the following argument to the connection string `_auth`.
This will enable user authentication within the database. This option however requires two additional arguments:
- `_auth_user`
- `_auth_pass`
When `_auth` is present on the connection string user authentication will be enabled and the provided user will be created
as an `admin` user. After initial creation, the parameter `_auth` has no effect anymore and can be omitted from the connection string.
Example connection string:
Create an user authentication database with user `admin` and password `admin`.
`file:test.s3db?_auth&_auth_user=admin&_auth_pass=admin`
Create an user authentication database with user `admin` and password `admin` and use `SHA1` for the password encoding.
`file:test.s3db?_auth&_auth_user=admin&_auth_pass=admin&_auth_crypt=sha1`
### Password Encoding
The passwords within the user authentication module of SQLite are encoded with the SQLite function `sqlite_cryp`.
This function uses a ceasar-cypher which is quite insecure.
This library provides several additional password encoders which can be configured through the connection string.
The password cypher can be configured with the key `_auth_crypt`. And if the configured password encoder also requires an
salt this can be configured with `_auth_salt`.
#### Available Encoders
- SHA1
- SSHA1 (Salted SHA1)
- SHA256
- SSHA256 (salted SHA256)
- SHA384
- SSHA384 (salted SHA384)
- SHA512
- SSHA512 (salted SHA512)
### Restrictions
Operations on the database regarding to user management can only be preformed by an administrator user.
### Support
The user authentication supports two kinds of users
- administrators
- regular users
### User Management
User management can be done by directly using the `*SQLiteConn` or by SQL.
#### SQL
The following sql functions are available for user management.
| Function | Arguments | Description |
|----------|-----------|-------------|
| `authenticate` | username `string`, password `string` | Will authenticate an user, this is done by the connection; and should not be used manually. |
| `auth_user_add` | username `string`, password `string`, admin `int` | This function will add an user to the database.<br>if the database is not protected by user authentication it will enable it. Argument `admin` is an integer identifying if the added user should be an administrator. Only Administrators can add administrators. |
| `auth_user_change` | username `string`, password `string`, admin `int` | Function to modify an user. Users can change their own password, but only an administrator can change the administrator flag. |
| `authUserDelete` | username `string` | Delete an user from the database. Can only be used by an administrator. The current logged in administrator cannot be deleted. This is to make sure their is always an administrator remaining. |
These functions will return an integer.
- 0 (SQLITE_OK)
- 23 (SQLITE_AUTH) Failed to perform due to authentication or insufficient privileges
##### Examples
```sql
// Autheticate user
// Create Admin User
SELECT auth_user_add('admin2', 'admin2', 1);
// Change password for user
SELECT auth_user_change('user', 'userpassword', 0);
// Delete user
SELECT user_delete('user');
```
#### *SQLiteConn
The following functions are available for User authentication from the `*SQLiteConn`.
| Function | Description |
|----------|-------------|
| `Authenticate(username, password string) error` | Authenticate user |
| `AuthUserAdd(username, password string, admin bool) error` | Add user |
| `AuthUserChange(username, password string, admin bool) error` | Modify user |
| `AuthUserDelete(username string) error` | Delete user |
### Attached database
When using attached databases. SQLite will use the authentication from the `main` database for the attached database(s).
# Extensions
If you want your own extension to be listed here or you want to add a reference to an extension; please submit an Issue for this.

View File

@ -331,8 +331,18 @@ func callbackRetText(ctx *C.sqlite3_context, v reflect.Value) error {
return nil
}
func callbackRetNil(ctx *C.sqlite3_context, v reflect.Value) error {
return nil
}
func callbackRet(typ reflect.Type) (callbackRetConverter, error) {
switch typ.Kind() {
case reflect.Interface:
errorInterface := reflect.TypeOf((*error)(nil)).Elem()
if typ.Implements(errorInterface) {
return callbackRetNil, nil
}
fallthrough
case reflect.Slice:
if typ.Elem().Kind() != reflect.Uint8 {
return nil, errors.New("the only supported slice type is []byte")

View File

@ -209943,4 +209943,359 @@ SQLITE_API const char *sqlite3_sourceid(void){ return SQLITE_SOURCE_ID; }
#else // USE_LIBSQLITE3
// If users really want to link against the system sqlite3 we
// need to make this file a noop.
#endif
#endif
/*
** 2014-09-08
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
**
** This file contains the bulk of the implementation of the
** user-authentication extension feature. Some parts of the user-
** authentication code are contained within the SQLite core (in the
** src/ subdirectory of the main source code tree) but those parts
** that could reasonable be separated out are moved into this file.
**
** To compile with the user-authentication feature, append this file to
** end of an SQLite amalgamation, then add the SQLITE_USER_AUTHENTICATION
** compile-time option. See the user-auth.txt file in the same source
** directory as this file for additional information.
*/
#ifdef SQLITE_USER_AUTHENTICATION
#ifndef SQLITEINT_H
# include "sqliteInt.h"
#endif
/*
** Prepare an SQL statement for use by the user authentication logic.
** Return a pointer to the prepared statement on success. Return a
** NULL pointer if there is an error of any kind.
*/
static sqlite3_stmt *sqlite3UserAuthPrepare(
sqlite3 *db,
const char *zFormat,
...
){
sqlite3_stmt *pStmt;
char *zSql;
int rc;
va_list ap;
int savedFlags = db->flags;
va_start(ap, zFormat);
zSql = sqlite3_vmprintf(zFormat, ap);
va_end(ap);
if( zSql==0 ) return 0;
db->flags |= SQLITE_WriteSchema;
rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
db->flags = savedFlags;
sqlite3_free(zSql);
if( rc ){
sqlite3_finalize(pStmt);
pStmt = 0;
}
return pStmt;
}
/*
** Check to see if the sqlite_user table exists in database zDb.
*/
static int userTableExists(sqlite3 *db, const char *zDb){
int rc;
sqlite3_mutex_enter(db->mutex);
sqlite3BtreeEnterAll(db);
if( db->init.busy==0 ){
char *zErr = 0;
sqlite3Init(db, &zErr);
sqlite3DbFree(db, zErr);
}
rc = sqlite3FindTable(db, "sqlite_user", zDb)!=0;
sqlite3BtreeLeaveAll(db);
sqlite3_mutex_leave(db->mutex);
return rc;
}
/*
** Check to see if database zDb has a "sqlite_user" table and if it does
** whether that table can authenticate zUser with nPw,zPw. Write one of
** the UAUTH_* user authorization level codes into *peAuth and return a
** result code.
*/
static int userAuthCheckLogin(
sqlite3 *db, /* The database connection to check */
const char *zDb, /* Name of specific database to check */
u8 *peAuth /* OUT: One of UAUTH_* constants */
){
sqlite3_stmt *pStmt;
int rc;
*peAuth = UAUTH_Unknown;
if( !userTableExists(db, "main") ){
*peAuth = UAUTH_Admin; /* No sqlite_user table. Everybody is admin. */
return SQLITE_OK;
}
if( db->auth.zAuthUser==0 ){
*peAuth = UAUTH_Fail;
return SQLITE_OK;
}
pStmt = sqlite3UserAuthPrepare(db,
"SELECT pw=sqlite_crypt(?1,pw), isAdmin FROM \"%w\".sqlite_user"
" WHERE uname=?2", zDb);
if( pStmt==0 ) return SQLITE_NOMEM;
sqlite3_bind_blob(pStmt, 1, db->auth.zAuthPW, db->auth.nAuthPW,SQLITE_STATIC);
sqlite3_bind_text(pStmt, 2, db->auth.zAuthUser, -1, SQLITE_STATIC);
rc = sqlite3_step(pStmt);
if( rc==SQLITE_ROW && sqlite3_column_int(pStmt,0) ){
*peAuth = sqlite3_column_int(pStmt, 1) + UAUTH_User;
}else{
*peAuth = UAUTH_Fail;
}
return sqlite3_finalize(pStmt);
}
int sqlite3UserAuthCheckLogin(
sqlite3 *db, /* The database connection to check */
const char *zDb, /* Name of specific database to check */
u8 *peAuth /* OUT: One of UAUTH_* constants */
){
int rc;
u8 savedAuthLevel;
assert( zDb!=0 );
assert( peAuth!=0 );
savedAuthLevel = db->auth.authLevel;
db->auth.authLevel = UAUTH_Admin;
rc = userAuthCheckLogin(db, zDb, peAuth);
db->auth.authLevel = savedAuthLevel;
return rc;
}
/*
** If the current authLevel is UAUTH_Unknown, the take actions to figure
** out what authLevel should be
*/
void sqlite3UserAuthInit(sqlite3 *db){
if( db->auth.authLevel==UAUTH_Unknown ){
u8 authLevel = UAUTH_Fail;
sqlite3UserAuthCheckLogin(db, "main", &authLevel);
db->auth.authLevel = authLevel;
if( authLevel<UAUTH_Admin ) db->flags &= ~SQLITE_WriteSchema;
}
}
/*
** Implementation of the sqlite_crypt(X,Y) function.
**
** If Y is NULL then generate a new hash for password X and return that
** hash. If Y is not null, then generate a hash for password X using the
** same salt as the previous hash Y and return the new hash.
*/
void sqlite3CryptFunc(
sqlite3_context *context,
int NotUsed,
sqlite3_value **argv
){
const char *zIn;
int nIn, ii;
u8 *zOut;
char zSalt[8];
zIn = sqlite3_value_blob(argv[0]);
nIn = sqlite3_value_bytes(argv[0]);
if( sqlite3_value_type(argv[1])==SQLITE_BLOB
&& sqlite3_value_bytes(argv[1])==nIn+sizeof(zSalt)
){
memcpy(zSalt, sqlite3_value_blob(argv[1]), sizeof(zSalt));
}else{
sqlite3_randomness(sizeof(zSalt), zSalt);
}
zOut = sqlite3_malloc( nIn+sizeof(zSalt) );
if( zOut==0 ){
sqlite3_result_error_nomem(context);
}else{
memcpy(zOut, zSalt, sizeof(zSalt));
for(ii=0; ii<nIn; ii++){
zOut[ii+sizeof(zSalt)] = zIn[ii]^zSalt[ii&0x7];
}
sqlite3_result_blob(context, zOut, nIn+sizeof(zSalt), sqlite3_free);
}
}
/*
** If a database contains the SQLITE_USER table, then the
** sqlite3_user_authenticate() interface must be invoked with an
** appropriate username and password prior to enable read and write
** access to the database.
**
** Return SQLITE_OK on success or SQLITE_ERROR if the username/password
** combination is incorrect or unknown.
**
** If the SQLITE_USER table is not present in the database file, then
** this interface is a harmless no-op returnning SQLITE_OK.
*/
int sqlite3_user_authenticate(
sqlite3 *db, /* The database connection */
const char *zUsername, /* Username */
const char *zPW, /* Password or credentials */
int nPW /* Number of bytes in aPW[] */
){
int rc;
u8 authLevel = UAUTH_Fail;
db->auth.authLevel = UAUTH_Unknown;
sqlite3_free(db->auth.zAuthUser);
sqlite3_free(db->auth.zAuthPW);
memset(&db->auth, 0, sizeof(db->auth));
db->auth.zAuthUser = sqlite3_mprintf("%s", zUsername);
if( db->auth.zAuthUser==0 ) return SQLITE_NOMEM;
db->auth.zAuthPW = sqlite3_malloc( nPW+1 );
if( db->auth.zAuthPW==0 ) return SQLITE_NOMEM;
memcpy(db->auth.zAuthPW,zPW,nPW);
db->auth.nAuthPW = nPW;
rc = sqlite3UserAuthCheckLogin(db, "main", &authLevel);
db->auth.authLevel = authLevel;
sqlite3ExpirePreparedStatements(db);
if( rc ){
return rc; /* OOM error, I/O error, etc. */
}
if( authLevel<UAUTH_User ){
return SQLITE_AUTH; /* Incorrect username and/or password */
}
return SQLITE_OK; /* Successful login */
}
/*
** The sqlite3_user_add() interface can be used (by an admin user only)
** to create a new user. When called on a no-authentication-required
** database, this routine converts the database into an authentication-
** required database, automatically makes the added user an
** administrator, and logs in the current connection as that user.
** The sqlite3_user_add() interface only works for the "main" database, not
** for any ATTACH-ed databases. Any call to sqlite3_user_add() by a
** non-admin user results in an error.
*/
int sqlite3_user_add(
sqlite3 *db, /* Database connection */
const char *zUsername, /* Username to be added */
const char *aPW, /* Password or credentials */
int nPW, /* Number of bytes in aPW[] */
int isAdmin /* True to give new user admin privilege */
){
sqlite3_stmt *pStmt;
int rc;
sqlite3UserAuthInit(db);
if( db->auth.authLevel<UAUTH_Admin ) return SQLITE_AUTH;
if( !userTableExists(db, "main") ){
if( !isAdmin ) return SQLITE_AUTH;
pStmt = sqlite3UserAuthPrepare(db,
"CREATE TABLE sqlite_user(\n"
" uname TEXT PRIMARY KEY,\n"
" isAdmin BOOLEAN,\n"
" pw BLOB\n"
") WITHOUT ROWID;");
if( pStmt==0 ) return SQLITE_NOMEM;
sqlite3_step(pStmt);
rc = sqlite3_finalize(pStmt);
if( rc ) return rc;
}
pStmt = sqlite3UserAuthPrepare(db,
"INSERT INTO sqlite_user(uname,isAdmin,pw)"
" VALUES(%Q,%d,sqlite_crypt(?1,NULL))",
zUsername, isAdmin!=0);
if( pStmt==0 ) return SQLITE_NOMEM;
sqlite3_bind_blob(pStmt, 1, aPW, nPW, SQLITE_STATIC);
sqlite3_step(pStmt);
rc = sqlite3_finalize(pStmt);
if( rc ) return rc;
if( db->auth.zAuthUser==0 ){
assert( isAdmin!=0 );
sqlite3_user_authenticate(db, zUsername, aPW, nPW);
}
return SQLITE_OK;
}
/*
** The sqlite3_user_change() interface can be used to change a users
** login credentials or admin privilege. Any user can change their own
** login credentials. Only an admin user can change another users login
** credentials or admin privilege setting. No user may change their own
** admin privilege setting.
*/
int sqlite3_user_change(
sqlite3 *db, /* Database connection */
const char *zUsername, /* Username to change */
const char *aPW, /* Modified password or credentials */
int nPW, /* Number of bytes in aPW[] */
int isAdmin /* Modified admin privilege for the user */
){
sqlite3_stmt *pStmt;
int rc;
u8 authLevel;
authLevel = db->auth.authLevel;
if( authLevel<UAUTH_User ){
/* Must be logged in to make a change */
return SQLITE_AUTH;
}
if( strcmp(db->auth.zAuthUser, zUsername)!=0 ){
if( db->auth.authLevel<UAUTH_Admin ){
/* Must be an administrator to change a different user */
return SQLITE_AUTH;
}
}else if( isAdmin!=(authLevel==UAUTH_Admin) ){
/* Cannot change the isAdmin setting for self */
return SQLITE_AUTH;
}
db->auth.authLevel = UAUTH_Admin;
if( !userTableExists(db, "main") ){
/* This routine is a no-op if the user to be modified does not exist */
}else{
pStmt = sqlite3UserAuthPrepare(db,
"UPDATE sqlite_user SET isAdmin=%d, pw=sqlite_crypt(?1,NULL)"
" WHERE uname=%Q", isAdmin, zUsername);
if( pStmt==0 ){
rc = SQLITE_NOMEM;
}else{
sqlite3_bind_blob(pStmt, 1, aPW, nPW, SQLITE_STATIC);
sqlite3_step(pStmt);
rc = sqlite3_finalize(pStmt);
}
}
db->auth.authLevel = authLevel;
return rc;
}
/*
** The sqlite3_user_delete() interface can be used (by an admin user only)
** to delete a user. The currently logged-in user cannot be deleted,
** which guarantees that there is always an admin user and hence that
** the database cannot be converted into a no-authentication-required
** database.
*/
int sqlite3_user_delete(
sqlite3 *db, /* Database connection */
const char *zUsername /* Username to remove */
){
sqlite3_stmt *pStmt;
if( db->auth.authLevel<UAUTH_Admin ){
/* Must be an administrator to delete a user */
return SQLITE_AUTH;
}
if( strcmp(db->auth.zAuthUser, zUsername)==0 ){
/* Cannot delete self */
return SQLITE_AUTH;
}
if( !userTableExists(db, "main") ){
/* This routine is a no-op if the user to be deleted does not exist */
return SQLITE_OK;
}
pStmt = sqlite3UserAuthPrepare(db,
"DELETE FROM sqlite_user WHERE uname=%Q", zUsername);
if( pStmt==0 ) return SQLITE_NOMEM;
sqlite3_step(pStmt);
return sqlite3_finalize(pStmt);
}
#endif /* SQLITE_USER_AUTHENTICATION */

View File

@ -11179,4 +11179,100 @@ struct fts5_api {
#else // USE_LIBSQLITE3
// If users really want to link against the system sqlite3 we
// need to make this file a noop.
#endif
#endif
/*
** 2014-09-08
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
**
** This file contains the application interface definitions for the
** user-authentication extension feature.
**
** To compile with the user-authentication feature, append this file to
** end of an SQLite amalgamation header file ("sqlite3.h"), then add
** the SQLITE_USER_AUTHENTICATION compile-time option. See the
** user-auth.txt file in the same source directory as this file for
** additional information.
*/
#ifdef SQLITE_USER_AUTHENTICATION
#ifdef __cplusplus
extern "C" {
#endif
/*
** If a database contains the SQLITE_USER table, then the
** sqlite3_user_authenticate() interface must be invoked with an
** appropriate username and password prior to enable read and write
** access to the database.
**
** Return SQLITE_OK on success or SQLITE_ERROR if the username/password
** combination is incorrect or unknown.
**
** If the SQLITE_USER table is not present in the database file, then
** this interface is a harmless no-op returnning SQLITE_OK.
*/
int sqlite3_user_authenticate(
sqlite3 *db, /* The database connection */
const char *zUsername, /* Username */
const char *aPW, /* Password or credentials */
int nPW /* Number of bytes in aPW[] */
);
/*
** The sqlite3_user_add() interface can be used (by an admin user only)
** to create a new user. When called on a no-authentication-required
** database, this routine converts the database into an authentication-
** required database, automatically makes the added user an
** administrator, and logs in the current connection as that user.
** The sqlite3_user_add() interface only works for the "main" database, not
** for any ATTACH-ed databases. Any call to sqlite3_user_add() by a
** non-admin user results in an error.
*/
int sqlite3_user_add(
sqlite3 *db, /* Database connection */
const char *zUsername, /* Username to be added */
const char *aPW, /* Password or credentials */
int nPW, /* Number of bytes in aPW[] */
int isAdmin /* True to give new user admin privilege */
);
/*
** The sqlite3_user_change() interface can be used to change a users
** login credentials or admin privilege. Any user can change their own
** login credentials. Only an admin user can change another users login
** credentials or admin privilege setting. No user may change their own
** admin privilege setting.
*/
int sqlite3_user_change(
sqlite3 *db, /* Database connection */
const char *zUsername, /* Username to change */
const char *aPW, /* New password or credentials */
int nPW, /* Number of bytes in aPW[] */
int isAdmin /* Modified admin privilege for the user */
);
/*
** The sqlite3_user_delete() interface can be used (by an admin user only)
** to delete a user. The currently logged-in user cannot be deleted,
** which guarantees that there is always an admin user and hence that
** the database cannot be converted into a no-authentication-required
** database.
*/
int sqlite3_user_delete(
sqlite3 *db, /* Database connection */
const char *zUsername /* Username to remove */
);
#ifdef __cplusplus
} /* end of the 'extern "C"' block */
#endif
#endif /* SQLITE_USER_AUTHENTICATION */

View File

@ -891,6 +891,11 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
// Options
var loc *time.Location
authCreate := false
authUser := ""
authPass := ""
authCrypt := ""
authSalt := ""
mutex := C.int(C.SQLITE_OPEN_FULLMUTEX)
txlock := "BEGIN"
@ -916,6 +921,23 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
return nil, err
}
// Authentication
if _, ok := params["_auth"]; ok {
authCreate = true
}
if val := params.Get("_auth_user"); val != "" {
authUser = val
}
if val := params.Get("_auth_pass"); val != "" {
authPass = val
}
if val := params.Get("_auth_crypt"); val != "" {
authCrypt = val
}
if val := params.Get("_auth_salt"); val != "" {
authSalt = val
}
// _loc
if val := params.Get("_loc"); val != "" {
switch strings.ToLower(val) {
@ -1248,7 +1270,149 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
return nil
}
// USER AUTHENTICATION
//
// User Authentication is always performed even when
// sqlite_userauth is not compiled in, because without user authentication
// the authentication is a no-op.
//
// Workflow
// - Authenticate
// ON::SUCCESS => Continue
// ON::SQLITE_AUTH => Return error and exit Open(...)
//
// - Activate User Authentication
// Check if the user wants to activate User Authentication.
// If so then first create a temporary AuthConn to the database
// This is possible because we are already succesfully authenticated.
//
// - Check if `sqlite_user`` table exists
// YES => Add the provided user from DSN as Admin User and
// activate user authentication.
// NO => Continue
//
// Create connection to SQLite
conn := &SQLiteConn{db: db, loc: loc, txlock: txlock}
// Password Cipher has to be registerd before authentication
if len(authCrypt) > 0 {
switch strings.ToUpper(authCrypt) {
case "SHA1":
if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSHA1, true); err != nil {
return nil, fmt.Errorf("CryptEncoderSHA1: %s", err)
}
case "SSHA1":
if len(authSalt) == 0 {
return nil, fmt.Errorf("_auth_crypt=ssha1, requires _auth_salt")
}
if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSSHA1(authSalt), true); err != nil {
return nil, fmt.Errorf("CryptEncoderSSHA1: %s", err)
}
case "SHA256":
if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSHA256, true); err != nil {
return nil, fmt.Errorf("CryptEncoderSHA256: %s", err)
}
case "SSHA256":
if len(authSalt) == 0 {
return nil, fmt.Errorf("_auth_crypt=ssha256, requires _auth_salt")
}
if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSSHA256(authSalt), true); err != nil {
return nil, fmt.Errorf("CryptEncoderSSHA256: %s", err)
}
case "SHA384":
if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSHA384, true); err != nil {
return nil, fmt.Errorf("CryptEncoderSHA384: %s", err)
}
case "SSHA384":
if len(authSalt) == 0 {
return nil, fmt.Errorf("_auth_crypt=ssha384, requires _auth_salt")
}
if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSSHA384(authSalt), true); err != nil {
return nil, fmt.Errorf("CryptEncoderSSHA384: %s", err)
}
case "SHA512":
if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSHA512, true); err != nil {
return nil, fmt.Errorf("CryptEncoderSHA512: %s", err)
}
case "SSHA512":
if len(authSalt) == 0 {
return nil, fmt.Errorf("_auth_crypt=ssha512, requires _auth_salt")
}
if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSSHA512(authSalt), true); err != nil {
return nil, fmt.Errorf("CryptEncoderSSHA512: %s", err)
}
}
}
// Preform Authentication
if err := conn.Authenticate(authUser, authPass); err != nil {
return nil, err
}
// Register: authenticate
// Authenticate will perform an authentication of the provided username
// and password against the database.
//
// If a database contains the SQLITE_USER table, then the
// call to Authenticate must be invoked with an
// appropriate username and password prior to enable read and write
//access to the database.
//
// Return SQLITE_OK on success or SQLITE_ERROR if the username/password
// combination is incorrect or unknown.
//
// If the SQLITE_USER table is not present in the database file, then
// this interface is a harmless no-op returnning SQLITE_OK.
if err := conn.RegisterFunc("authenticate", conn.authenticate, true); err != nil {
return nil, err
}
//
// Register: auth_user_add
// auth_user_add can be used (by an admin user only)
// to create a new user. When called on a no-authentication-required
// database, this routine converts the database into an authentication-
// required database, automatically makes the added user an
// administrator, and logs in the current connection as that user.
// The AuthUserAdd only works for the "main" database, not
// for any ATTACH-ed databases. Any call to AuthUserAdd by a
// non-admin user results in an error.
if err := conn.RegisterFunc("auth_user_add", conn.authUserAdd, true); err != nil {
return nil, err
}
//
// Register: auth_user_change
// auth_user_change can be used to change a users
// login credentials or admin privilege. Any user can change their own
// login credentials. Only an admin user can change another users login
// credentials or admin privilege setting. No user may change their own
// admin privilege setting.
if err := conn.RegisterFunc("auth_user_change", conn.authUserChange, true); err != nil {
return nil, err
}
//
// Register: auth_user_delete
// auth_user_delete can be used (by an admin user only)
// to delete a user. The currently logged-in user cannot be deleted,
// which guarantees that there is always an admin user and hence that
// the database cannot be converted into a no-authentication-required
// database.
if err := conn.RegisterFunc("auth_user_delete", conn.authUserDelete, true); err != nil {
return nil, err
}
// Register: auth_enabled
// auth_enabled can be used to check if user authentication is enabled
if err := conn.RegisterFunc("auth_enabled", conn.authEnabled, true); err != nil {
return nil, err
}
// Auto Vacuum
// Moved auto_vacuum command, the user preference for auto_vacuum needs to be implemented directly after
// the authentication and before the sqlite_user table gets created if the user
// decides to activate User Authentication because
// auto_vacuum needs to be set before any tables are created
// and activating user authentication creates the internal table `sqlite_user`.
if autoVacuum > -1 {
if err := exec(fmt.Sprintf("PRAGMA auto_vacuum = %d;", autoVacuum)); err != nil {
C.sqlite3_close_v2(db)
@ -1256,6 +1420,27 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
}
}
// Check if user wants to activate User Authentication
if authCreate {
// Before going any further, we need to check that the user
// has provided an username and password within the DSN.
// We are not allowed to continue.
if len(authUser) < 0 {
return nil, fmt.Errorf("Missing '_auth_user' while user authentication was requested with '_auth'")
}
if len(authPass) < 0 {
return nil, fmt.Errorf("Missing '_auth_pass' while user authentication was requested with '_auth'")
}
// Check if User Authentication is Enabled
authExists := conn.AuthEnabled()
if !authExists {
if err := conn.AuthUserAdd(authUser, authPass, true); err != nil {
return nil, err
}
}
}
// Case Sensitive LIKE
if caseSensitiveLike > -1 {
if err := exec(fmt.Sprintf("PRAGMA case_sensitive_like = %d;", caseSensitiveLike)); err != nil {
@ -1347,8 +1532,6 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
}
}
conn := &SQLiteConn{db: db, loc: loc, txlock: txlock}
if len(d.Extensions) > 0 {
if err := conn.loadExtensions(d.Extensions); err != nil {
conn.Close()

8
sqlite3.goconvey Normal file
View File

@ -0,0 +1,8 @@
// GoConvey Test Profile
// Activate Coverage
-cover
-tags=sqlite_userauth
// EOF

120
sqlite3_func_crypt.go Normal file
View File

@ -0,0 +1,120 @@
// Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package sqlite3
import (
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
)
// This file provides several different implementations for the
// default embedded sqlite_crypt function.
// This function is uses a ceasar-cypher by default
// and is used within the UserAuthentication module to encode
// the password.
//
// The provided functions can be used as an overload to the sqlite_crypt
// function through the use of the RegisterFunc on the connection.
//
// Because the functions can serv a purpose to an end-user
// without using the UserAuthentication module
// the functions are default compiled in.
//
// From SQLITE3 - user-auth.txt
// The sqlite_user.pw field is encoded by a built-in SQL function
// "sqlite_crypt(X,Y)". The two arguments are both BLOBs. The first argument
// is the plaintext password supplied to the sqlite3_user_authenticate()
// interface. The second argument is the sqlite_user.pw value and is supplied
// so that the function can extract the "salt" used by the password encoder.
// The result of sqlite_crypt(X,Y) is another blob which is the value that
// ends up being stored in sqlite_user.pw. To verify credentials X supplied
// by the sqlite3_user_authenticate() routine, SQLite runs:
//
// sqlite_user.pw == sqlite_crypt(X, sqlite_user.pw)
//
// To compute an appropriate sqlite_user.pw value from a new or modified
// password X, sqlite_crypt(X,NULL) is run. A new random salt is selected
// when the second argument is NULL.
//
// The built-in version of of sqlite_crypt() uses a simple Ceasar-cypher
// which prevents passwords from being revealed by searching the raw database
// for ASCII text, but is otherwise trivally broken. For better password
// security, the database should be encrypted using the SQLite Encryption
// Extension or similar technology. Or, the application can use the
// sqlite3_create_function() interface to provide an alternative
// implementation of sqlite_crypt() that computes a stronger password hash,
// perhaps using a cryptographic hash function like SHA1.
// CryptEncoderSHA1 encodes a password with SHA1
func CryptEncoderSHA1(pass []byte, hash interface{}) []byte {
h := sha1.Sum(pass)
return h[:]
}
// CryptEncoderSSHA1 encodes a password with SHA1 with the
// configured salt.
func CryptEncoderSSHA1(salt string) func(pass []byte, hash interface{}) []byte {
return func(pass []byte, hash interface{}) []byte {
s := []byte(salt)
p := append(pass, s...)
h := sha1.Sum(p)
return h[:]
}
}
// CryptEncoderSHA256 encodes a password with SHA256
func CryptEncoderSHA256(pass []byte, hash interface{}) []byte {
h := sha256.Sum256(pass)
return h[:]
}
// CryptEncoderSSHA256 encodes a password with SHA256
// with the configured salt
func CryptEncoderSSHA256(salt string) func(pass []byte, hash interface{}) []byte {
return func(pass []byte, hash interface{}) []byte {
s := []byte(salt)
p := append(pass, s...)
h := sha256.Sum256(p)
return h[:]
}
}
// CryptEncoderSHA384 encodes a password with SHA256
func CryptEncoderSHA384(pass []byte, hash interface{}) []byte {
h := sha512.Sum384(pass)
return h[:]
}
// CryptEncoderSSHA384 encodes a password with SHA256
// with the configured salt
func CryptEncoderSSHA384(salt string) func(pass []byte, hash interface{}) []byte {
return func(pass []byte, hash interface{}) []byte {
s := []byte(salt)
p := append(pass, s...)
h := sha512.Sum384(p)
return h[:]
}
}
// CryptEncoderSHA512 encodes a password with SHA256
func CryptEncoderSHA512(pass []byte, hash interface{}) []byte {
h := sha512.Sum512(pass)
return h[:]
}
// CryptEncoderSSHA512 encodes a password with SHA256
// with the configured salt
func CryptEncoderSSHA512(salt string) func(pass []byte, hash interface{}) []byte {
return func(pass []byte, hash interface{}) []byte {
s := []byte(salt)
p := append(pass, s...)
h := sha512.Sum512(p)
return h[:]
}
}
// EOF

289
sqlite3_opt_userauth.go Normal file
View File

@ -0,0 +1,289 @@
// Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
// +build sqlite_userauth
package sqlite3
/*
#cgo CFLAGS: -DSQLITE_USER_AUTHENTICATION
#cgo LDFLAGS: -lm
#ifndef USE_LIBSQLITE3
#include <sqlite3-binding.h>
#else
#include <sqlite3.h>
#endif
#include <stdlib.h>
static int
_sqlite3_user_authenticate(sqlite3* db, const char* zUsername, const char* aPW, int nPW)
{
return sqlite3_user_authenticate(db, zUsername, aPW, nPW);
}
static int
_sqlite3_user_add(sqlite3* db, const char* zUsername, const char* aPW, int nPW, int isAdmin)
{
return sqlite3_user_add(db, zUsername, aPW, nPW, isAdmin);
}
static int
_sqlite3_user_change(sqlite3* db, const char* zUsername, const char* aPW, int nPW, int isAdmin)
{
return sqlite3_user_change(db, zUsername, aPW, nPW, isAdmin);
}
static int
_sqlite3_user_delete(sqlite3* db, const char* zUsername)
{
return sqlite3_user_delete(db, zUsername);
}
static int
_sqlite3_auth_enabled(sqlite3* db)
{
int exists = -1;
sqlite3_stmt *stmt;
sqlite3_prepare_v2(db, "select count(type) from sqlite_master WHERE type='table' and name='sqlite_user';", -1, &stmt, NULL);
while ( sqlite3_step(stmt) == SQLITE_ROW) {
exists = sqlite3_column_int(stmt, 0);
}
sqlite3_finalize(stmt);
return exists;
}
*/
import "C"
import (
"errors"
"unsafe"
)
const (
SQLITE_AUTH = C.SQLITE_AUTH
)
var (
ErrUnauthorized = errors.New("SQLITE_AUTH: Unauthorized")
ErrAdminRequired = errors.New("SQLITE_AUTH: Unauthorized; Admin Privileges Required")
)
// Authenticate will perform an authentication of the provided username
// and password against the database.
//
// If a database contains the SQLITE_USER table, then the
// call to Authenticate must be invoked with an
// appropriate username and password prior to enable read and write
//access to the database.
//
// Return SQLITE_OK on success or SQLITE_ERROR if the username/password
// combination is incorrect or unknown.
//
// If the SQLITE_USER table is not present in the database file, then
// this interface is a harmless no-op returnning SQLITE_OK.
func (c *SQLiteConn) Authenticate(username, password string) error {
rv := c.authenticate(username, password)
switch rv {
case C.SQLITE_ERROR, C.SQLITE_AUTH:
return ErrUnauthorized
case C.SQLITE_OK:
return nil
default:
return c.lastError()
}
}
// authenticate provides the actual authentication to SQLite.
// This is not exported for usage in Go.
// It is however exported for usage within SQL by the user.
//
// Returns:
// C.SQLITE_OK (0)
// C.SQLITE_ERROR (1)
// C.SQLITE_AUTH (23)
func (c *SQLiteConn) authenticate(username, password string) int {
// Allocate C Variables
cuser := C.CString(username)
cpass := C.CString(password)
// Free C Variables
defer func() {
C.free(unsafe.Pointer(cuser))
C.free(unsafe.Pointer(cpass))
}()
return int(C._sqlite3_user_authenticate(c.db, cuser, cpass, C.int(len(password))))
}
// AuthUserAdd can be used (by an admin user only)
// to create a new user. When called on a no-authentication-required
// database, this routine converts the database into an authentication-
// required database, automatically makes the added user an
// administrator, and logs in the current connection as that user.
// The AuthUserAdd only works for the "main" database, not
// for any ATTACH-ed databases. Any call to AuthUserAdd by a
// non-admin user results in an error.
func (c *SQLiteConn) AuthUserAdd(username, password string, admin bool) error {
isAdmin := 0
if admin {
isAdmin = 1
}
rv := c.authUserAdd(username, password, isAdmin)
switch rv {
case C.SQLITE_ERROR, C.SQLITE_AUTH:
return ErrAdminRequired
case C.SQLITE_OK:
return nil
default:
return c.lastError()
}
}
// authUserAdd enables the User Authentication if not enabled.
// Otherwise it will add a user.
//
// When user authentication is already enabled then this function
// can only be called by an admin.
//
// This is not exported for usage in Go.
// It is however exported for usage within SQL by the user.
//
// Returns:
// C.SQLITE_OK (0)
// C.SQLITE_ERROR (1)
// C.SQLITE_AUTH (23)
func (c *SQLiteConn) authUserAdd(username, password string, admin int) int {
// Allocate C Variables
cuser := C.CString(username)
cpass := C.CString(password)
// Free C Variables
defer func() {
C.free(unsafe.Pointer(cuser))
C.free(unsafe.Pointer(cpass))
}()
return int(C._sqlite3_user_add(c.db, cuser, cpass, C.int(len(password)), C.int(admin)))
}
// AuthUserChange can be used to change a users
// login credentials or admin privilege. Any user can change their own
// login credentials. Only an admin user can change another users login
// credentials or admin privilege setting. No user may change their own
// admin privilege setting.
func (c *SQLiteConn) AuthUserChange(username, password string, admin bool) error {
isAdmin := 0
if admin {
isAdmin = 1
}
rv := c.authUserChange(username, password, isAdmin)
switch rv {
case C.SQLITE_ERROR, C.SQLITE_AUTH:
return ErrAdminRequired
case C.SQLITE_OK:
return nil
default:
return c.lastError()
}
}
// authUserChange allows to modify a user.
// Users can change their own password.
//
// Only admins can change passwords for other users
// and modify the admin flag.
//
// The admin flag of the current logged in user cannot be changed.
// THis ensures that their is always an admin.
//
// This is not exported for usage in Go.
// It is however exported for usage within SQL by the user.
//
// Returns:
// C.SQLITE_OK (0)
// C.SQLITE_ERROR (1)
// C.SQLITE_AUTH (23)
func (c *SQLiteConn) authUserChange(username, password string, admin int) int {
// Allocate C Variables
cuser := C.CString(username)
cpass := C.CString(password)
// Free C Variables
defer func() {
C.free(unsafe.Pointer(cuser))
C.free(unsafe.Pointer(cpass))
}()
return int(C._sqlite3_user_change(c.db, cuser, cpass, C.int(len(password)), C.int(admin)))
}
// AuthUserDelete can be used (by an admin user only)
// to delete a user. The currently logged-in user cannot be deleted,
// which guarantees that there is always an admin user and hence that
// the database cannot be converted into a no-authentication-required
// database.
func (c *SQLiteConn) AuthUserDelete(username string) error {
rv := c.authUserDelete(username)
switch rv {
case C.SQLITE_ERROR, C.SQLITE_AUTH:
return ErrAdminRequired
case C.SQLITE_OK:
return nil
default:
return c.lastError()
}
}
// authUserDelete can be used to delete a user.
//
// This function can only be executed by an admin.
//
// This is not exported for usage in Go.
// It is however exported for usage within SQL by the user.
//
// Returns:
// C.SQLITE_OK (0)
// C.SQLITE_ERROR (1)
// C.SQLITE_AUTH (23)
func (c *SQLiteConn) authUserDelete(username string) int {
// Allocate C Variables
cuser := C.CString(username)
// Free C Variables
defer func() {
C.free(unsafe.Pointer(cuser))
}()
return int(C._sqlite3_user_delete(c.db, cuser))
}
// AuthEnabled checks if the database is protected by user authentication
func (c *SQLiteConn) AuthEnabled() (exists bool) {
rv := c.authEnabled()
if rv == 1 {
exists = true
}
return
}
// authEnabled perform the actual check for user authentication.
//
// This is not exported for usage in Go.
// It is however exported for usage within SQL by the user.
//
// Returns:
// 0 - Disabled
// 1 - Enabled
func (c *SQLiteConn) authEnabled() int {
return int(C._sqlite3_auth_enabled(c.db))
}
// EOF

View File

@ -0,0 +1,152 @@
// Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
// +build !sqlite_userauth
package sqlite3
import (
"C"
)
// Authenticate will perform an authentication of the provided username
// and password against the database.
//
// If a database contains the SQLITE_USER table, then the
// call to Authenticate must be invoked with an
// appropriate username and password prior to enable read and write
//access to the database.
//
// Return SQLITE_OK on success or SQLITE_ERROR if the username/password
// combination is incorrect or unknown.
//
// If the SQLITE_USER table is not present in the database file, then
// this interface is a harmless no-op returnning SQLITE_OK.
func (c *SQLiteConn) Authenticate(username, password string) error {
// NOOP
return nil
}
// authenticate provides the actual authentication to SQLite.
// This is not exported for usage in Go.
// It is however exported for usage within SQL by the user.
//
// Returns:
// C.SQLITE_OK (0)
// C.SQLITE_ERROR (1)
// C.SQLITE_AUTH (23)
func (c *SQLiteConn) authenticate(username, password string) int {
// NOOP
return 0
}
// AuthUserAdd can be used (by an admin user only)
// to create a new user. When called on a no-authentication-required
// database, this routine converts the database into an authentication-
// required database, automatically makes the added user an
// administrator, and logs in the current connection as that user.
// The AuthUserAdd only works for the "main" database, not
// for any ATTACH-ed databases. Any call to AuthUserAdd by a
// non-admin user results in an error.
func (c *SQLiteConn) AuthUserAdd(username, password string, admin bool) error {
// NOOP
return nil
}
// authUserAdd enables the User Authentication if not enabled.
// Otherwise it will add a user.
//
// When user authentication is already enabled then this function
// can only be called by an admin.
//
// This is not exported for usage in Go.
// It is however exported for usage within SQL by the user.
//
// Returns:
// C.SQLITE_OK (0)
// C.SQLITE_ERROR (1)
// C.SQLITE_AUTH (23)
func (c *SQLiteConn) authUserAdd(username, password string, admin int) int {
// NOOP
return 0
}
// AuthUserChange can be used to change a users
// login credentials or admin privilege. Any user can change their own
// login credentials. Only an admin user can change another users login
// credentials or admin privilege setting. No user may change their own
// admin privilege setting.
func (c *SQLiteConn) AuthUserChange(username, password string, admin bool) error {
// NOOP
return nil
}
// authUserChange allows to modify a user.
// Users can change their own password.
//
// Only admins can change passwords for other users
// and modify the admin flag.
//
// The admin flag of the current logged in user cannot be changed.
// THis ensures that their is always an admin.
//
// This is not exported for usage in Go.
// It is however exported for usage within SQL by the user.
//
// Returns:
// C.SQLITE_OK (0)
// C.SQLITE_ERROR (1)
// C.SQLITE_AUTH (23)
func (c *SQLiteConn) authUserChange(username, password string, admin int) int {
// NOOP
return 0
}
// AuthUserDelete can be used (by an admin user only)
// to delete a user. The currently logged-in user cannot be deleted,
// which guarantees that there is always an admin user and hence that
// the database cannot be converted into a no-authentication-required
// database.
func (c *SQLiteConn) AuthUserDelete(username string) error {
// NOOP
return nil
}
// authUserDelete can be used to delete a user.
//
// This function can only be executed by an admin.
//
// This is not exported for usage in Go.
// It is however exported for usage within SQL by the user.
//
// Returns:
// C.SQLITE_OK (0)
// C.SQLITE_ERROR (1)
// C.SQLITE_AUTH (23)
func (c *SQLiteConn) authUserDelete(username string) int {
// NOOP
return 0
}
// AuthEnabled checks if the database is protected by user authentication
func (c *SQLiteConn) AuthEnabled() (exists bool) {
// NOOP
return false
}
// authEnabled perform the actual check for user authentication.
//
// This is not exported for usage in Go.
// It is however exported for usage within SQL by the user.
//
// Returns:
// 0 - Disabled
// 1 - Enabled
func (c *SQLiteConn) authEnabled() int {
// NOOP
return 0
}
// EOF

1382
sqlite3_opt_userauth_test.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,111 +0,0 @@
// +build ignore
package main
import (
"archive/zip"
"bufio"
"bytes"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"github.com/PuerkitoBio/goquery"
)
func main() {
site := "https://www.sqlite.org/download.html"
fmt.Printf("scraping %v\n", site)
doc, err := goquery.NewDocument(site)
if err != nil {
log.Fatal(err)
}
var url string
doc.Find("a").Each(func(_ int, s *goquery.Selection) {
if url == "" && strings.HasPrefix(s.Text(), "sqlite-amalgamation-") {
url = "https://www.sqlite.org/2018/" + s.Text()
}
})
if url == "" {
return
}
fmt.Printf("downloading %v\n", url)
resp, err := http.Get(url)
if err != nil {
log.Fatal(err)
}
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
resp.Body.Close()
log.Fatal(err)
}
fmt.Printf("extracting %v\n", path.Base(url))
r, err := zip.NewReader(bytes.NewReader(b), resp.ContentLength)
if err != nil {
resp.Body.Close()
log.Fatal(err)
}
resp.Body.Close()
for _, zf := range r.File {
var f *os.File
switch path.Base(zf.Name) {
case "sqlite3.c":
f, err = os.Create("sqlite3-binding.c")
case "sqlite3.h":
f, err = os.Create("sqlite3-binding.h")
case "sqlite3ext.h":
f, err = os.Create("sqlite3ext.h")
default:
continue
}
if err != nil {
log.Fatal(err)
}
zr, err := zf.Open()
if err != nil {
log.Fatal(err)
}
_, err = io.WriteString(f, "#ifndef USE_LIBSQLITE3\n")
if err != nil {
zr.Close()
f.Close()
log.Fatal(err)
}
scanner := bufio.NewScanner(zr)
for scanner.Scan() {
text := scanner.Text()
if text == `#include "sqlite3.h"` {
text = `#include "sqlite3-binding.h"`
}
_, err = fmt.Fprintln(f, text)
if err != nil {
break
}
}
err = scanner.Err()
if err != nil {
zr.Close()
f.Close()
log.Fatal(err)
}
_, err = io.WriteString(f, "#else // USE_LIBSQLITE3\n // If users really want to link against the system sqlite3 we\n// need to make this file a noop.\n #endif")
if err != nil {
zr.Close()
f.Close()
log.Fatal(err)
}
zr.Close()
f.Close()
fmt.Printf("extracted %v\n", filepath.Base(f.Name()))
}
}

7
upgrade/package.go Normal file
View File

@ -0,0 +1,7 @@
// Package upgrade
//
// Dummy to ensure package can be loaded
//
// This file is to avoid the following error:
// can't load package: package go-sqlite3/upgrade: build constraints exclude all Go files in go-sqlite3\upgrade
package upgrade

2
upgrade/profile.goconvey Normal file
View File

@ -0,0 +1,2 @@
// Goconvey Profile
IGNORE

218
upgrade/upgrade.go Normal file
View File

@ -0,0 +1,218 @@
// +build !cgo
// +build upgrade
package main
import (
"archive/zip"
"bufio"
"bytes"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"time"
"github.com/PuerkitoBio/goquery"
)
func download(prefix string) (url string, content []byte, err error) {
year := time.Now().Year()
site := "https://www.sqlite.org/download.html"
//fmt.Printf("scraping %v\n", site)
doc, err := goquery.NewDocument(site)
if err != nil {
log.Fatal(err)
}
doc.Find("a").Each(func(_ int, s *goquery.Selection) {
if strings.HasPrefix(s.Text(), prefix) {
url = fmt.Sprintf("https://www.sqlite.org/%d/", year) + s.Text()
}
})
if url == "" {
return "", nil, fmt.Errorf("Unable to find prefix '%s' on sqlite.org", prefix)
}
fmt.Printf("Downloading %v\n", url)
resp, err := http.Get(url)
if err != nil {
log.Fatal(err)
}
// Ready Body Content
content, err = ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil {
return "", nil, err
}
return url, content, nil
}
func mergeFile(src string, dst string) error {
defer func() error {
fmt.Printf("Removing: %s\n", src)
err := os.Remove(src)
if err != nil {
return err
}
return nil
}()
// Open destination
fdst, err := os.OpenFile(dst, os.O_APPEND|os.O_WRONLY, 0666)
if err != nil {
return err
}
defer fdst.Close()
// Read source content
content, err := ioutil.ReadFile(src)
if err != nil {
return err
}
// Add Additional newline
if _, err := fdst.WriteString("\n"); err != nil {
return err
}
fmt.Printf("Merging: %s into %s\n", src, dst)
if _, err = fdst.Write(content); err != nil {
return err
}
return nil
}
func main() {
fmt.Println("Go-SQLite3 Upgrade Tool")
// Download Amalgamation
_, amalgamation, err := download("sqlite-amalgamation-")
if err != nil {
fmt.Println("Failed to download: sqlite-amalgamation; %s", err)
}
// Download Source
_, source, err := download("sqlite-src-")
if err != nil {
fmt.Println("Failed to download: sqlite-src; %s", err)
}
// Create Amalgamation Zip Reader
rAmalgamation, err := zip.NewReader(bytes.NewReader(amalgamation), int64(len(amalgamation)))
if err != nil {
log.Fatal(err)
}
// Create Source Zip Reader
rSource, err := zip.NewReader(bytes.NewReader(source), int64(len(source)))
if err != nil {
log.Fatal(err)
}
// Extract Amalgamation
for _, zf := range rAmalgamation.File {
var f *os.File
switch path.Base(zf.Name) {
case "sqlite3.c":
f, err = os.Create("sqlite3-binding.c")
case "sqlite3.h":
f, err = os.Create("sqlite3-binding.h")
case "sqlite3ext.h":
f, err = os.Create("sqlite3ext.h")
default:
continue
}
if err != nil {
log.Fatal(err)
}
zr, err := zf.Open()
if err != nil {
log.Fatal(err)
}
_, err = io.WriteString(f, "#ifndef USE_LIBSQLITE3\n")
if err != nil {
zr.Close()
f.Close()
log.Fatal(err)
}
scanner := bufio.NewScanner(zr)
for scanner.Scan() {
text := scanner.Text()
if text == `#include "sqlite3.h"` {
text = `#include "sqlite3-binding.h"`
}
_, err = fmt.Fprintln(f, text)
if err != nil {
break
}
}
err = scanner.Err()
if err != nil {
zr.Close()
f.Close()
log.Fatal(err)
}
_, err = io.WriteString(f, "#else // USE_LIBSQLITE3\n // If users really want to link against the system sqlite3 we\n// need to make this file a noop.\n #endif")
if err != nil {
zr.Close()
f.Close()
log.Fatal(err)
}
zr.Close()
f.Close()
fmt.Printf("Extracted: %v\n", filepath.Base(f.Name()))
}
//Extract Source
for _, zf := range rSource.File {
var f *os.File
switch path.Base(zf.Name) {
case "userauth.c":
f, err = os.Create("userauth.c")
case "sqlite3userauth.h":
f, err = os.Create("userauth.h")
default:
continue
}
if err != nil {
log.Fatal(err)
}
zr, err := zf.Open()
if err != nil {
log.Fatal(err)
}
_, err = io.Copy(f, zr)
if err != nil {
log.Fatal(err)
}
zr.Close()
f.Close()
fmt.Printf("extracted %v\n", filepath.Base(f.Name()))
}
// Merge SQLite User Authentication into amalgamation
if err := mergeFile("userauth.c", "sqlite3-binding.c"); err != nil {
log.Fatal(err)
}
if err := mergeFile("userauth.h", "sqlite3-binding.h"); err != nil {
log.Fatal(err)
}
os.Exit(0)
}