2020-03-11 09:17:11 -04:00
/*
* database . c
* vim : expandtab : ts = 4 : sts = 4 : sw = 4
*
* Copyright ( C ) 2020 Michael Vetter < jubalh @ idoru . org >
*
* This file is part of Profanity .
*
* Profanity is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* Profanity 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 General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with Profanity . If not , see < https : //www.gnu.org/licenses/>.
*
* In addition , as a special exception , the copyright holders give permission to
* link the code of portions of this program with the OpenSSL library under
* certain conditions as described in each individual source file , and
* distribute linked combinations including the two .
*
* You must obey the GNU General Public License in all respects for all of the
* code used other than OpenSSL . If you modify file ( s ) with this exception , you
* may extend this exception to your version of the file ( s ) , but you are not
* obligated to do so . If you do not wish to do so , delete this exception
* statement from your version . If you delete this exception statement from all
* source files in the program , then also delete it here .
*
*/
2020-03-19 07:10:54 -04:00
# define _GNU_SOURCE 1
2020-07-07 07:53:30 -04:00
# include <sys/stat.h>
2020-07-07 03:43:28 -04:00
# include <sqlite3.h>
2020-07-07 07:53:30 -04:00
# include <glib.h>
2020-03-19 07:10:54 -04:00
# include <stdio.h>
2020-04-06 16:42:22 -04:00
# include <stdlib.h>
# include <string.h>
2020-07-07 07:53:30 -04:00
# include <errno.h>
2020-03-11 09:17:11 -04:00
2020-07-07 07:53:30 -04:00
# include "log.h"
2020-04-18 04:01:40 -04:00
# include "common.h"
2020-03-18 13:56:22 -04:00
# include "config/files.h"
2020-03-11 09:17:11 -04:00
2020-07-07 08:18:57 -04:00
static sqlite3 * g_chatlog_database ;
2020-03-11 09:17:11 -04:00
2020-07-07 08:18:57 -04:00
static void _add_to_db ( ProfMessage * message , char * type , const Jid * const from_jid , const Jid * const to_jid ) ;
static char * _get_db_filename ( ProfAccount * account ) ;
static prof_msg_type_t _get_message_type_type ( const char * const type ) ;
2020-03-27 19:18:13 -04:00
2020-03-23 07:18:25 -04:00
static char *
2020-07-07 08:18:57 -04:00
_get_db_filename ( ProfAccount * account )
2020-03-23 07:18:25 -04:00
{
2020-07-07 08:18:57 -04:00
gchar * database_dir = files_get_account_data_path ( DIR_DATABASE , account - > jid ) ;
2020-03-23 07:18:25 -04:00
2020-07-01 04:05:45 -04:00
int res = g_mkdir_with_parents ( database_dir , S_IRWXU ) ;
2020-03-23 07:18:25 -04:00
if ( res = = - 1 ) {
2020-10-14 03:52:26 -04:00
const char * errmsg = strerror ( errno ) ;
2020-03-23 07:18:25 -04:00
if ( errmsg ) {
2020-07-01 04:05:45 -04:00
log_error ( " DATABASE: error creating directory: %s, %s " , database_dir , errmsg ) ;
2020-03-23 07:18:25 -04:00
} else {
2020-07-01 04:05:45 -04:00
log_error ( " DATABASE: creating directory: %s " , database_dir ) ;
2020-03-23 07:18:25 -04:00
}
2020-07-01 04:05:45 -04:00
g_free ( database_dir ) ;
2020-03-23 07:18:25 -04:00
return NULL ;
}
2020-07-07 08:18:57 -04:00
GString * chatlog_filename = g_string_new ( database_dir ) ;
2020-07-01 04:05:45 -04:00
g_string_append ( chatlog_filename , " /chatlog.db " ) ;
2020-07-07 08:18:57 -04:00
gchar * result = g_strdup ( chatlog_filename - > str ) ;
2020-07-01 04:05:45 -04:00
g_string_free ( chatlog_filename , TRUE ) ;
g_free ( database_dir ) ;
2020-03-23 07:18:25 -04:00
return result ;
}
2020-03-23 07:33:42 -04:00
gboolean
2020-07-07 08:18:57 -04:00
log_database_init ( ProfAccount * account )
2020-03-11 09:17:11 -04:00
{
int ret = sqlite3_initialize ( ) ;
2020-03-27 14:09:29 -04:00
if ( ret ! = SQLITE_OK ) {
2020-03-11 09:17:11 -04:00
log_error ( " Error initializing SQLite database: %d " , ret ) ;
return FALSE ;
2020-03-27 14:09:29 -04:00
}
2020-03-11 09:17:11 -04:00
2020-07-07 08:18:57 -04:00
char * filename = _get_db_filename ( account ) ;
2020-03-23 07:18:25 -04:00
if ( ! filename ) {
return FALSE ;
}
2020-03-18 13:56:22 -04:00
ret = sqlite3_open ( filename , & g_chatlog_database ) ;
2020-03-11 09:17:11 -04:00
if ( ret ! = SQLITE_OK ) {
2020-07-07 08:18:57 -04:00
const char * err_msg = sqlite3_errmsg ( g_chatlog_database ) ;
2020-03-11 09:17:11 -04:00
log_error ( " Error opening SQLite database: %s " , err_msg ) ;
2020-03-18 13:56:22 -04:00
free ( filename ) ;
2020-03-11 09:17:11 -04:00
return FALSE ;
}
2020-07-07 08:18:57 -04:00
char * err_msg ;
2020-03-27 14:09:29 -04:00
// id is the ID of DB the entry
// from_jid is the senders jid
// to_jid is the receivers jid
// from_resource is the senders resource
// to_jid is the receivers resource
// message is the message text
// timestamp the timestamp like "2020/03/24 11:12:14"
2020-03-27 14:50:24 -04:00
// type is there to distinguish: message (chat), MUC message (muc), muc pm (mucpm)
2020-07-23 09:19:13 -04:00
// stanza_id is the ID in <message>
// archive_id is the stanza-id from from XEP-0359: Unique and Stable Stanza IDs used for XEP-0313: Message Archive Management
2020-03-27 14:09:29 -04:00
// replace_id is the ID from XEP-0308: Last Message Correction
// encryption is to distinguish: none, omemo, otr, pgp
// marked_read is 0/1 whether a message has been marked as read via XEP-0333: Chat Markers
2020-07-07 08:18:57 -04:00
char * query = " CREATE TABLE IF NOT EXISTS `ChatLogs` ( `id` INTEGER PRIMARY KEY AUTOINCREMENT, `from_jid` TEXT NOT NULL, `to_jid` TEXT NOT NULL, `from_resource` TEXT, `to_resource` TEXT, `message` TEXT, `timestamp` TEXT, `type` TEXT, `stanza_id` TEXT, `archive_id` TEXT, `replace_id` TEXT, `encryption` TEXT, `marked_read` INTEGER) " ;
if ( SQLITE_OK ! = sqlite3_exec ( g_chatlog_database , query , NULL , 0 , & err_msg ) ) {
2020-03-23 10:21:31 -04:00
goto out ;
2020-03-23 10:18:55 -04:00
}
2020-03-27 14:09:29 -04:00
query = " CREATE TABLE IF NOT EXISTS `DbVersion` ( `dv_id` INTEGER PRIMARY KEY, `version` INTEGER UNIQUE) " ;
2020-07-07 08:18:57 -04:00
if ( SQLITE_OK ! = sqlite3_exec ( g_chatlog_database , query , NULL , 0 , & err_msg ) ) {
2020-03-23 10:21:31 -04:00
goto out ;
2020-03-23 10:18:55 -04:00
}
2020-03-27 14:09:29 -04:00
query = " INSERT OR IGNORE INTO `DbVersion` (`version`) VALUES('1') " ;
2020-07-07 08:18:57 -04:00
if ( SQLITE_OK ! = sqlite3_exec ( g_chatlog_database , query , NULL , 0 , & err_msg ) ) {
2020-03-23 10:21:31 -04:00
goto out ;
2020-03-18 13:42:16 -04:00
}
log_debug ( " Initialized SQLite database: %s " , filename ) ;
2020-03-18 13:56:22 -04:00
free ( filename ) ;
2020-03-11 09:17:11 -04:00
return TRUE ;
2020-03-23 10:21:31 -04:00
out :
if ( err_msg ) {
log_error ( " SQLite error: %s " , err_msg ) ;
sqlite3_free ( err_msg ) ;
} else {
log_error ( " Unknown SQLite error " ) ;
}
free ( filename ) ;
return FALSE ;
2020-03-11 09:17:11 -04:00
}
void
log_database_close ( void )
{
2020-03-23 07:23:21 -04:00
if ( g_chatlog_database ) {
sqlite3_close ( g_chatlog_database ) ;
sqlite3_shutdown ( ) ;
g_chatlog_database = NULL ;
}
2020-03-11 09:17:11 -04:00
}
2020-03-19 07:10:54 -04:00
2020-04-06 12:55:20 -04:00
void
2020-07-07 08:18:57 -04:00
log_database_add_incoming ( ProfMessage * message )
2020-03-27 20:16:31 -04:00
{
2020-05-27 16:06:04 -04:00
if ( message - > to_jid ) {
_add_to_db ( message , NULL , message - > from_jid , message - > to_jid ) ;
} else {
2021-03-25 11:44:36 -04:00
Jid * myjid = jid_create ( connection_get_fulljid ( ) ) ;
2020-03-27 19:50:32 -04:00
2020-05-27 16:06:04 -04:00
_add_to_db ( message , NULL , message - > from_jid , myjid ) ;
2020-03-27 19:50:32 -04:00
2020-05-27 16:06:04 -04:00
jid_destroy ( myjid ) ;
}
2020-03-27 19:18:13 -04:00
}
2020-03-27 20:16:31 -04:00
static void
2020-07-07 08:18:57 -04:00
_log_database_add_outgoing ( char * type , const char * const id , const char * const barejid , const char * const message , const char * const replace_id , prof_enc_t enc )
2020-03-27 19:50:32 -04:00
{
2020-07-07 08:18:57 -04:00
ProfMessage * msg = message_init ( ) ;
2020-03-27 19:50:32 -04:00
msg - > id = id ? strdup ( id ) : NULL ;
2020-04-11 11:11:53 -04:00
msg - > from_jid = jid_create ( barejid ) ;
2020-03-27 19:50:32 -04:00
msg - > plain = message ? strdup ( message ) : NULL ;
msg - > replace_id = replace_id ? strdup ( replace_id ) : NULL ;
msg - > timestamp = g_date_time_new_now_local ( ) ; //TODO: get from outside. best to have whole ProfMessage from outside
2020-03-27 20:16:31 -04:00
msg - > enc = enc ;
2020-03-27 19:50:32 -04:00
2021-03-25 11:45:30 -04:00
Jid * myjid = jid_create ( connection_get_fulljid ( ) ) ;
2020-03-27 19:50:32 -04:00
2020-04-11 11:11:53 -04:00
_add_to_db ( msg , type , myjid , msg - > from_jid ) ; // TODO: myjid now in profmessage
2020-03-27 19:50:32 -04:00
jid_destroy ( myjid ) ;
message_free ( msg ) ;
2020-03-27 19:18:13 -04:00
}
2020-03-27 20:16:31 -04:00
void
2020-07-07 08:18:57 -04:00
log_database_add_outgoing_chat ( const char * const id , const char * const barejid , const char * const message , const char * const replace_id , prof_enc_t enc )
2020-03-27 20:16:31 -04:00
{
_log_database_add_outgoing ( " chat " , id , barejid , message , replace_id , enc ) ;
}
void
2020-07-07 08:18:57 -04:00
log_database_add_outgoing_muc ( const char * const id , const char * const barejid , const char * const message , const char * const replace_id , prof_enc_t enc )
2020-03-27 20:16:31 -04:00
{
_log_database_add_outgoing ( " muc " , id , barejid , message , replace_id , enc ) ;
}
void
2020-07-07 08:18:57 -04:00
log_database_add_outgoing_muc_pm ( const char * const id , const char * const barejid , const char * const message , const char * const replace_id , prof_enc_t enc )
2020-03-27 20:16:31 -04:00
{
_log_database_add_outgoing ( " mucpm " , id , barejid , message , replace_id , enc ) ;
}
2020-04-06 12:55:20 -04:00
GSList *
2020-07-07 08:18:57 -04:00
log_database_get_previous_chat ( const gchar * const contact_barejid )
2020-04-06 12:55:20 -04:00
{
2020-07-07 08:18:57 -04:00
sqlite3_stmt * stmt = NULL ;
char * query ;
const char * jid = connection_get_fulljid ( ) ;
Jid * myjid = jid_create ( jid ) ;
2020-04-06 12:55:20 -04:00
2020-04-10 08:59:27 -04:00
if ( asprintf ( & query , " SELECT * FROM (SELECT `message`, `timestamp`, `from_jid`, `type` from `ChatLogs` WHERE (`from_jid` = '%s' AND `to_jid` = '%s') OR (`from_jid` = '%s' AND `to_jid` = '%s') ORDER BY `timestamp` DESC LIMIT 10) ORDER BY `timestamp` ASC; " , contact_barejid , myjid - > barejid , myjid - > barejid , contact_barejid ) = = - 1 ) {
2020-04-06 12:55:20 -04:00
log_error ( " log_database_get_previous_chat(): SQL query. could not allocate memory " ) ;
return NULL ;
}
2020-04-10 08:59:27 -04:00
jid_destroy ( myjid ) ;
2020-07-07 08:18:57 -04:00
int rc = sqlite3_prepare_v2 ( g_chatlog_database , query , - 1 , & stmt , NULL ) ;
if ( rc ! = SQLITE_OK ) {
2020-04-06 12:55:20 -04:00
log_error ( " log_database_get_previous_chat(): unknown SQLite error " ) ;
return NULL ;
}
2020-07-07 08:18:57 -04:00
GSList * history = NULL ;
2020-04-06 12:55:20 -04:00
2020-07-07 08:18:57 -04:00
while ( sqlite3_step ( stmt ) = = SQLITE_ROW ) {
2020-04-11 11:11:53 -04:00
// TODO: also save to jid. since now part of profmessage
2020-07-07 08:18:57 -04:00
char * message = ( char * ) sqlite3_column_text ( stmt , 0 ) ;
char * date = ( char * ) sqlite3_column_text ( stmt , 1 ) ;
char * from = ( char * ) sqlite3_column_text ( stmt , 2 ) ;
char * type = ( char * ) sqlite3_column_text ( stmt , 3 ) ;
2020-04-06 12:55:20 -04:00
2020-07-07 08:18:57 -04:00
ProfMessage * msg = message_init ( ) ;
2020-04-11 11:11:53 -04:00
msg - > from_jid = jid_create ( from ) ;
2020-04-06 12:55:20 -04:00
msg - > plain = strdup ( message ) ;
msg - > timestamp = g_date_time_new_from_iso8601 ( date , NULL ) ;
2020-04-08 06:50:23 -04:00
msg - > type = _get_message_type_type ( type ) ;
2020-04-06 12:55:20 -04:00
// TODO: later we can get more fields like 'enc'. then we can display the history like regular chats with all info the user enabled.
history = g_slist_append ( history , msg ) ;
2020-07-07 08:18:57 -04:00
}
sqlite3_finalize ( stmt ) ;
2020-04-06 12:55:20 -04:00
free ( query ) ;
return history ;
}
2020-07-07 08:18:57 -04:00
static const char *
_get_message_type_str ( prof_msg_type_t type )
{
2020-04-06 12:55:20 -04:00
switch ( type ) {
case PROF_MSG_TYPE_CHAT :
return " chat " ;
case PROF_MSG_TYPE_MUC :
return " muc " ;
case PROF_MSG_TYPE_MUCPM :
return " mucpm " ;
case PROF_MSG_TYPE_UNINITIALIZED :
return NULL ;
}
return NULL ;
}
2020-07-07 08:18:57 -04:00
static prof_msg_type_t
_get_message_type_type ( const char * const type )
{
2020-04-08 06:50:23 -04:00
if ( g_strcmp0 ( type , " chat " ) = = 0 ) {
return PROF_MSG_TYPE_CHAT ;
} else if ( g_strcmp0 ( type , " muc " ) = = 0 ) {
return PROF_MSG_TYPE_MUC ;
} else if ( g_strcmp0 ( type , " mucpm " ) = = 0 ) {
return PROF_MSG_TYPE_MUCPM ;
} else {
return PROF_MSG_TYPE_UNINITIALIZED ;
}
}
2020-07-07 08:18:57 -04:00
static const char *
_get_message_enc_str ( prof_enc_t enc )
{
2020-04-06 12:55:20 -04:00
switch ( enc ) {
2020-06-21 03:43:42 -04:00
case PROF_MSG_ENC_OX :
return " ox " ;
2020-04-06 12:55:20 -04:00
case PROF_MSG_ENC_PGP :
return " pgp " ;
case PROF_MSG_ENC_OTR :
return " otr " ;
case PROF_MSG_ENC_OMEMO :
return " omemo " ;
case PROF_MSG_ENC_NONE :
return " none " ;
}
2020-06-21 03:43:42 -04:00
return " none " ; // do not return none - return NULL
2020-04-06 12:55:20 -04:00
}
2020-03-27 19:18:13 -04:00
static void
2020-07-07 08:18:57 -04:00
_add_to_db ( ProfMessage * message , char * type , const Jid * const from_jid , const Jid * const to_jid )
2020-03-27 19:18:13 -04:00
{
2020-03-23 07:23:21 -04:00
if ( ! g_chatlog_database ) {
log_debug ( " log_database_add() called but db is not initialized " ) ;
return ;
}
2020-07-07 08:18:57 -04:00
char * err_msg ;
char * query ;
gchar * date_fmt ;
2020-04-06 06:11:08 -04:00
if ( message - > timestamp ) {
2020-09-30 13:41:18 -04:00
date_fmt = g_date_time_format_iso8601 ( message - > timestamp ) ;
2020-04-06 06:11:08 -04:00
} else {
2020-09-30 13:41:18 -04:00
date_fmt = g_date_time_format_iso8601 ( g_date_time_new_now_local ( ) ) ;
2020-04-06 06:11:08 -04:00
}
2020-03-19 07:10:54 -04:00
2020-07-07 08:18:57 -04:00
const char * enc = _get_message_enc_str ( message - > enc ) ;
2020-04-06 12:55:20 -04:00
if ( ! type ) {
type = ( char * ) _get_message_type_str ( message - > type ) ;
}
2020-07-07 08:18:57 -04:00
char * escaped_message = str_replace ( message - > plain , " ' " , " '' " ) ;
2020-04-18 04:01:40 -04:00
2020-07-23 09:19:13 -04:00
if ( asprintf ( & query , " INSERT INTO `ChatLogs` (`from_jid`, `from_resource`, `to_jid`, `to_resource`, `message`, `timestamp`, `stanza_id`, `archive_id`, `replace_id`, `type`, `encryption`) SELECT '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s' WHERE NOT EXISTS (SELECT 1 FROM `ChatLogs` WHERE `archive_id` = '%s') " ,
2020-07-07 08:18:57 -04:00
from_jid - > barejid ,
from_jid - > resourcepart ? from_jid - > resourcepart : " " ,
to_jid - > barejid ,
to_jid - > resourcepart ? to_jid - > resourcepart : " " ,
Calm OpenBSD syslog
On every write to `chatlog.db` syslog throws
```
profanity: vfprintf %s NULL in "INSERT INTO `ChatLogs` (`from_jid`,
`from_resource`, `to_jid`, `to_resource`, `message`, `timestamp`,
`stanza_id`, `archive_id`, `replace_id`, `type`, `encryption`) SELECT
'%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s' WHERE
NOT EXISTS (SELECT 1 FROM `ChatLogs` WHERE `archive_id` = '%s')"
```
in `/var/log/messages`. Checking for `NULL` except in the fields the DB
is expected to throw errors for, satisfies OpenBSDs security measures
2021-02-15 02:50:43 -05:00
escaped_message ? escaped_message : " " ,
date_fmt ? date_fmt : " " ,
2020-07-07 08:18:57 -04:00
message - > id ? message - > id : " " ,
2020-07-23 09:19:13 -04:00
message - > stanzaid ? message - > stanzaid : " " ,
2020-07-07 08:18:57 -04:00
message - > replace_id ? message - > replace_id : " " ,
Calm OpenBSD syslog
On every write to `chatlog.db` syslog throws
```
profanity: vfprintf %s NULL in "INSERT INTO `ChatLogs` (`from_jid`,
`from_resource`, `to_jid`, `to_resource`, `message`, `timestamp`,
`stanza_id`, `archive_id`, `replace_id`, `type`, `encryption`) SELECT
'%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s' WHERE
NOT EXISTS (SELECT 1 FROM `ChatLogs` WHERE `archive_id` = '%s')"
```
in `/var/log/messages`. Checking for `NULL` except in the fields the DB
is expected to throw errors for, satisfies OpenBSDs security measures
2021-02-15 02:50:43 -05:00
type ? type : " " ,
enc ? enc : " " ,
message - > stanzaid ? message - > stanzaid : " " )
2020-07-07 08:18:57 -04:00
= = - 1 ) {
2020-04-06 09:55:22 -04:00
log_error ( " log_database_add(): SQL query. could not allocate memory " ) ;
2020-03-19 07:10:54 -04:00
return ;
}
2020-04-18 04:01:40 -04:00
free ( escaped_message ) ;
2020-03-19 07:10:54 -04:00
g_free ( date_fmt ) ;
2020-07-07 08:18:57 -04:00
if ( SQLITE_OK ! = sqlite3_exec ( g_chatlog_database , query , NULL , 0 , & err_msg ) ) {
2020-03-19 07:10:54 -04:00
if ( err_msg ) {
log_error ( " SQLite error: %s " , err_msg ) ;
sqlite3_free ( err_msg ) ;
} else {
log_error ( " Unknown SQLite error " ) ;
}
}
free ( query ) ;
}