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