1 /*
2 * One-time password login library
3 *
4 * Markus Kuhn <http://www.cl.cam.ac.uk/~mgk25/>
5 */
6
7 #include <stdlib.h>
8 #include <stdarg.h>
9 #include <stdio.h>
10 #include <string.h>
11 #include <errno.h>
12 #include <unistd.h>
13 #include <pwd.h>
14 #include <time.h>
15 #include <fcntl.h>
16 #include <sys/time.h>
17 #include <sys/types.h>
18 #include <sys/stat.h>
19 #include "otpw.h"
20 #include "md.h"
21
22 #ifndef DEBUG_LOG
23 #define DEBUG_LOG(...) if (ch->flags & OTPW_DEBUG) \
24 { fprintf(stderr, __VA_ARGS__); fputc('\n', stderr); }
25 #endif
26
27 /* Some global variables with configuration options */
28
29 /* Path for the one-time password file relative to home directory of
30 * the user who tries to log in. (Ignored if otpw_pseudouser != NULL) */
31 char *otpw_file = ".otpw";
32
33 /* Suffix added to the one-time password filename to create lock symlink */
34 char *otpw_locksuffix = ".lock";
35
36 /* Number of passwords requested while another one is locked. */
37 int otpw_multi = 3;
38
39 /* Age of a lock file in seconds after which it will be deleted. */
40 double otpw_locktimeout = 24 * 60 * 60;
41
42 /* Length of stored hash in characters (each encoding 6 bits) */
43 int otpw_hlen = 12;
44
45 /* Characteristic first line, for recognition of an OTPW file */
46 char *otpw_magic = "OTPW1\n";
47
48 /*
49 * Normally, the password file is located in the home directory of the
50 * user who tries to log in, typically in the file ~/.otpw, and is
51 * accessed using the effective uid and gid of that user. However,
52 * otpw_pseudouser is not NULL, then the uid/gid of that pseudouser
53 * (e.g., "otpw") will be used instead to access the file, which will
54 * be located in the home directory of the pseudouser (e.g.,
55 * "/var/lib/otpw"), and will have the name of the user who tries to
56 * log in instead of ".otpw".
57 */
58 struct otpw_pwdbuf *otpw_pseudouser = NULL;
59
60 char *otpw_autopseudouser = "otpw";
61 long otpw_autopseudouser_maxuid = 999;
62
63 /* allocate a struct otpw_pwdbuf (of suitable size to also hold the strings) */
otpw_malloc_pwdbuf(void)64 static struct otpw_pwdbuf *otpw_malloc_pwdbuf(void)
65 {
66 struct otpw_pwdbuf *p;
67 long buflen;
68
69 buflen = sysconf(_SC_GETPW_R_SIZE_MAX); /* typical value: 1024 */
70 /* fprintf(stderr, "_SC_GETPW_R_SIZE_MAX = %ld\n", buflen); */
71 if (buflen < 0) buflen = 1024;
72 p = (struct otpw_pwdbuf *) malloc(sizeof(struct otpw_pwdbuf) + buflen);
73 if (p) p->buflen = buflen;
74 return p;
75 }
76
77 /* mallocating variant of getpwnam_r */
otpw_getpwnam(const char * name,struct otpw_pwdbuf ** result)78 int otpw_getpwnam(const char *name, struct otpw_pwdbuf **result)
79 {
80 struct otpw_pwdbuf *p;
81 struct passwd *r;
82 int err = ENOMEM;
83 if ((p = otpw_malloc_pwdbuf())) {
84 err = getpwnam_r(name, &p->pwd, p->buf, p->buflen, &r);
85 if (r) {
86 *result = p;
87 } else {
88 *result = NULL;
89 free(p);
90 }
91 }
92 return err;
93 }
94
95 /* mallocating variant of getpwuid_r */
otpw_getpwuid(uid_t uid,struct otpw_pwdbuf ** result)96 int otpw_getpwuid(uid_t uid, struct otpw_pwdbuf **result)
97 {
98 struct otpw_pwdbuf *p;
99 struct passwd *r;
100 int err = ENOMEM;
101 if ((p = otpw_malloc_pwdbuf())) {
102 err = getpwuid_r(uid, &p->pwd, p->buf, p->buflen, &r);
103 if (r) {
104 *result = p;
105 } else {
106 *result = NULL;
107 free(p);
108 }
109 }
110 return err;
111 }
112
113
114 /*
115 * Check if the user otpw_autopseudouser exists and has a UID of not
116 * higher than otpw_autopseudouser_maxuid. If so, malloc and set
117 * pseudouser accordingly. Usually: pseudouser == &otpw_pseudouser.
118 */
otpw_set_pseudouser(struct otpw_pwdbuf ** pseudouser)119 int otpw_set_pseudouser(struct otpw_pwdbuf **pseudouser)
120 {
121 int err;
122 err = otpw_getpwnam(otpw_autopseudouser, pseudouser);
123 if (otpw_pseudouser) {
124 if (otpw_autopseudouser_maxuid >= 0 &&
125 otpw_pseudouser->pwd.pw_uid > otpw_autopseudouser_maxuid) {
126 err = EINVAL;
127 free(*pseudouser);
128 otpw_pseudouser = NULL;
129 }
130 }
131 return err;
132 }
133
134 /*
135 * A random bit generator. Hashes together some quick sources of entropy
136 * to provide some reasonable random seed. (High entropy is not security
137 * critical here.)
138 */
139
rbg_seed(unsigned char * r)140 static void rbg_seed(unsigned char *r)
141 {
142 int devrandom;
143 char rbs[MD_LEN];
144 md_state md;
145 struct {
146 clock_t clk;
147 pid_t pid;
148 uid_t uid;
149 pid_t ppid;
150 struct timeval t;
151 } entropy;
152
153 md_init(&md);
154
155 /* read out kernel random number generator device if there is one */
156 devrandom = open("/dev/urandom", O_RDONLY);
157 if (devrandom >= 0) {
158 read(devrandom, rbs, sizeof(rbs));
159 md_add(&md, rbs, sizeof(rbs));
160 close(devrandom);
161 }
162
163 /* other minor sources of entropy */
164 entropy.clk = clock();
165 entropy.uid = getuid();
166 entropy.pid = getpid();
167 entropy.ppid = getppid();
168 gettimeofday(&entropy.t, NULL);
169 md_add(&md, (unsigned char *) &entropy, sizeof(entropy));
170
171 md_close(&md, r);
172 }
173
174
rbg_iter(unsigned char * r)175 static void rbg_iter(unsigned char *r)
176 {
177 md_state md;
178 struct timeval t;
179
180 md_init(&md);
181 gettimeofday(&t, NULL);
182 md_add(&md, (unsigned char *) &t, sizeof(t));
183 md_add(&md, r, MD_LEN);
184 md_add(&md, "AutomaGic", 9); /* feel free to change this as a site key */
185 md_close(&md, r);
186 }
187
188 /*
189 * Transform the first 6*chars bits of the binary string v into a chars
190 * character long string s. The encoding is a modification of the MIME
191 * base64 encoding where characters with easily confused glyphs are
192 * avoided (0 vs O, 1 vs. l vs. I).
193 */
194
conv_base64(char * s,const unsigned char * v,int chars)195 static void conv_base64(char *s, const unsigned char *v, int chars)
196 {
197 static const char tab[] =
198 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk%mnopqrstuvwxyz"
199 ":=23456789+/";
200 int i, j;
201
202 for (i = 0; i < chars; i++) {
203 j = (i / 4) * 3;
204 switch (i % 4) {
205 case 0: *s++ = tab[ v[j] >>2]; break;
206 case 1: *s++ = tab[((v[j] <<4) & 0x30) | (v[j+1]>>4)]; break;
207 case 2: *s++ = tab[((v[j+1]<<2) & 0x3c) | (v[j+2]>>6)]; break;
208 case 3: *s++ = tab[ v[j+2] & 0x3f]; break;
209 }
210 }
211 *s++ = '\0';
212 }
213
214
otpw_free(struct challenge * ch)215 static void otpw_free(struct challenge *ch)
216 {
217 int i;
218
219 if (ch->selection) free(ch->selection);
220 if (ch->hash) {
221 for (i = 0; i < otpw_multi; i++) {
222 if (ch->hash[i]) free(ch->hash[i]);
223 }
224 free(ch->hash);
225 }
226 if (ch->filename) free(ch->filename);
227 if (ch->lockfilename) free(ch->lockfilename);
228 }
229
230
otpw_prepare(struct challenge * ch,struct passwd * user,int flags)231 void otpw_prepare(struct challenge *ch, struct passwd *user, int flags)
232 {
233 FILE *f = NULL;
234 int i, j;
235 int count, repeat;
236 int olduid = -1;
237 int oldgid = -1;
238 char line[81];
239 char lock[81];
240 unsigned char r[MD_LEN];
241 struct stat lbuf;
242 char *hbuf = NULL; /* list of challenges and hashed passwords */
243 int hbuflen;
244
245 if (!ch) {
246 DEBUG_LOG("!ch");
247 return;
248 }
249 ch->passwords = 0;
250 ch->remaining = -1;
251 ch->entries = -1;
252 ch->pwlen = 0;
253 ch->locked = 0;
254 ch->challenge[0] = 0;
255 ch->flags = flags;
256 ch->filename = NULL;
257 ch->lockfilename = NULL;
258 ch->selection = NULL;
259 ch->hash = NULL;
260 ch->selection = (int *) calloc(otpw_multi, sizeof(int));
261 ch->hash = (char **) calloc(otpw_multi, sizeof(char *));
262 if (!ch->selection || !ch->hash) {
263 DEBUG_LOG("calloc() failed");
264 goto cleanup;
265 }
266 if (!user) {
267 DEBUG_LOG("No password database entry provided!");
268 goto cleanup;
269 }
270
271 /* prepare filename of one-time password file */
272 if (otpw_pseudouser) {
273 ch->filename = (char *) malloc(strlen(otpw_pseudouser->pwd.pw_dir) + 1 +
274 strlen(user->pw_name) + 1);
275 if (!ch->filename) {
276 DEBUG_LOG("malloc() for ch->filename failed");
277 goto cleanup;
278 }
279 strcpy(ch->filename, otpw_pseudouser->pwd.pw_dir);
280 strcat(ch->filename, "/");
281 strcat(ch->filename, user->pw_name);
282 ch->uid = otpw_pseudouser->pwd.pw_uid;
283 ch->gid = otpw_pseudouser->pwd.pw_gid;
284 } else {
285 ch->filename = (char *) malloc(strlen(user->pw_dir)+1+strlen(otpw_file)+1);
286 if (!ch->filename) {
287 DEBUG_LOG("malloc() for ch->filename failed");
288 goto cleanup;
289 }
290 strcpy(ch->filename, user->pw_dir);
291 strcat(ch->filename, "/");
292 strcat(ch->filename, otpw_file);
293 ch->uid = user->pw_uid;
294 ch->gid = user->pw_gid;
295 }
296 /* prepare associated lock filename */
297 ch->lockfilename = (char *) malloc(strlen(ch->filename) +
298 strlen(otpw_locksuffix) + 1);
299 if (!ch->lockfilename) {
300 DEBUG_LOG("malloc() for ch->lockfilename failed");
301 goto cleanup;
302 }
303 strcpy(ch->lockfilename, ch->filename);
304 strcat(ch->lockfilename, otpw_locksuffix);
305
306 /* set effective uid/gid temporarily */
307 olduid = geteuid();
308 oldgid = getegid();
309 if (setegid(ch->gid))
310 DEBUG_LOG("Failed to change egid %d -> %d", oldgid, ch->gid);
311 if (seteuid(ch->uid))
312 DEBUG_LOG("Failed to change euid %d -> %d", olduid, ch->uid);
313
314 /* open password file */
315 if (!(f = fopen(ch->filename, "r"))) {
316 DEBUG_LOG("fopen(\"%s\", \"r\"): %s", ch->filename, strerror(errno));
317 goto cleanup;
318 }
319
320 /* prepare random number generator */
321 rbg_seed(r);
322
323 /* check header */
324 if (!fgets(line, sizeof(line), f) ||
325 strcmp(line, otpw_magic) ||
326 !fgets(line, sizeof(line), f) ||
327 ((line[0] == '#') && !fgets(line, sizeof(line), f)) ||
328 sscanf(line, "%d%d%d%d\n", &ch->entries,
329 &ch->challen, &ch->hlen, &ch->pwlen) != 4) {
330 DEBUG_LOG("Header wrong in '%s'!", ch->filename);
331 goto cleanup;
332 }
333 if (ch->entries < 1 || ch->entries > 9999 ||
334 ch->challen < 1 ||
335 (ch->challen + 1) * otpw_multi > (int)sizeof(ch->challenge) ||
336 ch->pwlen < 4 || ch->pwlen > 999 ||
337 ch->hlen != otpw_hlen) {
338 DEBUG_LOG("Header parameters (%d %d %d %d) out of allowed range!",
339 ch->entries, ch->challen, ch->hlen, ch->pwlen);
340 goto cleanup;
341 }
342 hbuflen = ch->challen + ch->hlen;
343
344 hbuf = malloc(ch->entries * hbuflen);
345 if (!hbuf) {
346 DEBUG_LOG("malloc() for hbuf failed");
347 goto cleanup;
348 }
349
350 ch->remaining = 0;
351 j = -1;
352 for (i = 0; i < ch->entries; i++) {
353 if (!fgets(line, sizeof(line), f) ||
354 (int) strlen(line) != hbuflen + 1) {
355 DEBUG_LOG("%s too short!", ch->filename);
356 goto cleanup;
357 }
358 memcpy(hbuf + i*hbuflen, line, hbuflen);
359 if (hbuf[i*hbuflen] != '-') {
360 ch->remaining++;
361 if (j < 0)
362 j = i; /* select first unused hash */
363 }
364 }
365 if (ch->remaining < 1) {
366 DEBUG_LOG("No passwords left!");
367 goto cleanup;
368 }
369 strncpy(ch->challenge, hbuf + j*hbuflen, ch->challen);
370 ch->challenge[ch->challen] = 0;
371 ch->selection[0] = j;
372 ch->hash[0] = (char *) calloc(ch->hlen + 1, sizeof(char));
373 if (!ch->hash[0]) {
374 DEBUG_LOG("calloc() failed");
375 goto cleanup;
376 }
377 strncpy(ch->hash[0], hbuf + j*hbuflen + ch->challen, ch->hlen);
378
379 if (ch->flags & OTPW_NOLOCK) {
380 /* we were told not to worry about locking */
381 ch->passwords = 1;
382 goto cleanup;
383 }
384
385 count = 0;
386 do {
387 repeat = 0;
388
389 /* try to get a lock on this one */
390 if (symlink(ch->challenge, ch->lockfilename) == 0) {
391 /* ok, we got the lock */
392 ch->passwords = 1;
393 ch->locked = 1;
394 goto cleanup;
395 }
396 if (errno != EEXIST) {
397 DEBUG_LOG("symlink(\"%s\", \"%s\"): %s",
398 ch->challenge, ch->lockfilename, strerror(errno));
399 ch->challenge[0] = 0;
400 goto cleanup;
401 }
402
403 if (lstat(ch->lockfilename, &lbuf) == 0) {
404 if (otpw_locktimeout > 0 &&
405 difftime(time(NULL), lbuf.st_mtime) > otpw_locktimeout) {
406 /* remove a stale lock after a specified time out period */
407 unlink(ch->lockfilename);
408 repeat = 1;
409 }
410 } else if (errno == ENOENT)
411 repeat = 1;
412 else {
413 DEBUG_LOG("lstat(\"%s\", ...): %s", ch->lockfilename, strerror(errno));
414 ch->challenge[0] = 0;
415 goto cleanup;
416 }
417
418 } while (repeat && ++count < 5);
419 ch->challenge[0] = 0;
420
421 /* ok, there is already a fresh lock, so someone is currently logging in */
422 i = readlink(ch->lockfilename, lock, sizeof(lock)-1);
423 if (i > 0) {
424 lock[i] = 0;
425 if ((int) strlen(lock) != ch->challen) {
426 /* lock symlink seems to have been corrupted */
427 DEBUG_LOG("Removing corrupt lock symlink to %s -> %s.",
428 ch->lockfilename, lock);
429 unlink(ch->lockfilename);
430 }
431 } else if (errno != ENOENT) {
432 DEBUG_LOG("Could not read lock symlink '%s'.", ch->lockfilename);
433 goto cleanup;
434 }
435
436 /* now we generate otpw_multi challenges */
437 if (ch->remaining < otpw_multi+1 || ch->remaining < 10) {
438 DEBUG_LOG("%d remaining passwords are not enough for "
439 "multi challenge.", ch->remaining);
440 goto cleanup;
441 }
442 while (ch->passwords < otpw_multi &&
443 strlen(ch->challenge) < sizeof(ch->challenge) - ch->challen - 2) {
444 count = 0;
445 /* random scan for remaining password */
446 do {
447 /* pick a random entry */
448 rbg_iter(r);
449 j = *((unsigned int *) r) % ch->entries;
450 } while ((hbuf[j*hbuflen] == '-' ||
451 !strncmp(hbuf + j*hbuflen + ch->challen, lock, ch->challen)) &&
452 count++ < 2 * ch->entries);
453 /* fallback scan for remaining password */
454 while (hbuf[j*hbuflen] == '-' ||
455 !strncmp(hbuf + j*hbuflen + ch->challen, lock, ch->challen))
456 j = (j + 1) % ch->entries;
457 /* add password j to multi challenge */
458 sprintf(ch->challenge + strlen(ch->challenge), "%s%.*s",
459 ch->passwords ? "/" : "", ch->challen, hbuf + j*hbuflen);
460
461 if (!ch->hash[ch->passwords])
462 ch->hash[ch->passwords] = (char *) calloc(ch->hlen + 1, sizeof(char));
463 if (!ch->hash[ch->passwords]) {
464 DEBUG_LOG("calloc() failed");
465 goto cleanup;
466 }
467 strncpy(ch->hash[ch->passwords], hbuf + j*hbuflen + ch->challen, ch->hlen);
468 ch->selection[ch->passwords++] = j;
469 hbuf[j*hbuflen] = '-'; /* avoid same pw occuring twice per challenge */
470 }
471
472 cleanup:
473 if (f)
474 fclose(f);
475 /* restore uid/gid */
476 if (olduid != -1)
477 if (seteuid(olduid))
478 DEBUG_LOG("Failed when trying to change euid back to %d", olduid);
479 if (oldgid != -1)
480 if (setegid(oldgid))
481 DEBUG_LOG("Failed when trying to change egid back to %d", oldgid);
482 if (hbuf)
483 free(hbuf);
484 if (!ch->challenge[0])
485 otpw_free(ch);
486
487 return;
488 }
489
490
otpw_verify(struct challenge * ch,char * password)491 int otpw_verify(struct challenge *ch, char *password)
492 {
493 FILE *f = NULL;
494 int result = OTPW_ERROR;
495 int i, j = 0, l;
496 int entries;
497 int deleted, clear;
498 int olduid = -1;
499 int oldgid = -1;
500 char *otpw = NULL;
501 char line[81];
502 unsigned char h[MD_LEN];
503 md_state md;
504 int challen, pwlen, hlen;
505
506 if (!ch) {
507 DEBUG_LOG("!ch");
508 return OTPW_ERROR;
509 }
510
511 if (!password || ch->passwords < 1 ||
512 ch->passwords > otpw_multi) {
513 DEBUG_LOG("otpw_verify(): Invalid parameters or no challenge issued.");
514 goto cleanup;
515 }
516
517 otpw = calloc(ch->passwords, ch->pwlen);
518 if (!otpw) {
519 DEBUG_LOG("malloc failed");
520 goto cleanup;
521 }
522
523 /* set effective uid/gid temporarily */
524 olduid = geteuid();
525 oldgid = getegid();
526 if (setegid(ch->gid))
527 DEBUG_LOG("Failed when trying to change egid %d -> %d", oldgid, ch->gid);
528 if (seteuid(ch->uid))
529 DEBUG_LOG("Failed when trying to change euid %d -> %d", olduid, ch->uid);
530
531 /*
532 * Scan in the one-time passwords, eliminating any spurious characters
533 * (such as whitespace, control characters) that might have been added
534 * accidentally
535 */
536 l = strlen(password) - 1;
537 for (i = ch->passwords-1; i >= 0 && l >= 0; i--) {
538 for (j = ch->pwlen - 1; j >= 0 && l >= 0; j--) {
539 while (!otpw[i*ch->pwlen + j] && l >= 0) {
540 /* remove DEL/BS characters */
541 deleted = 0;
542 while (l >= 0 &&
543 (password[l] == 8 || password[l] == 127 || deleted > 0)) {
544 if (password[l] == 8 || password[l] == 127)
545 deleted++;
546 else
547 deleted--;
548 l--;
549 }
550 if (l < 0) break;
551 if (password[l] == 'l' || password[l] == '1' || password[l] == '|')
552 otpw[i*ch->pwlen + j] = 'I';
553 else if (password[l] == '0')
554 otpw[i*ch->pwlen + j] = 'O';
555 else if (password[l] == '\\')
556 otpw[i*ch->pwlen + j] = '/';
557 else if ((password[l] >= 'A' && password[l] <= 'Z') ||
558 (password[l] >= 'a' && password[l] <= 'z') ||
559 (password[l] >= '2' && password[l] <= '9') ||
560 password[l] == ':' ||
561 password[l] == '%' ||
562 password[l] == '=' ||
563 password[l] == '+' ||
564 password[l] == '/')
565 otpw[i*ch->pwlen + j] = password[l];
566 l--;
567 }
568 }
569 DEBUG_LOG("Password %d = '%.*s'", i, ch->pwlen, otpw + i*ch->pwlen);
570 }
571 if (i >= 0 || j >= 0) {
572 DEBUG_LOG("Entered password was too short.");
573 result = OTPW_WRONG;
574 goto cleanup;
575 }
576
577 l++; /* l is now the length of the prefix password */
578 DEBUG_LOG("Prefix = '%.*s'", l, password);
579
580 /* now compare all entered passwords */
581 for (i = 0; i < ch->passwords; i++) {
582 md_init(&md);
583 /* feed prefix password into hash function */
584 md_add(&md, password, l);
585 /* feed one-time password into hash function */
586 md_add(&md, otpw + i*ch->pwlen, ch->pwlen);
587 /* transform hash result into the base64 form used in ch->filename */
588 md_close(&md, h);
589 conv_base64(line, h, ch->hlen);
590 DEBUG_LOG("hash(password): '%s', hash from file: '%s'",
591 line, ch->hash[i]);
592 if (strcmp(line, ch->hash[i])) {
593 DEBUG_LOG("Entered password did not match.");
594 result = OTPW_WRONG;
595 goto cleanup;
596 }
597 }
598
599 /* ok, all passwords were correct */
600 result = OTPW_OK;
601 DEBUG_LOG("Entered password(s) are ok.");
602
603 /* Now overwrite the used passwords in ch->filename */
604 if (!(f = fopen(ch->filename, "r+"))) {
605 DEBUG_LOG("Failed getting write access to '%s': %s",
606 ch->filename, strerror(errno));
607 goto writefail;
608 }
609 /* check header */
610 if (!fgets(line, sizeof(line), f) ||
611 strcmp(line, otpw_magic) ||
612 !fgets(line, sizeof(line), f) ||
613 ((line[0] == '#') && !fgets(line, sizeof(line), f)) ||
614 sscanf(line, "%d%d%d%d\n", &entries,
615 &challen, &hlen, &pwlen) != 4 ||
616 entries != ch->entries || pwlen != ch->pwlen ||
617 hlen != ch->hlen || challen != ch->challen ||
618 (challen + 1) * otpw_multi > (int) sizeof(ch->challenge)) {
619 DEBUG_LOG("Overwrite failed because of header mismatch.");
620 goto writefail;
621 }
622 for (i = 0; i < entries; i++) {
623 clear = 0;
624 for (j = 0; j < ch->passwords; j++)
625 if (ch->selection[j] == i)
626 clear = 1;
627 if (clear) {
628 fseek(f, 0L, SEEK_CUR);
629 for (l = 0; l < challen + hlen; l++)
630 fputc('-', f);
631 fputc('\n', f);
632 fseek(f, 0L, SEEK_CUR);
633 ch->remaining--;
634 } else
635 if (!fgets(line, sizeof(line), f)) {
636 DEBUG_LOG("Overwrite failed because of unexpected EOF.");
637 goto writefail;
638 }
639 }
640 goto cleanup;
641
642 writefail:
643 /* entered one-time passwords were correct, but overwriting them failed */
644 if (ch->passwords == 1) {
645 /* for a single password, permit login, but keep lock in place */
646 DEBUG_LOG("Keeping lock on password.");
647 ch->locked = 0; /* supress removal of lock */
648 }
649
650 cleanup:
651 if (f)
652 fclose(f);
653 /* remove lock */
654 if (ch->locked) {
655 DEBUG_LOG("Removing lock file");
656 if (unlink(ch->lockfilename))
657 DEBUG_LOG("Failed when trying to unlink lock file: %s", strerror(errno));
658 }
659 /* restore uid/gid */
660 if (olduid != -1)
661 if (seteuid(olduid))
662 DEBUG_LOG("Failed when trying to change euid back to %d", olduid);
663 if (oldgid != -1)
664 if (setegid(oldgid))
665 DEBUG_LOG("Failed when trying to change egid back to %d", oldgid);
666 /* make sure, we are not called a second time */
667 ch->passwords = 0;
668
669 if (otpw)
670 free(otpw);
671 otpw_free(ch);
672
673 return result;
674 }
675