1 /*
2 * common.c - Common functions for stoken and stoken-gui
3 *
4 * Copyright 2012 Kevin Cernekee <cernekee@gmail.com>
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 #include "config.h"
22
23 #include <ctype.h>
24 #include <getopt.h>
25 #include <stdarg.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <sys/stat.h>
30 #include <sys/types.h>
31
32 #ifdef HAVE_MLOCKALL
33 #include <sys/mman.h>
34 #endif
35
36 #include "common.h"
37 #include "securid.h"
38 #include "stoken.h"
39 #include "stoken-internal.h"
40
41 /* globals - shared with cli.c or gui.c */
42
43 int opt_random, opt_keep_password, opt_blocks, opt_iphone, opt_android,
44 opt_v3, opt_show_qr, opt_seed, opt_sdtid, opt_small, opt_next;
45 int opt_debug, opt_version, opt_help, opt_batch, opt_force, opt_stdin;
46 char *opt_rcfile, *opt_file, *opt_token, *opt_devid, *opt_password,
47 *opt_pin, *opt_use_time, *opt_new_password, *opt_new_devid,
48 *opt_new_pin, *opt_template, *opt_qr;
49 struct securid_token *current_token;
50
51 static int debug_level;
52 static struct stoken_cfg *cfg;
53
prompt(const char * fmt,...)54 void prompt(const char *fmt, ...)
55 {
56 va_list ap;
57 va_start(ap, fmt);
58 if (!opt_stdin)
59 vfprintf(stdout, fmt, ap);
60 va_end(ap);
61 }
62
warn(const char * fmt,...)63 void warn(const char *fmt, ...)
64 {
65 va_list ap;
66 va_start(ap, fmt);
67 fflush(stdout);
68 vfprintf(stderr, fmt, ap);
69 va_end(ap);
70 }
71
dbg(const char * fmt,...)72 void dbg(const char *fmt, ...)
73 {
74 va_list ap;
75
76 if (!debug_level)
77 return;
78 va_start(ap, fmt);
79 fflush(stdout);
80 vfprintf(stderr, fmt, ap);
81 va_end(ap);
82 }
83
die(const char * fmt,...)84 void die(const char *fmt, ...)
85 {
86 va_list ap;
87
88 va_start(ap, fmt);
89 fflush(stdout);
90 vfprintf(stderr, fmt, ap);
91 va_end(ap);
92 exit(1);
93 }
94
xstrdup(const char * s)95 char *xstrdup(const char *s)
96 {
97 char *ret = strdup(s);
98 if (!ret)
99 die("out of memory\n");
100 return ret;
101 }
102
xconcat(const char * s1,const char * s2)103 char *xconcat(const char *s1, const char *s2)
104 {
105 char *ret = xmalloc(strlen(s1) + strlen(s2) + 1);
106 strcpy(ret, s1);
107 strcat(ret, s2);
108 return ret;
109 }
110
xstrncpy(char * dest,const char * src,size_t n)111 void xstrncpy(char *dest, const char *src, size_t n)
112 {
113 strncpy(dest, src, n);
114 dest[n - 1] = 0;
115 }
116
xmalloc(size_t size)117 void *xmalloc(size_t size)
118 {
119 void *ret = malloc(size);
120 if (!ret)
121 die("out of memory\n");
122 return ret;
123 }
124
xzalloc(size_t size)125 void *xzalloc(size_t size)
126 {
127 void *ret = xmalloc(size);
128 memset(ret, 0, size);
129 return ret;
130 }
131
132 enum {
133 OPT_DEVID = 1,
134 OPT_USE_TIME,
135 OPT_NEW_PASSWORD,
136 OPT_NEW_DEVID,
137 OPT_NEW_PIN,
138 OPT_TEMPLATE,
139 OPT_QR,
140 };
141
142 static const struct option long_opts[] = {
143 /* global: token sources */
144 { "rcfile", 1, NULL, 'r' },
145 { "file", 1, NULL, 'i' },
146 { "token", 1, NULL, 't' },
147 { "random", 0, &opt_random, 1, },
148
149 /* global: secrets used to decrypt/use a seed */
150 { "devid", 1, NULL, OPT_DEVID },
151 { "password", 1, NULL, 'p' },
152 { "pin", 1, NULL, 'n' },
153
154 /* GUI: use smaller window */
155 { "small", 0, &opt_small, 1 },
156
157 /* global: misc/debug */
158 { "debug", 0, NULL, 'd' },
159 { "version", 0, NULL, 'v' },
160 { "force", 0, NULL, 'f' },
161 { "use-time", 1, NULL, OPT_USE_TIME },
162 { "help", 0, NULL, 'h' },
163
164 /* all remaining options are for CLI only */
165 #define FINAL_GUI_OPTION "help"
166
167 { "batch", 0, NULL, 'b' },
168
169 /* used for tokencode generation */
170 { "next", 0, &opt_next, 1 },
171
172 /* these are mostly for exporting/issuing tokens */
173 { "new-password", 1, NULL, OPT_NEW_PASSWORD },
174 { "new-devid", 1, NULL, OPT_NEW_DEVID },
175 { "new-pin", 1, NULL, OPT_NEW_PIN },
176 { "template", 1, NULL, OPT_TEMPLATE },
177 { "keep-password", 0, &opt_keep_password, 1 },
178 { "blocks", 0, &opt_blocks, 1 },
179 { "iphone", 0, &opt_iphone, 1 },
180 { "android", 0, &opt_android, 1 },
181 { "v3", 0, &opt_v3, 1 },
182 { "sdtid", 0, &opt_sdtid, 1 },
183 { "xml", 0, &opt_sdtid, 1 },
184 { "qr", 1, NULL, OPT_QR },
185 { "show-qr", 0, &opt_show_qr, 1 },
186 { "seed", 0, &opt_seed, 1 },
187 { "stdin", 0, NULL, 's' },
188 { NULL, 0, NULL, 0 },
189 };
190
usage_common(void)191 static void usage_common(void)
192 {
193 puts("Alternate seed sources:");
194 puts("");
195 puts(" --rcfile=<alt_rcfile>");
196 puts(" --token=<token_string>");
197 puts(" --file=<token_file>");
198 puts(" --random");
199 puts("");
200 puts("See the stoken(1) man page for additional information.");
201 }
202
usage_gui(void)203 static void usage_gui(void)
204 {
205 puts("usage: stoken-gui [ <options> ]");
206 puts("");
207 usage_common();
208 exit(1);
209 }
210
usage_cli(void)211 static void usage_cli(void)
212 {
213 puts("usage: stoken <cmd> [ <options> ]");
214 puts("");
215 puts("Common operations:");
216 puts("");
217 puts(" stoken [ tokencode ] [ --stdin ]");
218 puts(" stoken import { --token=<token_string> | --file=<token_file> } [ --force ]");
219 puts(" stoken setpass");
220 puts(" stoken setpin");
221 puts("");
222 puts("Other commands:");
223 puts("");
224 puts(" stoken show [ --seed ]");
225 puts(" stoken export [ { --blocks | --iphone | --android | --v3 | --sdtid |");
226 puts(" --qr=<file> | --show-qr } ]");
227 puts(" stoken issue [ --template=<sdtid_skeleton> ]");
228 puts("");
229 usage_common();
230 exit(1);
231 }
232
show_version(void)233 static void show_version(void)
234 {
235 puts(PACKAGE_STRING " - software token for Linux/UNIX systems");
236 puts("Copyright (C) 2014 Kevin Cernekee <cernekee@gmail.com>");
237 puts("");
238 puts("This is free software with ABSOLUTELY NO WARRANTY.");
239 puts("For details see the COPYING.LIB file in the source distribution.");
240 exit(0);
241 }
242
parse_cmdline(int argc,char ** argv,int is_gui)243 char *parse_cmdline(int argc, char **argv, int is_gui)
244 {
245 int ret, longindex = 0, last_gui_opt = 0;
246 const struct option *opt = long_opts;
247 char *cmd = NULL;
248
249 for (; strcmp(opt->name, FINAL_GUI_OPTION); last_gui_opt++, opt++)
250 ;
251
252 while (1) {
253 ret = getopt_long(argc, argv, "r:i:t:p:n:dvhbfs",
254 long_opts, &longindex);
255 if (ret == -1)
256 break;
257
258 if (is_gui && longindex > last_gui_opt)
259 die("error: --%s is not valid in GUI mode\n",
260 long_opts[longindex].name);
261
262 switch (ret) {
263 case 'r': opt_rcfile = optarg; break;
264 case 'i': opt_file = optarg; break;
265 case 't': opt_token = optarg; break;
266 case 'p': opt_password = optarg; break;
267 case 'n': opt_pin = optarg; break;
268 case 'd': opt_debug = 1; break;
269 case 'v': opt_version = 1; break;
270 case 'h': opt_help = 1; break;
271 case 'b': opt_batch = 1; break;
272 case 'f': opt_force = 1; break;
273 case 's': opt_stdin = 1; break;
274 case OPT_DEVID: opt_devid = optarg; break;
275 case OPT_USE_TIME: opt_use_time = optarg; break;
276 case OPT_NEW_PASSWORD: opt_new_password = optarg; break;
277 case OPT_NEW_DEVID: opt_new_devid = optarg; break;
278 case OPT_NEW_PIN: opt_new_pin = optarg; break;
279 case OPT_TEMPLATE: opt_template = optarg; break;
280 case OPT_QR: opt_qr = optarg; break;
281 case 0: break;
282 default: opt_help = 1;
283 }
284 }
285
286 if (!is_gui && optind == argc - 1)
287 cmd = argv[optind];
288 else if (optind == argc)
289 cmd = xstrdup("tokencode"); /* default command */
290 else
291 warn("error: too many command-line arguments\n");
292
293 if (!cmd || !strcmp(cmd, "help") || opt_help) {
294 if (is_gui)
295 usage_gui();
296 else
297 usage_cli();
298 }
299
300 if (!strcmp(cmd, "version") || opt_version)
301 show_version();
302
303 return cmd;
304 }
305
read_token_from_file(char * filename,struct securid_token * t)306 static int read_token_from_file(char *filename, struct securid_token *t)
307 {
308 char buf[65536], *p;
309 int rc = ERR_BAD_LEN;
310 FILE *f;
311 size_t len;
312
313 f = fopen(filename, "r");
314 if (f == NULL)
315 return ERR_FILE_READ;
316
317 len = fread(buf, 1, sizeof(buf) - 1, f);
318 if (ferror(f))
319 len = 0;
320 fclose(f);
321
322 if (len == 0)
323 return ERR_FILE_READ;
324 buf[len] = 0;
325
326 for (p = buf; *p; ) {
327 rc = __stoken_parse_and_decode_token(p, t, 1);
328
329 /*
330 * keep checking more lines until we find something that
331 * looks like a token
332 */
333 if (rc != ERR_GENERAL)
334 break;
335
336 p = strchr(p, '\n');
337 if (!p)
338 break;
339 p++;
340 }
341
342 return rc;
343 }
344
decode_rc_token(struct stoken_cfg * cfg,struct securid_token * t)345 static int decode_rc_token(struct stoken_cfg *cfg, struct securid_token *t)
346 {
347 int rc = securid_decode_token(cfg->rc_token, t);
348
349 if (rc != ERR_NONE) {
350 warn("rcfile: token data is garbled, ignoring\n");
351 return rc;
352 }
353
354 if (cfg->rc_pin) {
355 if (t->flags & FL_PASSPROT)
356 t->enc_pin_str = xstrdup(cfg->rc_pin);
357 else {
358 if (securid_pin_format_ok(cfg->rc_pin) == ERR_NONE)
359 xstrncpy(t->pin, cfg->rc_pin, MAX_PIN + 1);
360 else
361 warn("rcfile: invalid PIN format\n");
362 }
363 }
364 return ERR_NONE;
365 }
366
common_init(char * cmd)367 int common_init(char *cmd)
368 {
369 int rc;
370 struct securid_token *t;
371 int is_import = !strcmp(cmd, "import");
372
373 /*
374 * we don't actually scrub memory, but at least try to keep the seeds
375 * from being swapped out to disk
376 */
377 #ifdef HAVE_MLOCKALL
378 mlockall(MCL_CURRENT | MCL_FUTURE);
379 #endif
380
381 stc_standalone_init();
382
383 cfg = xzalloc(sizeof(*cfg));
384 if (__stoken_read_rcfile(opt_rcfile, cfg,
385 is_import ? &dbg : &warn) != ERR_NONE)
386 __stoken_zap_rcfile_data(cfg);
387
388 /* accept a token from the command line, or fall back to the rcfile */
389 do {
390 t = xzalloc(sizeof(struct securid_token));
391
392 if (opt_token) {
393 rc = __stoken_parse_and_decode_token(opt_token, t, 1);
394 if (rc != ERR_NONE)
395 die("error: --token string is garbled: %s\n",
396 stoken_errstr[rc]);
397 current_token = t;
398 break;
399 }
400 if (opt_file) {
401 rc = read_token_from_file(opt_file, t);
402 if (rc == ERR_MULTIPLE_TOKENS)
403 die("error: multiple tokens found; use 'stoken split' to create separate files\n");
404 else if (rc != ERR_NONE)
405 die("error: no valid token in file '%s': %s\n",
406 opt_file, stoken_errstr[rc]);
407 current_token = t;
408 break;
409 }
410 if (opt_random) {
411 rc = securid_random_token(t);
412 if (rc != ERR_NONE)
413 die("error: can't generate random token\n");
414 current_token = t;
415 break;
416 }
417 if (cfg->rc_token) {
418 if (is_import)
419 die("error: please specify --file, --token, or --random\n");
420 if (decode_rc_token(cfg, t) == ERR_NONE) {
421 current_token = t;
422 break;
423 }
424 }
425 free(t);
426 } while (0);
427
428 if (is_import && cfg->rc_token && !opt_force)
429 die("error: token already exists; use --force to overwrite it\n");
430
431 return ERR_NONE;
432 }
433
write_token_and_pin(char * token_str,char * pin_str,char * password)434 int write_token_and_pin(char *token_str, char *pin_str, char *password)
435 {
436 free(cfg->rc_ver);
437 free(cfg->rc_token);
438 free(cfg->rc_pin);
439
440 cfg->rc_token = xstrdup(token_str);
441
442 if (pin_str && !password)
443 cfg->rc_pin = xstrdup(pin_str);
444 else if (pin_str && password) {
445 cfg->rc_pin = securid_encrypt_pin(pin_str, password);
446 if (!cfg->rc_pin)
447 return ERR_GENERAL;
448 } else
449 cfg->rc_pin = NULL;
450
451 cfg->rc_ver = xstrdup("1");
452
453 return __stoken_write_rcfile(opt_rcfile, cfg, &warn);
454 }
455
format_token(const char * token_str)456 char *format_token(const char *token_str)
457 {
458 int i;
459 char *out, *p;
460
461 if (opt_iphone)
462 return xconcat("com.rsa.securid.iphone://ctf?ctfData=",
463 token_str);
464 else if (opt_android || opt_v3)
465 return xconcat("http://127.0.0.1/securid/ctf?ctfData=",
466 token_str);
467 else if (!opt_blocks)
468 return xstrdup(token_str);
469
470 /* user requested blocks of 5 digits (--blocks) */
471 i = strlen(token_str);
472 out = xzalloc(i + (i / 5) + 2);
473
474 for (i = 0, p = out; token_str[i]; i++) {
475 if (i % 5 == 0 && i)
476 *(p++) = '-';
477 *(p++) = token_str[i];
478 }
479
480 return out;
481 }
482