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