]> arthur.barton.de Git - netatalk.git/blob - etc/uams/uams_dhx2_passwd.c
Merge master
[netatalk.git] / etc / uams / uams_dhx2_passwd.c
1 /*
2  * $Id: uams_dhx2_passwd.c,v 1.8 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 #ifdef UAM_DHX2
14
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <errno.h>
19 #include <pwd.h>
20 #include <arpa/inet.h>
21 #include <unistd.h>
22
23 #ifdef HAVE_CRYPT_H
24 #include <crypt.h>
25 #endif
26
27 #include <sys/time.h>
28 #include <time.h>
29
30 #ifdef SHADOWPW
31 #include <shadow.h>
32 #endif
33
34 #ifdef HAVE_LIBGCRYPT
35 #include <gcrypt.h>
36 #endif
37
38 #include <atalk/afp.h>
39 #include <atalk/uam.h>
40 #include <atalk/logger.h>
41
42 /* Number of bits for p which we generate. Everybode out there uses 512, so we beet them */
43 #define PRIMEBITS 1024
44
45 /* hash a number to a 16-bit quantity */
46 #define dhxhash(a) ((((unsigned long) (a) >> 8) ^   \
47                      (unsigned long) (a)) & 0xffff)
48
49 /* Some parameters need be maintained across calls */
50 static gcry_mpi_t p, Ra;
51 static gcry_mpi_t serverNonce;
52 static char *K_MD5hash = NULL;
53 static int K_hash_len;
54 static uint16_t ID;
55
56 /* The initialization vectors for CAST128 are fixed by Apple. */
57 static unsigned char dhx_c2siv[] = { 'L', 'W', 'a', 'l', 'l', 'a', 'c', 'e' };
58 static unsigned char dhx_s2civ[] = { 'C', 'J', 'a', 'l', 'b', 'e', 'r', 't' };
59
60 /* Static variables used to communicate between the conversation function
61  * and the server_login function */
62 static struct passwd *dhxpwd;
63
64 /*********************************************************
65  * Crypto helper func to generate p and g for use in DH.
66  * libgcrypt doesn't provide one directly.
67  * Algorithm taken from GNUTLS:gnutls_dh_primes.c
68  *********************************************************/
69
70 /**
71  * This function will generate a new pair of prime and generator for use in
72  * the Diffie-Hellman key exchange.
73  * The bits value should be one of 768, 1024, 2048, 3072 or 4096.
74  **/
75
76 static int
77 dh_params_generate (gcry_mpi_t *ret_p, gcry_mpi_t *ret_g, unsigned int bits) {
78
79     int result, times = 0, qbits;
80
81     gcry_mpi_t g = NULL, prime = NULL;
82     gcry_mpi_t *factors = NULL;
83     gcry_error_t err;
84
85     /* Version check should be the very first call because it
86        makes sure that important subsystems are intialized. */
87     if (!gcry_check_version (GCRYPT_VERSION)) {
88         LOG(log_info, logtype_uams, "PAM DHX2: libgcrypt versions mismatch. Need: %s", GCRYPT_VERSION);
89         result = AFPERR_MISC;
90         goto error;
91     }
92
93     if (bits < 256)
94         qbits = bits / 2;
95     else
96         qbits = (bits / 40) + 105;
97
98     if (qbits & 1) /* better have an even number */
99         qbits++;
100
101     /* find a prime number of size bits. */
102     do {
103         if (times) {
104             gcry_mpi_release (prime);
105             gcry_prime_release_factors (factors);
106         }
107         err = gcry_prime_generate (&prime, bits, qbits, &factors, NULL, NULL,
108                                    GCRY_STRONG_RANDOM, GCRY_PRIME_FLAG_SPECIAL_FACTOR);
109         if (err != 0) {
110             result = AFPERR_MISC;
111             goto error;
112         }
113         err = gcry_prime_check (prime, 0);
114         times++;
115     } while (err != 0 && times < 10);
116
117     if (err != 0) {
118         result = AFPERR_MISC;
119         goto error;
120     }
121
122     /* generate the group generator. */
123     err = gcry_prime_group_generator (&g, prime, factors, NULL);
124     if (err != 0) {
125         result = AFPERR_MISC;
126         goto error;
127     }
128
129     gcry_prime_release_factors (factors);
130     factors = NULL;
131
132     if (ret_g)
133         *ret_g = g;
134     else
135         gcry_mpi_release (g);
136     if (ret_p)
137         *ret_p = prime;
138     else
139         gcry_mpi_release (prime);
140
141     return 0;
142
143 error:
144     gcry_prime_release_factors (factors);
145     gcry_mpi_release (g);
146     gcry_mpi_release (prime);
147
148     return result;
149 }
150
151 static int dhx2_setup(void *obj, char *ibuf _U_, size_t ibuflen _U_,
152                       char *rbuf, size_t *rbuflen)
153 {
154     int ret;
155     size_t nwritten;
156     gcry_mpi_t g, Ma;
157     char *Ra_binary = NULL;
158 #ifdef SHADOWPW
159     struct spwd *sp;
160 #endif /* SHADOWPW */
161
162     *rbuflen = 0;
163
164     /* Initialize passwd/shadow */
165 #ifdef SHADOWPW
166     if (( sp = getspnam( dhxpwd->pw_name )) == NULL ) {
167         LOG(log_info, logtype_uams, "DHX2: no shadow passwd entry for this user");
168         return AFPERR_NOTAUTH;
169     }
170     dhxpwd->pw_passwd = sp->sp_pwdp;
171 #endif /* SHADOWPW */
172
173     if (!dhxpwd->pw_passwd)
174         return AFPERR_NOTAUTH;
175
176     /* Initialize DH params */
177
178     p = gcry_mpi_new(0);
179     g = gcry_mpi_new(0);
180     Ra = gcry_mpi_new(0);
181     Ma = gcry_mpi_new(0);
182
183     /* Generate p and g for DH */
184     ret = dh_params_generate( &p, &g, PRIMEBITS);
185     if (ret != 0) {
186         LOG(log_info, logtype_uams, "DHX2: Couldn't generate p and g");
187         ret = AFPERR_MISC;
188         goto error;
189     }
190
191     /* Generate our random number Ra. */
192     Ra_binary = calloc(1, PRIMEBITS/8);
193     if (Ra_binary == NULL) {
194         ret = AFPERR_MISC;
195         goto error;
196     }
197     gcry_randomize(Ra_binary, PRIMEBITS/8, GCRY_STRONG_RANDOM);
198     gcry_mpi_scan(&Ra, GCRYMPI_FMT_USG, Ra_binary, PRIMEBITS/8, NULL);
199     free(Ra_binary);
200     Ra_binary = NULL;
201
202     /* Ma = g^Ra mod p. This is our "public" key */
203     gcry_mpi_powm(Ma, g, Ra, p);
204
205     /* ------- DH Init done ------ */
206     /* Start building reply packet */
207
208     /* Session ID first */
209     ID = dhxhash(obj);
210     *(uint16_t *)rbuf = htons(ID);
211     rbuf += 2;
212     *rbuflen += 2;
213
214     /* g is next */
215     gcry_mpi_print( GCRYMPI_FMT_USG, (unsigned char *)rbuf, 4, &nwritten, g);
216     if (nwritten < 4) {
217         memmove( rbuf+4-nwritten, rbuf, nwritten);
218         memset( rbuf, 0, 4-nwritten);
219     }
220     rbuf += 4;
221     *rbuflen += 4;
222
223     /* len = length of p = PRIMEBITS/8 */
224     *(uint16_t *)rbuf = htons((uint16_t) PRIMEBITS/8);
225     rbuf += 2;
226     *rbuflen += 2;
227
228     /* p */
229     gcry_mpi_print( GCRYMPI_FMT_USG, (unsigned char *)rbuf, PRIMEBITS/8, NULL, p);
230     rbuf += PRIMEBITS/8;
231     *rbuflen += PRIMEBITS/8;
232
233     /* Ma */
234     gcry_mpi_print( GCRYMPI_FMT_USG, (unsigned char *)rbuf, PRIMEBITS/8, &nwritten, Ma);
235     if (nwritten < PRIMEBITS/8) {
236         memmove(rbuf + (PRIMEBITS/8) - nwritten, rbuf, nwritten);
237         memset(rbuf, 0, (PRIMEBITS/8) - nwritten);
238     }
239     rbuf += PRIMEBITS/8;
240     *rbuflen += PRIMEBITS/8;
241
242     ret = AFPERR_AUTHCONT;
243
244 error:              /* We exit here anyway */
245     /* We will only need p and Ra later, but mustn't forget to release it ! */
246     gcry_mpi_release(g);
247     gcry_mpi_release(Ma);
248     return ret;
249 }
250
251 /* -------------------------------- */
252 static int login(void *obj, char *username, int ulen,  struct passwd **uam_pwd _U_,
253                  char *ibuf, size_t ibuflen,
254                  char *rbuf, size_t *rbuflen)
255 {
256     if (( dhxpwd = uam_getname(obj, username, ulen)) == NULL ) {
257         LOG(log_info, logtype_uams, "DHX2: unknown username");
258         return AFPERR_NOTAUTH;
259     }
260
261     LOG(log_info, logtype_uams, "DHX2 login: %s", username);
262     return dhx2_setup(obj, ibuf, ibuflen, rbuf, rbuflen);
263 }
264
265 /* -------------------------------- */
266 /* dhx login: things are done in a slightly bizarre order to avoid
267  * having to clean things up if there's an error. */
268 static int passwd_login(void *obj, struct passwd **uam_pwd,
269                         char *ibuf, size_t ibuflen,
270                         char *rbuf, size_t *rbuflen)
271 {
272     char *username;
273     size_t len, ulen;
274
275     *rbuflen = 0;
276
277     /* grab some of the options */
278     if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &ulen) < 0) {
279         LOG(log_info, logtype_uams, "DHX2: uam_afpserver_option didn't meet uam_option_username  -- %s",
280             strerror(errno));
281         return AFPERR_PARAM;
282     }
283
284     len = (unsigned char) *ibuf++;
285     if ( len > ulen ) {
286         LOG(log_info, logtype_uams, "DHX2: Signature Retieval Failure -- %s",
287             strerror(errno));
288         return AFPERR_PARAM;
289     }
290
291     memcpy(username, ibuf, len );
292     ibuf += len;
293     username[ len ] = '\0';
294
295     if ((unsigned long) ibuf & 1) /* pad to even boundary */
296         ++ibuf;
297
298     return (login(obj, username, ulen, uam_pwd, ibuf, ibuflen, rbuf, rbuflen));
299 }
300
301 /* ----------------------------- */
302 static int passwd_login_ext(void *obj, char *uname, struct passwd **uam_pwd,
303                             char *ibuf, size_t ibuflen,
304                             char *rbuf, size_t *rbuflen)
305 {
306     char *username;
307     size_t len, ulen;
308     uint16_t  temp16;
309
310     *rbuflen = 0;
311
312     /* grab some of the options */
313     if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &ulen) < 0) {
314         LOG(log_info, logtype_uams, "DHX2: uam_afpserver_option didn't meet uam_option_username  -- %s",
315             strerror(errno));
316         return AFPERR_PARAM;
317     }
318
319     if (*uname != 3)
320         return AFPERR_PARAM;
321     uname++;
322     memcpy(&temp16, uname, sizeof(temp16));
323     len = ntohs(temp16);
324
325     if ( !len || len > ulen ) {
326         LOG(log_info, logtype_uams, "DHX2: Signature Retrieval Failure -- %s",
327             strerror(errno));
328         return AFPERR_PARAM;
329     }
330     memcpy(username, uname +2, len );
331     username[ len ] = '\0';
332
333     return (login(obj, username, ulen, uam_pwd, ibuf, ibuflen, rbuf, rbuflen));
334 }
335
336 /* -------------------------------- */
337
338 static int logincont1(void *obj _U_, struct passwd **uam_pwd _U_,
339                       char *ibuf, size_t ibuflen,
340                       char *rbuf, size_t *rbuflen)
341 {
342     size_t nwritten;
343     int ret;
344     gcry_mpi_t Mb, K, clientNonce;
345     unsigned char *K_bin = NULL;
346     char serverNonce_bin[16];
347     gcry_cipher_hd_t ctx;
348     gcry_error_t ctxerror;
349
350     *rbuflen = 0;
351
352     Mb = gcry_mpi_new(0);
353     K = gcry_mpi_new(0);
354     clientNonce = gcry_mpi_new(0);
355     serverNonce = gcry_mpi_new(0);
356
357     /* Packet size should be: Session ID + Ma + Encrypted client nonce */
358     if (ibuflen != 2 + PRIMEBITS/8 + 16) {
359         LOG(log_error, logtype_uams, "DHX2: Paket length not correct");
360         ret = AFPERR_PARAM;
361         goto error_noctx;
362     }
363
364     /* Skip session id */
365     ibuf += 2;
366
367     /* Extract Mb, client's "public" key */
368     gcry_mpi_scan(&Mb, GCRYMPI_FMT_USG, ibuf, PRIMEBITS/8, NULL);
369     ibuf += PRIMEBITS/8;
370
371     /* Now finally generate the Key: K = Mb^Ra mod p */
372     gcry_mpi_powm(K, Mb, Ra, p);
373
374     /* We need K in binary form in order to ... */
375     K_bin = calloc(1, PRIMEBITS/8);
376     if (K_bin == NULL) {
377         ret = AFPERR_MISC;
378         goto error_noctx;
379     }
380     gcry_mpi_print(GCRYMPI_FMT_USG, K_bin, PRIMEBITS/8, &nwritten, K);
381     if (nwritten < PRIMEBITS/8) {
382         memmove(K_bin + PRIMEBITS/8 - nwritten, K_bin, nwritten);
383         memset(K_bin, 0, PRIMEBITS/8 - nwritten);
384     }
385
386     /* ... generate the MD5 hash of K. K_MD5hash is what we actually use ! */
387     K_MD5hash = calloc(1, K_hash_len = gcry_md_get_algo_dlen(GCRY_MD_MD5));
388     if (K_MD5hash == NULL) {
389         ret = AFPERR_MISC;
390         free(K_bin);
391         K_bin = NULL;
392         goto error_noctx;
393     }
394     gcry_md_hash_buffer(GCRY_MD_MD5, K_MD5hash, K_bin, PRIMEBITS/8);
395     free(K_bin);
396     K_bin = NULL;
397
398     /* FIXME: To support the Reconnect UAM, we need to store this key somewhere */
399
400     /* Set up our encryption context. */
401     ctxerror = gcry_cipher_open( &ctx, GCRY_CIPHER_CAST5, GCRY_CIPHER_MODE_CBC, 0);
402     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
403         ret = AFPERR_MISC;
404         goto error_ctx;
405     }
406     /* Set key */
407     ctxerror = gcry_cipher_setkey(ctx, K_MD5hash, K_hash_len);
408     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
409         ret = AFPERR_MISC;
410         goto error_ctx;
411     }
412     /* Set the initialization vector for client->server transfer. */
413     ctxerror = gcry_cipher_setiv(ctx, dhx_c2siv, sizeof(dhx_c2siv));
414     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
415         ret = AFPERR_MISC;
416         goto error_ctx;
417     }
418     /* Finally: decrypt client's md5_K(client nonce, C2SIV) inplace */
419     ctxerror = gcry_cipher_decrypt(ctx, ibuf, 16, NULL, 0);
420     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
421         ret = AFPERR_MISC;
422         goto error_ctx;
423     }
424     /* Pull out clients nonce */
425     gcry_mpi_scan(&clientNonce, GCRYMPI_FMT_USG, ibuf, 16, NULL);
426     /* Increment nonce */
427     gcry_mpi_add_ui(clientNonce, clientNonce, 1);
428
429     /* Generate our nonce and remember it for Logincont2 */
430     gcry_create_nonce(serverNonce_bin, 16); /* We'll use this here */
431     gcry_mpi_scan(&serverNonce, GCRYMPI_FMT_USG, serverNonce_bin, 16, NULL); /* For use in Logincont2 */
432
433     /* ---- Start building reply packet ---- */
434
435     /* Session ID + 1 first */
436     *(uint16_t *)rbuf = htons(ID+1);
437     rbuf += 2;
438     *rbuflen += 2;
439
440     /* Client nonce + 1 */
441     gcry_mpi_print(GCRYMPI_FMT_USG, (unsigned char *)rbuf, PRIMEBITS/8, NULL, clientNonce);
442     /* Server nonce */
443     memcpy(rbuf+16, serverNonce_bin, 16);
444
445     /* Set the initialization vector for server->client transfer. */
446     ctxerror = gcry_cipher_setiv(ctx, dhx_s2civ, sizeof(dhx_s2civ));
447     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
448         ret = AFPERR_MISC;
449         goto error_ctx;
450     }
451     /* Encrypt md5_K(clientNonce+1, serverNonce) inplace */
452     ctxerror = gcry_cipher_encrypt(ctx, rbuf, 32, NULL, 0);
453     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
454         ret = AFPERR_MISC;
455         goto error_ctx;
456     }
457     rbuf += 32;
458     *rbuflen += 32;
459
460     ret = AFPERR_AUTHCONT;
461     goto exit;
462
463 error_ctx:
464     gcry_cipher_close(ctx);
465 error_noctx:
466     gcry_mpi_release(serverNonce);
467     free(K_MD5hash);
468     K_MD5hash=NULL;
469 exit:
470     gcry_mpi_release(K);
471     gcry_mpi_release(Mb);
472     gcry_mpi_release(Ra);
473     gcry_mpi_release(p);
474     gcry_mpi_release(clientNonce);
475     return ret;
476 }
477
478 static int logincont2(void *obj _U_, struct passwd **uam_pwd,
479                       char *ibuf, size_t ibuflen,
480                       char *rbuf _U_, size_t *rbuflen)
481 {
482 #ifdef SHADOWPW
483     struct spwd *sp;
484 #endif /* SHADOWPW */
485     int ret;
486     char *p;
487     gcry_mpi_t retServerNonce;
488     gcry_cipher_hd_t ctx;
489     gcry_error_t ctxerror;
490
491     *rbuflen = 0;
492     retServerNonce = gcry_mpi_new(0);
493
494     /* Packet size should be: Session ID + ServerNonce + Passwd buffer (evantually +10 extra bytes, see Apples Docs)*/
495     if ((ibuflen != 2 + 16 + 256) && (ibuflen != 2 + 16 + 256 + 10)) {
496         LOG(log_error, logtype_uams, "DHX2: Paket length not correct: %d. Should be 274 or 284.", ibuflen);
497         ret = AFPERR_PARAM;
498         goto error_noctx;
499     }
500
501     /* Set up our encryption context. */
502     ctxerror = gcry_cipher_open( &ctx, GCRY_CIPHER_CAST5, GCRY_CIPHER_MODE_CBC, 0);
503     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
504         ret = AFPERR_MISC;
505         goto error_ctx;
506     }
507     /* Set key */
508     ctxerror = gcry_cipher_setkey(ctx, K_MD5hash, K_hash_len);
509     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
510         ret = AFPERR_MISC;
511         goto error_ctx;
512     }
513     /* Set the initialization vector for client->server transfer. */
514     ctxerror = gcry_cipher_setiv(ctx, dhx_c2siv, sizeof(dhx_c2siv));
515     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
516         ret = AFPERR_MISC;
517         goto error_ctx;
518     }
519
520     /* Skip Session ID */
521     ibuf += 2;
522
523     /* Finally: decrypt client's md5_K(serverNonce+1, passwor, C2SIV) inplace */
524     ctxerror = gcry_cipher_decrypt(ctx, ibuf, 16+256, NULL, 0);
525     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
526         ret = AFPERR_MISC;
527         goto error_ctx;
528     }
529     /* Pull out nonce. Should be serverNonce+1 */
530     gcry_mpi_scan(&retServerNonce, GCRYMPI_FMT_USG, ibuf, 16, NULL);
531     gcry_mpi_sub_ui(retServerNonce, retServerNonce, 1);
532     if ( gcry_mpi_cmp( serverNonce, retServerNonce) != 0) {
533         /* We're hacked!  */
534         ret = AFPERR_NOTAUTH;
535         goto error_ctx;
536     }
537     ibuf += 16;         /* ibuf now point to passwd in cleartext */
538
539     /* ---- Start authentication --- */
540     ret = AFPERR_NOTAUTH;
541
542     p = crypt( ibuf, dhxpwd->pw_passwd );
543     memset(ibuf, 0, 255);
544     if ( strcmp( p, dhxpwd->pw_passwd ) == 0 ) {
545         *uam_pwd = dhxpwd;
546         ret = AFP_OK;
547     }
548
549 #ifdef SHADOWPW
550     if (( sp = getspnam( dhxpwd->pw_name )) == NULL ) {
551         LOG(log_info, logtype_uams, "no shadow passwd entry for %s", dhxpwd->pw_name);
552         ret = AFPERR_NOTAUTH;
553         goto exit;
554     }
555
556     /* check for expired password */
557     if (sp && sp->sp_max != -1 && sp->sp_lstchg) {
558         time_t now = time(NULL) / (60*60*24);
559         int32_t expire_days = sp->sp_lstchg - now + sp->sp_max;
560         if ( expire_days < 0 ) {
561             LOG(log_info, logtype_uams, "password for user %s expired", dhxpwd->pw_name);
562             ret = AFPERR_PWDEXPR;
563             goto exit;
564         }
565     }
566 #endif /* SHADOWPW */
567
568 error_ctx:
569     gcry_cipher_close(ctx);
570 error_noctx:
571 exit:
572     free(K_MD5hash);
573     K_MD5hash=NULL;
574     gcry_mpi_release(serverNonce);
575     gcry_mpi_release(retServerNonce);
576     return ret;
577 }
578
579 static int passwd_logincont(void *obj, struct passwd **uam_pwd,
580                             char *ibuf, size_t ibuflen,
581                             char *rbuf, size_t *rbuflen)
582 {
583     uint16_t retID;
584     int ret;
585
586     /* check for session id */
587     retID = ntohs(*(uint16_t *)ibuf);
588     if (retID == ID)
589         ret = logincont1(obj, uam_pwd, ibuf, ibuflen, rbuf, rbuflen);
590     else if (retID == ID+1)
591         ret = logincont2(obj, uam_pwd, ibuf,ibuflen, rbuf, rbuflen);
592     else {
593         LOG(log_info, logtype_uams, "DHX2: Session ID Mismatch");
594         ret = AFPERR_PARAM;
595     }
596     return ret;
597 }
598
599 static int uam_setup(const char *path)
600 {
601     if (uam_register(UAM_SERVER_LOGIN_EXT, path, "DHX2", passwd_login,
602                      passwd_logincont, NULL, passwd_login_ext) < 0)
603         return -1;
604     return 0;
605 }
606
607 static void uam_cleanup(void)
608 {
609     uam_unregister(UAM_SERVER_LOGIN, "DHX2");
610 }
611
612
613 UAM_MODULE_EXPORT struct uam_export uams_dhx2 = {
614     UAM_MODULE_SERVER,
615     UAM_MODULE_VERSION,
616     uam_setup, uam_cleanup
617 };
618
619
620 UAM_MODULE_EXPORT struct uam_export uams_dhx2_passwd = {
621     UAM_MODULE_SERVER,
622     UAM_MODULE_VERSION,
623     uam_setup, uam_cleanup
624 };
625
626 #endif /* UAM_DHX2 */
627