Skip to content

Commit

Permalink
Enhance the CLI
Browse files Browse the repository at this point in the history
* completely refactor the code
* add support for specifying a database without having to open the GUI (fix #340)
  • Loading branch information
paolostivanin committed Feb 9, 2024
1 parent 8cbf824 commit 1931c03
Show file tree
Hide file tree
Showing 7 changed files with 430 additions and 388 deletions.
10 changes: 6 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
14 changes: 14 additions & 0 deletions data/com.github.paolostivanin.OTPClient.appdata.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
<keyword>2fa</keyword>
<keyword>2factor</keyword>
<keyword>2fa-client</keyword>
<keyword>2step</keyword>
<keyword>twostep</keyword>
</keywords>

<description>
Expand Down Expand Up @@ -87,6 +89,18 @@
</content_rating>

<releases>
<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>
Expand Down
251 changes: 251 additions & 0 deletions src/cli/exec-action.c
Original file line number Diff line number Diff line change
@@ -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;
}
Loading

0 comments on commit 1931c03

Please sign in to comment.