@@ -48,12 +48,17 @@ static sqlite3_mem_methods default_alloc_methods = {0};
4848ErlNifPid * log_hook_pid = NULL ;
4949ErlNifMutex * log_hook_mutex = NULL ;
5050
51+ // Denied authorizer action codes. Sized to 64 for margin — highest
52+ // currently defined SQLite action code is SQLITE_RECURSIVE (33).
53+ #define AUTHORIZER_DENY_SIZE 64
54+
5155typedef struct connection
5256{
5357 sqlite3 * db ;
5458 ErlNifMutex * mutex ;
5559 ErlNifMutex * interrupt_mutex ;
5660 ErlNifPid update_hook_pid ;
61+ int authorizer_deny [AUTHORIZER_DENY_SIZE ];
5762} connection_t ;
5863
5964typedef struct statement
@@ -340,6 +345,7 @@ exqlite_open(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
340345 conn -> db = db ;
341346 conn -> mutex = mutex ;
342347 conn -> interrupt_mutex = enif_mutex_create ("exqlite:interrupt" );
348+ memset (conn -> authorizer_deny , 0 , sizeof (conn -> authorizer_deny ));
343349 if (conn -> interrupt_mutex == NULL ) {
344350 // conn->db and conn->mutex are set; the destructor will clean them up.
345351 enif_release_resource (conn );
@@ -1425,6 +1431,128 @@ exqlite_set_update_hook(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
14251431 return am_ok ;
14261432}
14271433
1434+ //
1435+ // Authorizer
1436+ //
1437+
1438+ static int
1439+ authorizer_callback (void * user_data , int action , const char * arg1 ,
1440+ const char * arg2 , const char * db_name , const char * trigger )
1441+ {
1442+ connection_t * conn = (connection_t * )user_data ;
1443+ if (action >= 0 && action < AUTHORIZER_DENY_SIZE && conn -> authorizer_deny [action ]) {
1444+ return SQLITE_DENY ;
1445+ }
1446+ return SQLITE_OK ;
1447+ }
1448+
1449+ // Maps atom names to SQLite authorizer action codes
1450+ static int
1451+ action_code_from_atom (ErlNifEnv * env , ERL_NIF_TERM atom )
1452+ {
1453+ char buf [32 ];
1454+ if (!enif_get_atom (env , atom , buf , sizeof (buf ), ERL_NIF_LATIN1 )) {
1455+ return -1 ;
1456+ }
1457+
1458+ if (strcmp (buf , "create_index" ) == 0 ) return SQLITE_CREATE_INDEX ;
1459+ if (strcmp (buf , "create_table" ) == 0 ) return SQLITE_CREATE_TABLE ;
1460+ if (strcmp (buf , "create_temp_index" ) == 0 ) return SQLITE_CREATE_TEMP_INDEX ;
1461+ if (strcmp (buf , "create_temp_table" ) == 0 ) return SQLITE_CREATE_TEMP_TABLE ;
1462+ if (strcmp (buf , "create_temp_trigger" ) == 0 ) return SQLITE_CREATE_TEMP_TRIGGER ;
1463+ if (strcmp (buf , "create_temp_view" ) == 0 ) return SQLITE_CREATE_TEMP_VIEW ;
1464+ if (strcmp (buf , "create_trigger" ) == 0 ) return SQLITE_CREATE_TRIGGER ;
1465+ if (strcmp (buf , "create_view" ) == 0 ) return SQLITE_CREATE_VIEW ;
1466+ if (strcmp (buf , "delete" ) == 0 ) return SQLITE_DELETE ;
1467+ if (strcmp (buf , "drop_index" ) == 0 ) return SQLITE_DROP_INDEX ;
1468+ if (strcmp (buf , "drop_table" ) == 0 ) return SQLITE_DROP_TABLE ;
1469+ if (strcmp (buf , "drop_temp_index" ) == 0 ) return SQLITE_DROP_TEMP_INDEX ;
1470+ if (strcmp (buf , "drop_temp_table" ) == 0 ) return SQLITE_DROP_TEMP_TABLE ;
1471+ if (strcmp (buf , "drop_temp_trigger" ) == 0 ) return SQLITE_DROP_TEMP_TRIGGER ;
1472+ if (strcmp (buf , "drop_temp_view" ) == 0 ) return SQLITE_DROP_TEMP_VIEW ;
1473+ if (strcmp (buf , "drop_trigger" ) == 0 ) return SQLITE_DROP_TRIGGER ;
1474+ if (strcmp (buf , "drop_view" ) == 0 ) return SQLITE_DROP_VIEW ;
1475+ if (strcmp (buf , "insert" ) == 0 ) return SQLITE_INSERT ;
1476+ if (strcmp (buf , "pragma" ) == 0 ) return SQLITE_PRAGMA ;
1477+ if (strcmp (buf , "read" ) == 0 ) return SQLITE_READ ;
1478+ if (strcmp (buf , "select" ) == 0 ) return SQLITE_SELECT ;
1479+ if (strcmp (buf , "transaction" ) == 0 ) return SQLITE_TRANSACTION ;
1480+ if (strcmp (buf , "update" ) == 0 ) return SQLITE_UPDATE ;
1481+ if (strcmp (buf , "attach" ) == 0 ) return SQLITE_ATTACH ;
1482+ if (strcmp (buf , "detach" ) == 0 ) return SQLITE_DETACH ;
1483+ if (strcmp (buf , "alter_table" ) == 0 ) return SQLITE_ALTER_TABLE ;
1484+ if (strcmp (buf , "reindex" ) == 0 ) return SQLITE_REINDEX ;
1485+ if (strcmp (buf , "analyze" ) == 0 ) return SQLITE_ANALYZE ;
1486+ if (strcmp (buf , "create_vtable" ) == 0 ) return SQLITE_CREATE_VTABLE ;
1487+ if (strcmp (buf , "drop_vtable" ) == 0 ) return SQLITE_DROP_VTABLE ;
1488+ if (strcmp (buf , "function" ) == 0 ) return SQLITE_FUNCTION ;
1489+ if (strcmp (buf , "savepoint" ) == 0 ) return SQLITE_SAVEPOINT ;
1490+ if (strcmp (buf , "recursive" ) == 0 ) return SQLITE_RECURSIVE ;
1491+
1492+ return -1 ;
1493+ }
1494+
1495+ // set_authorizer(conn, deny_list) -> :ok | {:error, reason}
1496+ // deny_list is a list of atoms: [:attach, :detach, :pragma, ...]
1497+ // Pass an empty list to clear the authorizer.
1498+ ERL_NIF_TERM
1499+ exqlite_set_authorizer (ErlNifEnv * env , int argc , const ERL_NIF_TERM argv [])
1500+ {
1501+ assert (env );
1502+ connection_t * conn = NULL ;
1503+
1504+ if (argc != 2 ) {
1505+ return enif_make_badarg (env );
1506+ }
1507+
1508+ if (!enif_get_resource (env , argv [0 ], connection_type , (void * * )& conn )) {
1509+ return am_invalid_connection ;
1510+ }
1511+
1512+ connection_acquire_lock (conn );
1513+
1514+ if (conn -> db == NULL ) {
1515+ connection_release_lock (conn );
1516+ return make_error_tuple (env , am_connection_closed );
1517+ }
1518+
1519+ // Parse the deny list
1520+ unsigned int list_len ;
1521+ if (!enif_get_list_length (env , argv [1 ], & list_len )) {
1522+ connection_release_lock (conn );
1523+ return enif_make_badarg (env );
1524+ }
1525+
1526+ if (list_len == 0 ) {
1527+ // Empty list: clear the authorizer
1528+ memset (conn -> authorizer_deny , 0 , sizeof (conn -> authorizer_deny ));
1529+ sqlite3_set_authorizer (conn -> db , NULL , NULL );
1530+ connection_release_lock (conn );
1531+ return am_ok ;
1532+ }
1533+
1534+ // Validate all atoms before mutating state — a bad atom in the list
1535+ // should not clear an existing authorizer as a side effect.
1536+ int new_deny [AUTHORIZER_DENY_SIZE ] = {0 };
1537+ ERL_NIF_TERM head , tail = argv [1 ];
1538+ while (enif_get_list_cell (env , tail , & head , & tail )) {
1539+ int code = action_code_from_atom (env , head );
1540+ if (code < 0 || code >= AUTHORIZER_DENY_SIZE ) {
1541+ connection_release_lock (conn );
1542+ return enif_make_badarg (env );
1543+ }
1544+ new_deny [code ] = 1 ;
1545+ }
1546+
1547+ // Validation passed — apply atomically
1548+ memcpy (conn -> authorizer_deny , new_deny , sizeof (conn -> authorizer_deny ));
1549+ sqlite3_set_authorizer (conn -> db , authorizer_callback , conn );
1550+
1551+ connection_release_lock (conn );
1552+
1553+ return am_ok ;
1554+ }
1555+
14281556//
14291557// Log Notifications
14301558//
@@ -1590,6 +1718,7 @@ static ErlNifFunc nif_funcs[] = {
15901718 {"release" , 2 , exqlite_release , ERL_NIF_DIRTY_JOB_IO_BOUND },
15911719 {"enable_load_extension" , 2 , exqlite_enable_load_extension , ERL_NIF_DIRTY_JOB_IO_BOUND },
15921720 {"set_update_hook" , 2 , exqlite_set_update_hook , ERL_NIF_DIRTY_JOB_IO_BOUND },
1721+ {"set_authorizer" , 2 , exqlite_set_authorizer , ERL_NIF_DIRTY_JOB_IO_BOUND },
15931722 {"set_log_hook" , 1 , exqlite_set_log_hook , ERL_NIF_DIRTY_JOB_IO_BOUND },
15941723 {"interrupt" , 1 , exqlite_interrupt , ERL_NIF_DIRTY_JOB_IO_BOUND },
15951724 {"errmsg" , 1 , exqlite_errmsg },
0 commit comments