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