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