@@ -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,160 @@ 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 , const char * arg2 , const char * db_name , const char * trigger )
1440+ {
1441+ connection_t * conn = (connection_t * )user_data ;
1442+ if (action >= 0 && action < AUTHORIZER_DENY_SIZE && conn -> authorizer_deny [action ]) {
1443+ return SQLITE_DENY ;
1444+ }
1445+ return SQLITE_OK ;
1446+ }
1447+
1448+ // Maps atom names to SQLite authorizer action codes
1449+ static int
1450+ action_code_from_atom (ErlNifEnv * env , ERL_NIF_TERM atom )
1451+ {
1452+ char buf [32 ];
1453+ if (!enif_get_atom (env , atom , buf , sizeof (buf ), ERL_NIF_LATIN1 )) {
1454+ return -1 ;
1455+ }
1456+
1457+ if (strcmp (buf , "create_index" ) == 0 )
1458+ return SQLITE_CREATE_INDEX ;
1459+ if (strcmp (buf , "create_table" ) == 0 )
1460+ return SQLITE_CREATE_TABLE ;
1461+ if (strcmp (buf , "create_temp_index" ) == 0 )
1462+ return SQLITE_CREATE_TEMP_INDEX ;
1463+ if (strcmp (buf , "create_temp_table" ) == 0 )
1464+ return SQLITE_CREATE_TEMP_TABLE ;
1465+ if (strcmp (buf , "create_temp_trigger" ) == 0 )
1466+ return SQLITE_CREATE_TEMP_TRIGGER ;
1467+ if (strcmp (buf , "create_temp_view" ) == 0 )
1468+ return SQLITE_CREATE_TEMP_VIEW ;
1469+ if (strcmp (buf , "create_trigger" ) == 0 )
1470+ return SQLITE_CREATE_TRIGGER ;
1471+ if (strcmp (buf , "create_view" ) == 0 )
1472+ return SQLITE_CREATE_VIEW ;
1473+ if (strcmp (buf , "delete" ) == 0 )
1474+ return SQLITE_DELETE ;
1475+ if (strcmp (buf , "drop_index" ) == 0 )
1476+ return SQLITE_DROP_INDEX ;
1477+ if (strcmp (buf , "drop_table" ) == 0 )
1478+ return SQLITE_DROP_TABLE ;
1479+ if (strcmp (buf , "drop_temp_index" ) == 0 )
1480+ return SQLITE_DROP_TEMP_INDEX ;
1481+ if (strcmp (buf , "drop_temp_table" ) == 0 )
1482+ return SQLITE_DROP_TEMP_TABLE ;
1483+ if (strcmp (buf , "drop_temp_trigger" ) == 0 )
1484+ return SQLITE_DROP_TEMP_TRIGGER ;
1485+ if (strcmp (buf , "drop_temp_view" ) == 0 )
1486+ return SQLITE_DROP_TEMP_VIEW ;
1487+ if (strcmp (buf , "drop_trigger" ) == 0 )
1488+ return SQLITE_DROP_TRIGGER ;
1489+ if (strcmp (buf , "drop_view" ) == 0 )
1490+ return SQLITE_DROP_VIEW ;
1491+ if (strcmp (buf , "insert" ) == 0 )
1492+ return SQLITE_INSERT ;
1493+ if (strcmp (buf , "pragma" ) == 0 )
1494+ return SQLITE_PRAGMA ;
1495+ if (strcmp (buf , "read" ) == 0 )
1496+ return SQLITE_READ ;
1497+ if (strcmp (buf , "select" ) == 0 )
1498+ return SQLITE_SELECT ;
1499+ if (strcmp (buf , "transaction" ) == 0 )
1500+ return SQLITE_TRANSACTION ;
1501+ if (strcmp (buf , "update" ) == 0 )
1502+ return SQLITE_UPDATE ;
1503+ if (strcmp (buf , "attach" ) == 0 )
1504+ return SQLITE_ATTACH ;
1505+ if (strcmp (buf , "detach" ) == 0 )
1506+ return SQLITE_DETACH ;
1507+ if (strcmp (buf , "alter_table" ) == 0 )
1508+ return SQLITE_ALTER_TABLE ;
1509+ if (strcmp (buf , "reindex" ) == 0 )
1510+ return SQLITE_REINDEX ;
1511+ if (strcmp (buf , "analyze" ) == 0 )
1512+ return SQLITE_ANALYZE ;
1513+ if (strcmp (buf , "create_vtable" ) == 0 )
1514+ return SQLITE_CREATE_VTABLE ;
1515+ if (strcmp (buf , "drop_vtable" ) == 0 )
1516+ return SQLITE_DROP_VTABLE ;
1517+ if (strcmp (buf , "function" ) == 0 )
1518+ return SQLITE_FUNCTION ;
1519+ if (strcmp (buf , "savepoint" ) == 0 )
1520+ return SQLITE_SAVEPOINT ;
1521+ if (strcmp (buf , "recursive" ) == 0 )
1522+ return SQLITE_RECURSIVE ;
1523+
1524+ return -1 ;
1525+ }
1526+
1527+ // set_authorizer(conn, deny_list) -> :ok | {:error, reason}
1528+ // deny_list is a list of atoms: [:attach, :detach, :pragma, ...]
1529+ // Pass an empty list to clear the authorizer.
1530+ ERL_NIF_TERM
1531+ exqlite_set_authorizer (ErlNifEnv * env , int argc , const ERL_NIF_TERM argv [])
1532+ {
1533+ assert (env );
1534+ connection_t * conn = NULL ;
1535+
1536+ if (argc != 2 ) {
1537+ return enif_make_badarg (env );
1538+ }
1539+
1540+ if (!enif_get_resource (env , argv [0 ], connection_type , (void * * )& conn )) {
1541+ return am_invalid_connection ;
1542+ }
1543+
1544+ connection_acquire_lock (conn );
1545+
1546+ if (conn -> db == NULL ) {
1547+ connection_release_lock (conn );
1548+ return make_error_tuple (env , am_connection_closed );
1549+ }
1550+
1551+ // Parse the deny list
1552+ unsigned int list_len ;
1553+ if (!enif_get_list_length (env , argv [1 ], & list_len )) {
1554+ connection_release_lock (conn );
1555+ return enif_make_badarg (env );
1556+ }
1557+
1558+ if (list_len == 0 ) {
1559+ // Empty list: clear the authorizer
1560+ memset (conn -> authorizer_deny , 0 , sizeof (conn -> authorizer_deny ));
1561+ sqlite3_set_authorizer (conn -> db , NULL , NULL );
1562+ connection_release_lock (conn );
1563+ return am_ok ;
1564+ }
1565+
1566+ // Validate all atoms before mutating state — a bad atom in the list
1567+ // should not clear an existing authorizer as a side effect.
1568+ int new_deny [AUTHORIZER_DENY_SIZE ] = {0 };
1569+ ERL_NIF_TERM head , tail = argv [1 ];
1570+ while (enif_get_list_cell (env , tail , & head , & tail )) {
1571+ int code = action_code_from_atom (env , head );
1572+ if (code < 0 || code >= AUTHORIZER_DENY_SIZE ) {
1573+ connection_release_lock (conn );
1574+ return enif_make_badarg (env );
1575+ }
1576+ new_deny [code ] = 1 ;
1577+ }
1578+
1579+ // Validation passed — apply atomically
1580+ memcpy (conn -> authorizer_deny , new_deny , sizeof (conn -> authorizer_deny ));
1581+ sqlite3_set_authorizer (conn -> db , authorizer_callback , conn );
1582+
1583+ connection_release_lock (conn );
1584+
1585+ return am_ok ;
1586+ }
1587+
14281588//
14291589// Log Notifications
14301590//
@@ -1590,6 +1750,7 @@ static ErlNifFunc nif_funcs[] = {
15901750 {"release" , 2 , exqlite_release , ERL_NIF_DIRTY_JOB_IO_BOUND },
15911751 {"enable_load_extension" , 2 , exqlite_enable_load_extension , ERL_NIF_DIRTY_JOB_IO_BOUND },
15921752 {"set_update_hook" , 2 , exqlite_set_update_hook , ERL_NIF_DIRTY_JOB_IO_BOUND },
1753+ {"set_authorizer" , 2 , exqlite_set_authorizer , ERL_NIF_DIRTY_JOB_IO_BOUND },
15931754 {"set_log_hook" , 1 , exqlite_set_log_hook , ERL_NIF_DIRTY_JOB_IO_BOUND },
15941755 {"interrupt" , 1 , exqlite_interrupt , ERL_NIF_DIRTY_JOB_IO_BOUND },
15951756 {"errmsg" , 1 , exqlite_errmsg },
0 commit comments