Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package otpclient for openSUSE:Factory checked in at 2024-02-12 18:52:55 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/otpclient (Old) and /work/SRC/openSUSE:Factory/.otpclient.new.1815 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "otpclient" Mon Feb 12 18:52:55 2024 rev:32 rq:1146089 version:3.4.1 Changes: -------- --- /work/SRC/openSUSE:Factory/otpclient/otpclient.changes 2024-02-07 18:52:57.964579634 +0100 +++ /work/SRC/openSUSE:Factory/.otpclient.new.1815/otpclient.changes 2024-02-12 18:55:23.365387860 +0100 @@ -1,0 +2,18 @@ +Mon Feb 12 07:53:22 UTC 2024 - Paolo Stivanin <i...@paolostivanin.com> + +- Update to 3.4.1: + * FIX: FreeOTP+ export (thanks @hubnut) + +------------------------------------------------------------------- +Sat Feb 10 15:07:04 UTC 2024 - Paolo Stivanin <i...@paolostivanin.com> + +- Update to 3.4.0: + * NEW: CLI was completely refactored, and the options have changed. + Be sure to check the new options using the help command (-h) + * NEW: you can now specify a database when calling the CLI (#340) + * FIX: handling errors when path and/or password is incorrect (#336) + * FIX: prompt for file again, if needed (#335) + * FIX: prevent about dialog from hiding + * FIX: use system RNG as source of entropy + +------------------------------------------------------------------- Old: ---- v3.3.0.tar.gz v3.3.0.tar.gz.asc New: ---- v3.4.1.tar.gz v3.4.1.tar.gz.asc ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ otpclient.spec ++++++ --- /var/tmp/diff_new_pack.kkujo8/_old 2024-02-12 18:55:23.881406496 +0100 +++ /var/tmp/diff_new_pack.kkujo8/_new 2024-02-12 18:55:23.881406496 +0100 @@ -18,7 +18,7 @@ %define uclname OTPClient Name: otpclient -Version: 3.3.0 +Version: 3.4.1 Release: 0 Summary: Simple GTK+ client for managing TOTP and HOTP License: GPL-3.0-or-later ++++++ v3.3.0.tar.gz -> v3.4.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OTPClient-3.3.0/CMakeLists.txt new/OTPClient-3.4.1/CMakeLists.txt --- old/OTPClient-3.3.0/CMakeLists.txt 2024-01-08 12:16:54.000000000 +0100 +++ new/OTPClient-3.4.1/CMakeLists.txt 2024-02-12 08:48:17.000000000 +0100 @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.16) -project(OTPClient VERSION "3.3.0" LANGUAGES "C") +project(OTPClient VERSION "3.4.1" LANGUAGES "C") include(GNUInstallDirs) configure_file("src/common/version.h.in" "version.h") @@ -88,7 +88,8 @@ src/shortcuts-cb.h src/webcam-add-cb.h src/edit-row-cb.h - src/show-qr-cb.h src/dbinfo-cb.h) + src/show-qr-cb.h src/dbinfo-cb.h + src/change-file-cb.h) set(GUI_SOURCE_FILES src/common/common.c @@ -128,10 +129,10 @@ src/about_diag_cb.c src/show-qr-cb.c src/setup-signals-shortcuts.c - src/change-pwd-cb.c src/dbinfo-cb.c) + src/change-pwd-cb.c + src/dbinfo-cb.c) set(CLI_HEADER_FILES - src/cli/help.h src/cli/get-data.h src/common/common.h src/db-misc.h @@ -141,15 +142,17 @@ src/parse-uri.h src/common/get-providers-data.h src/google-migration.pb-c.h - src/secret-schema.h) + src/secret-schema.h + src/cli/main.h +) set(CLI_SOURCE_FILES src/cli/main.c - src/cli/help.c src/cli/get-data.c src/common/common.c src/db-misc.c src/gquarks.c + src/cli/exec-action.c src/file-size.c src/parse-uri.c src/common/andotp.c diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OTPClient-3.3.0/SECURITY.md new/OTPClient-3.4.1/SECURITY.md --- old/OTPClient-3.3.0/SECURITY.md 2024-01-08 12:16:54.000000000 +0100 +++ new/OTPClient-3.4.1/SECURITY.md 2024-02-12 08:48:17.000000000 +0100 @@ -7,7 +7,7 @@ | Version | Supported | EOL | |---------|--------------------|-------------| | 3.3.x | :white_check_mark: | - | -| 3.2.x | :white_check_mark: | 31-Jan-2024 | +| 3.2.x | :x: | 31-Jan-2024 | | 3.1.x | :x: | 30-Nov-2023 | | 3.0.x | :x: | 31-Dec-2022 | | 2.6.x | :x: | 15-Jan-2023 | diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OTPClient-3.3.0/data/com.github.paolostivanin.OTPClient.appdata.xml new/OTPClient-3.4.1/data/com.github.paolostivanin.OTPClient.appdata.xml --- old/OTPClient-3.3.0/data/com.github.paolostivanin.OTPClient.appdata.xml 2024-01-08 12:16:54.000000000 +0100 +++ new/OTPClient-3.4.1/data/com.github.paolostivanin.OTPClient.appdata.xml 2024-02-12 08:48:17.000000000 +0100 @@ -14,6 +14,8 @@ <keyword>2fa</keyword> <keyword>2factor</keyword> <keyword>2fa-client</keyword> + <keyword>2step</keyword> + <keyword>twostep</keyword> </keywords> <description> @@ -87,6 +89,26 @@ </content_rating> <releases> + <release version="3.4.1" date="2024-02-12"> + <description> + <p>OTPClient 3.4.1 brings a single fix::</p> + <ul> + <li>FIX: fix FreeOTP+ export (thanks @hubnut)</li> + </ul> + </description> + </release> + <release version="3.4.0" date="2024-02-09"> + <description> + <p>OTPClient 3.4.0 brings the following changes:</p> + <ul> + <li>NEW: you can now specify a database when calling the CLI (#340)</li> + <li>FIX: handling errors when path and/or password is incorrect (#336)</li> + <li>FIX: prompt for file again, if needed (#335)</li> + <li>FIX: prevent about dialog from hiding</li> + <li>FIX: use system RNG as source of entropy</li> + </ul> + </description> + </release> <release version="3.3.0" date="2024-01-08"> <description> <p>OTPClient 3.3.0 brings the following changes:</p> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OTPClient-3.3.0/src/about_diag_cb.c new/OTPClient-3.4.1/src/about_diag_cb.c --- old/OTPClient-3.3.0/src/about_diag_cb.c 2024-01-08 12:16:54.000000000 +0100 +++ new/OTPClient-3.4.1/src/about_diag_cb.c 2024-02-12 08:48:17.000000000 +0100 @@ -16,11 +16,11 @@ gchar *icon_abs_path = g_strconcat (INSTALL_PREFIX, "/", partial_path, NULL); GtkWidget *ab_diag = gtk_about_dialog_new (); - gtk_window_set_transient_for (GTK_WINDOW(app_data->main_window), GTK_WINDOW(ab_diag)); + gtk_window_set_transient_for (GTK_WINDOW(ab_diag), GTK_WINDOW(app_data->main_window)); gtk_about_dialog_set_program_name (GTK_ABOUT_DIALOG(ab_diag), PROJECT_NAME); gtk_about_dialog_set_version (GTK_ABOUT_DIALOG(ab_diag), PROJECT_VER); - gtk_about_dialog_set_copyright (GTK_ABOUT_DIALOG(ab_diag), "2017-2022"); + gtk_about_dialog_set_copyright (GTK_ABOUT_DIALOG(ab_diag), "2017-2024"); gtk_about_dialog_set_comments (GTK_ABOUT_DIALOG(ab_diag), _("Highly secure and easy to use GTK+ software for two-factor authentication that supports both Time-based One-time Passwords (TOTP) and HMAC-Based One-Time Passwords (HOTP).")); gtk_about_dialog_set_license_type (GTK_ABOUT_DIALOG(ab_diag), GTK_LICENSE_GPL_3_0); gtk_about_dialog_set_website (GTK_ABOUT_DIALOG(ab_diag), "https://github.com/paolostivanin/OTPClient"); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OTPClient-3.3.0/src/app.c new/OTPClient-3.4.1/src/app.c --- old/OTPClient-3.3.0/src/app.c 2024-01-08 12:16:54.000000000 +0100 +++ new/OTPClient-3.4.1/src/app.c 2024-02-12 08:48:17.000000000 +0100 @@ -24,6 +24,7 @@ #include "edit-row-cb.h" #include "show-qr-cb.h" #include "dbinfo-cb.h" +#include "change-file-cb.h" #ifndef USE_FLATPAK_APP_FOLDER static gchar *get_db_path (AppData *app_data); @@ -208,11 +209,14 @@ retry: app_data->db_data->key = prompt_for_password (app_data, NULL, NULL, FALSE); if (app_data->db_data->key == NULL) { - if (change_file (app_data) == FALSE) { + retry_change_file: + if (change_file (app_data) == QUIT_APP) { g_free (app_data->db_data); g_free (app_data); g_application_quit (G_APPLICATION(app)); return; + } else { + goto retry_change_file; } } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OTPClient-3.3.0/src/change-db-cb.c new/OTPClient-3.4.1/src/change-db-cb.c --- old/OTPClient-3.3.0/src/change-db-cb.c 2024-01-08 12:16:54.000000000 +0100 +++ new/OTPClient-3.4.1/src/change-db-cb.c 2024-02-12 08:48:17.000000000 +0100 @@ -7,15 +7,11 @@ #include "password-cb.h" #include "db-actions.h" #include "secret-schema.h" +#include "change-file-cb.h" - -void -change_db_cb (GSimpleAction *simple __attribute__((unused)), - GVariant *parameter __attribute__((unused)), - gpointer user_data) +int +change_db (AppData *app_data) { - AppData *app_data = (AppData *)user_data; - GtkWidget *changedb_diag = GTK_WIDGET(gtk_builder_get_object (app_data->builder, "changedb_diag_id")); GtkWidget *old_changedb_entry = GTK_WIDGET(gtk_builder_get_object (app_data->builder, "changedb_olddb_entry_id")); GtkWidget *new_changedb_entry = GTK_WIDGET(gtk_builder_get_object (app_data->builder, "changedb_entry_id")); @@ -29,29 +25,60 @@ gint result = gtk_dialog_run (GTK_DIALOG (changedb_diag)); switch (result) { case GTK_RESPONSE_OK: + if (gtk_entry_get_text_length (GTK_ENTRY(new_changedb_entry)) == 0) { + show_message_dialog (app_data->main_window, "Input path cannot be empty.", GTK_MESSAGE_ERROR); + gtk_widget_hide (changedb_diag); + return RETRY_CHANGE; + } new_db_path = gtk_entry_get_text (GTK_ENTRY(new_changedb_entry)); if (!g_file_test (new_db_path, G_FILE_TEST_IS_REGULAR) || g_file_test (new_db_path,G_FILE_TEST_IS_SYMLINK)){ show_message_dialog (app_data->main_window, "Selected file is either a symlink or a non regular file.\nPlease choose another file.", GTK_MESSAGE_ERROR); - } else { - g_free (app_data->db_data->db_path); - app_data->db_data->db_path = g_strdup (new_db_path); - update_cfg_file (app_data); - gcry_free (app_data->db_data->key); - app_data->db_data->key = prompt_for_password (app_data, NULL, NULL, FALSE); - secret_password_store (OTPCLIENT_SCHEMA, SECRET_COLLECTION_DEFAULT, "main_pwd", app_data->db_data->key, NULL, on_password_stored, NULL, "string", "main_pwd", NULL); - GError *err = NULL; - load_new_db (app_data, &err); - if (err != NULL) { - show_message_dialog (app_data->main_window, err->message, GTK_MESSAGE_ERROR); - g_clear_error (&err); - } + gtk_widget_hide (changedb_diag); + return RETRY_CHANGE; + } + gchar *old_db_path = g_strdup (app_data->db_data->db_path); + g_free (app_data->db_data->db_path); + app_data->db_data->db_path = g_strdup (new_db_path); + update_cfg_file (app_data); + gcry_free (app_data->db_data->key); + app_data->db_data->key = prompt_for_password (app_data, NULL, NULL, FALSE); + if (app_data->db_data->key == NULL) { + gtk_widget_hide (changedb_diag); + revert_db_path (app_data, old_db_path); + return RETRY_CHANGE; } + secret_password_store (OTPCLIENT_SCHEMA, SECRET_COLLECTION_DEFAULT, "main_pwd", app_data->db_data->key, NULL, on_password_stored, NULL, "string", "main_pwd", NULL); + GError *err = NULL; + load_new_db (app_data, &err); + if (err != NULL) { + show_message_dialog (app_data->main_window, err->message, GTK_MESSAGE_ERROR); + g_clear_error (&err); + gtk_widget_hide (changedb_diag); + revert_db_path (app_data, old_db_path); + return RETRY_CHANGE; + } + g_free (old_db_path); break; case GTK_RESPONSE_CANCEL: + gtk_widget_destroy (changedb_diag); + return QUIT_APP; default: break; } gtk_widget_destroy (changedb_diag); + + return CHANGE_OK; +} + + +void +change_db_cb (GSimpleAction *action_name __attribute__((unused)), + GVariant *parameter __attribute__((unused)), + gpointer user_data) +{ + AppData *app_data = (AppData *)user_data; + + change_db (app_data); } @@ -60,4 +87,4 @@ gpointer user_data) { change_db_cb (NULL, NULL, user_data); -} +} \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OTPClient-3.3.0/src/change-db-cb.h new/OTPClient-3.4.1/src/change-db-cb.h --- old/OTPClient-3.3.0/src/change-db-cb.h 2024-01-08 12:16:54.000000000 +0100 +++ new/OTPClient-3.4.1/src/change-db-cb.h 2024-02-12 08:48:17.000000000 +0100 @@ -4,6 +4,8 @@ G_BEGIN_DECLS +int change_db (AppData *app_data); + void change_db_cb (GSimpleAction *simple, GVariant *parameter, gpointer user_data); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OTPClient-3.3.0/src/change-file-cb.c new/OTPClient-3.4.1/src/change-file-cb.c --- old/OTPClient-3.3.0/src/change-file-cb.c 2024-01-08 12:16:54.000000000 +0100 +++ new/OTPClient-3.4.1/src/change-file-cb.c 2024-02-12 08:48:17.000000000 +0100 @@ -1,10 +1,10 @@ #include <gtk/gtk.h> #include "new-db-cb.h" #include "change-db-cb.h" -#include "db-misc.h" +#include "change-file-cb.h" #include "message-dialogs.h" -gboolean +int change_file (AppData *app_data) { GtkWidget *label = GTK_WIDGET(gtk_builder_get_object (app_data->builder, "diag_changefile_label_id")); @@ -21,18 +21,18 @@ switch (result) { case GTK_RESPONSE_ACCEPT: // select an existing DB. - change_db_cb (NULL, NULL, app_data); - res = TRUE; + res = change_db (app_data); break; case GTK_RESPONSE_OK: // create a new db. - new_db_cb (NULL, NULL, app_data); - res = TRUE; + res = new_db (app_data); break; - case GTK_RESPONSE_CANCEL: + case GTK_RESPONSE_CLOSE: + res = QUIT_APP; default: break; } + gtk_widget_hide (diag_changefile); return res; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OTPClient-3.3.0/src/change-file-cb.h new/OTPClient-3.4.1/src/change-file-cb.h --- old/OTPClient-3.3.0/src/change-file-cb.h 1970-01-01 01:00:00.000000000 +0100 +++ new/OTPClient-3.4.1/src/change-file-cb.h 2024-02-12 08:48:17.000000000 +0100 @@ -0,0 +1,13 @@ +#pragma once + +#include "data.h" + +G_BEGIN_DECLS + +#define QUIT_APP 50 +#define RETRY_CHANGE 51 +#define CHANGE_OK 52 + +int change_file (AppData *app_data); + +G_END_DECLS \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OTPClient-3.3.0/src/cli/exec-action.c new/OTPClient-3.4.1/src/cli/exec-action.c --- old/OTPClient-3.3.0/src/cli/exec-action.c 1970-01-01 01:00:00.000000000 +0100 +++ new/OTPClient-3.4.1/src/cli/exec-action.c 2024-02-12 08:48:17.000000000 +0100 @@ -0,0 +1,251 @@ +#include <glib.h> +#include <glib/gi18n.h> +#include <gcrypt.h> +#include <termios.h> +#include <libsecret/secret.h> +#include "main.h" +#include "../secret-schema.h" +#include "../common/common.h" +#include "../db-misc.h" +#include "get-data.h" +#include "../common/exports.h" + +#ifndef USE_FLATPAK_APP_FOLDER +static gchar *get_db_path (void); +#endif + +static gchar *get_pwd (const gchar *pwd_msg); + +static gboolean get_use_secretservice (void); + + +gboolean exec_action (CmdlineOpts *cmdline_opts, + DatabaseData *db_data) +{ +#ifdef USE_FLATPAK_APP_FOLDER + db_data->db_path = g_build_filename (g_get_user_data_dir (), "otpclient-db.enc", NULL); + // on the first run the cfg file is not created in the flatpak version because we use a non-changeable db path + gchar *cfg_file_path = g_build_filename (g_get_user_data_dir (), "otpclient.cfg", NULL); + if (!g_file_test (cfg_file_path, G_FILE_TEST_EXISTS)) { + g_file_set_contents (cfg_file_path, "[config]", -1, NULL); + } + g_free (cfg_file_path); +#else + db_data->db_path = (cmdline_opts->database != NULL) ? g_strdup (cmdline_opts->database) : get_db_path (); + if (db_data->db_path == NULL) { + g_free (db_data); + return FALSE; + } +#endif + + gboolean use_secret_service = get_use_secretservice (); + if (use_secret_service == TRUE) { + gchar *pwd = secret_password_lookup_sync (OTPCLIENT_SCHEMA, NULL, NULL, "string", "main_pwd", NULL); + if (pwd == NULL) { + goto get_pwd; + } else { + db_data->key_stored = TRUE; + db_data->key= secure_strdup (pwd); + secret_password_free (pwd); + } + } else { + get_pwd: + db_data->key = get_pwd (_("Type the DB decryption password: ")); + if (db_data->key == NULL) { + g_print ("Password was NULL, exiting...\n"); + g_free (db_data); + return FALSE; + } + } + + GError *err = NULL; + load_db (db_data, &err); + if (err != NULL) { + gchar *msg = g_strconcat (_("Error while loading the database: "), err->message, NULL); + g_printerr ("%s\n", msg); + g_free (msg); + free_dbdata (db_data); + return FALSE; + } + + if (use_secret_service == TRUE && db_data->key_stored == FALSE) { + secret_password_store (OTPCLIENT_SCHEMA, SECRET_COLLECTION_DEFAULT, "main_pwd", db_data->key, NULL, on_password_stored, NULL, "string", "main_pwd", NULL); + } + + if (cmdline_opts->show) { + show_token (db_data, cmdline_opts->account, cmdline_opts->issuer, cmdline_opts->match_exact, cmdline_opts->show_next); + } + + if (cmdline_opts->list) { + list_all_acc_iss (db_data); + } + + if (cmdline_opts->export) { + gchar *export_directory; +#ifdef USE_FLATPAK_APP_FOLDER + export_directory = g_get_user_data_dir (); +#else + export_directory = (cmdline_opts->export_dir != NULL) ? cmdline_opts->export_dir : (gchar *)g_get_home_dir (); + if (!g_file_test (export_directory, G_FILE_TEST_IS_DIR)) { + g_printerr (_("%s is not a directory or the folder doesn't exist. The output will be saved into the HOME directory.\n"), export_directory); + export_directory = (gchar *)g_get_home_dir (); + } +#endif + gboolean exported = FALSE; + gchar *export_pwd = NULL, *exported_file_path = NULL, *ret_msg = NULL; + if (g_ascii_strcasecmp (cmdline_opts->export_type, "andotp_plain") == 0 || g_ascii_strcasecmp (cmdline_opts->export_type, "andotp_encrypted") == 0) { + if (g_ascii_strcasecmp (cmdline_opts->export_type, "andotp_encrypted") == 0) { + export_pwd = get_pwd (_("Type the export encryption password: ")); + if (export_pwd == NULL) { + free_dbdata (db_data); + return FALSE; + } + } + exported_file_path = g_build_filename (export_directory, export_pwd != NULL ? "andotp_exports.json.aes" : "andotp_exports.json", NULL); + ret_msg = export_andotp (exported_file_path, export_pwd, db_data->json_data); + gcry_free (export_pwd); + exported = TRUE; + } + if (g_ascii_strcasecmp (cmdline_opts->export_type, "freeotpplus") == 0) { + exported_file_path = g_build_filename (export_directory, "freeotpplus-exports.txt", NULL); + ret_msg = export_freeotpplus (exported_file_path, db_data->json_data); + exported = TRUE; + } + if (g_ascii_strcasecmp (cmdline_opts->export_type, "aegis_plain") == 0 || g_ascii_strcasecmp (cmdline_opts->export_type, "aegis_encrypted") == 0) { + if (g_ascii_strcasecmp (cmdline_opts->export_type, "aegis_encrypted") == 0) { + export_pwd = get_pwd (_("Type the export encryption password: ")); + if (export_pwd == NULL) { + free_dbdata (db_data); + return FALSE; + } + } + exported_file_path = g_build_filename (export_directory, export_pwd != NULL ? "aegis_exports.json.aes" : "aegis_exports.json", NULL); + ret_msg = export_aegis (exported_file_path, db_data->json_data, export_pwd); + gcry_free (export_pwd); + exported = TRUE; + } + if (ret_msg != NULL) { + g_printerr (_("An error occurred while exporting the data: %s\n"), ret_msg); + g_free (ret_msg); + } else { + if (exported) { + g_print (_("Data successfully exported to: %s\n"), exported_file_path); + } else { + gchar *msg = g_strconcat ("Option not recognized: ", cmdline_opts->export_type, NULL); + g_print ("%s\n", msg); + g_free (msg); + return FALSE; + } + } + g_free (exported_file_path); + } + return TRUE; +} + +#ifndef USE_FLATPAK_APP_FOLDER +static gchar * +get_db_path (void) +{ + gchar *db_path = NULL; + GError *err = NULL; + GKeyFile *kf = g_key_file_new (); + gchar *cfg_file_path = g_build_filename (g_get_user_config_dir (), "otpclient.cfg", NULL); + if (g_file_test (cfg_file_path, G_FILE_TEST_EXISTS)) { + if (!g_key_file_load_from_file (kf, cfg_file_path, G_KEY_FILE_NONE, &err)) { + g_printerr ("%s\n", err->message); + g_key_file_free (kf); + g_clear_error (&err); + return NULL; + } + db_path = g_key_file_get_string (kf, "config", "db_path", NULL); + if (db_path == NULL) { + goto type_db_path; + } + if (!g_file_test (db_path, G_FILE_TEST_EXISTS)) { + gchar *msg = g_strconcat ("Database file/location (", db_path, ") does not exist.\n", NULL); + g_printerr ("%s\n", msg); + g_free (msg); + goto type_db_path; + } + goto end; + } + type_db_path: ; // empty statement workaround + g_print ("%s", _("Type the absolute path to the database: ")); + db_path = g_malloc0 (MAX_ABS_PATH_LEN); + if (fgets (db_path, MAX_ABS_PATH_LEN, stdin) == NULL) { + g_printerr ("%s\n", _("Couldn't get db path from stdin")); + g_free (cfg_file_path); + g_free (db_path); + return NULL; + } else { + // remove the newline char + db_path[g_utf8_strlen (db_path, -1) - 1] = '\0'; + if (!g_file_test (db_path, G_FILE_TEST_EXISTS)) { + g_printerr (_("File '%s' does not exist\n"), db_path); + g_free (cfg_file_path); + g_free (db_path); + return NULL; + } + } + + end: + g_free (cfg_file_path); + + return db_path; +} +#endif + + +static gchar * +get_pwd (const gchar *pwd_msg) +{ + gchar *pwd = gcry_calloc_secure (256, 1); + g_print ("%s", pwd_msg); + + struct termios old, new; + if (tcgetattr (STDIN_FILENO, &old) != 0) { + g_printerr ("%s\n", _("Couldn't get termios info")); + gcry_free (pwd); + return NULL; + } + new = old; + new.c_lflag &= ~ECHO; + if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &new) != 0) { + g_printerr ("%s\n", _("Couldn't turn echoing off")); + gcry_free (pwd); + return NULL; + } + if (fgets (pwd, 256, stdin) == NULL) { + g_printerr ("%s\n", _("Couldn't read password from stdin")); + gcry_free (pwd); + return NULL; + } + g_print ("\n"); + tcsetattr (STDIN_FILENO, TCSAFLUSH, &old); + + pwd[g_utf8_strlen (pwd, -1) - 1] = '\0'; + + gchar *realloc_pwd = gcry_realloc (pwd, g_utf8_strlen (pwd, -1) + 1); + + return realloc_pwd; +} + + +static gboolean +get_use_secretservice (void) +{ + gboolean use_secret_service = TRUE; // by default, we enable it + GError *err = NULL; + GKeyFile *kf = g_key_file_new (); + gchar *cfg_file_path = g_build_filename (g_get_user_config_dir (), "otpclient.cfg", NULL); + if (g_file_test (cfg_file_path, G_FILE_TEST_EXISTS)) { + if (!g_key_file_load_from_file (kf, cfg_file_path, G_KEY_FILE_NONE, &err)) { + g_printerr ("%s\n", err->message); + g_key_file_free (kf); + g_clear_error (&err); + return FALSE; + } + use_secret_service = g_key_file_get_boolean (kf, "config", "use_secret_service", NULL); + } + return use_secret_service; +} \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OTPClient-3.3.0/src/cli/help.c new/OTPClient-3.4.1/src/cli/help.c --- old/OTPClient-3.3.0/src/cli/help.c 2024-01-08 12:16:54.000000000 +0100 +++ new/OTPClient-3.4.1/src/cli/help.c 1970-01-01 01:00:00.000000000 +0100 @@ -1,121 +0,0 @@ -#include <glib.h> -#include <glib/gi18n.h> -#include "version.h" -#include "../common/common.h" - -static void print_main_help (const gchar *prg_name); - -static void print_show_help (const gchar *prg_name); - -static void print_export_help (const gchar *prg_name); - - -gboolean show_help (const gchar *prg_name, - const gchar *help_command) -{ - gboolean help_displayed = FALSE; - if (g_strcmp0 (help_command, "-h") == 0 || g_strcmp0 (help_command, "--help") == 0 || g_strcmp0 (help_command, "help") == 0 || - help_command == NULL || g_utf8_strlen (help_command, -1) < 2) { - print_main_help (prg_name); - help_displayed = TRUE; - } else if (g_strcmp0 (help_command, "-v") == 0 || g_strcmp0 (help_command, "--version") == 0) { - g_print ("%s v%s\n", PROJECT_NAME, PROJECT_VER); - help_displayed = TRUE; - } else if (g_strcmp0 (help_command, "--help-show") == 0 || g_strcmp0 (help_command, "help-show") == 0) { - print_show_help (prg_name); - help_displayed = TRUE; - } else if (g_strcmp0 (help_command, "--help-export") == 0 || g_strcmp0 (help_command, "help-export") == 0) { - print_export_help (prg_name); - help_displayed = TRUE; - } - - return help_displayed; -} - - -static void -print_main_help (const gchar *prg_name) -{ - GString *msg = g_string_new (_("Usage:\n %s <main option> [option 1] [option 2] ...")); -#if GLIB_CHECK_VERSION(2, 68, 0) - g_string_replace (msg, "%s", prg_name, 0); -#else - g_string_replace_backported (msg, "%s", prg_name, 0); -#endif - g_print ("%s\n\n", msg->str); - g_string_free (msg, TRUE); - - // Translators: please do not translate 'help' - g_print ("%s\n", _("help command options:")); - - // Translators: please do not translate '-h, --help' - g_print ("%s\n", _(" -h, --help\t\tShow this help")); - - // Translators: please do not translate '--help-show' - g_print ("%s\n", _(" --help-show\t\tShow options")); - - // Translators: please do not translate '--help-export' - g_print ("%s\n\n", _(" --help-export\t\tExport options")); - g_print ("%s\n", _("Main options:")); - - // Translators: please do not translate '-v, --version' - g_print ("%s\n", _(" -v, --version\t\t\t\tShow program version")); - - // Translators: please do not translate 'show <-a ..> [-i ..] [-m] [-n]' - g_print ("%s\n", _(" show <-a ..> [-i ..] [-m] [-n]\tShow a token")); - - // Translators: please do not translate 'list' - g_print ("%s\n", _(" list\t\t\t\t\tList all pairs of account and issuer")); - - // Translators: please do not translate 'export <-t ..> [-d ..]' - g_print ("%s\n\n", _(" export <-t ..> [-d ..]\t\tExport data")); -} - - -static void -print_show_help (const gchar *prg_name) -{ - // Translators: please do not translate '%s show' - GString *msg = g_string_new (_("Usage:\n %s show <-a ..> [-i ..] [-m]")); -#if GLIB_CHECK_VERSION(2, 68, 0) - g_string_replace (msg, "%s", prg_name, 0); -#else - g_string_replace_backported (msg, "%s", prg_name, 0); -#endif - g_print ("%s\n\n", msg->str); - g_string_free (msg, TRUE); - - // Translators: please do not translate 'show' - g_print ("%s\n", _("show command options:")); - // Translators: please do not translate '-a, --account' - g_print ("%s\n", _(" -a, --account\t\tThe account name (mandatory)")); - // Translators: please do not translate '-i, --issuer' - g_print ("%s\n", _(" -i, --issuer\t\tThe issuer name (optional)")); - // Translators: please do not translate '-m, --match-exactly' - g_print ("%s\n", _(" -m, --match-exactly\tShow the token only if it matches exactly the account and/or the issuer (optional)")); - // Translators: please do not translate '-n, --next' - g_print ("%s\n\n", _(" -n, --next\tShow also the next token, not only the current one (optional, valid only for TOTP)")); -} - - -static void -print_export_help (const gchar *prg_name) -{ - // Translators: please do not translate '%s export' - GString *msg = g_string_new (_("Usage:\n %s export <-t> <andotp | freeotpplus | aegis> [-d ..]")); -#if GLIB_CHECK_VERSION(2, 68, 0) - g_string_replace (msg, "%s", prg_name, 0); -#else - g_string_replace_backported (msg, "%s", prg_name, 0); -#endif - g_print ("%s\n\n", msg->str); - g_string_free (msg, TRUE); - - // Translators: please do not translate 'export' - g_print ("%s\n", _("export command options:")); - // Translators: please do not translate '-t, --type' - g_print ("%s\n", _(" -t, --type\t\tExport format. Must be either one of: andotp_plain, andotp_encrypted, freeotpplus, aegis")); - // Translators: please do not translate '-d, --directory' - g_print ("%s\n", _(" -d, --directory\tThe output directory where the exported file will be saved.")); - g_print ("%s\n\n", _("\t\t\tIf nothing is specified OR flatpak is being used, the output folder will be the user's HOME directory.")); -} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OTPClient-3.3.0/src/cli/help.h new/OTPClient-3.4.1/src/cli/help.h --- old/OTPClient-3.3.0/src/cli/help.h 2024-01-08 12:16:54.000000000 +0100 +++ new/OTPClient-3.4.1/src/cli/help.h 1970-01-01 01:00:00.000000000 +0100 @@ -1,10 +0,0 @@ -#pragma once - -#include <glib.h> - -G_BEGIN_DECLS - -gboolean show_help (const gchar *prg_name, - const gchar *help_command); - -G_END_DECLS diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OTPClient-3.3.0/src/cli/main.c new/OTPClient-3.4.1/src/cli/main.c --- old/OTPClient-3.3.0/src/cli/main.c 2024-01-08 12:16:54.000000000 +0100 +++ new/OTPClient-3.4.1/src/cli/main.c 2024-02-12 08:48:17.000000000 +0100 @@ -1,303 +1,181 @@ #include <glib.h> -#include <string.h> -#include <gcrypt.h> -#include <termios.h> -#include <libsecret/secret.h> -#include <glib/gi18n.h> -#include "help.h" -#include "get-data.h" +#include <gio/gio.h> +#include "version.h" +#include "main.h" #include "../common/common.h" -#include "../common/exports.h" -#include "../secret-schema.h" -#define MAX_ABS_PATH_LEN 256 +static gint handle_local_options (GApplication *application, + GVariantDict *options, + gpointer user_data); + +static int command_line (GApplication *application, + GApplicationCommandLine *cmdline, + gpointer user_data); -#ifndef USE_FLATPAK_APP_FOLDER -static gchar *get_db_path (void); -#endif - -static gchar *get_pwd (const gchar *pwd_msg); +static gboolean parse_options (GApplicationCommandLine *cmdline, + CmdlineOpts *cmdline_opts); -static gboolean get_use_secretservice (void); +static void g_free_cmdline_opts (CmdlineOpts *co); gint main (gint argc, gchar **argv) { - if (show_help (argv[0], argv[1])) { - return 0; - } + GOptionEntry entries[] = + { +#ifndef USE_FLATPAK_APP_FOLDER + { "database", 'd', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, NULL, "(optional) path to the database. Default value is taken from otpclient.cfg", NULL }, +#endif + { "show", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, NULL, "Show a token for a given account.", NULL }, + { "account", 'a', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, NULL, "Account name (to be used with --show, mandatory)", NULL}, + { "issuer", 'i', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, NULL, "Issuer (to be used with --show, optional)", NULL}, + { "match-exact", 'm', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, NULL, "Match exactly the provided account/issuer (to be used with --show, optional)", NULL}, + { "show-next", 'n', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, NULL, "Show also next OTP (to be used with --show, optional)", NULL}, + { "list", 'l', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, NULL, "List all accounts and issuers for a given database.", NULL }, + { "export", 'e', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, NULL, "Export a database.", NULL }, + { "type", 't', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, NULL, "The export type for the database. Must be either one of: andotp_plain, andotp_encrypted, freeotpplus, aegis, aegis_encrypted (to be used with --export, mandatory)", NULL }, +#ifndef USE_FLATPAK_APP_FOLDER + { "output-dir", 'o', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, NULL, "The output directory (defaults to the user's home. To be used with --export, optional)", NULL }, +#endif + { "version", 'v', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, NULL, "Show the program version.", NULL }, + { NULL } + }; - DatabaseData *db_data = g_new0 (DatabaseData, 1); - db_data->key_stored = FALSE; - db_data->max_file_size_from_memlock = get_max_file_size_from_memlock (); - gchar *init_msg = init_libs (db_data->max_file_size_from_memlock); - if (init_msg != NULL) { - g_printerr ("%s\n", init_msg); - g_free (init_msg); - g_free (db_data); - return -1; - } + const gchar *ctx_text = "- Highly secure and easy to use OTP client that supports both TOTP and HOTP"; -#ifdef USE_FLATPAK_APP_FOLDER - db_data->db_path = g_build_filename (g_get_user_data_dir (), "otpclient-db.enc", NULL); - // on the first run the cfg file is not created in the flatpak version because we use a non-changeable db path - gchar *cfg_file_path = g_build_filename (g_get_user_data_dir (), "otpclient.cfg", NULL); - if (!g_file_test (cfg_file_path, G_FILE_TEST_EXISTS)) { - g_file_set_contents (cfg_file_path, "[config]", -1, NULL); - } - g_free (cfg_file_path); -#else - db_data->db_path = get_db_path (); - if (db_data->db_path == NULL) { - g_free (db_data); - return -1; - } -#endif + GApplication *app = g_application_new ("com.github.paolostivanin.OTPClient", G_APPLICATION_HANDLES_COMMAND_LINE); - gboolean use_secret_service = get_use_secretservice (); - if (use_secret_service == TRUE) { - gchar *pwd = secret_password_lookup_sync (OTPCLIENT_SCHEMA, NULL, NULL, "string", "main_pwd", NULL); - if (pwd == NULL) { - goto get_pwd; - } else { - db_data->key_stored = TRUE; - db_data->key= secure_strdup (pwd); - secret_password_free (pwd); - } - } else { - get_pwd: - db_data->key = get_pwd (_("Type the DB decryption password: ")); - if (db_data->key == NULL) { - g_free (db_data); - return -1; - } - } + g_application_add_main_option_entries (app, entries); + g_application_set_option_context_parameter_string (app, ctx_text); - db_data->objects_hash = NULL; + g_signal_connect (app, "handle-local-options", G_CALLBACK (handle_local_options), NULL); + g_signal_connect (app, "command-line", G_CALLBACK(command_line), NULL); - GError *err = NULL; - load_db (db_data, &err); - if (err != NULL) { - const gchar *tmp_msg = _("Error while loading the database:"); - gchar *msg = g_strconcat (tmp_msg, " %s\n", err->message, NULL); - g_printerr ("%s\n", msg); - gcry_free (db_data->key); - g_free (db_data); - g_free (msg); - return -1; - } + int status = g_application_run (app, argc, argv); - if (use_secret_service == TRUE && db_data->key_stored == FALSE) { - secret_password_store (OTPCLIENT_SCHEMA, SECRET_COLLECTION_DEFAULT, "main_pwd", db_data->key, NULL, on_password_stored, NULL, "string", "main_pwd", NULL); - } + g_object_unref (app); - gchar *account = NULL, *issuer = NULL; - gboolean show_next_token = FALSE, match_exactly = FALSE; + return status; +} - if (g_strcmp0 (argv[1], "show") == 0) { - if (argc < 4 || argc > 8) { - // Translators: please do not translate '%s --help-show' - g_printerr (_("Wrong argument(s). Please type '%s --help-show' to see the available options.\n"), argv[0]); - g_free (db_data); - return -1; - } - for (gint i = 2; i < argc; i++) { - if (g_strcmp0 (argv[i], "-a") == 0) { - account = argv[i + 1]; - } else if (g_strcmp0 (argv[i], "-i") == 0) { - issuer = argv[i + 1]; - } else if (g_strcmp0 (argv[i], "-m") == 0) { - match_exactly = TRUE; - } else if (g_strcmp0 (argv[i], "-n") == 0) { - show_next_token = TRUE; - } - } - if (account == NULL) { - // Translators: please do not translate 'account' - g_printerr ("%s\n", _("[ERROR]: The account option (-a) must be specified and can not be empty.")); - goto end; - } - show_token (db_data, account, issuer, match_exactly, show_next_token); - } else if (g_strcmp0 (argv[1], "list") == 0) { - list_all_acc_iss (db_data); - } else if (g_strcmp0 (argv[1], "export") == 0) { - if (g_ascii_strcasecmp (argv[3], "andotp_plain") != 0 && g_ascii_strcasecmp (argv[3], "andotp_encrypted") != 0 && - g_ascii_strcasecmp (argv[3], "freeotpplus") != 0 && g_ascii_strcasecmp (argv[3], "aegis") != 0) { - // Translators: please do not translate '%s --help-export' - g_printerr (_("Wrong argument(s). Please type '%s --help-export' to see the available options.\n"), argv[0]); - g_free (db_data); - return -1; - } - const gchar *base_dir = NULL; -#ifndef USE_FLATPAK_APP_FOLDER - if (argv[4] == NULL) { - base_dir = g_get_home_dir (); - } else { - if (g_ascii_strcasecmp (argv[4], "-d") == 0 && argv[5] != NULL) { - if (!g_file_test (argv[5], G_FILE_TEST_IS_DIR)) { - g_printerr (_("%s is not a directory or the folder doesn't exist. The output will be saved into the HOME directory.\n"), argv[5]); - base_dir = g_get_home_dir (); - } else { - base_dir = argv[5]; - } - } else { - g_printerr ("%s\n", _("Incorrect parameters used for setting the output folder. Therefore, the exported file will be saved into the HOME directory.")); - base_dir = g_get_home_dir (); - } - } -#else - base_dir = g_get_user_data_dir (); -#endif - gchar *andotp_export_pwd = NULL, *exported_file_path = NULL, *ret_msg = NULL; - if (g_ascii_strcasecmp (argv[3], "andotp_plain") == 0 || g_ascii_strcasecmp (argv[3], "andotp_encrypted") == 0) { - if (g_ascii_strcasecmp (argv[3], "andotp_encrypted")) { - andotp_export_pwd = get_pwd (_("Type the export encryption password: ")); - if (andotp_export_pwd == NULL) { - goto end; - } - } - exported_file_path = g_build_filename (base_dir, andotp_export_pwd != NULL ? "andotp_exports.json.aes" : "andotp_exports.json", NULL); - ret_msg = export_andotp (exported_file_path, andotp_export_pwd, db_data->json_data); - gcry_free (andotp_export_pwd); - } - if (g_ascii_strcasecmp (argv[3], "freeotpplus") == 0) { - exported_file_path = g_build_filename (base_dir, "freeotpplus-exports.txt", NULL); - ret_msg = export_freeotpplus (exported_file_path, db_data->json_data); - } - if (g_ascii_strcasecmp (argv[3], "aegis") == 0) { - exported_file_path = g_build_filename (base_dir, "aegis_export_plain.json", NULL); - ret_msg = export_aegis (exported_file_path, db_data->json_data, NULL); - } - if (ret_msg != NULL) { - g_printerr (_("An error occurred while exporting the data: %s\n"), ret_msg); - g_free (ret_msg); - } else { - g_print (_("Data successfully exported to: %s\n"), exported_file_path); - } - g_free (exported_file_path); - } else { - show_help (argv[0], "help"); - return -1; - } - end: +void +free_dbdata (DatabaseData *db_data) +{ gcry_free (db_data->key); g_free (db_data->db_path); g_slist_free_full (db_data->objects_hash, g_free); json_decref (db_data->json_data); g_free (db_data); - - return 0; } -#ifndef USE_FLATPAK_APP_FOLDER -static gchar * -get_db_path (void) +static gint +handle_local_options (GApplication *application __attribute__((unused)), + GVariantDict *options, + gpointer user_data __attribute__((unused))) { - gchar *db_path = NULL; - GError *err = NULL; - GKeyFile *kf = g_key_file_new (); - gchar *cfg_file_path = g_build_filename (g_get_user_config_dir (), "otpclient.cfg", NULL); - if (g_file_test (cfg_file_path, G_FILE_TEST_EXISTS)) { - if (!g_key_file_load_from_file (kf, cfg_file_path, G_KEY_FILE_NONE, &err)) { - g_printerr ("%s\n", err->message); - g_key_file_free (kf); - g_clear_error (&err); - return NULL; - } - db_path = g_key_file_get_string (kf, "config", "db_path", NULL); - if (db_path == NULL) { - goto type_db_path; - } - if (!g_file_test (db_path, G_FILE_TEST_EXISTS)) { - gchar *msg = g_strconcat ("Database file/location (", db_path, ") does not exist.\n", NULL); - g_printerr ("%s\n", msg); - g_free (msg); - goto type_db_path; - } - goto end; - } - type_db_path: ; // empty statement workaround - g_print ("%s", _("Type the absolute path to the database: ")); - db_path = g_malloc0 (MAX_ABS_PATH_LEN); - if (fgets (db_path, MAX_ABS_PATH_LEN, stdin) == NULL) { - g_printerr ("%s\n", _("Couldn't get db path from stdin")); - g_free (cfg_file_path); - g_free (db_path); - return NULL; - } else { - // remove the newline char - db_path[g_utf8_strlen (db_path, -1) - 1] = '\0'; - if (!g_file_test (db_path, G_FILE_TEST_EXISTS)) { - g_printerr (_("File '%s' does not exist\n"), db_path); - g_free (cfg_file_path); - g_free (db_path); - return NULL; - } + guint32 count; + if (g_variant_dict_lookup (options, "version", "b", &count)) { + gchar *msg = g_strconcat ("OTPClient version ", PROJECT_VER, NULL); + g_print ("%s\n", msg); + g_free (msg); + return 0; } - - end: - g_free (cfg_file_path); - - return db_path; + return -1; } -#endif -static gchar * -get_pwd (const gchar *pwd_msg) +static int +command_line (GApplication *application __attribute__((unused)), + GApplicationCommandLine *cmdline, + gpointer user_data __attribute__((unused))) { - gchar *pwd = gcry_calloc_secure (256, 1); - g_print ("%s", pwd_msg); + DatabaseData *db_data = g_new0 (DatabaseData, 1); + db_data->key_stored = FALSE; + db_data->objects_hash = NULL; - struct termios old, new; - if (tcgetattr (STDIN_FILENO, &old) != 0) { - g_printerr ("%s\n", _("Couldn't get termios info")); - gcry_free (pwd); - return NULL; - } - new = old; - new.c_lflag &= ~ECHO; - if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &new) != 0) { - g_printerr ("%s\n", _("Couldn't turn echoing off")); - gcry_free (pwd); - return NULL; + db_data->max_file_size_from_memlock = get_max_file_size_from_memlock (); + gchar *init_msg = init_libs (db_data->max_file_size_from_memlock); + if (init_msg != NULL) { + g_application_command_line_printerr(cmdline, "Error while initializing GCrypt: %s\n", init_msg); + g_free (init_msg); + g_free (db_data); + return -1; } - if (fgets (pwd, 256, stdin) == NULL) { - g_printerr ("%s\n", _("Couldn't read password from stdin")); - gcry_free (pwd); - return NULL; + + CmdlineOpts *cmdline_opts = g_new0 (CmdlineOpts, 1); + cmdline_opts->database = NULL; + cmdline_opts->show = FALSE; + cmdline_opts->account = NULL; + cmdline_opts->issuer = NULL; + cmdline_opts->match_exact = FALSE; + cmdline_opts->show_next = FALSE; + cmdline_opts->list = FALSE; + cmdline_opts->export = FALSE; + cmdline_opts->export_type = NULL; + cmdline_opts->export_dir = NULL; + + if (!parse_options (cmdline, cmdline_opts)) { + g_free (db_data); + g_free_cmdline_opts (cmdline_opts); + return -1; } - g_print ("\n"); - tcsetattr (STDIN_FILENO, TCSAFLUSH, &old); - pwd[g_utf8_strlen (pwd, -1) - 1] = '\0'; + if (!exec_action (cmdline_opts, db_data)) { + g_free_cmdline_opts (cmdline_opts); + return -1; + } - gchar *realloc_pwd = gcry_realloc (pwd, g_utf8_strlen (pwd, -1) + 1); + free_dbdata (db_data); + g_free_cmdline_opts (cmdline_opts); - return realloc_pwd; + return 0; } static gboolean -get_use_secretservice (void) +parse_options (GApplicationCommandLine *cmdline, + CmdlineOpts *cmdline_opts) { - gboolean use_secret_service = TRUE; // by default, we enable it - GError *err = NULL; - GKeyFile *kf = g_key_file_new (); - gchar *cfg_file_path = g_build_filename (g_get_user_config_dir (), "otpclient.cfg", NULL); - if (g_file_test (cfg_file_path, G_FILE_TEST_EXISTS)) { - if (!g_key_file_load_from_file (kf, cfg_file_path, G_KEY_FILE_NONE, &err)) { - g_printerr ("%s\n", err->message); - g_key_file_free (kf); - g_clear_error (&err); + GVariantDict *options = g_application_command_line_get_options_dict (cmdline); + + g_variant_dict_lookup (options, "database", "s", &cmdline_opts->database); + + if (g_variant_dict_lookup (options, "show", "b", &cmdline_opts->show)) { + if (!g_variant_dict_lookup (options, "account", "s", &cmdline_opts->account)) { + g_application_command_line_print (cmdline, "Please provide at least the account option.\n"); return FALSE; } - use_secret_service = g_key_file_get_boolean (kf, "config", "use_secret_service", NULL); + g_variant_dict_lookup (options, "issuer", "s", &cmdline_opts->issuer); + g_variant_dict_lookup (options, "match-exact", "b", &cmdline_opts->match_exact); + g_variant_dict_lookup (options, "show-next", "b", &cmdline_opts->show_next); } - return use_secret_service; + + g_variant_dict_lookup (options, "list", "b", &cmdline_opts->list); + + if (g_variant_dict_lookup (options, "export", "b", &cmdline_opts->export)) { + if (!g_variant_dict_lookup (options, "type", "s", &cmdline_opts->export_type)) { + g_application_command_line_print (cmdline, "Please provide at least export type.\n"); + return FALSE; + } +#ifndef USE_FLATPAK_APP_FOLDER + g_variant_dict_lookup (options, "output-dir", "s", &cmdline_opts->export_dir); +#endif + } + return TRUE; +} + + +static void +g_free_cmdline_opts (CmdlineOpts *co) +{ + g_free (co->database); + g_free (co->account); + g_free (co->issuer); + g_free (co->export_type); + g_free (co->export_dir); + g_free (co); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OTPClient-3.3.0/src/cli/main.h new/OTPClient-3.4.1/src/cli/main.h --- old/OTPClient-3.3.0/src/cli/main.h 1970-01-01 01:00:00.000000000 +0100 +++ new/OTPClient-3.4.1/src/cli/main.h 2024-02-12 08:48:17.000000000 +0100 @@ -0,0 +1,28 @@ +#pragma once + +#include <glib.h> +#include "../data.h" + +G_BEGIN_DECLS + +#define MAX_ABS_PATH_LEN 256 + +typedef struct _cmdline_opts { + gchar *database; + gboolean show; + gchar *account; + gchar *issuer; + gboolean match_exact; + gboolean show_next; + gboolean list; + gboolean export; + gchar *export_type; + gchar *export_dir; +} CmdlineOpts; + +gboolean exec_action (CmdlineOpts *cmdline_opts, + DatabaseData *db_data); + +void free_dbdata (DatabaseData *db_data); + +G_END_DECLS \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OTPClient-3.3.0/src/common/common.c new/OTPClient-3.4.1/src/common/common.c --- old/OTPClient-3.3.0/src/common/common.c 2024-01-08 12:16:54.000000000 +0100 +++ new/OTPClient-3.4.1/src/common/common.c 2024-02-12 08:48:17.000000000 +0100 @@ -34,6 +34,7 @@ gchar * init_libs (gint32 max_file_size) { + gcry_control(GCRYCTL_SET_PREFERRED_RNG_TYPE, GCRY_RNG_TYPE_SYSTEM); if (!gcry_check_version ("1.8.0")) { return g_strdup ("The required version of GCrypt is 1.8.0 or greater."); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OTPClient-3.3.0/src/common/freeotp.c new/OTPClient-3.4.1/src/common/freeotp.c --- old/OTPClient-3.3.0/src/common/freeotp.c 2024-01-08 12:16:54.000000000 +0100 +++ new/OTPClient-3.4.1/src/common/freeotp.c 2024-02-12 08:48:17.000000000 +0100 @@ -45,7 +45,7 @@ if (err == NULL) { json_array_foreach (json_db_data, index, db_obj) { gchar *uri = get_otpauth_uri (NULL, db_obj); - if (g_output_stream_write (G_OUTPUT_STREAM(out_stream), uri, g_utf8_strlen (uri, -1) + 1, NULL, &err) == -1) { + if (g_output_stream_write (G_OUTPUT_STREAM(out_stream), uri, g_utf8_strlen (uri, -1), NULL, &err) == -1) { g_set_error (&err, generic_error_gquark (), GENERIC_ERRCODE, "couldn't dump json data to file"); } g_free (uri); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OTPClient-3.3.0/src/db-actions.c new/OTPClient-3.4.1/src/db-actions.c --- old/OTPClient-3.3.0/src/db-actions.c 2024-01-08 12:16:54.000000000 +0100 +++ new/OTPClient-3.4.1/src/db-actions.c 2024-02-12 08:48:17.000000000 +0100 @@ -67,3 +67,14 @@ g_free (cfg_file_path); g_key_file_free (kf); } + + +void +revert_db_path (AppData *app_data, + gchar *old_db_path) +{ + g_free (app_data->db_data->db_path); + app_data->db_data->db_path = g_strdup (old_db_path); + update_cfg_file (app_data); + g_free (old_db_path); +} \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OTPClient-3.3.0/src/db-actions.h new/OTPClient-3.4.1/src/db-actions.h --- old/OTPClient-3.3.0/src/db-actions.h 2024-01-08 12:16:54.000000000 +0100 +++ new/OTPClient-3.4.1/src/db-actions.h 2024-02-12 08:48:17.000000000 +0100 @@ -8,11 +8,14 @@ #define ACTION_OPEN 5 #define ACTION_SAVE 10 -void select_file_icon_pressed_cb (GtkEntry *entry, - gint position, - GdkEventButton *event, - gpointer data); +void select_file_icon_pressed_cb (GtkEntry *entry, + gint position, + GdkEventButton *event, + gpointer data); -void update_cfg_file (AppData *app_data); +void update_cfg_file (AppData *app_data); + +void revert_db_path (AppData *app_data, + gchar *old_db_path); G_END_DECLS \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OTPClient-3.3.0/src/new-db-cb.c new/OTPClient-3.4.1/src/new-db-cb.c --- old/OTPClient-3.3.0/src/new-db-cb.c 2024-01-08 12:16:54.000000000 +0100 +++ new/OTPClient-3.4.1/src/new-db-cb.c 2024-02-12 08:48:17.000000000 +0100 @@ -7,13 +7,11 @@ #include "password-cb.h" #include "db-actions.h" #include "secret-schema.h" +#include "change-file-cb.h" -void -new_db_cb (GSimpleAction *simple __attribute__((unused)), - GVariant *parameter __attribute__((unused)), - gpointer user_data) +int +new_db (AppData *app_data) { - AppData *app_data = (AppData *)user_data; GtkWidget *newdb_diag = GTK_WIDGET(gtk_builder_get_object (app_data->builder, "newdb_diag_id")); GtkWidget *newdb_entry = GTK_WIDGET(gtk_builder_get_object (app_data->builder, "newdb_entry_id")); @@ -24,35 +22,69 @@ gint result = gtk_dialog_run (GTK_DIALOG (newdb_diag)); switch (result) { case GTK_RESPONSE_OK: + if (gtk_entry_get_text_length (GTK_ENTRY(newdb_entry)) == 0) { + show_message_dialog (app_data->main_window, "Input cannot be empty.", GTK_MESSAGE_ERROR); + gtk_widget_hide (newdb_diag); + return RETRY_CHANGE; + } new_db_path_with_suffix = g_string_new (gtk_entry_get_text (GTK_ENTRY(newdb_entry))); g_string_append (new_db_path_with_suffix, ".enc"); if (g_file_test (new_db_path_with_suffix->str, G_FILE_TEST_IS_REGULAR) || g_file_test (new_db_path_with_suffix->str, G_FILE_TEST_IS_SYMLINK)) { show_message_dialog (app_data->main_window, "Selected file already exists, please choose another filename.", GTK_MESSAGE_ERROR); + g_string_free (new_db_path_with_suffix, TRUE); + return RETRY_CHANGE; } else { + gchar *old_db_path = g_strdup (app_data->db_data->db_path); g_free (app_data->db_data->db_path); app_data->db_data->db_path = g_strdup (new_db_path_with_suffix->str); update_cfg_file (app_data); gcry_free (app_data->db_data->key); app_data->db_data->key = prompt_for_password (app_data, NULL, NULL, FALSE); + if (app_data->db_data->key == NULL) { + gtk_widget_hide (newdb_diag); + revert_db_path (app_data, old_db_path); + return RETRY_CHANGE; + } secret_password_store (OTPCLIENT_SCHEMA, SECRET_COLLECTION_DEFAULT, "main_pwd", app_data->db_data->key, NULL, on_password_stored, NULL, "string", "main_pwd", NULL); GError *err = NULL; write_db_to_disk (app_data->db_data, &err); if (err != NULL) { show_message_dialog (app_data->main_window, err->message, GTK_MESSAGE_ERROR); g_clear_error (&err); - } else { - load_new_db (app_data, &err); - if (err != NULL) { - show_message_dialog (app_data->main_window, err->message, GTK_MESSAGE_ERROR); - g_clear_error (&err); - } + gtk_widget_hide (newdb_diag); + revert_db_path (app_data, old_db_path); + return RETRY_CHANGE; + } + load_new_db (app_data, &err); + if (err != NULL) { + show_message_dialog (app_data->main_window, err->message, GTK_MESSAGE_ERROR); + g_clear_error (&err); + gtk_widget_hide (newdb_diag); + revert_db_path (app_data, old_db_path); + return RETRY_CHANGE; } + g_free (old_db_path); } g_string_free (new_db_path_with_suffix, TRUE); break; case GTK_RESPONSE_CANCEL: + gtk_widget_destroy (newdb_diag); + return QUIT_APP; default: break; } gtk_widget_destroy (newdb_diag); + + return CHANGE_OK; +} + + +void +new_db_cb (GSimpleAction *simple __attribute__((unused)), + GVariant *parameter __attribute__((unused)), + gpointer user_data) +{ + AppData *app_data = (AppData *)user_data; + + new_db (app_data); } \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OTPClient-3.3.0/src/new-db-cb.h new/OTPClient-3.4.1/src/new-db-cb.h --- old/OTPClient-3.3.0/src/new-db-cb.h 2024-01-08 12:16:54.000000000 +0100 +++ new/OTPClient-3.4.1/src/new-db-cb.h 2024-02-12 08:48:17.000000000 +0100 @@ -1,11 +1,14 @@ #pragma once #include <gtk/gtk.h> +#include "data.h" G_BEGIN_DECLS -void new_db_cb (GSimpleAction *simple, - GVariant *parameter, - gpointer user_data); +gboolean new_db (AppData *app_data); + +void new_db_cb (GSimpleAction *simple, + GVariant *parameter, + gpointer user_data); G_END_DECLS diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/OTPClient-3.3.0/src/otpclient.h new/OTPClient-3.4.1/src/otpclient.h --- old/OTPClient-3.3.0/src/otpclient.h 2024-01-08 12:16:54.000000000 +0100 +++ new/OTPClient-3.4.1/src/otpclient.h 2024-02-12 08:48:17.000000000 +0100 @@ -11,8 +11,6 @@ void activate (GtkApplication *app, gpointer user_data); -gboolean change_file (AppData *app_data); - void add_qr_from_file (GSimpleAction *simple, GVariant *parameter, gpointer user_data);