/*
webchat, an HTML5/websocket chat platform
Copyright (C) 2015
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, version 3 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include "db.h"
#define db_processing(code) ((code)==SQLITE_ROW)
sqlite3* db=0;
void db_init(void)
{
// TODO: Specify filename somewhere else?
sqlite3_initialize();
if(sqlite3_open_v2("chat.db", &db, SQLITE_OPEN_READWRITE, 0))
{
printf("Error: Failed to open database! accounts/channel registration/mods will not work\n");
}
sqlite3_exec(db, "PRAGMA foreign_keys = ON;", 0, 0, 0);
}
void db_shutdown(void)
{
sqlite3_shutdown();
}
char db_findchannel(const char* name, int* id, char** password)
{
sqlite3_stmt* statement;
// TODO: consider saving statements and reusing them by sqlite3_reset() and sqlite3_clear_bindings()
sqlite3_prepare_v2(db, "select password, id from channels where name=?1;", -1, &statement, 0);
sqlite3_bind_text(statement, 1, name, strlen(name), SQLITE_STATIC);
char foundchannel=0;
int r;
while(db_processing(r=sqlite3_step(statement)))
{
if(r==SQLITE_ROW)
{
if(password){*password=strdup(sqlite3_column_text(statement, 0));}
if(id){*id=sqlite3_column_int(statement, 1);}
foundchannel=1;
break;
}
}
sqlite3_finalize(statement);
return foundchannel;
}
char db_finduser(const char* name, int* id, char** password, char** salt)
{
sqlite3_stmt* statement;
// TODO: consider saving statements and reusing them by sqlite3_reset() and sqlite3_clear_bindings()
sqlite3_prepare_v2(db, "select password, salt, id from users where name=?1;", -1, &statement, 0);
sqlite3_bind_text(statement, 1, name, strlen(name), SQLITE_STATIC);
char founduser=0;
int r;
while(db_processing(r=sqlite3_step(statement)))
{
if(r==SQLITE_ROW)
{
if(password){*password=strdup(sqlite3_column_text(statement, 0));}
if(salt){*salt=strdup(sqlite3_column_text(statement, 1));}
if(id){*id=sqlite3_column_int(statement, 2);}
founduser=1;
break;
}
}
sqlite3_finalize(statement);
return founduser;
}
int db_getmod(int channel, int user)
{
int privilege=0;
sqlite3_stmt* statement;
// TODO: consider saving statements and reusing them by sqlite3_reset() and sqlite3_clear_bindings()
sqlite3_prepare_v2(db, "select privilege from mods where channel=?1 and userid=?2;", -1, &statement, 0);
sqlite3_bind_int(statement, 1, channel);
sqlite3_bind_int(statement, 2, user);
int r;
while(db_processing(r=sqlite3_step(statement)))
{
if(r==SQLITE_ROW)
{
privilege=sqlite3_column_int(statement, 0);
break;
}
}
sqlite3_finalize(statement);
return privilege;
}
char db_setchannelpassword(const char* channel, const char* password)
{
sqlite3_stmt* statement;
// TODO: consider saving statements and reusing them by sqlite3_reset() and sqlite3_clear_bindings()
sqlite3_prepare_v2(db, "update channels set password=?1 where name=?2;", -1, &statement, 0);
sqlite3_bind_text(statement, 1, password, strlen(password), SQLITE_STATIC);
sqlite3_bind_text(statement, 2, channel, strlen(channel), SQLITE_STATIC);
int r;
while(db_processing(r=sqlite3_step(statement)));
sqlite3_finalize(statement);
return (r==SQLITE_DONE);
}
void db_mkhash(const char* password, const char* salt, char output[129])
{
// Hash the given password+salt
gnutls_datum_t data;
data.size=strlen(salt)+strlen(password);
char buf[data.size+1];
data.data=buf;
strcpy(buf, salt);
strcat(buf, password);
char hash[64];
size_t hashsize=64;
char ret=0;
int r=gnutls_fingerprint(GNUTLS_DIG_SHA512, &data, hash, &hashsize);
// Encode the binary hash to hexadecimal
data.data=hash;
data.size=hashsize;
hashsize=129;
r=gnutls_hex_encode(&data, output, &hashsize);
output[128]=0;
// printf("db_mkhash(): '%s'\n", output);
}
void db_gensalt(char salt[17])
{
int f=open("/dev/urandom", O_RDONLY);
unsigned int i;
for(i=0; i<16; ++i)
{
do{
read(f, &salt[i], 1);
}while(!isprint(salt[i]));
}
salt[16]=0;
close(f);
// printf("db_gensalt(): '%s'\n", salt);
}
char db_setuserpassword(const char* user, const char* password)
{
// Generate random 16-byte salt
char salt[17];
db_gensalt(salt);
char hash[129];
db_mkhash(password, salt, hash);
sqlite3_stmt* statement;
// TODO: consider saving statements and reusing them by sqlite3_reset() and sqlite3_clear_bindings()
sqlite3_prepare_v2(db, "update users set password=?1, salt=?2 where name=?3;", -1, &statement, 0);
sqlite3_bind_text(statement, 1, hash, strlen(hash), SQLITE_STATIC);
sqlite3_bind_text(statement, 2, salt, 16, SQLITE_STATIC);
sqlite3_bind_text(statement, 3, user, strlen(user), SQLITE_STATIC);
int r;
while(db_processing(r=sqlite3_step(statement)));
sqlite3_finalize(statement);
return (r==SQLITE_DONE);
}
char db_createuser(const char* user, const char* password)
{
// Generate random 16-byte salt
char salt[17];
db_gensalt(salt);
char hash[129];
db_mkhash(password, salt, hash);
sqlite3_stmt* statement;
// TODO: consider saving statements and reusing them by sqlite3_reset() and sqlite3_clear_bindings()
sqlite3_prepare_v2(db, "insert into users values(?1, ?2, ?3, null);", -1, &statement, 0);
sqlite3_bind_text(statement, 1, user, strlen(user), SQLITE_STATIC);
sqlite3_bind_text(statement, 2, hash, strlen(hash), SQLITE_STATIC);
sqlite3_bind_text(statement, 3, salt, 16, SQLITE_STATIC);
int r;
while(db_processing(r=sqlite3_step(statement)));
sqlite3_finalize(statement);
return (r==SQLITE_DONE);
}
char db_addmod(int channel, int user, int privileges)
{
sqlite3_stmt* statement;
// TODO: consider saving statements and reusing them by sqlite3_reset() and sqlite3_clear_bindings()
sqlite3_prepare_v2(db, "insert into mods values(?1, ?2, ?3);", -1, &statement, 0);
sqlite3_bind_int(statement, 1, channel);
sqlite3_bind_int(statement, 2, user);
sqlite3_bind_int(statement, 3, privileges);
int r;
while(db_processing(r=sqlite3_step(statement)));
sqlite3_finalize(statement);
return (r==SQLITE_DONE);
}
char db_removemod(int channel, int user)
{
sqlite3_stmt* statement;
// TODO: consider saving statements and reusing them by sqlite3_reset() and sqlite3_clear_bindings()
sqlite3_prepare_v2(db, "delete from mods where channel=?1 and userid=?2;", -1, &statement, 0);
sqlite3_bind_int(statement, 1, channel);
sqlite3_bind_int(statement, 2, user);
int r;
while(db_processing(r=sqlite3_step(statement)));
sqlite3_finalize(statement);
return (r==SQLITE_DONE);
}
char db_changemod(int channel, int user, int privileges)
{
sqlite3_stmt* statement;
// TODO: consider saving statements and reusing them by sqlite3_reset() and sqlite3_clear_bindings()
sqlite3_prepare_v2(db, "update mods set privilege=?1 where channel=?2 and userid=?3;", -1, &statement, 0);
sqlite3_bind_int(statement, 1, privileges);
sqlite3_bind_int(statement, 2, channel);
sqlite3_bind_int(statement, 3, user);
int r;
while(db_processing(r=sqlite3_step(statement)));
sqlite3_finalize(statement);
return (r==SQLITE_DONE);
}
void db_listmods(int channel, void(*modcallback)(int userid, int privileges))
{
sqlite3_stmt* statement;
// TODO: consider saving statements and reusing them by sqlite3_reset() and sqlite3_clear_bindings()
sqlite3_prepare_v2(db, "select userid, privilege from mods where channel=?1;", -1, &statement, 0);
sqlite3_bind_int(statement, 1, channel);
int r;
while(db_processing(r=sqlite3_step(statement)))
{
if(r==SQLITE_ROW)
{
modcallback(sqlite3_column_int(statement, 0), sqlite3_column_int(statement, 1));
}
}
sqlite3_finalize(statement);
}
char* db_getusername(int userid) // Caller will need to free the returned string
{
sqlite3_stmt* statement;
// TODO: consider saving statements and reusing them by sqlite3_reset() and sqlite3_clear_bindings()
sqlite3_prepare_v2(db, "select name from users where id=?1;", -1, &statement, 0);
sqlite3_bind_int(statement, 1, userid);
char* username=0;
int r;
while(db_processing(r=sqlite3_step(statement)))
{
if(r==SQLITE_ROW)
{
username=strdup(sqlite3_column_text(statement, 0));
break;
}
}
sqlite3_finalize(statement);
return username;
}
char db_createchannel(const char* channel)
{
sqlite3_stmt* statement;
// TODO: consider saving statements and reusing them by sqlite3_reset() and sqlite3_clear_bindings()
sqlite3_prepare_v2(db, "insert into channels values(?1, '', null);", -1, &statement, 0);
sqlite3_bind_text(statement, 1, channel, strlen(channel), SQLITE_STATIC);
int r;
while(db_processing(r=sqlite3_step(statement)));
sqlite3_finalize(statement);
return (r==SQLITE_DONE);
}
// TODO: function to remove account too? though this brings up questions about how to deal with sole channel owners
char db_removechannel(const char* channel)
{
sqlite3_stmt* statement;
int id;
if(!db_findchannel(channel, &id, 0)){return 0;}
// Delete channel's mods
// TODO: consider saving statements and reusing them by sqlite3_reset() and sqlite3_clear_bindings()
sqlite3_prepare_v2(db, "delete from mods where channel=?1;", -1, &statement, 0);
sqlite3_bind_int(statement, 1, id);
int r;
while(db_processing(r=sqlite3_step(statement)));
sqlite3_finalize(statement);
// Delete the channel itself
sqlite3_prepare_v2(db, "delete from channels where name=?1;", -1, &statement, 0);
sqlite3_bind_text(statement, 1, channel, strlen(channel), SQLITE_STATIC);
while(db_processing(r=sqlite3_step(statement)));
sqlite3_finalize(statement);
return (r==SQLITE_DONE);
}