1/*
2 * $Id: uams_randnum.c,v 1.21 2010-03-30 10:25:49 franklahm Exp $
3 *
4 * Copyright (c) 1990,1993 Regents of The University of Michigan.
5 * Copyright (c) 1999 Adrian Sun (asun@u.washington.edu)
6 * All Rights Reserved.  See COPYRIGHT.
7 */
8
9#ifdef HAVE_CONFIG_H
10#include "config.h"
11#endif /* HAVE_CONFIG_H */
12
13#include <stdio.h>
14#include <stdlib.h>
15
16/* STDC check */
17#if STDC_HEADERS
18#include <string.h>
19#else /* STDC_HEADERS */
20#ifndef HAVE_STRCHR
21#define strchr index
22#define strrchr index
23#endif /* HAVE_STRCHR */
24char *strchr (), *strrchr ();
25#ifndef HAVE_MEMCPY
26#define memcpy(d,s,n) bcopy ((s), (d), (n))
27#define memmove(d,s,n) bcopy ((s), (d), (n))
28#endif /* ! HAVE_MEMCPY */
29#endif /* STDC_HEADERS */
30
31#ifdef HAVE_UNISTD_H
32#include <unistd.h>
33#endif /* HAVE_UNISTD_H */
34#ifdef HAVE_FCNTL_H
35#include <fcntl.h>
36#endif /* HAVE_FCNTL_H */
37#include <ctype.h>
38#include <pwd.h>
39#include <sys/stat.h>
40#include <sys/param.h>
41
42#include <atalk/logger.h>
43
44#include <netatalk/endian.h>
45
46#include <atalk/afp.h>
47#include <atalk/uam.h>
48
49
50#include <des.h>
51
52#ifdef USE_CRACKLIB
53#include <crack.h>
54#endif /* USE_CRACKLIB */
55
56#define PASSWDLEN 8
57
58static C_Block		seskey;
59static Key_schedule	seskeysched;
60static struct passwd	*randpwd;
61static u_int8_t         randbuf[8];
62
63/* hash to a 16-bit number. this will generate completely harmless
64 * warnings on 64-bit machines. */
65#define randhash(a) (((((unsigned long) a) >> 8) ^ \
66		      ((unsigned long)a)) & 0xffff)
67
68
69/* handle ~/.passwd. courtesy of shirsch@ibm.net. */
70static  int home_passwd(const struct passwd *pwd,
71				   const char *path, const int pathlen _U_,
72				   unsigned char *passwd, const int len,
73				   const int set)
74{
75  struct stat st;
76  int fd, i;
77
78  if ( (fd = open(path, (set) ? O_WRONLY : O_RDONLY)) < 0 ) {
79    LOG(log_error, logtype_uams, "Failed to open %s", path);
80    return AFPERR_ACCESS;
81  }
82
83  if ( fstat( fd, &st ) < 0 )
84    goto home_passwd_fail;
85
86  /* If any of these are true, disallow login:
87   * - not a regular file
88   * - gid or uid don't match user
89   * - anyone else has permissions of any sort
90   */
91  if (!S_ISREG(st.st_mode) || (pwd->pw_uid != st.st_uid) ||
92      (pwd->pw_gid != st.st_gid) ||
93      (st.st_mode & ( S_IRWXG | S_IRWXO )) ) {
94    LOG(log_info, logtype_uams, "Insecure permissions found for %s.", path);
95    goto home_passwd_fail;
96  }
97
98  /* get the password */
99  if (set) {
100    if (write(fd, passwd, len) < 0) {
101      LOG(log_error, logtype_uams, "Failed to write to %s", path );
102      goto home_passwd_fail;
103    }
104  } else {
105    if (read(fd, passwd, len) < 0) {
106      LOG(log_error, logtype_uams, "Failed to read from %s", path );
107      goto home_passwd_fail;
108    }
109
110  /* get rid of pesky characters */
111  for (i = 0; i < len; i++)
112    if ((passwd[i] != ' ') && isspace(passwd[i]))
113      passwd[i] = '\0';
114  }
115
116  close(fd);
117  return AFP_OK;
118
119home_passwd_fail:
120  close(fd);
121  return AFPERR_ACCESS;
122}
123
124
125
126/*
127 * handle /path/afppasswd with an optional key file. we're a lot more
128 * trusting of this file. NOTE: we use our own password entry writing
129 * bits as we want to avoid tromping over global variables. in addition,
130 * we look for a key file and use that if it's there. here are the
131 * formats:
132 * password file:
133 * username:password:last login date:failedcount
134 *
135 * password is just the hex equivalent of either the ASCII password
136 * (if the key file doesn't exist) or the des encrypted password.
137 *
138 * key file:
139 * key (in hex) */
140#define PASSWD_ILLEGAL '*'
141#define unhex(x)  (isdigit(x) ? (x) - '0' : toupper(x) + 10 - 'A')
142static int afppasswd(const struct passwd *pwd,
143		     const char *path, const int pathlen,
144		     unsigned char *passwd, int len,
145		     const int set)
146{
147  u_int8_t key[DES_KEY_SZ*2];
148  char buf[MAXPATHLEN + 1], *p;
149  Key_schedule	schedule;
150  FILE *fp;
151  unsigned int i, j;
152  int keyfd = -1, err = 0;
153  off_t pos;
154
155  if ((fp = fopen(path, (set) ? "r+" : "r")) == NULL) {
156    LOG(log_error, logtype_uams, "Failed to open %s", path);
157    return AFPERR_ACCESS;
158  }
159
160  /* open the key file if it exists */
161  strcpy(buf, path);
162  if (pathlen < (int) sizeof(buf) - 5) {
163    strcat(buf, ".key");
164    keyfd = open(buf, O_RDONLY);
165  }
166
167  pos = ftell(fp);
168  memset(buf, 0, sizeof(buf));
169  while (fgets(buf, sizeof(buf), fp)) {
170    if ((p = strchr(buf, ':'))) {
171      if ( strlen(pwd->pw_name) == (p - buf) &&
172           strncmp(buf, pwd->pw_name, p - buf) == 0) {
173	p++;
174	if (*p == PASSWD_ILLEGAL) {
175	  LOG(log_info, logtype_uams, "invalid password entry for %s", pwd->pw_name);
176	  err = AFPERR_ACCESS;
177	  goto afppasswd_done;
178	}
179	goto afppasswd_found;
180      }
181    }
182    pos = ftell(fp);
183    memset(buf, 0, sizeof(buf));
184  }
185  err = AFPERR_PARAM;
186  goto afppasswd_done;
187
188afppasswd_found:
189  if (!set) {
190    /* convert to binary. */
191    for (i = j = 0; i < sizeof(key); i += 2, j++)
192      p[j] = (unhex(p[i]) << 4) | unhex(p[i + 1]);
193    if (j <= DES_KEY_SZ)
194      memset(p + j, 0, sizeof(key) - j);
195  }
196
197  if (keyfd > -1) {
198      /* read in the hex representation of an 8-byte key */
199      read(keyfd, key, sizeof(key));
200
201      /* convert to binary key */
202      for (i = j = 0; i < strlen((char *) key); i += 2, j++)
203	key[j] = (unhex(key[i]) << 4) | unhex(key[i + 1]);
204      if (j <= DES_KEY_SZ)
205	memset(key + j, 0, sizeof(key) - j);
206      key_sched((C_Block *) key, schedule);
207      memset(key, 0, sizeof(key));
208
209      if (set) {
210	/* NOTE: this takes advantage of the fact that passwd doesn't
211	 *       get used after this call if it's being set. */
212	ecb_encrypt((C_Block *) passwd, (C_Block *) passwd, schedule,
213		    DES_ENCRYPT);
214      } else {
215	/* decrypt the password */
216	ecb_encrypt((C_Block *) p, (C_Block *) p, schedule, DES_DECRYPT);
217      }
218      memset(&schedule, 0, sizeof(schedule));
219  }
220
221  if (set) {
222    const unsigned char hextable[] = "0123456789ABCDEF";
223    struct flock lock;
224    int fd = fileno(fp);
225
226    /* convert to hex password */
227    for (i = j = 0; i < DES_KEY_SZ; i++, j += 2) {
228      key[j] = hextable[(passwd[i] & 0xF0) >> 4];
229      key[j + 1] = hextable[passwd[i] & 0x0F];
230    }
231    memcpy(p, key, sizeof(key));
232
233    /* get exclusive access to the user's password entry. we don't
234     * worry so much on reads. in the worse possible case there, the
235     * user will just need to re-enter their password. */
236    lock.l_type = F_WRLCK;
237    lock.l_start = pos;
238    lock.l_len = 1;
239    lock.l_whence = SEEK_SET;
240
241    fseek(fp, pos, SEEK_SET);
242    fcntl(fd, F_SETLKW, &lock);
243    fwrite(buf, p - buf + sizeof(key), 1, fp);
244    lock.l_type = F_UNLCK;
245    fcntl(fd, F_SETLK, &lock);
246  } else
247    memcpy(passwd, p, len);
248
249  memset(buf, 0, sizeof(buf));
250
251afppasswd_done:
252  if (keyfd > -1)
253    close(keyfd);
254  fclose(fp);
255  return err;
256}
257
258
259/* this sets the uid. it needs to do slightly different things
260 * depending upon whether or not the password is in ~/.passwd
261 * or in a global location */
262static int randpass(const struct passwd *pwd, const char *file,
263		    unsigned char *passwd, const int len, const int set)
264{
265  int i;
266  uid_t uid = geteuid();
267
268  /* Build pathname to user's '.passwd' file */
269  i = strlen(file);
270  if (*file == '~') {
271    char path[MAXPATHLEN + 1];
272
273    if ( (strlen(pwd->pw_dir) + i - 1) > MAXPATHLEN)
274    return AFPERR_PARAM;
275
276    strcpy(path,  pwd->pw_dir );
277    strcat(path, "/" );
278    strcat(path, file + 2);
279    if (!uid)
280      seteuid(pwd->pw_uid); /* change ourselves to the user */
281    i = home_passwd(pwd, path, i, passwd, len, set);
282    if (!uid)
283      seteuid(0); /* change ourselves back to root */
284    return i;
285  }
286
287  if (i > MAXPATHLEN)
288    return AFPERR_PARAM;
289
290  /* handle afppasswd file. we need to make sure that we're root
291   * when we do this. */
292  if (uid)
293    seteuid(0);
294  i = afppasswd(pwd, file, i, passwd, len, set);
295  if (uid)
296    seteuid(uid);
297  return i;
298}
299
300/* randnum sends an 8-byte number and uses the user's password to
301 * check against the encrypted reply. */
302static int rand_login(void *obj, char *username, int ulen, struct passwd **uam_pwd _U_,
303                        char *ibuf _U_, size_t ibuflen _U_,
304                        char *rbuf, size_t *rbuflen)
305{
306
307  char *passwdfile;
308  u_int16_t sessid;
309  size_t len;
310  int err;
311
312  if (( randpwd = uam_getname(obj, username, ulen)) == NULL )
313    return AFPERR_NOTAUTH; /* unknown user */
314
315  LOG(log_info, logtype_uams, "randnum/rand2num login: %s", username);
316  if (uam_checkuser(randpwd) < 0)
317    return AFPERR_NOTAUTH;
318
319  len = UAM_PASSWD_FILENAME;
320  if (uam_afpserver_option(obj, UAM_OPTION_PASSWDOPT,
321                             (void *) &passwdfile, &len) < 0)
322    return AFPERR_PARAM;
323
324  if ((err = randpass(randpwd, passwdfile, seskey,
325		      sizeof(seskey), 0)) != AFP_OK)
326    return err;
327
328  /* get a random number */
329  len = sizeof(randbuf);
330  if (uam_afpserver_option(obj, UAM_OPTION_RANDNUM,
331			   (void *) randbuf, &len) < 0)
332    return AFPERR_PARAM;
333
334  /* session id is a hashed version of the obj pointer */
335  sessid = randhash(obj);
336  memcpy(rbuf, &sessid, sizeof(sessid));
337  rbuf += sizeof(sessid);
338  *rbuflen = sizeof(sessid);
339
340  /* send the random number off */
341  memcpy(rbuf, randbuf, sizeof(randbuf));
342  *rbuflen += sizeof(randbuf);
343  return AFPERR_AUTHCONT;
344}
345
346
347/* check encrypted reply. we actually setup the encryption stuff
348 * here as the first part of randnum and rand2num are identical. */
349static int randnum_logincont(void *obj, struct passwd **uam_pwd,
350			     char *ibuf, size_t ibuflen _U_,
351			     char *rbuf _U_, size_t *rbuflen)
352{
353  u_int16_t sessid;
354
355  *rbuflen = 0;
356
357  memcpy(&sessid, ibuf, sizeof(sessid));
358  if (sessid != randhash(obj))
359    return AFPERR_PARAM;
360
361  ibuf += sizeof(sessid);
362
363  /* encrypt. this saves a little space by using the fact that
364   * des can encrypt in-place without side-effects. */
365  key_sched((C_Block *) seskey, seskeysched);
366  memset(seskey, 0, sizeof(seskey));
367  ecb_encrypt((C_Block *) randbuf, (C_Block *) randbuf,
368	       seskeysched, DES_ENCRYPT);
369  memset(&seskeysched, 0, sizeof(seskeysched));
370
371  /* test against what the client sent */
372  if (memcmp( randbuf, ibuf, sizeof(randbuf) )) { /* != */
373    memset(randbuf, 0, sizeof(randbuf));
374    return AFPERR_NOTAUTH;
375  }
376
377  memset(randbuf, 0, sizeof(randbuf));
378  *uam_pwd = randpwd;
379  return AFP_OK;
380}
381
382
383/* differences from randnum:
384 * 1) each byte of the key is shifted left one bit
385 * 2) client sends the server a 64-bit number. the server encrypts it
386 *    and sends it back as part of the reply.
387 */
388static int rand2num_logincont(void *obj, struct passwd **uam_pwd,
389			      char *ibuf, size_t ibuflen _U_,
390			      char *rbuf, size_t *rbuflen)
391{
392  u_int16_t sessid;
393  unsigned int i;
394
395  *rbuflen = 0;
396
397  /* compare session id */
398  memcpy(&sessid, ibuf, sizeof(sessid));
399  if (sessid != randhash(obj))
400    return AFPERR_PARAM;
401
402  ibuf += sizeof(sessid);
403
404  /* shift key elements left one bit */
405  for (i = 0; i < sizeof(seskey); i++)
406    seskey[i] <<= 1;
407
408  /* encrypt randbuf */
409  key_sched((C_Block *) seskey, seskeysched);
410  memset(seskey, 0, sizeof(seskey));
411  ecb_encrypt( (C_Block *) randbuf, (C_Block *) randbuf,
412	       seskeysched, DES_ENCRYPT);
413
414  /* test against client's reply */
415  if (memcmp(randbuf, ibuf, sizeof(randbuf))) { /* != */
416    memset(randbuf, 0, sizeof(randbuf));
417    memset(&seskeysched, 0, sizeof(seskeysched));
418    return AFPERR_NOTAUTH;
419  }
420  ibuf += sizeof(randbuf);
421  memset(randbuf, 0, sizeof(randbuf));
422
423  /* encrypt client's challenge and send back */
424  ecb_encrypt( (C_Block *) ibuf, (C_Block *) rbuf,
425	       seskeysched, DES_ENCRYPT);
426  memset(&seskeysched, 0, sizeof(seskeysched));
427  *rbuflen = sizeof(randbuf);
428
429  *uam_pwd = randpwd;
430  return AFP_OK;
431}
432
433/* change password  --
434 * NOTE: an FPLogin must already have completed successfully for this
435 *       to work.
436 */
437static int randnum_changepw(void *obj, const char *username _U_,
438			    struct passwd *pwd, char *ibuf,
439			    size_t ibuflen _U_, char *rbuf _U_, size_t *rbuflen _U_)
440{
441    char *passwdfile;
442    int err;
443    size_t len;
444
445    if (uam_checkuser(pwd) < 0)
446      return AFPERR_ACCESS;
447
448    len = UAM_PASSWD_FILENAME;
449    if (uam_afpserver_option(obj, UAM_OPTION_PASSWDOPT,
450			     (void *) &passwdfile, &len) < 0)
451      return AFPERR_PARAM;
452
453    /* old password is encrypted with new password and new password is
454     * encrypted with old. */
455    if ((err = randpass(pwd, passwdfile, seskey,
456			sizeof(seskey), 0)) != AFP_OK)
457      return err;
458
459    /* use old passwd to decrypt new passwd */
460    key_sched((C_Block *) seskey, seskeysched);
461    ibuf += PASSWDLEN; /* new passwd */
462    ibuf[PASSWDLEN] = '\0';
463    ecb_encrypt( (C_Block *) ibuf, (C_Block *) ibuf, seskeysched, DES_DECRYPT);
464
465    /* now use new passwd to decrypt old passwd */
466    key_sched((C_Block *) ibuf, seskeysched);
467    ibuf -= PASSWDLEN; /* old passwd */
468    ecb_encrypt((C_Block *) ibuf, (C_Block *) ibuf, seskeysched, DES_DECRYPT);
469    if (memcmp(seskey, ibuf, sizeof(seskey)))
470	err = AFPERR_NOTAUTH;
471    else if (memcmp(seskey, ibuf + PASSWDLEN, sizeof(seskey)) == 0)
472        err = AFPERR_PWDSAME;
473#ifdef USE_CRACKLIB
474    else if (FascistCheck(ibuf + PASSWDLEN, _PATH_CRACKLIB))
475        err = AFPERR_PWDPOLCY;
476#endif /* USE_CRACKLIB */
477
478    if (!err)
479        err = randpass(pwd, passwdfile, (unsigned char *)ibuf + PASSWDLEN, sizeof(seskey), 1);
480
481    /* zero out some fields */
482    memset(&seskeysched, 0, sizeof(seskeysched));
483    memset(seskey, 0, sizeof(seskey));
484    memset(ibuf, 0, sizeof(seskey)); /* old passwd */
485    memset(ibuf + PASSWDLEN, 0, sizeof(seskey)); /* new passwd */
486
487    if (err)
488      return err;
489
490  return( AFP_OK );
491}
492
493/* randnum login */
494static int randnum_login(void *obj, struct passwd **uam_pwd,
495                        char *ibuf, size_t ibuflen,
496                        char *rbuf, size_t *rbuflen)
497{
498    char *username;
499    size_t len, ulen;
500
501    *rbuflen = 0;
502
503    if (uam_afpserver_option(obj, UAM_OPTION_USERNAME,
504                             (void *) &username, &ulen) < 0)
505        return AFPERR_MISC;
506
507    if (ibuflen < 2) {
508        return( AFPERR_PARAM );
509    }
510
511    len = (unsigned char) *ibuf++;
512    ibuflen--;
513    if (!len || len > ibuflen || len > ulen ) {
514        return( AFPERR_PARAM );
515    }
516    memcpy(username, ibuf, len );
517    ibuf += len;
518    ibuflen -=len;
519    username[ len ] = '\0';
520
521    if ((unsigned long) ibuf & 1) { /* pad character */
522        ++ibuf;
523        ibuflen--;
524    }
525    return (rand_login(obj, username, ulen, uam_pwd, ibuf, ibuflen, rbuf, rbuflen));
526}
527
528/* randnum login ext */
529static int randnum_login_ext(void *obj, char *uname, struct passwd **uam_pwd,
530                        char *ibuf, size_t ibuflen,
531                        char *rbuf, size_t *rbuflen)
532{
533    char       *username;
534    size_t     len, ulen;
535    u_int16_t  temp16;
536
537    *rbuflen = 0;
538
539    if (uam_afpserver_option(obj, UAM_OPTION_USERNAME,
540                             (void *) &username, &ulen) < 0)
541        return AFPERR_MISC;
542
543    if (*uname != 3)
544        return AFPERR_PARAM;
545    uname++;
546    memcpy(&temp16, uname, sizeof(temp16));
547    len = ntohs(temp16);
548    if (!len || len > ulen ) {
549        return( AFPERR_PARAM );
550    }
551    memcpy(username, uname +2, len );
552    username[ len ] = '\0';
553    return (rand_login(obj, username, ulen, uam_pwd, ibuf, ibuflen, rbuf, rbuflen));
554}
555
556static int uam_setup(const char *path)
557{
558  if (uam_register(UAM_SERVER_LOGIN_EXT, path, "Randnum exchange",
559		   randnum_login, randnum_logincont, NULL, randnum_login_ext) < 0)
560    return -1;
561
562  if (uam_register(UAM_SERVER_LOGIN_EXT, path, "2-Way Randnum exchange",
563		   randnum_login, rand2num_logincont, NULL, randnum_login_ext) < 0) {
564    uam_unregister(UAM_SERVER_LOGIN, "Randnum exchange");
565    return -1;
566  }
567
568  if (uam_register(UAM_SERVER_CHANGEPW, path, "Randnum Exchange",
569		   randnum_changepw) < 0) {
570    uam_unregister(UAM_SERVER_LOGIN, "Randnum exchange");
571    uam_unregister(UAM_SERVER_LOGIN, "2-Way Randnum exchange");
572    return -1;
573  }
574  /*uam_register(UAM_SERVER_PRINTAUTH, path, "Randnum Exchange",
575    pam_printer);*/
576
577  return 0;
578}
579
580static void uam_cleanup(void)
581{
582  uam_unregister(UAM_SERVER_LOGIN, "Randnum exchange");
583  uam_unregister(UAM_SERVER_LOGIN, "2-Way Randnum exchange");
584  uam_unregister(UAM_SERVER_CHANGEPW, "Randnum Exchange");
585  /*uam_unregister(UAM_SERVER_PRINTAUTH, "Randnum Exchange");*/
586}
587
588UAM_MODULE_EXPORT struct uam_export uams_randnum = {
589  UAM_MODULE_SERVER,
590  UAM_MODULE_VERSION,
591  uam_setup, uam_cleanup
592};
593