From af69923606406714d6921d998e17ed922c409368 Mon Sep 17 00:00:00 2001 From: Paolo Stivanin Date: Wed, 7 Feb 2024 15:48:22 +0100 Subject: [PATCH 1/4] Prompt for file again, fix #335 --- CMakeLists.txt | 5 ++-- src/app.c | 6 ++++- src/change-db-cb.c | 56 +++++++++++++++++++++++++++++--------------- src/change-db-cb.h | 2 ++ src/change-file-cb.c | 14 +++++------ src/change-file-cb.h | 13 ++++++++++ src/new-db-cb.c | 30 ++++++++++++++++++++---- src/new-db-cb.h | 9 ++++--- src/otpclient.h | 2 -- 9 files changed, 98 insertions(+), 39 deletions(-) create mode 100644 src/change-file-cb.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f153bc9..d511c0f5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.16) -project(OTPClient VERSION "3.3.0" LANGUAGES "C") +project(OTPClient VERSION "3.4.0" LANGUAGES "C") include(GNUInstallDirs) configure_file("src/common/version.h.in" "version.h") @@ -88,7 +88,8 @@ set(GUI_HEADER_FILES 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 diff --git a/src/app.c b/src/app.c index 2b34897d..18970834 100644 --- a/src/app.c +++ b/src/app.c @@ -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 @@ activate (GtkApplication *app, 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 --git a/src/change-db-cb.c b/src/change-db-cb.c index cdbc5773..2b6fca75 100644 --- a/src/change-db-cb.c +++ b/src/change-db-cb.c @@ -7,15 +7,12 @@ #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 +26,50 @@ change_db_cb (GSimpleAction *simple __attribute__((unused)), 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; + } + 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); } 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); } diff --git a/src/change-db-cb.h b/src/change-db-cb.h index 1cfb9aec..4b995b02 100644 --- a/src/change-db-cb.h +++ b/src/change-db-cb.h @@ -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 --git a/src/change-file-cb.c b/src/change-file-cb.c index 6396cbe7..dd5e4cee 100644 --- a/src/change-file-cb.c +++ b/src/change-file-cb.c @@ -1,10 +1,10 @@ #include #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 @@ change_file (AppData *app_data) 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 --git a/src/change-file-cb.h b/src/change-file-cb.h new file mode 100644 index 00000000..f3fcaf8d --- /dev/null +++ b/src/change-file-cb.h @@ -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 --git a/src/new-db-cb.c b/src/new-db-cb.c index 3038ba3d..75df8b7e 100644 --- a/src/new-db-cb.c +++ b/src/new-db-cb.c @@ -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,10 +22,17 @@ new_db_cb (GSimpleAction *simple __attribute__((unused)), 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 { g_free (app_data->db_data->db_path); app_data->db_data->db_path = g_strdup (new_db_path_with_suffix->str); @@ -51,8 +56,23 @@ new_db_cb (GSimpleAction *simple __attribute__((unused)), 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 TRUE; +} + + +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 --git a/src/new-db-cb.h b/src/new-db-cb.h index cc7a0e2a..96818677 100644 --- a/src/new-db-cb.h +++ b/src/new-db-cb.h @@ -1,11 +1,14 @@ #pragma once #include +#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 --git a/src/otpclient.h b/src/otpclient.h index 99f76c4e..2e4dc5e6 100644 --- a/src/otpclient.h +++ b/src/otpclient.h @@ -11,8 +11,6 @@ G_BEGIN_DECLS 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); From 8cbf8246a737bdb11c692d8c4c27b74ee39ba30a Mon Sep 17 00:00:00 2001 From: Paolo Stivanin Date: Thu, 8 Feb 2024 11:08:14 +0100 Subject: [PATCH 2/4] Handle errors when path and/or password is incorrect, fix #336 --- src/change-db-cb.c | 13 +++++++++++-- src/db-actions.c | 11 +++++++++++ src/db-actions.h | 13 ++++++++----- src/new-db-cb.c | 26 +++++++++++++++++++------- 4 files changed, 49 insertions(+), 14 deletions(-) diff --git a/src/change-db-cb.c b/src/change-db-cb.c index 2b6fca75..a1afa16f 100644 --- a/src/change-db-cb.c +++ b/src/change-db-cb.c @@ -9,7 +9,6 @@ #include "secret-schema.h" #include "change-file-cb.h" - int change_db (AppData *app_data) { @@ -37,18 +36,28 @@ change_db (AppData *app_data) 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); @@ -78,4 +87,4 @@ change_db_cb_shortcut (GtkWidget *w __attribute__((unused)), gpointer user_data) { change_db_cb (NULL, NULL, user_data); -} +} \ No newline at end of file diff --git a/src/db-actions.c b/src/db-actions.c index 7b5d2b78..01c70e76 100644 --- a/src/db-actions.c +++ b/src/db-actions.c @@ -67,3 +67,14 @@ update_cfg_file (AppData *app_data) 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 --git a/src/db-actions.h b/src/db-actions.h index b0fb47fc..075961cc 100644 --- a/src/db-actions.h +++ b/src/db-actions.h @@ -8,11 +8,14 @@ G_BEGIN_DECLS #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 --git a/src/new-db-cb.c b/src/new-db-cb.c index 75df8b7e..e4cda812 100644 --- a/src/new-db-cb.c +++ b/src/new-db-cb.c @@ -34,24 +34,36 @@ new_db (AppData *app_data) 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; @@ -63,7 +75,7 @@ new_db (AppData *app_data) } gtk_widget_destroy (newdb_diag); - return TRUE; + return CHANGE_OK; } From 1931c03aea9d2285d8dc2a2042c4a2e221eaa9e9 Mon Sep 17 00:00:00 2001 From: Paolo Stivanin Date: Fri, 9 Feb 2024 15:43:07 +0100 Subject: [PATCH 3/4] Enhance the CLI * completely refactor the code * add support for specifying a database without having to open the GUI (fix #340) --- CMakeLists.txt | 10 +- ...github.paolostivanin.OTPClient.appdata.xml | 14 + src/cli/exec-action.c | 251 ++++++++++++ src/cli/help.c | 121 ------ src/cli/help.h | 10 - src/cli/main.c | 384 ++++++------------ src/cli/main.h | 28 ++ 7 files changed, 430 insertions(+), 388 deletions(-) create mode 100644 src/cli/exec-action.c delete mode 100644 src/cli/help.c delete mode 100644 src/cli/help.h create mode 100644 src/cli/main.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d511c0f5..a4501714 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -129,10 +129,10 @@ set(GUI_SOURCE_FILES 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 @@ -142,15 +142,17 @@ set(CLI_HEADER_FILES 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 --git a/data/com.github.paolostivanin.OTPClient.appdata.xml b/data/com.github.paolostivanin.OTPClient.appdata.xml index 55d9fb68..db4b52ba 100644 --- a/data/com.github.paolostivanin.OTPClient.appdata.xml +++ b/data/com.github.paolostivanin.OTPClient.appdata.xml @@ -14,6 +14,8 @@ 2fa 2factor 2fa-client + 2step + twostep @@ -87,6 +89,18 @@ + + +

OTPClient 3.4.0 brings the following changes:

+
    +
  • 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
  • +
+
+

OTPClient 3.3.0 brings the following changes:

diff --git a/src/cli/exec-action.c b/src/cli/exec-action.c new file mode 100644 index 00000000..3626cb04 --- /dev/null +++ b/src/cli/exec-action.c @@ -0,0 +1,251 @@ +#include +#include +#include +#include +#include +#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 --git a/src/cli/help.c b/src/cli/help.c deleted file mode 100644 index a5b710be..00000000 --- a/src/cli/help.c +++ /dev/null @@ -1,121 +0,0 @@ -#include -#include -#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
[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> [-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 --git a/src/cli/help.h b/src/cli/help.h deleted file mode 100644 index 7cc521b7..00000000 --- a/src/cli/help.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include - -G_BEGIN_DECLS - -gboolean show_help (const gchar *prg_name, - const gchar *help_command); - -G_END_DECLS diff --git a/src/cli/main.c b/src/cli/main.c index d8b844a8..d0661e78 100644 --- a/src/cli/main.c +++ b/src/cli/main.c @@ -1,303 +1,181 @@ #include -#include -#include -#include -#include -#include -#include "help.h" -#include "get-data.h" +#include +#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); -#ifndef USE_FLATPAK_APP_FOLDER -static gchar *get_db_path (void); -#endif +static int command_line (GApplication *application, + GApplicationCommandLine *cmdline, + gpointer user_data); -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 }, + G_OPTION_ENTRY_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); - - 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; + DatabaseData *db_data = g_new0 (DatabaseData, 1); + db_data->key_stored = FALSE; + db_data->objects_hash = 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); +} \ No newline at end of file diff --git a/src/cli/main.h b/src/cli/main.h new file mode 100644 index 00000000..89321fd6 --- /dev/null +++ b/src/cli/main.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#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 From 703354e6cc038c8e8d974d70beabd63528290a40 Mon Sep 17 00:00:00 2001 From: Paolo Stivanin Date: Fri, 9 Feb 2024 16:21:14 +0100 Subject: [PATCH 4/4] Fix for Ubuntu 20.04 --- src/cli/main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cli/main.c b/src/cli/main.c index d0661e78..9bb888a4 100644 --- a/src/cli/main.c +++ b/src/cli/main.c @@ -39,7 +39,7 @@ main (gint argc, { "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 }, - G_OPTION_ENTRY_NULL + { NULL } }; @@ -178,4 +178,4 @@ g_free_cmdline_opts (CmdlineOpts *co) g_free (co->export_type); g_free (co->export_dir); g_free (co); -} \ No newline at end of file +}