OSDN Git Service

4ae1252f08cee007b460ea7fe762424676025642
[slunkcrypt/SlunkCrypt.git] / frontend / src / main.c
1 /******************************************************************************/
2 /* SlunkCrypt, by LoRd_MuldeR <MuldeR2@GMX.de>                                */
3 /* This work has been released under the CC0 1.0 Universal license!           */
4 /******************************************************************************/
5
6 /* Internal */
7 #include "utils.h"
8 #include "crypt.h"
9 #include "pwgen.h"
10 #include "selftest.h"
11
12 /* Library */
13 #include <slunkcrypt.h>
14
15 /* CRT */
16 #include <string.h>
17 #include <time.h>
18 #include <inttypes.h>
19 #include <ctype.h>
20 #include <signal.h>
21
22 // ==========================================================================
23 // Constants
24 // ==========================================================================
25
26 #define MODE_HELP 0
27 #define MODE_VERS 1
28 #define MODE_ENCR 2
29 #define MODE_DECR 3
30 #define MODE_PASS 4
31 #define MODE_TEST 5
32
33 static const size_t RCMD_PWDLEN_LENGTH = 12U;
34 static const size_t DFLT_PWDLEN_LENGTH = 24U;
35
36 static const CHR* const ENV_PASSWORD = T("SLUNK_PASSPHRASE");
37 static const CHR* const ENV_KEEPFILE = T("SLUNK_KEEP_INCOMPLETE");
38 static const CHR* const ENV_NTHREADS = T("SLUNK_THREADS");
39 static const CHR* const ENV_LGCYCMPT = T("SLUNK_LEGACY_COMPAT");
40
41 static const CHR* const PREFIX_PASS = T("pass:");
42 static const CHR* const PREFIX_FILE = T("file:");
43
44 // ==========================================================================
45 // Auxiliary functions
46 // ==========================================================================
47
48 #define PW_FROM_ENV (!(argc > 4))
49
50 static int parse_slunk_mode(const CHR* const command)
51 {
52         if ((!STRICMP(command, T("-h"))) || (!STRICMP(command, T("/?"))) || (!STRICMP(command, T("--help"))))
53         {
54                 return MODE_HELP;
55         }
56         else if ((!STRICMP(command, T("-v"))) || (!STRICMP(command, T("--version"))))
57         {
58                 return MODE_VERS;
59         }
60         else if ((!STRICMP(command, T("-e"))) || (!STRICMP(command, T("--encrypt"))))
61         {
62                 return MODE_ENCR;
63         }
64         else if ((!STRICMP(command, T("-d"))) || (!STRICMP(command, T("--decrypt"))))
65         {
66                 return MODE_DECR;
67         }
68         else if ((!STRICMP(command, T("-p"))) || (!STRICMP(command, T("--make-pw"))))
69         {
70                 return MODE_PASS;
71         }
72         else if ((!STRICMP(command, T("-t"))) || (!STRICMP(command, T("--self-test"))))
73         {
74                 return MODE_TEST;
75         }
76         else
77         {
78                 return -1; /*invalid command*/
79         }
80 }
81
82 static void print_manpage(const CHR *const program)
83 {
84         FPUTS(T("====================================================================\n"), stderr);
85         FPUTS(T("This software has been released under the CC0 1.0 Universal license:\n"), stderr);
86         FPUTS(T("https://creativecommons.org/publicdomain/zero/1.0/legalcode\n"), stderr);
87         FPUTS(T("====================================================================\n\n"), stderr);
88         FPUTS(T("Usage:\n"), stderr);
89         FPRINTF(stderr, T("  %") T(PRISTR) T(" --encrypt [pass:<pass>|file:<file>] <input.txt> <output.enc>\n"), program);
90         FPRINTF(stderr, T("  %") T(PRISTR) T(" --decrypt [pass:<pass>|file:<file>] <input.enc> <output.txt>\n"), program);
91         FPRINTF(stderr, T("  %") T(PRISTR) T(" --make-pw [<length>]\n\n"), program);
92         FPRINTF(stderr, T("Optionally, reads passphrase from the %") T(PRISTR) T(" environment variable.\n\n"), ENV_PASSWORD);
93 }
94
95 static char *copy_passphrase(const CHR *const passphrase)
96 {
97         if ((!passphrase) || (!passphrase[0U]))
98         {
99                 FPUTS(T("Error: The passphrase input string must not be empty!\n\n"), stderr);
100                 return NULL;
101         }
102
103         char *const buffer = CHR_to_utf8(passphrase);
104         if (!buffer)
105         {
106                 FPUTS(T("Error: Failed to allocate the string buffer!\n\n"), stderr);
107         }
108
109         return buffer;
110 }
111
112 static uint32_t environ_get_uint(const CHR *const name)
113 {
114         const CHR *const value = GETENV(name);
115         if (value)
116         {
117                 return (uint32_t) STRTOUL(value);
118         }
119         return 0U;
120 }
121
122 static int environ_get_flag(const CHR *const name)
123 {
124         return (environ_get_uint(name) != 0U) ? SLUNKCRYPT_TRUE : SLUNKCRYPT_FALSE;
125 }
126
127 static void check_excess_arguments(const int argc, int maximum)
128 {
129         if (argc > maximum)
130         {
131                 FPUTS(T("Warning: Excess command-line argument(s) will be ignored!\n\n"), stderr);
132                 fflush(stderr);
133         }
134 }
135
136 static void sigint_handler(const int sig)
137 {
138         if (sig == SIGINT)
139         {
140                 g_slunkcrypt_abort_flag = 1;
141         }
142 }
143
144 // ==========================================================================
145 // Main function
146 // ==========================================================================
147
148 int MAIN(const int argc, CHR *const argv[])
149 {
150         int result = EXIT_FAILURE;
151         const CHR *input_file = NULL, *output_file = NULL;
152         char *passphrase_buffer = NULL;
153
154         init_terminal();
155         setup_signal_handler(SIGINT, sigint_handler);
156
157         FPRINTF(stderr, T("SlunkCrypt Utility (%") T(PRIstr) T("-%") T(PRIstr) T("), by LoRd_MuldeR <MuldeR2@GMX.de>\n"), OS_TYPE_NAME, CPU_ARCH);
158         FPRINTF(stderr, T("Using libSlunkCrypt-%") T(PRIstr) T(" v%u.%u.%u [%") T(PRIstr) T("]\n\n"),
159                 SLUNKCRYPT_HAVE_THREADS ? "MT" : "ST", SLUNKCRYPT_VERSION_MAJOR, SLUNKCRYPT_VERSION_MINOR, SLUNKCRYPT_VERSION_PATCH, SLUNKCRYPT_BUILD);
160
161         fflush(stderr);
162
163         /* ----------------------------------------------------- */
164         /* Parse arguments                                       */
165         /* ----------------------------------------------------- */
166
167         if ((argc < 1) || (!argv[0]))
168         {
169                 FPUTS(T("Error: Argument array is empty. The program was called incorrectly!\n\n"), stderr);
170                 goto clean_up;
171         }
172
173         if (argc < 2)
174         {
175                 FPRINTF(stderr, T("Error: Nothing to do. Please type '%") T(PRISTR) T(" --help' for details!\n\n"), get_file_name(argv[0U]));
176                 goto clean_up;
177         }
178
179         const int slunk_mode = parse_slunk_mode(argv[1U]);
180         switch (slunk_mode)
181         {
182         case MODE_HELP:
183                 print_manpage(get_file_name(argv[0U]));
184         case MODE_VERS:
185                 result = EXIT_SUCCESS;
186                 goto clean_up;
187         case MODE_ENCR:
188         case MODE_DECR:
189                 break; /*fallthrough*/
190         case MODE_PASS:
191                 check_excess_arguments(argc, 3);
192                 result = generate_passphrase((argc > 2) ? STRTOUL(argv[2U]) : DFLT_PWDLEN_LENGTH);
193                 goto clean_up;
194         case MODE_TEST:
195                 check_excess_arguments(argc, 2);
196                 result = run_selftest_routine(environ_get_uint(ENV_NTHREADS));
197                 goto clean_up;
198         default:
199                 FPRINTF(stderr, T("Error: The specified command \"%") T(PRISTR) T("\" is unknown!\n\n"), argv[1U]);
200                 goto clean_up;
201         }
202
203         if (argc < 4)
204         {
205                 FPRINTF(stderr, T("Error: Required argument is missing. Please type '%") T(PRISTR) T(" --help' for details!\n\n"), get_file_name(argv[0U]));
206                 goto clean_up;
207         }
208
209         check_excess_arguments(argc, 5);
210
211         const CHR *const passphrase = PW_FROM_ENV ? GETENV(ENV_PASSWORD) : argv[2U];
212         if ((!passphrase) || (!passphrase[0U]))
213         {
214                 FPUTS(T("Error: The passphrase must be specified, directly or indirectly!\n\n"), stderr);
215                 goto clean_up;
216         }
217
218         if ((!PW_FROM_ENV) && STRICMP(passphrase, T("-")))
219         {
220                 if ((!STARTS_WITH(passphrase, PREFIX_PASS)) && (!STARTS_WITH(passphrase, PREFIX_FILE)))
221                 {
222                         FPRINTF(stderr, T("Error: The passphrase must start with a '%") T(PRISTR) T("' or '%") T(PRISTR) T("' prefix!\n\n"), PREFIX_PASS, PREFIX_FILE);
223                         goto clean_up;
224                 }
225         }
226
227         input_file = argv[PW_FROM_ENV ? 2U : 3U];
228         if (!input_file[0U])
229         {
230                 FPUTS(T("Error: The specified input file name must not be empty!\n\n"), stderr);
231                 goto clean_up;
232         }
233
234         output_file = argv[PW_FROM_ENV ? 3U : 4U];
235         if (!output_file[0U])
236         {
237                 FPUTS(T("Error: The specified output file name must not be empty!\n\n"), stderr);
238                 goto clean_up;
239         }
240
241         if (same_file(input_file, output_file) > 0)
242         {
243                 FPUTS(T("Error: The input and output files must not be the same!\n\n"), stderr);
244                 goto clean_up;
245         }
246
247         /* ----------------------------------------------------- */
248         /* Initialize passphrase                                 */
249         /* ----------------------------------------------------- */
250
251         if (!(passphrase_buffer = PW_FROM_ENV ? copy_passphrase(passphrase) :
252                         (STARTS_WITH(passphrase, PREFIX_PASS) ? copy_passphrase(passphrase + STRLEN(PREFIX_PASS)) :
253                         (STARTS_WITH(passphrase, PREFIX_FILE) ? read_passphrase(passphrase + STRLEN(PREFIX_FILE)) : read_passphrase(T("-"))))))
254         {
255                 goto clean_up;
256         }
257
258         slunkcrypt_bzero((CHR*)passphrase, STRLEN(passphrase) * sizeof(CHR));
259
260         const size_t passphrase_len = strlen(passphrase_buffer);
261         if (passphrase_len < SLUNKCRYPT_PWDLEN_MIN)
262         {
263                 FPRINTF(stderr, T("Error: Passphrase must be at least %u characters in length!\n\n"), (unsigned)SLUNKCRYPT_PWDLEN_MIN);
264                 goto clean_up;
265         }
266         else if (passphrase_len > SLUNKCRYPT_PWDLEN_MAX)
267         {
268                 FPRINTF(stderr, T("Error: Passphrase must be at most %u characters in length!\n\n"),  (unsigned)SLUNKCRYPT_PWDLEN_MAX);
269                 goto clean_up;
270         }
271
272         if (slunk_mode == MODE_ENCR)
273         {
274                 if (passphrase_len < RCMD_PWDLEN_LENGTH)
275                 {
276                         FPRINTF(stderr, T("Warning: Using a *short* passphrase; a length of %u characters or more is recommended!\n\n"), (unsigned)RCMD_PWDLEN_LENGTH);
277                 }
278                 else if (weak_passphrase(passphrase_buffer))
279                 {
280                         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);
281                 }
282         }
283
284         fflush(stderr);
285
286         /* ----------------------------------------------------- */
287         /* Encrypt or decrypt                                    */
288         /* ----------------------------------------------------- */
289
290         const uint64_t clk_start = clock_read();
291         const crypt_options_t options = { environ_get_flag(ENV_KEEPFILE), environ_get_flag(ENV_LGCYCMPT), environ_get_uint(ENV_NTHREADS) };
292
293         switch (slunk_mode)
294         {
295         case MODE_ENCR:
296                 result = encrypt(passphrase_buffer, input_file, output_file, &options);
297                 break;
298         case MODE_DECR:
299                 result = decrypt(passphrase_buffer, input_file, output_file, &options);
300                 break;
301         default:
302                 FPUTS(T("Unexpected mode encountered!\n\n"), stderr);
303         }
304
305         if (!g_slunkcrypt_abort_flag)
306         {
307                 FPRINTF(stderr, T("--------\n\nOperation completed after %.1f seconds.\n\n"), (clock_read() - clk_start) / ((double)clock_freq()));
308         }
309
310         /* ----------------------------------------------------- */
311         /* Final clean-up                                        */
312         /* ----------------------------------------------------- */
313
314 clean_up:
315
316         fflush(stderr);
317
318         if (passphrase_buffer)
319         {
320                 slunkcrypt_bzero(passphrase_buffer, strlen(passphrase_buffer));
321                 free(passphrase_buffer);
322         }
323
324         return result;
325 }
326
327 #if defined(_WIN32) && defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)
328 void __wgetmainargs(int*, wchar_t***, wchar_t***, int, int*);
329 int main()
330 {
331         wchar_t** enpv, ** argv;
332         int argc, si = 0;
333         __wgetmainargs(&argc, &argv, &enpv, 1, &si);
334         return wmain(argc, argv);
335 }
336 #endif