Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release 3.6.0 #354

Merged
merged 6 commits into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Big refactoring to import/export
* decouple import/export from GTK, so that CLI can be built without it
* add possibility to import files via the CLI
  • Loading branch information
paolostivanin committed Mar 11, 2024
commit 4bf9e36380f63f7d86242b5f6ba85f7789e30b97
68 changes: 57 additions & 11 deletions src/cli/exec-action.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
#include <libsecret/secret.h>
#include "main.h"
#include "get-data.h"
#include "../common/exports.h"
#include "../common/import-export.h"
#include "../common/secret-schema.h"
#include "../common/db-common.h"
#include "../common/gquarks.h"

#ifndef IS_FLATPAK
static gchar *get_db_path (void);
Expand Down Expand Up @@ -79,6 +79,51 @@ gboolean exec_action (CmdlineOpts *cmdline_opts,
list_all_acc_iss (db_data);
}

if (cmdline_opts->import) {
if (!g_file_test (cmdline_opts->import_file, G_FILE_TEST_EXISTS) || !g_file_test (cmdline_opts->import_file, G_FILE_TEST_IS_REGULAR)) {
g_printerr (_("%s doesn't exist or is not a valid file.\n"), cmdline_opts->import_file);
return FALSE;
}

gchar *pwd = get_pwd (_("Type the password for the file you want to import: "));
if (pwd == NULL) {
return FALSE;
}

GSList *otps = get_data_from_provider (cmdline_opts->import_type, cmdline_opts->import_file, pwd, get_max_file_size_from_memlock (), &err);
if (otps == NULL) {
const gchar *msg = "An error occurred while importing, so nothing has been added to the database.";
gchar *msg_with_err = NULL;
if (err != NULL) {
msg_with_err = g_strconcat (msg, " The error is: ", err->message, NULL);
}
g_printerr ("%s\n", err == NULL ? msg : msg_with_err);
if (err != NULL) {
g_free (msg_with_err);
g_clear_error (&err);
}
gcry_free (pwd);

return FALSE;
}
gcry_free (pwd);

add_otps_to_db (otps, db_data);
free_otps_gslist (otps, g_slist_length (otps));

update_db (db_data, &err);
if (err != NULL && !g_error_matches (err, missing_file_gquark (), MISSING_FILE_CODE)) {
g_printerr ("Error while updating the database: %s\n", err->message);
return FALSE;
}
reload_db (db_data, &err);
if (err != NULL && !g_error_matches (err, missing_file_gquark (), MISSING_FILE_CODE)) {
g_printerr ("Error while reloading the database: %s\n", err->message);
return FALSE;
}
g_print ("Data successfully imported.\n");
}

if (cmdline_opts->export) {
gchar *export_directory;
#ifdef IS_FLATPAK
Expand All @@ -92,8 +137,8 @@ gboolean exec_action (CmdlineOpts *cmdline_opts,
#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) {
if (g_ascii_strcasecmp (cmdline_opts->export_type, ANDOTP_PLAIN_ACTION_NAME) == 0 || g_ascii_strcasecmp (cmdline_opts->export_type, ANDOTP_ENC_ACTION_NAME) == 0) {
if (g_ascii_strcasecmp (cmdline_opts->export_type, ANDOTP_ENC_ACTION_NAME) == 0) {
export_pwd = get_pwd (_("Type the export encryption password: "));
if (export_pwd == NULL) {
free_dbdata (db_data);
Expand All @@ -105,13 +150,13 @@ gboolean exec_action (CmdlineOpts *cmdline_opts,
gcry_free (export_pwd);
exported = TRUE;
}
if (g_ascii_strcasecmp (cmdline_opts->export_type, "freeotpplus") == 0) {
if (g_ascii_strcasecmp (cmdline_opts->export_type, FREEOTPPLUS_PLAIN_ACTION_NAME) == 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) {
if (g_ascii_strcasecmp (cmdline_opts->export_type, AEGIS_PLAIN_ACTION_NAME) == 0 || g_ascii_strcasecmp (cmdline_opts->export_type, AEGIS_ENC_ACTION_NAME) == 0) {
if (g_ascii_strcasecmp (cmdline_opts->export_type, AEGIS_ENC_ACTION_NAME) == 0) {
export_pwd = get_pwd (_("Type the export encryption password: "));
if (export_pwd == NULL) {
free_dbdata (db_data);
Expand All @@ -123,8 +168,8 @@ gboolean exec_action (CmdlineOpts *cmdline_opts,
gcry_free (export_pwd);
exported = TRUE;
}
if (g_ascii_strcasecmp (cmdline_opts->export_type, "twofas_plain") == 0 || g_ascii_strcasecmp (cmdline_opts->export_type, "twofas_encrypted") == 0) {
if (g_ascii_strcasecmp (cmdline_opts->export_type, "twofas_encrypted") == 0) {
if (g_ascii_strcasecmp (cmdline_opts->export_type, TWOFAS_PLAIN_ACTION_NAME) == 0 || g_ascii_strcasecmp (cmdline_opts->export_type, TWOFAS_ENC_ACTION_NAME) == 0) {
if (g_ascii_strcasecmp (cmdline_opts->export_type, TWOFAS_ENC_ACTION_NAME) == 0) {
export_pwd = get_pwd (_("Type the export encryption password: "));
if (export_pwd == NULL) {
free_dbdata (db_data);
Expand All @@ -136,8 +181,8 @@ gboolean exec_action (CmdlineOpts *cmdline_opts,
gcry_free (export_pwd);
exported = TRUE;
}
if (g_ascii_strcasecmp (cmdline_opts->export_type, "authpro_plain") == 0 || g_ascii_strcasecmp (cmdline_opts->export_type, "authpro_encrypted") == 0) {
if (g_ascii_strcasecmp (cmdline_opts->export_type, "authpro_encrypted") == 0) {
if (g_ascii_strcasecmp (cmdline_opts->export_type, AUTHPRO_PLAIN_ACTION_NAME) == 0 || g_ascii_strcasecmp (cmdline_opts->export_type, AUTHPRO_ENC_ACTION_NAME) == 0) {
if (g_ascii_strcasecmp (cmdline_opts->export_type, AUTHPRO_ENC_ACTION_NAME) == 0) {
export_pwd = get_pwd (_("Type the export encryption password: "));
if (export_pwd == NULL) {
free_dbdata (db_data);
Expand All @@ -164,6 +209,7 @@ gboolean exec_action (CmdlineOpts *cmdline_opts,
}
g_free (exported_file_path);
}

return TRUE;
}

Expand Down
67 changes: 64 additions & 3 deletions src/cli/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <gio/gio.h>
#include <gcrypt.h>
#include "version.h"
#include "../common/import-export.h"
#include "main.h"

static gint handle_local_options (GApplication *application,
Expand All @@ -15,13 +16,23 @@ static int command_line (GApplication *application,
static gboolean parse_options (GApplicationCommandLine *cmdline,
CmdlineOpts *cmdline_opts);

static gboolean is_valid_type (const gchar *type);

static void g_free_cmdline_opts (CmdlineOpts *co);


gint
main (gint argc,
gchar **argv)
{
g_autofree gchar *type_msg = g_strconcat ("The import/export type for the database (to be used with --import/--export, mandatory). Must be either one of: ",
ANDOTP_PLAIN_ACTION_NAME, ", ", ANDOTP_ENC_ACTION_NAME, ", ",
AEGIS_PLAIN_ACTION_NAME, ", ", AEGIS_ENC_ACTION_NAME, ", ",
AUTHPRO_PLAIN_ACTION_NAME, ", ", AUTHPRO_ENC_ACTION_NAME, ", ",
TWOFAS_PLAIN_ACTION_NAME, ", ", TWOFAS_ENC_ACTION_NAME, ", ",
FREEOTPPLUS_PLAIN_ACTION_NAME,
NULL);

GOptionEntry entries[] =
{
#ifndef IS_FLATPAK
Expand All @@ -33,8 +44,10 @@ main (gint argc,
{ "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_plain, aegis_encrypted, twofas_plain, twofas_encrypted, authpro_plain, authpro_encrypted (to be used with --export, mandatory)", NULL },
{ "import", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, NULL, "Import a database.", NULL },
{ "export", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, NULL, "Export a database.", NULL },
{ "type", 't', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, NULL, type_msg, NULL },
{ "file", 'f', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, NULL, "File to import (to be used with --import, mandatory).", NULL },
#ifndef IS_FLATPAK
{ "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
Expand Down Expand Up @@ -114,6 +127,9 @@ command_line (GApplication *application __attribute__((unused)),
cmdline_opts->match_exact = FALSE;
cmdline_opts->show_next = FALSE;
cmdline_opts->list = FALSE;
cmdline_opts->import = FALSE;
cmdline_opts->import_type = NULL;
cmdline_opts->import_file = NULL;
cmdline_opts->export = FALSE;
cmdline_opts->export_type = NULL;
cmdline_opts->export_dir = NULL;
Expand Down Expand Up @@ -156,10 +172,31 @@ parse_options (GApplicationCommandLine *cmdline,

g_variant_dict_lookup (options, "list", "b", &cmdline_opts->list);

if (g_variant_dict_lookup (options, "import", "b", &cmdline_opts->import)) {
if (!g_variant_dict_lookup (options, "type", "s", &cmdline_opts->import_type)) {
g_application_command_line_print (cmdline, "Please provide an import type.\n");
return FALSE;
} else {
if (!is_valid_type (cmdline_opts->import_type)) {
g_application_command_line_print (cmdline, "Please provide a valid import type (see --help).\n");
return FALSE;
}
}
if (!g_variant_dict_lookup (options, "file", "s", &cmdline_opts->import_file)) {
g_application_command_line_print (cmdline, "Please provide a file to import.\n");
return FALSE;
}
}

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");
g_application_command_line_print (cmdline, "Please provide an export type (see --help).\n");
return FALSE;
} else {
if (!is_valid_type (cmdline_opts->export_type)) {
g_application_command_line_print (cmdline, "Please provide a valid export type.\n");
return FALSE;
}
}
#ifndef IS_FLATPAK
g_variant_dict_lookup (options, "output-dir", "s", &cmdline_opts->export_dir);
Expand All @@ -169,12 +206,36 @@ parse_options (GApplicationCommandLine *cmdline,
}


static gboolean
is_valid_type (const gchar *type)
{
const gchar *supported_types[] = {ANDOTP_PLAIN_ACTION_NAME, ANDOTP_ENC_ACTION_NAME,
AEGIS_PLAIN_ACTION_NAME, AEGIS_ENC_ACTION_NAME,
TWOFAS_PLAIN_ACTION_NAME, TWOFAS_ENC_ACTION_NAME,
AUTHPRO_PLAIN_ACTION_NAME, AUTHPRO_ENC_ACTION_NAME,
FREEOTPPLUS_PLAIN_ACTION_NAME};

gint array_size = sizeof(supported_types) / sizeof(supported_types[0]);

gboolean found = FALSE;
for (gint i = 0; i < array_size; i++) {
if (g_strcmp0 (type, supported_types[i]) == 0) {
found = TRUE;
break;
}
}
return found;
}


static void
g_free_cmdline_opts (CmdlineOpts *co)
{
g_free (co->database);
g_free (co->account);
g_free (co->issuer);
g_free (co->import_type);
g_free (co->import_file);
g_free (co->export_type);
g_free (co->export_dir);
g_free (co);
Expand Down
3 changes: 3 additions & 0 deletions src/cli/main.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ typedef struct cmdline_opts_t {
gboolean match_exact;
gboolean show_next;
gboolean list;
gboolean import;
gchar *import_type;
gchar *import_file;
gboolean export;
gchar *export_type;
gchar *export_dir;
Expand Down
47 changes: 47 additions & 0 deletions src/common/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -375,4 +375,51 @@ json_object_get_hash (json_t *obj)
gcry_free (tmp_string);

return hash;
}


void
free_otps_gslist (GSList *otps,
guint list_len)
{
otp_t *otp_data;
for (guint i = 0; i < list_len; i++) {
otp_data = g_slist_nth_data (otps, i);
g_free (otp_data->type);
g_free (otp_data->algo);
g_free (otp_data->account_name);
g_free (otp_data->issuer);
gcry_free (otp_data->secret);
}
g_slist_free (otps);
}


json_t *
build_json_obj (const gchar *type,
const gchar *acc_label,
const gchar *acc_iss,
const gchar *acc_key,
guint digits,
const gchar *algo,
guint period,
guint64 ctr)
{
json_t *obj = json_object ();
json_object_set (obj, "type", json_string (type));
json_object_set (obj, "label", json_string (acc_label));
json_object_set (obj, "issuer", json_string (acc_iss));
json_object_set (obj, "secret", json_string (acc_key));
json_object_set (obj, "digits", json_integer (digits));
json_object_set (obj, "algo", json_string (algo));

json_object_set (obj, "secret", json_string (acc_key));

if (g_ascii_strcasecmp (type, "TOTP") == 0) {
json_object_set (obj, "period", json_integer (period));
} else {
json_object_set (obj, "counter", json_integer (ctr));
}

return obj;
}
11 changes: 11 additions & 0 deletions src/common/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,16 @@ guchar *get_authpro_derived_key (const gchar *password,

guint32 json_object_get_hash (json_t *obj);

void free_otps_gslist (GSList *otps,
guint list_len);

json_t *build_json_obj (const gchar *type,
const gchar *acc_label,
const gchar *acc_iss,
const gchar *acc_key,
guint digits,
const gchar *algo,
guint period,
guint64 ctr);

G_END_DECLS
32 changes: 32 additions & 0 deletions src/common/db-common.c
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,38 @@ get_db_derived_key (const gchar *pwd,
}


void
add_otps_to_db (GSList *otps,
DatabaseData *db_data)
{
json_t *obj;
guint list_len = g_slist_length (otps);
for (guint i = 0; i < list_len; i++) {
otp_t *otp = g_slist_nth_data (otps, i);
obj = build_json_obj (otp->type, otp->account_name, otp->issuer, otp->secret, otp->digits, otp->algo, otp->period, otp->counter);
guint hash = json_object_get_hash (obj);
if (g_slist_find_custom (db_data->objects_hash, GUINT_TO_POINTER(hash), check_duplicate) == NULL) {
db_data->objects_hash = g_slist_append (db_data->objects_hash, g_memdup2 (&hash, sizeof (guint)));
db_data->data_to_add = g_slist_append (db_data->data_to_add, obj);
} else {
g_print ("[INFO] Duplicate element not added\n");
}
}
}


gint
check_duplicate (gconstpointer data,
gconstpointer user_data)
{
guint list_elem = *(guint *)data;
if (list_elem == GPOINTER_TO_UINT(user_data)) {
return 0;
}
return -1;
}


void
cleanup_db_gfile (GFile *file,
gpointer stream,
Expand Down
Loading