]> arthur.barton.de Git - netatalk.git/blob - etc/uams/uams_dhx2_pam.c
DHX2, remove some gcc warnings, from Frank Lahm
[netatalk.git] / etc / uams / uams_dhx2_pam.c
1 /*
2  * $Id: uams_dhx2_pam.c,v 1.2 2008-11-24 20:15:55 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 ret;
260     size_t nwritten;
261     gcry_mpi_t g, Ma;
262     char *Ra_binary = NULL;
263
264     *rbuflen = 0;
265
266     p = gcry_mpi_new(0);
267     g = gcry_mpi_new(0);
268     Ra = gcry_mpi_new(0);
269     Ma = gcry_mpi_new(0);
270
271     /* Generate p and g for DH */
272     ret = dh_params_generate( &p, &g, PRIMEBITS);
273     if (ret != 0) {
274         LOG(log_info, logtype_uams, "DHX2: Couldn't generate p and g");
275         ret = AFPERR_MISC;
276         goto error;
277     }
278
279     /* Generate our random number Ra. */
280     Ra_binary = calloc(1, PRIMEBITS/8);
281     if (Ra_binary == NULL) {
282         ret = AFPERR_MISC;
283         goto error;
284     }
285     gcry_randomize(Ra_binary, PRIMEBITS/8, GCRY_STRONG_RANDOM);
286     gcry_mpi_scan(&Ra, GCRYMPI_FMT_USG, Ra_binary, PRIMEBITS/8, NULL);
287     free(Ra_binary);
288     Ra_binary = NULL;
289
290     /* Ma = g^Ra mod p. This is our "public" key */
291     gcry_mpi_powm(Ma, g, Ra, p);
292
293     /* ------- DH Init done ------ */
294     /* Start building reply packet */
295
296     /* Session ID first */
297     ID = dhxhash(obj);
298     *(u_int16_t *)rbuf = htons(ID);
299     rbuf += 2;
300     *rbuflen += 2;
301
302     /* g is next */
303     gcry_mpi_print( GCRYMPI_FMT_USG, (unsigned char *)rbuf, 4, &nwritten, g);
304     if (nwritten < 4) {
305         memmove( rbuf+4-nwritten, rbuf, nwritten);
306         memset( rbuf, 0, 4-nwritten);
307     }
308     rbuf += 4;
309     *rbuflen += 4;
310
311     /* len = length of p = PRIMEBITS/8 */
312     *(u_int16_t *)rbuf = htons((u_int16_t) PRIMEBITS/8);
313     rbuf += 2;
314     *rbuflen += 2;
315
316     /* p */
317     gcry_mpi_print( GCRYMPI_FMT_USG, (unsigned char *)rbuf, PRIMEBITS/8, NULL, p);
318     rbuf += PRIMEBITS/8;
319     *rbuflen += PRIMEBITS/8;
320
321     /* Ma */
322     gcry_mpi_print( GCRYMPI_FMT_USG, (unsigned char *)rbuf, PRIMEBITS/8, &nwritten, Ma);
323     if (nwritten < PRIMEBITS/8) {
324         memmove(rbuf + (PRIMEBITS/8) - nwritten, rbuf, nwritten);
325         memset(rbuf, 0, (PRIMEBITS/8) - nwritten);
326     }
327     rbuf += PRIMEBITS/8;
328     *rbuflen += PRIMEBITS/8;
329
330     ret = AFPERR_AUTHCONT;
331
332 error:                          /* We exit here anyway */
333     /* We will only need p and Ra later, but mustn't forget to release it ! */
334     gcry_mpi_release(g);
335     gcry_mpi_release(Ma);
336     return ret;
337 }
338
339 /* -------------------------------- */
340 static int login(void *obj, char *username, int ulen,  struct passwd **uam_pwd _U_,
341                  char *ibuf, int ibuflen,
342                  char *rbuf, int *rbuflen)
343 {
344     if (( dhxpwd = uam_getname(obj, username, ulen)) == NULL ) {
345         LOG(log_info, logtype_uams, "DHX2: unknown username");
346         return AFPERR_PARAM;
347     }
348
349     PAM_username = username;
350     LOG(log_info, logtype_uams, "DHX2 login: %s", username);
351     return dhx2_setup(obj, ibuf, ibuflen, rbuf, rbuflen);
352 }
353
354 /* -------------------------------- */
355 /* dhx login: things are done in a slightly bizarre order to avoid
356  * having to clean things up if there's an error. */
357 static int pam_login(void *obj, struct passwd **uam_pwd,
358                      char *ibuf, int ibuflen,
359                      char *rbuf, int *rbuflen)
360 {
361     char *username;
362     int len, ulen;
363
364     *rbuflen = 0;
365
366     /* grab some of the options */
367     if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &ulen) < 0) {
368         LOG(log_info, logtype_uams, "DHX2: uam_afpserver_option didn't meet uam_option_username  -- %s",
369             strerror(errno));
370         return AFPERR_PARAM;
371     }
372
373     len = (unsigned char) *ibuf++;
374     if ( len > ulen ) {
375         LOG(log_info, logtype_uams, "DHX2: Signature Retieval Failure -- %s",
376             strerror(errno));
377         return AFPERR_PARAM;
378     }
379
380     memcpy(username, ibuf, len );
381     ibuf += len;
382     username[ len ] = '\0';
383
384     if ((unsigned long) ibuf & 1) /* pad to even boundary */
385         ++ibuf;
386
387     return (login(obj, username, ulen, uam_pwd, ibuf, ibuflen, rbuf, rbuflen));
388 }
389
390 /* ----------------------------- */
391 static int pam_login_ext(void *obj, char *uname, struct passwd **uam_pwd,
392                          char *ibuf, int ibuflen,
393                          char *rbuf, int *rbuflen)
394 {
395     char *username;
396     int len, ulen;
397     u_int16_t  temp16;
398
399     *rbuflen = 0;
400
401     /* grab some of the options */
402     if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &ulen) < 0) {
403         LOG(log_info, logtype_uams, "DHX2: uam_afpserver_option didn't meet uam_option_username  -- %s",
404             strerror(errno));
405         return AFPERR_PARAM;
406     }
407
408     if (*uname != 3)
409         return AFPERR_PARAM;
410     uname++;
411     memcpy(&temp16, uname, sizeof(temp16));
412     len = ntohs(temp16);
413
414     if ( !len || len > ulen ) {
415         LOG(log_info, logtype_uams, "DHX2: Signature Retrieval Failure -- %s",
416             strerror(errno));
417         return AFPERR_PARAM;
418     }
419     memcpy(username, uname +2, len );
420     username[ len ] = '\0';
421
422     return (login(obj, username, ulen, uam_pwd, ibuf, ibuflen, rbuf, rbuflen));
423 }
424
425 /* -------------------------------- */
426
427 static int logincont1(void *obj, char *ibuf, int ibuflen, char *rbuf, int *rbuflen)
428 {
429     int ret;
430     size_t nwritten;
431     gcry_mpi_t Mb, K, clientNonce;
432     unsigned char *K_bin = NULL;
433     char serverNonce_bin[16];
434     gcry_cipher_hd_t ctx;
435     gcry_error_t ctxerror;
436
437     Mb = gcry_mpi_new(0);
438     K = gcry_mpi_new(0);
439     clientNonce = gcry_mpi_new(0);
440     serverNonce = gcry_mpi_new(0);
441
442     /* Packet size should be: Session ID + Ma + Encrypted client nonce */
443     if (ibuflen != 2 + PRIMEBITS/8 + 16) {
444         LOG(log_error, logtype_uams, "DHX2: Paket length not correct");
445         ret = AFPERR_PARAM;
446         goto error_noctx;
447     }
448
449     /* Skip session id */
450     ibuf += 2;
451
452     /* Extract Mb, client's "public" key */
453     gcry_mpi_scan(&Mb, GCRYMPI_FMT_USG, ibuf, PRIMEBITS/8, NULL);
454     ibuf += PRIMEBITS/8;
455
456     /* Now finally generate the Key: K = Mb^Ra mod p */
457     gcry_mpi_powm(K, Mb, Ra, p);
458
459     /* We need K in binary form in order to ... */
460     K_bin = calloc(1, PRIMEBITS/8);
461     if (K_bin == NULL) {
462         ret = AFPERR_MISC;
463         goto error_noctx;
464     }
465     gcry_mpi_print(GCRYMPI_FMT_USG, K_bin, PRIMEBITS/8, &nwritten, K);
466     if (nwritten < PRIMEBITS/8) {
467         memmove(K_bin + PRIMEBITS/8 - nwritten, K_bin, nwritten);
468         memset(K_bin, 0, PRIMEBITS/8 - nwritten);
469     }
470
471     /* ... generate the MD5 hash of K. K_MD5hash is what we actually use ! */
472     K_MD5hash = calloc(1, K_hash_len = gcry_md_get_algo_dlen(GCRY_MD_MD5));
473     if (K_MD5hash == NULL) {
474         ret = AFPERR_MISC;
475         goto error_noctx;
476     }
477     gcry_md_hash_buffer(GCRY_MD_MD5, K_MD5hash, K_bin, PRIMEBITS/8);
478     free(K_bin);
479     K_bin = NULL;
480
481     /* FIXME: To support the Reconnect UAM, we need to store this key somewhere */
482
483     /* Set up our encryption context. */
484     ctxerror = gcry_cipher_open( &ctx, GCRY_CIPHER_CAST5, GCRY_CIPHER_MODE_CBC, 0);
485     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
486         ret = AFPERR_MISC;
487         goto error_ctx;
488     }
489     /* Set key */
490     ctxerror = gcry_cipher_setkey(ctx, K_MD5hash, K_hash_len);
491     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
492         ret = AFPERR_MISC;
493         goto error_ctx;
494     }
495     /* Set the initialization vector for client->server transfer. */
496     ctxerror = gcry_cipher_setiv(ctx, dhx_c2siv, sizeof(dhx_c2siv));
497     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
498         ret = AFPERR_MISC;
499         goto error_ctx;
500     }
501     /* Finally: decrypt client's md5_K(client nonce, C2SIV) inplace */
502     ctxerror = gcry_cipher_decrypt(ctx, ibuf, 16, NULL, 0);
503     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
504         ret = AFPERR_MISC;
505         goto error_ctx;
506     }
507     /* Pull out clients nonce */
508     gcry_mpi_scan(&clientNonce, GCRYMPI_FMT_USG, ibuf, 16, NULL);
509     /* Increment nonce */
510     gcry_mpi_add_ui(clientNonce, clientNonce, 1);
511
512     /* Generate our nonce and remember it for Logincont2 */
513     gcry_create_nonce(serverNonce_bin, 16); /* We'll use this here */
514     gcry_mpi_scan(&serverNonce, GCRYMPI_FMT_USG, serverNonce_bin, 16, NULL); /* For use in Logincont2 */
515
516     /* ---- Start building reply packet ---- */
517
518     /* Session ID + 1 first */
519     *(u_int16_t *)rbuf = htons(ID+1);
520     rbuf += 2;
521     *rbuflen += 2;
522
523     /* Client nonce + 1 */
524     gcry_mpi_print(GCRYMPI_FMT_USG, (unsigned char *)rbuf, PRIMEBITS/8, NULL, clientNonce);
525     /* Server nonce */
526     memcpy(rbuf+16, serverNonce_bin, 16);
527
528     /* Set the initialization vector for server->client transfer. */
529     ctxerror = gcry_cipher_setiv(ctx, dhx_s2civ, sizeof(dhx_s2civ));
530     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
531         ret = AFPERR_MISC;
532         goto error_ctx;
533     }
534     /* Encrypt md5_K(clientNonce+1, serverNonce) inplace */
535     ctxerror = gcry_cipher_encrypt(ctx, rbuf, 32, NULL, 0);
536     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
537         ret = AFPERR_MISC;
538         goto error_ctx;
539     }
540     rbuf += 32;
541     *rbuflen += 32;
542
543     ret = AFPERR_AUTHCONT;
544     goto exit;
545
546 error_ctx:
547     gcry_cipher_close(ctx);
548 error_noctx:
549     gcry_mpi_release(serverNonce);
550     free(K_MD5hash);
551     K_MD5hash=NULL;
552 exit:
553     gcry_mpi_release(K);
554     gcry_mpi_release(Mb);
555     gcry_mpi_release(Ra);
556     gcry_mpi_release(p);
557     gcry_mpi_release(clientNonce);
558     return ret;
559 }
560
561 static int logincont2(void *obj, struct passwd **uam_pwd,
562                       char *ibuf, int ibuflen,
563                       char *rbuf, int *rbuflen)
564 {
565     int ret;
566     int PAM_error;
567     char *hostname = NULL;
568     gcry_mpi_t retServerNonce;
569     gcry_cipher_hd_t ctx;
570     gcry_error_t ctxerror;
571
572     *rbuflen = 0;
573
574     /* Packet size should be: Session ID + ServerNonce + Passwd buffer */
575     if (ibuflen != 2 + 16 + 256) {
576         LOG(log_error, logtype_uams, "DHX2: Paket length not correct");
577         ret = AFPERR_PARAM;
578         goto error_noctx;
579     }
580
581     retServerNonce = gcry_mpi_new(0);
582
583     /* For PAM */
584     uam_afpserver_option(obj, UAM_OPTION_CLIENTNAME, (void *) &hostname, NULL);
585
586     /* Set up our encryption context. */
587     ctxerror = gcry_cipher_open( &ctx, GCRY_CIPHER_CAST5, GCRY_CIPHER_MODE_CBC, 0);
588     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
589         ret = AFPERR_MISC;
590         goto error_ctx;
591     }
592     /* Set key */
593     ctxerror = gcry_cipher_setkey(ctx, K_MD5hash, K_hash_len);
594     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
595         ret = AFPERR_MISC;
596         goto error_ctx;
597     }
598     /* Set the initialization vector for client->server transfer. */
599     ctxerror = gcry_cipher_setiv(ctx, dhx_c2siv, sizeof(dhx_c2siv));
600     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
601         ret = AFPERR_MISC;
602         goto error_ctx;
603     }
604
605     /* Skip Session ID */
606     ibuf += 2;
607
608     /* Finally: decrypt client's md5_K(serverNonce+1, passwor, C2SIV) inplace */
609     ctxerror = gcry_cipher_decrypt(ctx, ibuf, 16+256, NULL, 0);
610     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
611         ret = AFPERR_MISC;
612         goto error_ctx;
613     }
614     /* Pull out nonce. Should be serverNonce+1 */
615     gcry_mpi_scan(&retServerNonce, GCRYMPI_FMT_USG, ibuf, 16, NULL);
616     gcry_mpi_sub_ui(retServerNonce, retServerNonce, 1);
617     if ( gcry_mpi_cmp( serverNonce, retServerNonce) != 0) {
618         /* We're hacked!  */
619         ret = AFPERR_NOTAUTH;
620         goto error_ctx;
621     }
622     ibuf += 16;
623
624     LOG(log_info, logtype_uams, "DHX2: logincont2 alive!");
625
626     /* ---- Start authentication with PAM --- */
627
628     /* Set these things up for the conv function */
629     PAM_password = ibuf;
630
631     ret = AFPERR_NOTAUTH;
632     PAM_error = pam_start("netatalk", PAM_username, &PAM_conversation, &pamh);
633     if (PAM_error != PAM_SUCCESS) {
634         LOG(log_info, logtype_uams, "DHX2: PAM_Error: %s",
635             pam_strerror(pamh,PAM_error));
636         goto error_ctx;
637     }
638
639     /* solaris craps out if PAM_TTY and PAM_RHOST aren't set. */
640     pam_set_item(pamh, PAM_TTY, "afpd");
641     pam_set_item(pamh, PAM_RHOST, hostname);
642     PAM_error = pam_authenticate(pamh, 0);
643     if (PAM_error != PAM_SUCCESS) {
644         if (PAM_error == PAM_MAXTRIES)
645             ret = AFPERR_PWDEXPR;
646         LOG(log_info, logtype_uams, "DHX2: PAM_Error: %s",
647             pam_strerror(pamh, PAM_error));
648         goto error_ctx;
649     }
650
651     PAM_error = pam_acct_mgmt(pamh, 0);
652     if (PAM_error != PAM_SUCCESS ) {
653         LOG(log_info, logtype_uams, "DHX2: PAM_Error: %s",
654             pam_strerror(pamh, PAM_error));
655         if (PAM_error == PAM_NEW_AUTHTOK_REQD)    /* password expired */
656             ret = AFPERR_PWDEXPR;
657 #ifdef PAM_AUTHTOKEN_REQD
658         else if (PAM_error == PAM_AUTHTOKEN_REQD)
659             ret = AFPERR_PWDCHNG;
660 #endif
661         else
662             goto error_ctx;
663     }
664
665 #ifndef PAM_CRED_ESTABLISH
666 #define PAM_CRED_ESTABLISH PAM_ESTABLISH_CRED
667 #endif
668     PAM_error = pam_setcred(pamh, PAM_CRED_ESTABLISH);
669     if (PAM_error != PAM_SUCCESS) {
670         LOG(log_info, logtype_uams, "DHX2: PAM_Error: %s",
671             pam_strerror(pamh, PAM_error));
672         goto error_ctx;
673     }
674
675     PAM_error = pam_open_session(pamh, 0);
676     if (PAM_error != PAM_SUCCESS) {
677         LOG(log_info, logtype_uams, "DHX2: PAM_Error: %s",
678             pam_strerror(pamh, PAM_error));
679         goto error_ctx;
680     }
681
682     memset(ibuf, 0, 256); /* zero out the password */
683     *uam_pwd = dhxpwd;
684     LOG(log_info, logtype_uams, "DHX2: PAM Auth OK!");
685     if ( ret == AFPERR_PWDEXPR)
686         return ret;
687     ret = AFP_OK;
688
689 error_ctx:
690     gcry_cipher_close(ctx);
691 error_noctx:
692     free(K_MD5hash);
693     K_MD5hash=NULL;
694     gcry_mpi_release(serverNonce);
695     gcry_mpi_release(retServerNonce);    
696     return ret;
697 }
698
699 static int pam_logincont(void *obj, struct passwd **uam_pwd,
700                          char *ibuf, int ibuflen,
701                          char *rbuf, int *rbuflen)
702 {
703     u_int16_t retID;
704     int ret;
705
706     /* check for session id */
707     retID = ntohs(*(u_int16_t *)ibuf);
708     if (retID == ID)
709         ret = logincont1(obj, ibuf, ibuflen, rbuf, rbuflen);
710     else if (retID == ID+1)
711         ret = logincont2(obj, uam_pwd, ibuf,ibuflen, rbuf, rbuflen);
712     else {
713         LOG(log_info, logtype_uams, "DHX2: Session ID Mismatch");
714         ret = AFPERR_PARAM;
715     }
716     return ret;
717 }
718
719
720 /* logout */
721 static void pam_logout() {
722     pam_close_session(pamh, 0);
723     pam_end(pamh, 0);
724     pamh = NULL;
725 }
726
727 /****************************
728  * --- Change pwd stuff --- */
729
730 static int changepw_1(void *obj, char *uname,
731                       char *ibuf, int ibuflen, char *rbuf, int *rbuflen)
732 {
733     *rbuflen = 0;
734
735     /* Remember it now, use it in changepw_3 */
736     PAM_username = uname;
737     return( dhx2_setup(obj, ibuf, ibuflen, rbuf, rbuflen) );
738 }
739
740 static int changepw_2(void *obj, 
741                       char *ibuf, int ibuflen, char *rbuf, int *rbuflen)
742 {
743     return( logincont1(obj, ibuf, ibuflen, rbuf, rbuflen) );
744 }
745
746 static int changepw_3(void *obj _U_,
747                       char *ibuf, int ibuflen _U_, 
748                       char *rbuf _U_, int *rbuflen _U_)
749 {
750     int ret;
751     int PAM_error;
752     uid_t uid;
753     pam_handle_t *lpamh;
754     char *hostname = NULL;
755     gcry_mpi_t retServerNonce;
756     gcry_cipher_hd_t ctx;
757     gcry_error_t ctxerror;
758
759     *rbuflen = 0;
760
761     LOG(log_error, logtype_uams, "DHX2 ChangePW: packet 3 processing");
762
763     /* Packet size should be: Session ID + ServerNonce + 2*Passwd buffer */
764     if (ibuflen != 2 + 16 + 2*256) {
765         LOG(log_error, logtype_uams, "DHX2: Paket length not correct");
766         ret = AFPERR_PARAM;
767         goto error_noctx;
768     }
769
770     retServerNonce = gcry_mpi_new(0);
771
772     /* For PAM */
773     uam_afpserver_option(obj, UAM_OPTION_CLIENTNAME, (void *) &hostname, NULL);
774
775     /* Set up our encryption context. */
776     ctxerror = gcry_cipher_open( &ctx, GCRY_CIPHER_CAST5, GCRY_CIPHER_MODE_CBC, 0);
777     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
778         ret = AFPERR_MISC;
779         goto error_ctx;
780     }
781     /* Set key */
782     ctxerror = gcry_cipher_setkey(ctx, K_MD5hash, K_hash_len);
783     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
784         ret = AFPERR_MISC;
785         goto error_ctx;
786     }
787
788     /* Set the initialization vector for client->server transfer. */
789     ctxerror = gcry_cipher_setiv(ctx, dhx_c2siv, sizeof(dhx_c2siv));
790     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
791         ret = AFPERR_MISC;
792         goto error_ctx;
793     }
794
795     /* Skip Session ID */
796     ibuf += 2;
797
798     /* Finally: decrypt client's md5_K(serverNonce+1, 2*password, C2SIV) inplace */
799     ctxerror = gcry_cipher_decrypt(ctx, ibuf, 16+2*256, NULL, 0);
800     if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) {
801         ret = AFPERR_MISC;
802         goto error_ctx;
803     }
804     /* Pull out nonce. Should be serverNonce+1 */
805     gcry_mpi_scan(&retServerNonce, GCRYMPI_FMT_USG, ibuf, 16, NULL);
806     gcry_mpi_sub_ui(retServerNonce, retServerNonce, 1);
807     if ( gcry_mpi_cmp( serverNonce, retServerNonce) != 0) {
808         /* We're hacked!  */
809         ret = AFPERR_NOTAUTH;
810         goto error_ctx;
811     }
812     ibuf += 16;
813
814     /* ---- Start pwd changing with PAM --- */
815     ibuf[255] = '\0';           /* For safety */
816     ibuf[511] = '\0';
817
818     /* check if new and old password are equal */
819     if (memcmp(ibuf, ibuf + 256, 255) == 0) {
820         LOG(log_info, logtype_uams, "DHX2 Chgpwd: new and old password are equal");
821         ret = AFPERR_PWDSAME;
822         goto error_ctx;
823     }
824
825     /* Set these things up for the conv function. PAM_username was set in changepw_1 */
826     PAM_password = ibuf + 256;
827     PAM_error = pam_start("netatalk", PAM_username, &PAM_conversation, &lpamh);
828     if (PAM_error != PAM_SUCCESS) {
829         LOG(log_info, logtype_uams, "DHX2 Chgpwd: PAM error in pam_start");
830         ret = AFPERR_PARAM;
831         goto error_ctx;
832     }
833     pam_set_item(lpamh, PAM_TTY, "afpd");
834     uam_afpserver_option(obj, UAM_OPTION_CLIENTNAME, (void *) &hostname, NULL);
835     pam_set_item(lpamh, PAM_RHOST, hostname);
836     uid = geteuid();
837     seteuid(0);
838     PAM_error = pam_authenticate(lpamh,0);
839     if (PAM_error != PAM_SUCCESS) {
840         LOG(log_info, logtype_uams, "DHX2 Chgpwd: error authenticating with PAM");
841         seteuid(uid);
842         pam_end(lpamh, PAM_error);
843         ret = AFPERR_NOTAUTH;
844         goto error_ctx;
845     }
846     PAM_password = ibuf;
847     PAM_error = pam_chauthtok(lpamh, 0);
848     seteuid(uid); /* un-root ourselves. */
849     memset(ibuf, 0, 512);
850     if (PAM_error != PAM_SUCCESS) {
851         LOG(log_info, logtype_uams, "DHX2 Chgpwd: error changing pw with PAM");
852         pam_end(lpamh, PAM_error);
853         ret = AFPERR_ACCESS;
854         goto error_ctx;
855     }
856     pam_end(lpamh, 0);
857     ret = AFP_OK;
858
859 error_ctx:
860     gcry_cipher_close(ctx);
861 error_noctx:
862     free(K_MD5hash);
863     K_MD5hash=NULL;
864     gcry_mpi_release(serverNonce);
865     gcry_mpi_release(retServerNonce);
866     return ret;
867 }
868
869 static int dhx2_changepw(void *obj _U_, char *uname,
870                          struct passwd *pwd _U_, char *ibuf, int ibuflen _U_,
871                          char *rbuf _U_, int *rbuflen _U_)
872 {
873     /* We use this to serialize the three incoming FPChangePassword calls */
874     static int dhx2_changepw_status = 1;
875
876     int ret;
877
878     switch (dhx2_changepw_status) {
879     case 1:
880         ret = changepw_1( obj, uname, ibuf, ibuflen, rbuf, rbuflen);
881         if ( ret == AFPERR_AUTHCONT)
882             dhx2_changepw_status += 1;
883         break;
884     case 2:
885         ret = changepw_2( obj, ibuf, ibuflen, rbuf, rbuflen);
886         if ( ret == AFPERR_AUTHCONT)
887             dhx2_changepw_status += 1;
888         else
889             dhx2_changepw_status = 1;
890         break;
891     case 3:
892         ret = changepw_3( obj, ibuf, ibuflen, rbuf, rbuflen);
893         dhx2_changepw_status = 1; /* Whether is was succesfull or not: we
894                                      restart anyway !*/
895         break;
896     }
897     return ret;
898 }
899
900 static int uam_setup(const char *path)
901 {
902     if (uam_register(UAM_SERVER_LOGIN_EXT, path, "DHX2", pam_login,
903                      pam_logincont, pam_logout, pam_login_ext) < 0)
904         return -1;
905     if (uam_register(UAM_SERVER_CHANGEPW, path, "DHX2", dhx2_changepw) < 0)
906         return -1;
907     return 0;
908 }
909
910 static void uam_cleanup(void)
911 {
912     uam_unregister(UAM_SERVER_LOGIN, "DHX2");
913     uam_unregister(UAM_SERVER_CHANGEPW, "DHX2");
914 }
915
916
917 UAM_MODULE_EXPORT struct uam_export uams_dhx2 = {
918     UAM_MODULE_SERVER,
919     UAM_MODULE_VERSION,
920     uam_setup, uam_cleanup
921 };
922
923
924 UAM_MODULE_EXPORT struct uam_export uams_dhx2_pam = {
925     UAM_MODULE_SERVER,
926     UAM_MODULE_VERSION,
927     uam_setup, uam_cleanup
928 };
929
930 #endif /* USE_PAM && UAM_DHX2 */
931