SlunkCrypt/frontend/src/main.c

342 lines
9.9 KiB
C
Raw Normal View History

/******************************************************************************/
/* SlunkCrypt, by LoRd_MuldeR <MuldeR2@GMX.de> */
/* This work has been released under the CC0 1.0 Universal license! */
/******************************************************************************/
#ifdef _WIN32
# define _CRT_SECURE_NO_WARNINGS 1
#else
# define _GNU_SOURCE 1
#endif
/* Internal */
#include <slunkcrypt.h>
2020-10-13 15:04:59 +02:00
#include "utils.h"
#include "crypt.h"
#include "pwgen.h"
#include "selftest.h"
2020-11-04 23:17:59 +01:00
/* CRT */
#include <string.h>
#include <time.h>
2020-10-13 15:37:40 +02:00
#include <inttypes.h>
#include <ctype.h>
#include <signal.h>
// ==========================================================================
// Constants
// ==========================================================================
#define MODE_HELP 0
#define MODE_VERS 1
#define MODE_ENCR 2
#define MODE_DECR 3
#define MODE_PASS 4
#define MODE_TEST 5
static const size_t RCMD_PWDLEN_LENGTH = 12U;
static const size_t DFLT_PWDLEN_LENGTH = 24U;
static const CHR* const ENV_PASSWORD = T("SLUNK_PASSPHRASE");
static const CHR* const ENV_KEEPFILE = T("SLUNK_KEEP_INCOMPLETE");
static const CHR* const PREFIX_PASS = T("pass:");
static const CHR* const PREFIX_FILE = T("file:");
2020-10-20 15:21:00 +02:00
// ==========================================================================
// Auxiliary functions
// ==========================================================================
#define PW_FROM_ENV (!(argc > 4))
static int parse_slunk_mode(const CHR* const command)
2020-10-20 15:21:00 +02:00
{
if ((!STRICMP(command, T("-h"))) || (!STRICMP(command, T("/?"))) || (!STRICMP(command, T("--help"))))
{
return MODE_HELP;
2020-10-20 15:21:00 +02:00
}
else if ((!STRICMP(command, T("-v"))) || (!STRICMP(command, T("--version"))))
{
return MODE_VERS;
2020-10-20 15:21:00 +02:00
}
else if ((!STRICMP(command, T("-e"))) || (!STRICMP(command, T("--encrypt"))))
{
return MODE_ENCR;
2020-10-20 15:21:00 +02:00
}
else if ((!STRICMP(command, T("-d"))) || (!STRICMP(command, T("--decrypt"))))
{
return MODE_DECR;
2020-10-20 15:21:00 +02:00
}
else if ((!STRICMP(command, T("-p"))) || (!STRICMP(command, T("--make-pw"))))
{
return MODE_PASS;
}
2020-10-20 15:21:00 +02:00
else if ((!STRICMP(command, T("-t"))) || (!STRICMP(command, T("--self-test"))))
{
return MODE_TEST;
2020-10-20 15:21:00 +02:00
}
else
{
return -1; /*invalid command*/
2020-10-20 15:21:00 +02:00
}
}
static void print_manpage(const CHR *const program)
{
FPUTS(T("====================================================================\n"), stderr);
FPUTS(T("This software has been released under the CC0 1.0 Universal license:\n"), stderr);
FPUTS(T("https://creativecommons.org/publicdomain/zero/1.0/legalcode\n"), stderr);
FPUTS(T("====================================================================\n\n"), stderr);
2020-10-20 15:33:03 +02:00
FPUTS(T("Usage:\n"), stderr);
FPRINTF(stderr, T(" %") T(PRISTR) T(" --encrypt [pass:<pass>|file:<file>] <input.txt> <output.enc>\n"), program);
FPRINTF(stderr, T(" %") T(PRISTR) T(" --decrypt [pass:<pass>|file:<file>] <input.enc> <output.txt>\n"), program);
FPRINTF(stderr, T(" %") T(PRISTR) T(" --make-pw [<length>]\n\n"), program);
FPRINTF(stderr, T("Optionally, reads passphrase from the %") T(PRISTR) T(" environment variable.\n\n"), ENV_PASSWORD);
}
static char *copy_passphrase(const CHR *const passphrase)
{
if ((!passphrase) || (!passphrase[0U]))
{
FPUTS(T("Error: The passphrase input string must not be empty!\n\n"), stderr);
return NULL;
}
char *const buffer = CHR_to_utf8(passphrase);
if (!buffer)
{
FPUTS(T("Error: Failed to allocate the string buffer!\n\n"), stderr);
}
return buffer;
2020-10-20 15:21:00 +02:00
}
static int keep_incomplete_files(void)
{
const CHR *const keep_files = GETENV(ENV_KEEPFILE);
if (keep_files)
{
return BOOLIFY(STRTOUL(keep_files));
}
return 0;
2020-10-13 00:43:57 +02:00
}
static void check_excess_arguments(const int argc, int maximum)
{
if (argc > maximum)
{
FPUTS(T("Warning: Excess command-line argument(s) will be ignored!\n\n"), stderr);
fflush(stderr);
}
}
2020-10-20 15:21:00 +02:00
static void sigint_handler(const int sig)
{
if (sig == SIGINT)
{
g_slunkcrypt_abort_flag = 1;
}
}
// ==========================================================================
// Main function
// ==========================================================================
2020-10-20 15:21:00 +02:00
int MAIN(const int argc, CHR *const argv[])
{
2020-10-13 19:33:01 +02:00
init_terminal();
2020-10-20 19:13:11 +02:00
setup_signal_handler(SIGINT, sigint_handler);
2020-10-20 22:13:39 +02:00
int result = EXIT_FAILURE;
CHR *input_file = NULL, *output_file = NULL;
char *passphrase_buffer = NULL;
FPRINTF(stderr, T("SlunkCrypt Utility (%") T(PRIstr) T("-%") T(PRIstr) T("), by LoRd_MuldeR <MuldeR2@GMX.de>\n"), OS_TYPE, CPU_ARCH);
2020-10-20 15:21:00 +02:00
FPRINTF(stderr, T("Using libSlunkCrypt v%u.%u.%u [%") T(PRIstr) T("]\n\n"), SLUNKCRYPT_VERSION_MAJOR, SLUNKCRYPT_VERSION_MINOR, SLUNKCRYPT_VERSION_PATCH, SLUNKCRYPT_BUILD);
2020-10-13 19:33:01 +02:00
fflush(stderr);
2020-10-20 15:21:00 +02:00
/* ----------------------------------------------------- */
/* Parse arguments */
/* ----------------------------------------------------- */
if ((argc < 1) || (!argv[0]))
{
FPUTS(T("Error: Argument array is empty. The program was called incorrectly!\n\n"), stderr);
goto clean_up;
}
2020-10-20 22:13:39 +02:00
if (argc < 2)
2020-10-14 13:14:47 +02:00
{
2020-10-20 15:21:00 +02:00
FPRINTF(stderr, T("Error: Nothing to do. Please type '%") T(PRISTR) T(" --help' for details!\n\n"), get_file_name(argv[0U]));
goto clean_up;
2020-10-20 22:13:39 +02:00
}
const int slunk_mode = parse_slunk_mode(argv[1U]);
switch (slunk_mode)
2020-10-20 22:13:39 +02:00
{
case MODE_HELP:
2020-10-20 15:21:00 +02:00
print_manpage(get_file_name(argv[0U]));
case MODE_VERS:
result = EXIT_SUCCESS;
goto clean_up;
case MODE_ENCR:
case MODE_DECR:
break; /*fallthrough*/
case MODE_PASS:
check_excess_arguments(argc, 3);
result = generate_passphrase((argc > 2) ? STRTOUL(argv[2U]) : DFLT_PWDLEN_LENGTH);
goto clean_up;
case MODE_TEST:
check_excess_arguments(argc, 2);
result = run_selftest_routine();
goto clean_up;
default:
FPRINTF(stderr, T("Error: The specified command \"%") T(PRISTR) T("\" is unknown!\n\n"), argv[1U]);
goto clean_up;
2020-10-14 13:14:47 +02:00
}
if (argc < 4)
{
2020-10-20 15:21:00 +02:00
FPRINTF(stderr, T("Error: Required argument is missing. Please type '%") T(PRISTR) T(" --help' for details!\n\n"), get_file_name(argv[0U]));
goto clean_up;
}
check_excess_arguments(argc, 5);
const CHR *const passphrase = PW_FROM_ENV ? GETENV(ENV_PASSWORD) : argv[2U];
if ((!passphrase) || (!passphrase[0U]))
{
FPUTS(T("Error: The passphrase must be specified, directly or indirectly!\n\n"), stderr);
goto clean_up;
}
if ((!PW_FROM_ENV) && STRICMP(passphrase, T("-")))
{
if ((!STARTS_WITH(passphrase, PREFIX_PASS)) && (!STARTS_WITH(passphrase, PREFIX_FILE)))
{
FPRINTF(stderr, T("Error: The passphrase must start with a '%") T(PRISTR) T("' or '%") T(PRISTR) T("' prefix!\n\n"), PREFIX_PASS, PREFIX_FILE);
goto clean_up;
}
}
2020-10-13 19:33:01 +02:00
input_file = absolute_path(argv[PW_FROM_ENV ? 2U : 3U]);
if ((!input_file) || (!input_file[0U]))
{
FPUTS(T("Error: Input file path could not be resolved!\n\n"), stderr);
goto clean_up;
}
output_file = absolute_path(argv[PW_FROM_ENV ? 3U : 4U]);
if ((!output_file) || (!output_file[0U]))
2020-10-13 19:33:01 +02:00
{
FPUTS(T("Error: Output file path could not be resolved!\n\n"), stderr);
goto clean_up;
}
if (!path_compare(input_file, output_file))
{
FPUTS(T("Error: Input and output files must not be the same path! (effectively)\n\n"), stderr);
goto clean_up;
2020-10-13 19:33:01 +02:00
}
2020-10-20 15:21:00 +02:00
/* ----------------------------------------------------- */
/* Initialize passphrase */
/* ----------------------------------------------------- */
if (!(passphrase_buffer = PW_FROM_ENV ? copy_passphrase(passphrase) :
(STARTS_WITH(passphrase, PREFIX_PASS) ? copy_passphrase(passphrase + STRLEN(PREFIX_PASS)) :
(STARTS_WITH(passphrase, PREFIX_FILE) ? read_passphrase(passphrase + STRLEN(PREFIX_FILE)) : read_passphrase(T("-"))))))
2020-10-13 19:33:01 +02:00
{
goto clean_up;
2020-10-13 19:33:01 +02:00
}
slunkcrypt_bzero((CHR*)passphrase, STRLEN(passphrase) * sizeof(CHR));
2020-10-20 15:21:00 +02:00
const size_t passphrase_len = strlen(passphrase_buffer);
if (passphrase_len < SLUNKCRYPT_PWDLEN_MIN)
{
FPRINTF(stderr, T("Error: Passphrase must be at least %u characters in length!\n\n"), (unsigned)SLUNKCRYPT_PWDLEN_MIN);
goto clean_up;
2020-10-20 15:21:00 +02:00
}
else if (passphrase_len > SLUNKCRYPT_PWDLEN_MAX)
{
FPRINTF(stderr, T("Error: Passphrase must be at most %u characters in length!\n\n"), (unsigned)SLUNKCRYPT_PWDLEN_MAX);
goto clean_up;
2020-10-20 15:21:00 +02:00
}
if (slunk_mode == MODE_ENCR)
{
if (passphrase_len < RCMD_PWDLEN_LENGTH)
{
FPRINTF(stderr, T("Warning: Using a *short* passphrase; a length of %u characters or more is recommended!\n\n"), (unsigned)RCMD_PWDLEN_LENGTH);
}
else if (weak_passphrase(passphrase_buffer))
{
FPUTS(T("Warning: Using a *weak* passphrase; a mix of upper-case letters, lower-case letters, digits and other characters is recommended!\n\n"), stderr);
}
}
fflush(stderr);
2020-10-20 15:21:00 +02:00
/* ----------------------------------------------------- */
/* Encrypt or decrypt */
/* ----------------------------------------------------- */
const uint64_t clk_start = clock_read();
const int keep_incomplete = keep_incomplete_files();
switch (slunk_mode)
{
case MODE_ENCR:
result = encrypt(passphrase_buffer, input_file, output_file, keep_incomplete);
2020-10-20 15:21:00 +02:00
break;
case MODE_DECR:
result = decrypt(passphrase_buffer, input_file, output_file, keep_incomplete);
2020-10-20 15:21:00 +02:00
break;
default:
2020-10-20 22:13:39 +02:00
FPUTS(T("Unexpected mode encountered!\n\n"), stderr);
}
if (!g_slunkcrypt_abort_flag)
2020-10-14 21:55:39 +02:00
{
FPRINTF(stderr, T("--------\n\nOperation completed after %.1f seconds.\n\n"), (clock_read() - clk_start) / ((double)clock_freq()));
2020-10-14 21:55:39 +02:00
}
2020-10-13 19:33:01 +02:00
2020-10-20 15:21:00 +02:00
/* ----------------------------------------------------- */
/* Final clean-up */
/* ----------------------------------------------------- */
clean_up:
fflush(stderr);
if (passphrase_buffer)
2020-10-13 19:50:29 +02:00
{
slunkcrypt_bzero(passphrase_buffer, strlen(passphrase_buffer));
free(passphrase_buffer);
2020-10-13 19:50:29 +02:00
}
if (input_file)
{
free(input_file);
}
if (output_file)
{
free(output_file);
}
2020-10-15 22:41:28 +02:00
return result;
}
#if defined(_WIN32) && defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)
void __wgetmainargs(int*, wchar_t***, wchar_t***, int, int*);
int main()
{
wchar_t** enpv, ** argv;
int argc, si = 0;
__wgetmainargs(&argc, &argv, &enpv, 1, &si);
return wmain(argc, argv);
}
#endif