]> arthur.barton.de Git - netatalk.git/blob - etc/uams/uams_dhx2_pam.c
libgcrypt and DHX2 uam from Frank Lahm
[netatalk.git] / etc / uams / uams_dhx2_pam.c
1 /*
2  * $Id: uams_dhx2_pam.c,v 1.1 2008-11-22 12:07:26 didg 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 #if defined (USE_PAM) && defined (UAM_DHX2)
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <atalk/logger.h>
18
19 #ifdef HAVE_UNISTD_H
20 #include <unistd.h>
21 #endif /* HAVE_UNISTD_H */
22 #include <errno.h>
23 #ifdef HAVE_SECURITY_PAM_APPL_H
24 #include <security/pam_appl.h>
25 #endif
26 #ifdef HAVE_PAM_PAM_APPL_H
27 #include <pam/pam_appl.h>
28 #endif
29
30
31 #ifdef HAVE_LIBGCRYPT
32 #include <gcrypt.h>
33 #endif /* HAVE_LIBGCRYPT */
34
35 #include <atalk/afp.h>
36 #include <atalk/uam.h>
37 #include "../afpd/globals.h"
38
39 /* Number of bits for p which we generate. Everybode out there uses 512, so we beet them */
40 #define PRIMEBITS 1024
41
42 /* hash a number to a 16-bit quantity */
43 #define dhxhash(a) ((((unsigned long) (a) >> 8) ^ \
44                      (unsigned long) (a)) & 0xffff)
45
46 /* Some parameters need be maintained across calls */
47 static gcry_mpi_t p, Ra;
48 static gcry_mpi_t serverNonce;
49 static char *K_MD5hash = NULL;
50 static int K_hash_len;
51 static u_int16_t ID;
52
53 /* The initialization vectors for CAST128 are fixed by Apple. */
54 static unsigned char dhx_c2siv[] = { 'L', 'W', 'a', 'l', 'l', 'a', 'c', 'e' };
55 static unsigned char dhx_s2civ[] = { 'C', 'J', 'a', 'l', 'b', 'e', 'r', 't' };
56
57 /* Static variables used to communicate between the conversation function
58  * and the server_login function */
59 static pam_handle_t *pamh = NULL;
60 static char *PAM_username;
61 static char *PAM_password;
62 static struct passwd *dhxpwd;
63
64 /*********************************************************
65  * Crypto helper func to generate p and g for use in DH.
66  * libgcrpyt 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: %u", 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
152 /* PAM conversation function
153  * Here we assume (for now, at least) that echo on means login name, and
154  * echo off means password.
155  */
156 static int PAM_conv (int num_msg,
157                      const struct pam_message **msg,
158                      struct pam_response **resp,
159                      void *appdata_ptr _U_) {
160     int count = 0;
161     struct pam_response *reply;
162
163 #define COPY_STRING(s) (s) ? strdup(s) : NULL
164
165     errno = 0;
166
167     if (num_msg < 1) {
168         /* Log Entry */
169         LOG(log_info, logtype_uams, "PAM DHX2 Conversation Err -- %s",
170             strerror(errno));
171         /* Log Entry */
172         return PAM_CONV_ERR;
173     }
174
175     reply = (struct pam_response *)
176         calloc(num_msg, sizeof(struct pam_response));
177
178     if (!reply) {
179         /* Log Entry */
180         LOG(log_info, logtype_uams, "PAM DHX2: Conversation Err -- %s",
181             strerror(errno));
182         /* Log Entry */
183         return PAM_CONV_ERR;
184     }
185
186     for (count = 0; count < num_msg; count++) {
187         char *string = NULL;
188
189         switch (msg[count]->msg_style) {
190         case PAM_PROMPT_ECHO_ON:
191             if (!(string = COPY_STRING(PAM_username))) {
192                 /* Log Entry */
193                 LOG(log_info, logtype_uams, "PAM DHX2: username failure -- %s",
194                     strerror(errno));
195                 /* Log Entry */
196                 goto pam_fail_conv;
197             }
198             break;
199         case PAM_PROMPT_ECHO_OFF:
200             if (!(string = COPY_STRING(PAM_password))) {
201                 /* Log Entry */
202                 LOG(log_info, logtype_uams, "PAM DHX2: passwd failure: --: %s",
203                     strerror(errno));
204                 /* Log Entry */
205                 goto pam_fail_conv;
206             }
207             break;
208         case PAM_TEXT_INFO:
209 #ifdef PAM_BINARY_PROMPT
210         case PAM_BINARY_PROMPT:
211 #endif /* PAM_BINARY_PROMPT */
212             /* ignore it... */
213             break;
214         case PAM_ERROR_MSG:
215         default:
216             LOG(log_info, logtype_uams, "PAM DHX2: Binary_Prompt -- %s", strerror(errno));
217             goto pam_fail_conv;
218         }
219
220         if (string) {
221             reply[count].resp_retcode = 0;
222             reply[count].resp = string;
223             string = NULL;
224         }
225     }
226
227     *resp = reply;
228     LOG(log_info, logtype_uams, "PAM DHX2: PAM Success");
229     return PAM_SUCCESS;
230
231 pam_fail_conv:
232     for (count = 0; count < num_msg; count++) {
233         if (!reply[count].resp)
234             continue;
235         switch (msg[count]->msg_style) {
236         case PAM_PROMPT_ECHO_OFF:
237         case PAM_PROMPT_ECHO_ON:
238             free(reply[count].resp);
239             break;
240         }
241     }
242     free(reply);
243     /* Log Entry */
244     LOG(log_info, logtype_uams, "PAM DHX2: Conversation Err -- %s",
245         strerror(errno));
246     /* Log Entry */
247     return PAM_CONV_ERR;
248 }
249
250 static struct pam_conv PAM_conversation = {
251     &PAM_conv,
252     NULL
253 };
254
255
256 static int dhx2_setup(void *obj, char *ibuf, int ibuflen _U_,
257                       char *rbuf, int *rbuflen)
258 {
259     int i, ret;
260
261     unsigned int g_uint;
262     gcry_mpi_t g, Ma;
263     char *Ra_binary = NULL;
264     gcry_cipher_hd_t ctx;
265     gcry_error_t ctxerror;
266
267     const int g_len = 4;
268     size_t len;
269     size_t nwritten;
270
271     *rbuflen = 0;
272
273     p = gcry_mpi_new(0);
274     g = gcry_mpi_new(0);
275     Ra = gcry_mpi_new(0);
276     Ma = gcry_mpi_new(0);
277
278     /* Generate p and g for DH */
279     ret = dh_params_generate( &p, &g, PRIMEBITS);
280     if (ret != 0) {
281         LOG(log_info, logtype_uams, "DHX2: Couldn't generate p and g");
282         ret = AFPERR_MISC;
283         goto error;
284     }
285
286     /* Generate our random number Ra. */
287     Ra_binary = calloc(1, PRIMEBITS/8);
288     if (Ra_binary == NULL) {
289         ret = AFPERR_MISC;
290         goto error;
291     }
292     gcry_randomize(Ra_binary, PRIMEBITS/8, GCRY_STRONG_RANDOM);
293     gcry_mpi_scan(&Ra, GCRYMPI_FMT_USG, Ra_binary, PRIMEBITS/8, NULL);
294     free(Ra_binary);
295     Ra_binary = NULL;
296
297     /* Ma = g^Ra mod p. This is our "public" key */
298     gcry_mpi_powm(Ma, g, Ra, p);
299
300     /* ------- DH Init done ------ */
301     /* Start building reply packet */
302
303     /* Session ID first */
304     ID = dhxhash(obj);
305     *(u_int16_t *)rbuf = htons(ID);
306     rbuf += 2;
307     *rbuflen += 2;
308
309     /* g is next */
310     gcry_mpi_print( GCRYMPI_FMT_USG, rbuf, 4, &nwritten, g);
311     if (nwritten < 4) {
312         memmove( rbuf+4-nwritten, rbuf, nwritten);
313         memset( rbuf, 0, 4-nwritten);
314     }
315     rbuf += 4;
316     *rbuflen += 4;
317
318     /* len = length of p = PRIMEBITS/8 */
319     *(u_int16_t *)rbuf = htons((u_int16_t) PRIMEBITS/8);
320     rbuf += 2;
321     *rbuflen += 2;
322
323     /* p */
324     gcry_mpi_print( GCRYMPI_FMT_USG, rbuf, PRIMEBITS/8, NULL, p);
325     rbuf += PRIMEBITS/8;
326     *rbuflen += PRIMEBITS/8;
327
328     /* Ma */
329     gcry_mpi_print( GCRYMPI_FMT_USG, rbuf, PRIMEBITS/8, &len, Ma);
330     if (len < PRIMEBITS/8) {
331         memmove(rbuf + (PRIMEBITS/8) - len, rbuf, len);
332         memset(rbuf, 0, (PRIMEBITS/8) - len);
333     }
334     rbuf += PRIMEBITS/8;
335     *rbuflen += PRIMEBITS/8;
336
337     ret = AFPERR_AUTHCONT;
338
339 error:                          /* We exit here anyway */
340     /* We will only need p and Ra later, but mustn't forget to release it ! */
341     gcry_mpi_release(g);
342     gcry_mpi_release(Ma);
343     return ret;
344 }
345
346 /* -------------------------------- */
347 static int login(void *obj, char *username, int ulen,  struct passwd **uam_pwd _U_,
348                  char *ibuf, int ibuflen,
349                  char *rbuf, int *rbuflen)
350 {
351     if (( dhxpwd = uam_getname(obj, username, ulen)) == NULL ) {
352         LOG(log_info, logtype_uams, "DHX2: unknown username");
353         return AFPERR_PARAM;
354     }
355
356     PAM_username = username;
357     LOG(log_info, logtype_uams, "DHX2 login: %s", username);
358     return dhx2_setup(obj, ibuf, ibuflen, rbuf, rbuflen);
359 }
360
361 /* -------------------------------- */
362 /* dhx login: things are done in a slightly bizarre order to avoid
363  * having to clean things up if there's an error. */
364 static int pam_login(void *obj, struct passwd **uam_pwd,
365                      char *ibuf, int ibuflen,
366                      char *rbuf, int *rbuflen)
367 {
368     char *username;
369     int len, ulen;
370
371     *rbuflen = 0;
372
373     /* grab some of the options */
374     if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &ulen) < 0) {
375         LOG(log_info, logtype_uams, "DHX2: uam_afpserver_option didn't meet uam_option_username  -- %s",
376             strerror(errno));
377         return AFPERR_PARAM;
378     }
379
380     len = (unsigned char) *ibuf++;
381     if ( len > ulen ) {
382         LOG(log_info, logtype_uams, "DHX2: Signature Retieval Failure -- %s",
383             strerror(errno));
384         return AFPERR_PARAM;
385     }
386
387     memcpy(username, ibuf, len );
388     ibuf += len;
389     username[ len ] = '\0';
390
391     if ((unsigned long) ibuf & 1) /* pad to even boundary */
392         ++ibuf;
393
394     return (login(obj, username, ulen, uam_pwd, ibuf, ibuflen, rbuf, rbuflen));
395 }
396
397 /* ----------------------------- */
398 static int pam_login_ext(void *obj, char *uname, struct passwd **uam_pwd,
399                          char *ibuf, int ibuflen,
400                          char *rbuf, int *rbuflen)
401 {
402     char *username;
403     int len, ulen;
404     u_int16_t  temp16;
405
406     *rbuflen = 0;
407
408     /* grab some of the options */
409     if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &ulen) < 0) {
410         LOG(log_info, logtype_uams, "DHX2: uam_afpserver_option didn't meet uam_option_username  -- %s",
411             strerror(errno));
412         return AFPERR_PARAM;
413     }
414
415     if (*uname != 3)
416         return AFPERR_PARAM;
417     uname++;
418     memcpy(&temp16, uname, sizeof(temp16));
419     len = ntohs(temp16);
420
421     if ( !len || len > ulen ) {
422         LOG(log_info, logtype_uams, "DHX2: Signature Retrieval Failure -- %s",
423             strerror(errno));
424         return AFPERR_PARAM;
425     }
426     memcpy(username, uname +2, len );
427     username[ len ] = '\0';
428
429     return (login(obj, username, ulen, uam_pwd, ibuf, ibuflen, rbuf, rbuflen));
430 }
431
432 /* -------------------------------- */
433
434 static int logincont1(void *obj, char *ibuf, int ibuflen, char *rbuf, int *rbuflen)
435 {
436     u_int16_t retID;
437     size_t nwritten;
438     int ret;
439     *rbuflen = 0;
440
441     gcry_mpi_t Mb, K, clientNonce;
442     char *K_bin = NULL;
443     char serverNonce_bin[16];
444     gcry_cipher_hd_t ctx;
445     gcry_error_t ctxerror;
446
447     Mb = gcry_mpi_new(0);
448     K = gcry_mpi_new(0);
449     clientNonce = gcry_mpi_new(0);
450     serverNonce = gcry_mpi_new(0);
451
452     /* Packet size should be: Session ID + Ma + Encrypted client nonce */
453     if (ibuflen != 2 + PRIMEBITS/8 + 16) {
454         LOG(log_error, logtype_uams, "DHX2: Paket length not correct");
455         ret = AFPERR_PARAM;
456         goto error_noctx;
457     }
458
459     /* Skip session id */
460     ibuf += 2;
461
462     /* Extract Mb, client's "public" key */
463     gcry_mpi_scan(&Mb, GCRYMPI_FMT_USG, ibuf, PRIMEBITS/8, NULL);
464     ibuf += PRIMEBITS/8;
465
466     /* Now finally generate the Key: K = Mb^Ra mod p */
467     gcry_mpi_powm(K, Mb, Ra, p);
468
469     /* We need K in binary form in order to ... */
470     K_bin = calloc(1, PRIMEBITS/8);
471     if (K_bin == NULL) {
472         ret = AFPERR_MISC;
473         goto error_noctx;
474     }
475     gcry_mpi_print(GCRYMPI_FMT_USG, K_bin, PRIMEBITS/8, &nwritten, K);
476     if (nwritten < PRIMEBITS/8) {
477         memmove(K_bin + PRIMEBITS/8 - nwritten, K_bin, nwritten);
478         memset(K_bin, 0, PRIMEBITS/8 - nwritten);
479     }
480
481     /* ... generate the MD5 hash of K. K_MD5hash is what we actually use ! */
482     K_MD5hash = calloc(1, K_hash_len = gcry_md_get_algo_dlen(GCRY_MD_MD5));
483     if (K_MD5hash == NULL) {
484         ret = AFPERR_MISC;
485         goto error_noctx;
486     }
487     gcry_md_hash_buffer(GCRY_MD_MD5, K_MD5hash, K_bin, PRIMEBITS/8);
488     free(K_bin);
489     K_bin = NULL;
490
491     /* FIXME: To support the Reconnect UAM, we need to store this key somewhere */
492
493     /* Set up our encryption context. */
494     ctxerror = gcry_cipher_open( &ctx, GCRY_CIPHER_CAST5, GCRY_CIPHER_MODE_CBC, 0);
495     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
496         ret = AFPERR_MISC;
497         goto error_ctx;
498     }
499     /* Set key */
500     ctxerror = gcry_cipher_setkey(ctx, K_MD5hash, K_hash_len);
501     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
502         ret = AFPERR_MISC;
503         goto error_ctx;
504     }
505     /* Set the initialization vector for client->server transfer. */
506     ctxerror = gcry_cipher_setiv(ctx, dhx_c2siv, sizeof(dhx_c2siv));
507     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
508         ret = AFPERR_MISC;
509         goto error_ctx;
510     }
511     /* Finally: decrypt client's md5_K(client nonce, C2SIV) inplace */
512     ctxerror = gcry_cipher_decrypt(ctx, ibuf, 16, NULL, 0);
513     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
514         ret = AFPERR_MISC;
515         goto error_ctx;
516     }
517     /* Pull out clients nonce */
518     gcry_mpi_scan(&clientNonce, GCRYMPI_FMT_USG, ibuf, 16, NULL);
519     /* Increment nonce */
520     gcry_mpi_add_ui(clientNonce, clientNonce, 1);
521
522     /* Generate our nonce and remember it for Logincont2 */
523     gcry_create_nonce(serverNonce_bin, 16); /* We'll use this here */
524     gcry_mpi_scan(&serverNonce, GCRYMPI_FMT_USG, serverNonce_bin, 16, NULL); /* For use in Logincont2 */
525
526     /* ---- Start building reply packet ---- */
527
528     /* Session ID + 1 first */
529     *(u_int16_t *)rbuf = htons(ID+1);
530     rbuf += 2;
531     *rbuflen += 2;
532
533     /* Client nonce + 1 */
534     gcry_mpi_print(GCRYMPI_FMT_USG, rbuf, PRIMEBITS/8, NULL, clientNonce);
535     /* Server nonce */
536     memcpy(rbuf+16, serverNonce_bin, 16);
537
538     /* Set the initialization vector for server->client transfer. */
539     ctxerror = gcry_cipher_setiv(ctx, dhx_s2civ, sizeof(dhx_s2civ));
540     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
541         ret = AFPERR_MISC;
542         goto error_ctx;
543     }
544     /* Encrypt md5_K(clientNonce+1, serverNonce) inplace */
545     ctxerror = gcry_cipher_encrypt(ctx, rbuf, 32, NULL, 0);
546     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
547         ret = AFPERR_MISC;
548         goto error_ctx;
549     }
550     rbuf += 32;
551     *rbuflen += 32;
552
553     ret = AFPERR_AUTHCONT;
554     goto exit;
555
556 error_ctx:
557     gcry_cipher_close(ctx);
558 error_noctx:
559     gcry_mpi_release(serverNonce);
560     free(K_MD5hash);
561     K_MD5hash=NULL;
562 exit:
563     gcry_mpi_release(K);
564     gcry_mpi_release(Mb);
565     gcry_mpi_release(Ra);
566     gcry_mpi_release(p);
567     gcry_mpi_release(clientNonce);
568     return ret;
569 }
570
571 static int logincont2(void *obj, struct passwd **uam_pwd,
572                       char *ibuf, int ibuflen,
573                       char *rbuf, int *rbuflen)
574 {
575     int ret;
576     int PAM_error;
577     char *hostname = NULL;
578     gcry_mpi_t retServerNonce;
579     gcry_cipher_hd_t ctx;
580     gcry_error_t ctxerror;
581
582     *rbuflen = 0;
583
584     /* Packet size should be: Session ID + ServerNonce + Passwd buffer */
585     if (ibuflen != 2 + 16 + 256) {
586         LOG(log_error, logtype_uams, "DHX2: Paket length not correct");
587         ret = AFPERR_PARAM;
588         goto error_noctx;
589     }
590
591     retServerNonce = gcry_mpi_new(0);
592
593     /* For PAM */
594     uam_afpserver_option(obj, UAM_OPTION_CLIENTNAME, (void *) &hostname, NULL);
595
596     /* Set up our encryption context. */
597     ctxerror = gcry_cipher_open( &ctx, GCRY_CIPHER_CAST5, GCRY_CIPHER_MODE_CBC, 0);
598     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
599         ret = AFPERR_MISC;
600         goto error_ctx;
601     }
602     /* Set key */
603     ctxerror = gcry_cipher_setkey(ctx, K_MD5hash, K_hash_len);
604     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
605         ret = AFPERR_MISC;
606         goto error_ctx;
607     }
608     /* Set the initialization vector for client->server transfer. */
609     ctxerror = gcry_cipher_setiv(ctx, dhx_c2siv, sizeof(dhx_c2siv));
610     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
611         ret = AFPERR_MISC;
612         goto error_ctx;
613     }
614
615     /* Skip Session ID */
616     ibuf += 2;
617
618     /* Finally: decrypt client's md5_K(serverNonce+1, passwor, C2SIV) inplace */
619     ctxerror = gcry_cipher_decrypt(ctx, ibuf, 16+256, NULL, 0);
620     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
621         ret = AFPERR_MISC;
622         goto error_ctx;
623     }
624     /* Pull out nonce. Should be serverNonce+1 */
625     gcry_mpi_scan(&retServerNonce, GCRYMPI_FMT_USG, ibuf, 16, NULL);
626     gcry_mpi_sub_ui(retServerNonce, retServerNonce, 1);
627     if ( gcry_mpi_cmp( serverNonce, retServerNonce) != 0) {
628         /* We're hacked!  */
629         ret = AFPERR_NOTAUTH;
630         goto error_ctx;
631     }
632     ibuf += 16;
633
634     LOG(log_info, logtype_uams, "DHX2: logincont2 alive!");
635
636     /* ---- Start authentication with PAM --- */
637
638     /* Set these things up for the conv function */
639     PAM_password = ibuf;
640
641     ret = AFPERR_NOTAUTH;
642     PAM_error = pam_start("netatalk", PAM_username, &PAM_conversation, &pamh);
643     if (PAM_error != PAM_SUCCESS) {
644         LOG(log_info, logtype_uams, "DHX2: PAM_Error: %s",
645             pam_strerror(pamh,PAM_error));
646         goto error_ctx;
647     }
648
649     /* solaris craps out if PAM_TTY and PAM_RHOST aren't set. */
650     pam_set_item(pamh, PAM_TTY, "afpd");
651     pam_set_item(pamh, PAM_RHOST, hostname);
652     PAM_error = pam_authenticate(pamh, 0);
653     if (PAM_error != PAM_SUCCESS) {
654         if (PAM_error == PAM_MAXTRIES)
655             ret = AFPERR_PWDEXPR;
656         LOG(log_info, logtype_uams, "DHX2: PAM_Error: %s",
657             pam_strerror(pamh, PAM_error));
658         goto error_ctx;
659     }
660
661     PAM_error = pam_acct_mgmt(pamh, 0);
662     if (PAM_error != PAM_SUCCESS ) {
663         LOG(log_info, logtype_uams, "DHX2: PAM_Error: %s",
664             pam_strerror(pamh, PAM_error));
665         if (PAM_error == PAM_NEW_AUTHTOK_REQD)    /* password expired */
666             ret = AFPERR_PWDEXPR;
667 #ifdef PAM_AUTHTOKEN_REQD
668         else if (PAM_error == PAM_AUTHTOKEN_REQD)
669             ret = AFPERR_PWDCHNG;
670 #endif
671         else
672             goto error_ctx;
673     }
674
675 #ifndef PAM_CRED_ESTABLISH
676 #define PAM_CRED_ESTABLISH PAM_ESTABLISH_CRED
677 #endif
678     PAM_error = pam_setcred(pamh, PAM_CRED_ESTABLISH);
679     if (PAM_error != PAM_SUCCESS) {
680         LOG(log_info, logtype_uams, "DHX2: PAM_Error: %s",
681             pam_strerror(pamh, PAM_error));
682         goto error_ctx;
683     }
684
685     PAM_error = pam_open_session(pamh, 0);
686     if (PAM_error != PAM_SUCCESS) {
687         LOG(log_info, logtype_uams, "DHX2: PAM_Error: %s",
688             pam_strerror(pamh, PAM_error));
689         goto error_ctx;
690     }
691
692     memset(ibuf, 0, 256); /* zero out the password */
693     *uam_pwd = dhxpwd;
694     LOG(log_info, logtype_uams, "DHX2: PAM Auth OK!");
695     if ( ret == AFPERR_PWDEXPR)
696         return ret;
697     ret = AFP_OK;
698
699 error_ctx:
700     gcry_cipher_close(ctx);
701 error_noctx:
702     free(K_MD5hash);
703     K_MD5hash=NULL;
704     gcry_mpi_release(serverNonce);
705     gcry_mpi_release(retServerNonce);    
706     return ret;
707 }
708
709 static int pam_logincont(void *obj, struct passwd **uam_pwd,
710                          char *ibuf, int ibuflen,
711                          char *rbuf, int *rbuflen)
712 {
713     u_int16_t retID;
714     int ret;
715
716     /* check for session id */
717     retID = ntohs(*(u_int16_t *)ibuf);
718     if (retID == ID)
719         ret = logincont1(obj, ibuf, ibuflen, rbuf, rbuflen);
720     else if (retID == ID+1)
721         ret = logincont2(obj, uam_pwd, ibuf,ibuflen, rbuf, rbuflen);
722     else {
723         LOG(log_info, logtype_uams, "DHX2: Session ID Mismatch");
724         ret = AFPERR_PARAM;
725     }
726     return ret;
727 }
728
729
730 /* logout */
731 static void pam_logout() {
732     pam_close_session(pamh, 0);
733     pam_end(pamh, 0);
734     pamh = NULL;
735 }
736
737 /****************************
738  * --- Change pwd stuff --- */
739
740 static int changepw_1(void *obj, char *uname,
741                       char *ibuf, int ibuflen, char *rbuf, int *rbuflen)
742 {
743     unsigned int len;
744     *rbuflen = 0;
745
746     /* Remember it now, use it in changepw_3 */
747     PAM_username = uname;
748     LOG(log_error, logtype_uams, "DHX2 ChangePW: packet 1 processin for user: %s",PAM_username);
749
750     return( dhx2_setup(obj, ibuf, ibuflen, rbuf, rbuflen) );
751 }
752
753 static int changepw_2(void *obj, 
754                       char *ibuf, int ibuflen, char *rbuf, int *rbuflen)
755 {
756     LOG(log_error, logtype_uams, "DHX2 ChangePW: packet 2 processing");
757     return( logincont1(obj, ibuf, ibuflen, rbuf, rbuflen) );
758 }
759
760 static int changepw_3(void *obj _U_,
761                       char *ibuf, int ibuflen _U_, 
762                       char *rbuf _U_, int *rbuflen _U_)
763 {
764     int ret;
765     int PAM_error;
766     uid_t uid;
767     pam_handle_t *lpamh;
768     char *hostname = NULL;
769     gcry_mpi_t retServerNonce;
770     gcry_cipher_hd_t ctx;
771     gcry_error_t ctxerror;
772
773     *rbuflen = 0;
774
775     LOG(log_error, logtype_uams, "DHX2 ChangePW: packet 3 processing");
776
777     /* Packet size should be: Session ID + ServerNonce + 2*Passwd buffer */
778     if (ibuflen != 2 + 16 + 2*256) {
779         LOG(log_error, logtype_uams, "DHX2: Paket length not correct");
780         ret = AFPERR_PARAM;
781         goto error_noctx;
782     }
783
784     retServerNonce = gcry_mpi_new(0);
785
786     /* For PAM */
787     uam_afpserver_option(obj, UAM_OPTION_CLIENTNAME, (void *) &hostname, NULL);
788
789     /* Set up our encryption context. */
790     ctxerror = gcry_cipher_open( &ctx, GCRY_CIPHER_CAST5, GCRY_CIPHER_MODE_CBC, 0);
791     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
792         ret = AFPERR_MISC;
793         goto error_ctx;
794     }
795     /* Set key */
796     ctxerror = gcry_cipher_setkey(ctx, K_MD5hash, K_hash_len);
797     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
798         ret = AFPERR_MISC;
799         goto error_ctx;
800     }
801
802     /* Set the initialization vector for client->server transfer. */
803     ctxerror = gcry_cipher_setiv(ctx, dhx_c2siv, sizeof(dhx_c2siv));
804     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
805         ret = AFPERR_MISC;
806         goto error_ctx;
807     }
808
809     /* Skip Session ID */
810     ibuf += 2;
811
812     /* Finally: decrypt client's md5_K(serverNonce+1, 2*password, C2SIV) inplace */
813     ctxerror = gcry_cipher_decrypt(ctx, ibuf, 16+2*256, NULL, 0);
814     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
815         ret = AFPERR_MISC;
816         goto error_ctx;
817     }
818     /* Pull out nonce. Should be serverNonce+1 */
819     gcry_mpi_scan(&retServerNonce, GCRYMPI_FMT_USG, ibuf, 16, NULL);
820     gcry_mpi_sub_ui(retServerNonce, retServerNonce, 1);
821     if ( gcry_mpi_cmp( serverNonce, retServerNonce) != 0) {
822         /* We're hacked!  */
823         ret = AFPERR_NOTAUTH;
824         goto error_ctx;
825     }
826     ibuf += 16;
827
828     /* ---- Start pwd changing with PAM --- */
829     ibuf[255] = '\0';           /* For safety */
830     ibuf[511] = '\0';
831
832     LOG(log_info, logtype_uams, "DHX2 Chgpwd: new pwd \'%s\'",ibuf);
833     LOG(log_info, logtype_uams, "DHX2 Chgpwd: old pwd \'%s\'",ibuf+256);
834
835     /* check if new and old password are equal */
836     if (memcmp(ibuf, ibuf + 256, 255) == 0) {
837         LOG(log_info, logtype_uams, "DHX2 Chgpwd: new and old password are equal");
838         ret = AFPERR_PWDSAME;
839         goto error_ctx;
840     }
841
842     /* Set these things up for the conv function. PAM_username was set in changepw_1 */
843     PAM_password = ibuf + 256;
844     PAM_error = pam_start("netatalk", PAM_username, &PAM_conversation, &lpamh);
845     if (PAM_error != PAM_SUCCESS) {
846         LOG(log_info, logtype_uams, "DHX2 Chgpwd: PAM error in pam_start");
847         ret = AFPERR_PARAM;
848         goto error_ctx;
849     }
850     pam_set_item(lpamh, PAM_TTY, "afpd");
851     uam_afpserver_option(obj, UAM_OPTION_CLIENTNAME, (void *) &hostname, NULL);
852     pam_set_item(lpamh, PAM_RHOST, hostname);
853     uid = geteuid();
854     seteuid(0);
855     PAM_error = pam_authenticate(lpamh,0);
856     if (PAM_error != PAM_SUCCESS) {
857         LOG(log_info, logtype_uams, "DHX2 Chgpwd: error authenticating with PAM");
858         seteuid(uid);
859         pam_end(lpamh, PAM_error);
860         ret = AFPERR_NOTAUTH;
861         goto error_ctx;
862     }
863     PAM_password = ibuf;
864     PAM_error = pam_chauthtok(lpamh, 0);
865     seteuid(uid); /* un-root ourselves. */
866     memset(ibuf, 0, 512);
867     if (PAM_error != PAM_SUCCESS) {
868         LOG(log_info, logtype_uams, "DHX2 Chgpwd: error changing pw with PAM");
869         pam_end(lpamh, PAM_error);
870         ret = AFPERR_ACCESS;
871         goto error_ctx;
872     }
873     pam_end(lpamh, 0);
874     ret = AFP_OK;
875
876 error_ctx:
877     gcry_cipher_close(ctx);
878 error_noctx:
879 exit:
880     free(K_MD5hash);
881     K_MD5hash=NULL;
882     gcry_mpi_release(serverNonce);
883     gcry_mpi_release(retServerNonce);
884     return ret;
885 }
886
887 static int dhx2_changepw(void *obj _U_, char *uname,
888                          struct passwd *pwd _U_, char *ibuf, int ibuflen _U_,
889                          char *rbuf _U_, int *rbuflen _U_)
890 {
891     /* We use this to serialize the three incoming FPChangePassword calls */
892     static int dhx2_changepw_status = 1;
893
894     int ret;
895
896     LOG(log_error, logtype_uams, "DHX2 ChangePW: Start!");
897
898     switch (dhx2_changepw_status) {
899     case 1:
900         ret = changepw_1( obj, uname, ibuf, ibuflen, rbuf, rbuflen);
901         if ( ret == AFPERR_AUTHCONT)
902             dhx2_changepw_status += 1;
903         break;
904     case 2:
905         ret = changepw_2( obj, ibuf, ibuflen, rbuf, rbuflen);
906         if ( ret == AFPERR_AUTHCONT)
907             dhx2_changepw_status += 1;
908         else
909             dhx2_changepw_status = 1;
910         break;
911     case 3:
912         ret = changepw_3( obj, ibuf, ibuflen, rbuf, rbuflen);
913         dhx2_changepw_status = 1; /* Whether is was succesfull or not: we
914                                      restart anyway !*/
915         break;
916     }
917     return ret;
918 }
919
920 static int uam_setup(const char *path)
921 {
922     if (uam_register(UAM_SERVER_LOGIN_EXT, path, "DHX2", pam_login,
923                      pam_logincont, pam_logout, pam_login_ext) < 0)
924         return -1;
925     if (uam_register(UAM_SERVER_CHANGEPW, path, "DHX2", dhx2_changepw) < 0)
926         return -1;
927     return 0;
928 }
929
930 static void uam_cleanup(void)
931 {
932     uam_unregister(UAM_SERVER_LOGIN, "DHX2");
933     uam_unregister(UAM_SERVER_CHANGEPW, "DHX2");
934 }
935
936
937 UAM_MODULE_EXPORT struct uam_export uams_dhx2 = {
938     UAM_MODULE_SERVER,
939     UAM_MODULE_VERSION,
940     uam_setup, uam_cleanup
941 };
942
943
944 UAM_MODULE_EXPORT struct uam_export uams_dhx2_pam = {
945     UAM_MODULE_SERVER,
946     UAM_MODULE_VERSION,
947     uam_setup, uam_cleanup
948 };
949
950 #endif /* USE_PAM && UAM_DHX2 */
951