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