1 /*
2  * library.c - libstoken library implementation
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 <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 
30 #include "securid.h"
31 #include "sdtid.h"
32 #include "stoken-internal.h"
33 
34 struct stoken_ctx {
35 	struct securid_token	*t;
36 	struct stoken_cfg	cfg;
37 };
38 
39 static struct stoken_guid stoken_guid_list[] = {
40 	{ "iphone",   "iPhone",        "556f1985-33dd-442c-9155-3a0e994f21b1" },
41 	{ "android",  "Android",       "a01c4380-fc01-4df0-b113-7fb98ec74694" },
42 	{ "bb",       "BlackBerry",    "868c28f8-31bf-4911-9876-ebece5c3f2ab" },
43 	{ "bb10",     "BlackBerry 10", "b77a1d06-d505-4200-90d3-1bb397748704" },
44 	{ "winphone", "Windows Phone", "c483b592-63f0-4f19-b4cb-a6bce8e57159" },
45 	{ "win",      "Windows",       "8f94b226-d362-4204-ac52-3b21fa333b6f" },
46 	{ "mac",      "Mac OSX",       "d0955a53-569b-4ecc-9cf7-6c2a59d4e775" },
47 	{ },
48 };
49 
50 /***********************************************************************
51  * Internal functions (only called from within the stoken package)
52  ***********************************************************************/
53 
strstarts(const char * str,const char * prefix)54 static int strstarts(const char *str, const char *prefix)
55 {
56 	return strncmp(str, prefix, strlen(prefix)) == 0;
57 }
58 
__stoken_parse_and_decode_token(const char * str,struct securid_token * t,int interactive)59 int __stoken_parse_and_decode_token(const char *str, struct securid_token *t,
60 				    int interactive)
61 {
62 	char buf[BUFLEN];
63 	const char *p;
64 	int i, ret;
65 
66 	memset(t, 0, sizeof(*t));
67 	t->interactive = interactive;
68 
69 	do {
70 		/* try to handle broken quoted-printable input */
71 		p = strcasestr(str, "ctfData=3D");
72 		if (p) {
73 			p += 10;
74 			break;
75 		}
76 
77 		/* normal iPhone/Android soft token URLs */
78 		p = strcasestr(str, "ctfData=");
79 		if (p) {
80 			p += 8;
81 			break;
82 		}
83 
84 		/* sdtid (XML) token format */
85 		p = strcasestr(str, "<?xml ");
86 		if (p)
87 			return sdtid_decode(p, t);
88 
89 		p = str;
90 		if (isdigit(*p))
91 			break;
92 
93 		/* bogus token string */
94 		return ERR_GENERAL;
95 	} while (0);
96 
97 	if (p[0] == '1' || p[0] == '2') {
98 		for (i = 0; *p; p++) {
99 			if (i >= BUFLEN - 1)
100 				return ERR_BAD_LEN;
101 			if (isdigit(*p))
102 				buf[i++] = *p;
103 			else if (*p != '-')
104 				break;
105 		}
106 	} else if (p[0] == 'A') {
107 		for (i = 0; *p; p++) {
108 			if (i >= BUFLEN - 1)
109 				return ERR_BAD_LEN;
110 			buf[i++] = *p;
111 		}
112 	} else
113 		return ERR_GENERAL;
114 
115 	buf[i] = 0;
116 	ret = securid_decode_token(buf, t);
117 
118 	if (strstarts(str, "com.rsa.securid.iphone://ctf") ||
119 	    strstarts(str, "com.rsa.securid://ctf") ||
120 	    strstarts(str, "http://127.0.0.1/securid/ctf"))
121 		t->is_smartphone = 1;
122 	return ret;
123 }
124 
next_token(char ** in,char * tok,int maxlen)125 static int next_token(char **in, char *tok, int maxlen)
126 {
127 	int len;
128 
129 	for (len = 0; len < BUFLEN - 1; (*in)++) {
130 		if (**in == 0 || **in == '\r' || **in == '\n') {
131 			if (len == 0)
132 				return -1;
133 			goto done;
134 		}
135 		if (**in == ' ' || **in == '\t') {
136 			if (len != 0)
137 				goto done;
138 			continue;
139 		}
140 		*(tok++) = **in;
141 		len++;
142 	}
143 
144 	/* if the loop terminates here, truncate the line and return success */
145 
146 done:
147 	*tok = 0;
148 	return 0;
149 }
150 
parse_rcline(struct stoken_cfg * cfg,int linenum,char * line,warn_fn_t warn_fn)151 static int parse_rcline(struct stoken_cfg *cfg, int linenum, char *line,
152 	warn_fn_t warn_fn)
153 {
154 	char *p = line, key[BUFLEN], val[BUFLEN], **dst;
155 
156 	if (next_token(&p, key, BUFLEN) < 0)
157 		return ERR_NONE;	/* empty line */
158 
159 	if (key[0] == '#')
160 		return ERR_NONE;	/* comment */
161 
162 	if (next_token(&p, val, BUFLEN) < 0) {
163 		warn_fn("rcfile:%d: missing argument for '%s'\n", linenum, key);
164 		return ERR_GENERAL;
165 	}
166 
167 	dst = NULL;
168 	if (strcasecmp(key, "version") == 0)
169 		dst = &cfg->rc_ver;
170 	else if (strcasecmp(key, "token") == 0)
171 		dst = &cfg->rc_token;
172 	else if (strcasecmp(key, "pin") == 0)
173 		dst = &cfg->rc_pin;
174 
175 	if (!dst) {
176 		/* this isn't treated as a fatal error */
177 		warn_fn("rcfile:%d: unrecognized option '%s'\n", linenum, key);
178 		return ERR_NONE;
179 	}
180 
181 	free(*dst);
182 	*dst = strdup(val);
183 	if (!*dst) {
184 		warn_fn("rcfile:%d: out of memory\n", linenum);
185 		return ERR_GENERAL;
186 	}
187 
188 	return ERR_NONE;
189 }
190 
fopen_rcfile(const char * override,const char * mode,warn_fn_t warn_fn,FILE ** f)191 static int fopen_rcfile(const char *override, const char *mode,
192 	warn_fn_t warn_fn, FILE **f)
193 {
194 	char *homedir;
195 	const char *file = override;
196 	char filename[BUFLEN];
197 	mode_t old_umask;
198 
199 	if (!override) {
200 		homedir = getenv("HOME");
201 		if (!homedir) {
202 			homedir = getenv("USERPROFILE");
203 		}
204 		if (!homedir) {
205 			warn_fn("rcfile: HOME is not set so I can't read '%s'\n",
206 				RC_NAME);
207 			return ERR_GENERAL;
208 		}
209 
210 		snprintf(filename, BUFLEN, "%s/%s", homedir, RC_NAME);
211 		file = filename;
212 	}
213 
214 	/* force mode 0600 on creation */
215 	old_umask = umask(0177);
216 	*f = fopen(file, mode);
217 	umask(old_umask);
218 
219 	if (!*f && override)
220 		warn_fn("rcfile: can't open '%s'\n", override);
221 
222 	return *f ? ERR_NONE : ERR_GENERAL;
223 }
224 
__stoken_zap_rcfile_data(struct stoken_cfg * cfg)225 void __stoken_zap_rcfile_data(struct stoken_cfg *cfg)
226 {
227 	free(cfg->rc_ver);
228 	free(cfg->rc_token);
229 	free(cfg->rc_pin);
230 	memset(cfg, 0, sizeof(*cfg));
231 }
232 
__stoken_read_rcfile(const char * override,struct stoken_cfg * cfg,warn_fn_t warn_fn)233 int __stoken_read_rcfile(const char *override, struct stoken_cfg *cfg,
234 	warn_fn_t warn_fn)
235 {
236 	FILE *f;
237 	char buf[BUFLEN];
238 	int linenum = 1, ret;
239 
240 	__stoken_zap_rcfile_data(cfg);
241 
242 	/* XXX: kind of dumb return code here, but it gets the job done */
243 	ret = fopen_rcfile(override, "r", warn_fn, &f);
244 	if (ret != ERR_NONE)
245 		return ERR_MISSING_PASSWORD;
246 
247 	while (fgets(buf, BUFLEN, f) != NULL) {
248 		int ret2 = parse_rcline(cfg, linenum++, buf, warn_fn);
249 		if (ret2 != ERR_NONE)
250 			ret = ret2;
251 	}
252 
253 	if (ferror(f)) {
254 		ret = ERR_GENERAL;
255 		warn_fn("rcfile: read error(s) were detected\n");
256 	}
257 	fclose(f);
258 
259 	if (ret == ERR_NONE) {
260 		if (!cfg->rc_ver || !cfg->rc_token)
261 			return ERR_GENERAL;
262 		if (atoi(cfg->rc_ver) != RC_VER) {
263 			warn_fn("rcfile: version mismatch, ignoring contents\n");
264 			return ERR_TOKEN_VERSION;
265 		}
266 	}
267 
268 	return ret;
269 }
270 
__stoken_write_rcfile(const char * override,const struct stoken_cfg * cfg,warn_fn_t warn_fn)271 int __stoken_write_rcfile(const char *override, const struct stoken_cfg *cfg,
272 	warn_fn_t warn_fn)
273 {
274 	FILE *f;
275 	int ret;
276 
277 	ret = fopen_rcfile(override, "w", warn_fn, &f);
278 	if (ret != ERR_NONE)
279 		return ret;
280 
281 	if (cfg->rc_ver)
282 		fprintf(f, "version %s\n", cfg->rc_ver);
283 	if (cfg->rc_token)
284 		fprintf(f, "token %s\n", cfg->rc_token);
285 	if (cfg->rc_pin)
286 		fprintf(f, "pin %s\n", cfg->rc_pin);
287 
288 	if (ferror(f))
289 		ret = ERR_GENERAL;
290 	fclose(f);
291 
292 	return ret;
293 }
294 
zap_current_token(struct stoken_ctx * ctx)295 static void zap_current_token(struct stoken_ctx *ctx)
296 {
297 	if (ctx->t) {
298 		free(ctx->t->v3);
299 		sdtid_free(ctx->t->sdtid);
300 		free(ctx->t);
301 	}
302 	ctx->t = NULL;
303 }
304 
clone_token(struct stoken_ctx * ctx,struct securid_token * tmp)305 static int clone_token(struct stoken_ctx *ctx, struct securid_token *tmp)
306 {
307 	ctx->t = malloc(sizeof(*tmp));
308 	if (!ctx->t)
309 		return -EIO;
310 	memcpy(ctx->t, tmp, sizeof(*tmp));
311 	return 0;
312 }
313 
314 /***********************************************************************
315  * Exported functions
316  ***********************************************************************/
317 
stoken_new(void)318 struct stoken_ctx *stoken_new(void)
319 {
320 	struct stoken_ctx *ctx;
321 
322 	ctx = calloc(1, sizeof(*ctx));
323 	if (!ctx)
324 		return NULL;
325 
326 	return ctx;
327 }
328 
stoken_destroy(struct stoken_ctx * ctx)329 void stoken_destroy(struct stoken_ctx *ctx)
330 {
331 	zap_current_token(ctx);
332 	__stoken_zap_rcfile_data(&ctx->cfg);
333 	free(ctx);
334 }
335 
stoken_import_rcfile(struct stoken_ctx * ctx,const char * path)336 int stoken_import_rcfile(struct stoken_ctx *ctx, const char *path)
337 {
338 	struct securid_token tmp;
339 	int rc;
340 
341 	zap_current_token(ctx);
342 
343 	rc = __stoken_read_rcfile(path, &ctx->cfg, &__stoken_warn_empty);
344 	if (rc == ERR_MISSING_PASSWORD)
345 		return -ENOENT;
346 	else if (rc != ERR_NONE)
347 		goto bad;
348 
349 	if (__stoken_parse_and_decode_token(ctx->cfg.rc_token, &tmp, 0) !=
350 	    ERR_NONE)
351 		goto bad;
352 
353 	if (ctx->cfg.rc_pin) {
354 		if (tmp.flags & FL_PASSPROT)
355 			tmp.enc_pin_str = ctx->cfg.rc_pin;
356 		else {
357 			if (securid_pin_format_ok(ctx->cfg.rc_pin) == ERR_NONE)
358 				strncpy(tmp.pin, ctx->cfg.rc_pin,
359 					MAX_PIN + 1);
360 			else
361 				goto bad;
362 		}
363 	}
364 	return clone_token(ctx, &tmp);
365 
366 bad:
367 	__stoken_zap_rcfile_data(&ctx->cfg);
368 	return -EINVAL;
369 }
370 
stoken_import_string(struct stoken_ctx * ctx,const char * token_string)371 int stoken_import_string(struct stoken_ctx *ctx, const char *token_string)
372 {
373 	struct securid_token tmp;
374 
375 	zap_current_token(ctx);
376 
377 	if (__stoken_parse_and_decode_token(token_string, &tmp, 0) != ERR_NONE)
378 		return -EINVAL;
379 	return clone_token(ctx, &tmp);
380 }
381 
stoken_get_info(struct stoken_ctx * ctx)382 struct stoken_info *stoken_get_info(struct stoken_ctx *ctx)
383 {
384 	struct stoken_info *info = calloc(1, sizeof(*info));
385 	if (!info)
386 		return NULL;
387 
388 	strncpy(info->serial, ctx->t->serial, sizeof(info->serial) - 1);
389 	info->exp_date = securid_unix_exp_date(ctx->t);
390 	info->interval = securid_token_interval(ctx->t);
391 	info->token_version = ctx->t->version;
392 	info->uses_pin = securid_pin_required(ctx->t);
393 
394 	return info;
395 }
396 
stoken_pin_range(struct stoken_ctx * ctx,int * min_pin,int * max_pin)397 void stoken_pin_range(struct stoken_ctx *ctx, int *min_pin, int *max_pin)
398 {
399 	*min_pin = MIN_PIN;
400 	*max_pin = MAX_PIN;
401 }
402 
stoken_pin_required(struct stoken_ctx * ctx)403 int stoken_pin_required(struct stoken_ctx *ctx)
404 {
405 	/* don't prompt for a PIN if it was saved in the rcfile */
406 	if (ctx->t->enc_pin_str || strlen(ctx->t->pin))
407 		return 0;
408 	return securid_pin_required(ctx->t);
409 }
410 
stoken_pass_required(struct stoken_ctx * ctx)411 int stoken_pass_required(struct stoken_ctx *ctx)
412 {
413 	return securid_pass_required(ctx->t);
414 }
415 
stoken_devid_required(struct stoken_ctx * ctx)416 int stoken_devid_required(struct stoken_ctx *ctx)
417 {
418 	return securid_devid_required(ctx->t);
419 }
420 
stoken_check_pin(struct stoken_ctx * ctx,const char * pin)421 int stoken_check_pin(struct stoken_ctx *ctx, const char *pin)
422 {
423 	return securid_pin_format_ok(pin) == ERR_NONE ? 0 : -EINVAL;
424 }
425 
stoken_get_guid_list(void)426 const struct stoken_guid *stoken_get_guid_list(void)
427 {
428 	return stoken_guid_list;
429 }
430 
stoken_check_devid(struct stoken_ctx * ctx,const char * devid)431 int stoken_check_devid(struct stoken_ctx *ctx, const char *devid)
432 {
433 	if (securid_check_devid(ctx->t, devid) == ERR_NONE)
434 		return 0;
435 	return -EINVAL;
436 }
437 
stoken_decrypt_seed(struct stoken_ctx * ctx,const char * pass,const char * devid)438 int stoken_decrypt_seed(struct stoken_ctx *ctx, const char *pass,
439 	const char *devid)
440 {
441 	if (securid_decrypt_seed(ctx->t, pass, devid) != ERR_NONE)
442 		return -EINVAL;
443 	if (ctx->t->enc_pin_str) {
444 		if (securid_decrypt_pin(ctx->t->enc_pin_str, pass,
445 		    ctx->t->pin) != ERR_NONE)
446 			return -EINVAL;
447 	}
448 
449 	return 0;
450 }
451 
stoken_encrypt_seed(struct stoken_ctx * ctx,const char * pass,const char * devid)452 char *stoken_encrypt_seed(struct stoken_ctx *ctx, const char *pass,
453 	const char *devid)
454 {
455 	char *ret;
456 
457 	if (!ctx->t || !ctx->t->has_dec_seed)
458 		return NULL;
459 	ret = calloc(1, MAX_TOKEN_CHARS + 1);
460 	if (!ret)
461 		return NULL;
462 
463 	if (securid_encode_token(ctx->t, pass, devid, 2, ret) != ERR_NONE) {
464 		free(ret);
465 		return NULL;
466 	}
467 	return ret;
468 }
469 
stoken_compute_tokencode(struct stoken_ctx * ctx,time_t when,const char * pin,char * out)470 int stoken_compute_tokencode(struct stoken_ctx *ctx, time_t when,
471 	const char *pin, char *out)
472 {
473 	if (securid_pin_required(ctx->t)) {
474 		if (pin && strlen(pin)) {
475 			if (securid_pin_format_ok(pin) != ERR_NONE)
476 				return -EINVAL;
477 			strncpy(ctx->t->pin, pin, MAX_PIN + 1);
478 		} else if (stoken_pin_required(ctx)) {
479 			return -EINVAL;
480 		}
481 	}
482 	securid_compute_tokencode(ctx->t, when, out);
483 	return 0;
484 }
485 
stoken_format_tokencode(const char * tokencode)486 char *stoken_format_tokencode(const char *tokencode)
487 {
488 	int code_len = strlen(tokencode);
489 	char *str = malloc(code_len + 2);
490 	int i, j;
491 
492 	if (!str)
493 		return NULL;
494 
495 	for (i = 0, j = 0; i < code_len; i++) {
496 		if (i == code_len / 2)
497 			str[j++] = ' ';
498 		str[j++] = tokencode[i];
499 	}
500 	str[j] = 0;
501 
502 	return str;
503 }
504