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