#define _GNU_SOURCE #include #include #include #include #include #include "db.h" #include "hashing_settings.h" #include "schema.sql.h" #define SQLITE(func,...) do{if(sqlite3_##func(__VA_ARGS__)!=SQLITE_OK){die_sqlite("sqlite3_" #func);}}while(0) #define DATABASE_VERSION 3 #define MAX_DATABASE_SIZE_BYTES ((i64)2 * 1024 * 1024 * 1024) // #define INSECURE_PURE_SPEED sqlite3 *database=NULL; __attribute__((noreturn)) static void die_sqlite(const char *func){ die("%s: %s",func,sqlite3_errmsg(database)); } static void reset_stmt(sqlite3_stmt *stmt) { SQLITE(reset, stmt); SQLITE(clear_bindings, stmt); } struct hashed_password { char str[crypto_pwhash_STRBYTES + 1]; int length; // -1 if hashing failed }; static struct hashed_password hash_password(const char *pass) { struct hashed_password result; #ifdef INSECURE_PURE_SPEED int len = strlen(pass); if (len > (int)crypto_pwhash_STRBYTES) len = crypto_pwhash_STRBYTES; memcpy(result.str, pass, len); memset(result.str + len, 0, sizeof result.str - len); result.length = crypto_pwhash_STRBYTES; #else int ret = crypto_pwhash_str( result.str, pass, strlen(pass), PASSHASH_OPSLIMIT, PASSHASH_MEMLIMIT); if (ret != 0) { result.length = -1; } else { result.length = crypto_pwhash_STRBYTES; } #endif return result; } // Returns whether successful bool select_simple_int(const char *query, i64 *out) { sqlite3_stmt *stmt; int res = sqlite3_prepare_v2(database, query, -1, &stmt, NULL); bool retval = false; if (res == SQLITE_OK) { if (sqlite3_step(stmt) == SQLITE_ROW) { *out = sqlite3_column_int64(stmt, 0); retval = true; } SQLITE(finalize, stmt); } return retval; } void db_init(void){ SQLITE(config, SQLITE_CONFIG_SERIALIZED); SQLITE(initialize); #ifdef INSECURE_PURE_SPEED #warning INSECURE MODE, IN-MEMORY DATABASE, NO PASSWORDS CHECKED fprintf(stderr, "WARNING: INSECURE MODE, IN-MEMORY DATABASE, NO PASSWORDS CHECKED\n"); SQLITE(open_v2, "db.db", &database,SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_MEMORY, NULL); #else SQLITE(open_v2, "db.db", &database, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL); #endif SQLITE(busy_timeout, database, 500); SQLITE(exec, database, "pragma foreign_keys = 1", NULL, NULL, NULL); i64 page_size, page_count; if (select_simple_int("pragma page_size", &page_size) && select_simple_int("pragma page_count", &page_count)) { i64 max_num_pages = MAX_DATABASE_SIZE_BYTES / page_size; if (page_count >= max_num_pages - 1) { fprintf(stderr, "WARNING: Database already at maximum size!\n"); max_num_pages = page_count + 2; } // SQLite doesn't allow preparing a pragma, so let's splice the integer in directly char *str = NULL; asprintf(&str, "pragma max_page_count = %" PRIi64, max_num_pages); SQLITE(exec, database, str, NULL, NULL, NULL); free(str); } else { die("Failed to query database page statistics: %s", sqlite3_errmsg(database)); } i64 version; if (select_simple_int("select version from Meta", &version)) { if (version != DATABASE_VERSION) { die("Database version incompatible: database %" PRIi64 ", application %d", version, DATABASE_VERSION); } } else { char *str = malloc(schema_sql_len+1, char); memcpy(str, schema_sql, schema_sql_len); str[schema_sql_len] = '\0'; SQLITE(exec, database, str, NULL, NULL, NULL); free(str); sqlite3_stmt *stmt; SQLITE(prepare_v2, database, "insert into Meta (version) values (?)", -1, &stmt, NULL); SQLITE(bind_int64, stmt, 1, DATABASE_VERSION); if (sqlite3_step(stmt) != SQLITE_DONE) { die("Could not set database version: %s", sqlite3_errmsg(database)); } SQLITE(finalize, stmt); } } void db_reinit(void){ SQLITE(open_v2,"db.db",&database,SQLITE_OPEN_READWRITE,NULL); SQLITE(busy_timeout,database,500); } void db_close(void){ sqlite3_close(database); SQLITE(shutdown); database=NULL; } static char* gen_room_name(void){ const int name_len=8; const char *alphabet="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; const int alpha_len=strlen(alphabet); char *name=malloc(name_len+1,char); for(int i=0;i 0 " "from Members as A, Members as B " "where A.room = B.room and A.user = ? and B.user = ?" ,-1, &stmt, NULL); } SQLITE(bind_int64, stmt, 1, userid1); SQLITE(bind_int64, stmt, 2, userid2); assert(sqlite3_step(stmt) == SQLITE_ROW); bool found = sqlite3_column_int(stmt, 0) == 1; reset_stmt(stmt); return found; } i64 db_create_message(i64 roomid,i64 userid,i64 timestamp,i64 replyid,const char *message){ static sqlite3_stmt *stmt = NULL; if (!stmt) { SQLITE(prepare_v2,database, "insert into Messages (room, user, time, reply, message) " "values (?, ?, ?, ?, ?)" ,-1,&stmt,NULL); } SQLITE(bind_int64,stmt,1,roomid); SQLITE(bind_int64,stmt,2,userid); SQLITE(bind_int64,stmt,3,timestamp); if(replyid>=0)SQLITE(bind_int64,stmt,4,replyid); else SQLITE(bind_null,stmt,4); SQLITE(bind_blob,stmt,5,message,strlen(message),SQLITE_STATIC); if(sqlite3_step(stmt)!=SQLITE_DONE)die_sqlite("sqlite3_step"); reset_stmt(stmt); return sqlite3_last_insert_rowid(database); } struct db_message_list db_get_messages(i64 roomid,i64 count){ return db_get_messages_before(roomid,count,-1); } struct db_message_list db_get_messages_before(i64 roomid,i64 count,i64 beforeid){ assert(count>=0); static sqlite3_stmt *stmt1 = NULL, *stmt2 = NULL; sqlite3_stmt *stmt = NULL; // the one used for this invocation if(beforeid<0){ if (!stmt1) { SQLITE(prepare_v2,database, "select id, user, time, reply, message " "from Messages " "where room = ? " "order by time desc " "limit ?" ,-1,&stmt1,NULL); } SQLITE(bind_int64,stmt1,1,roomid); SQLITE(bind_int64,stmt1,2,count); stmt = stmt1; } else { if (!stmt2) { SQLITE(prepare_v2,database, "select id, user, time, reply, message " "from Messages " "where room = ? and time < (select time from Messages where id = ?) " "order by time desc " "limit ?" ,-1,&stmt2,NULL); } SQLITE(bind_int64,stmt2,1,roomid); SQLITE(bind_int64,stmt2,2,beforeid); SQLITE(bind_int64,stmt2,3,count); stmt = stmt2; } struct db_message_list ml; i64 cap=count; ml.count=0; ml.list=malloc(cap,struct db_message); int ret; while((ret=sqlite3_step(stmt))==SQLITE_ROW){ if(ml.count==cap){ die("sqlite gave too many rows while 'limit %" PRIi64 "' was present",count); } ml.list[ml.count].msgid=sqlite3_column_int64(stmt,0); ml.list[ml.count].roomid=roomid; ml.list[ml.count].userid=sqlite3_column_int64(stmt,1); ml.list[ml.count].timestamp=sqlite3_column_int64(stmt,2); if(sqlite3_column_type(stmt,3)==SQLITE_INTEGER){ ml.list[ml.count].replyid=sqlite3_column_int64(stmt,3); } else { ml.list[ml.count].replyid=-1; // NULL, not a reply } ml.list[ml.count].message=strdup((const char*)sqlite3_column_text(stmt,4)); ml.count++; } if(ret!=SQLITE_DONE)die_sqlite("sqlite3_step"); reset_stmt(stmt); return ml; } struct db_message db_get_message(i64 msgid) { static sqlite3_stmt *stmt = NULL; if (!stmt) { SQLITE(prepare_v2, database, "select room, user, time, reply, message " "from Messages where id = ?", -1, &stmt, NULL); } SQLITE(bind_int64, stmt, 1, msgid); struct db_message msg; if (sqlite3_step(stmt) == SQLITE_ROW) { msg.msgid = msgid; msg.roomid = sqlite3_column_int64(stmt,0); msg.userid = sqlite3_column_int64(stmt,1); msg.timestamp = sqlite3_column_int64(stmt,2); if (sqlite3_column_type(stmt, 3) == SQLITE_INTEGER) { msg.replyid = sqlite3_column_int64(stmt, 3); } else { msg.replyid = -1; // NULL, not a reply } msg.message = strdup((const char*)sqlite3_column_text(stmt, 4)); } else { msg.msgid = -1; msg.roomid = -1; msg.userid = -1; msg.timestamp = -1; msg.replyid = -1; msg.message = NULL; } reset_stmt(stmt); return msg; } void db_nullify_name_id(struct db_name_id ni){ if(ni.name)free(ni.name); } void db_nullify_room_list(struct db_room_list rl){ for(i64 i=0;i