From a8a8fb02d8cb3375dbd339cf0f16b2b18cff6323 Mon Sep 17 00:00:00 2001 From: didg Date: Sat, 22 Nov 2008 12:07:26 +0000 Subject: [PATCH] libgcrypt and DHX2 uam from Frank Lahm --- bin/afppasswd/Makefile.am | 4 +- configure.in | 16 +- etc/uams/Makefile.am | 85 +++- etc/uams/uams_dhx2_pam.c | 951 ++++++++++++++++++++++++++++++++++++ etc/uams/uams_dhx2_passwd.c | 647 ++++++++++++++++++++++++ macros/libgcrpyt.m4 | 51 ++ macros/ssl-check.m4 | 10 +- macros/summary.m4 | 16 +- 8 files changed, 1740 insertions(+), 40 deletions(-) create mode 100644 etc/uams/uams_dhx2_pam.c create mode 100644 etc/uams/uams_dhx2_passwd.c create mode 100644 macros/libgcrpyt.m4 diff --git a/bin/afppasswd/Makefile.am b/bin/afppasswd/Makefile.am index 4e4a5ef8..9a295983 100644 --- a/bin/afppasswd/Makefile.am +++ b/bin/afppasswd/Makefile.am @@ -2,7 +2,7 @@ pkgconfdir = @PKGCONFDIR@ -if USE_DHX +if HAVE_OPENSSL bin_PROGRAMS = afppasswd else bin_PROGRAMS = @@ -15,6 +15,6 @@ AM_CFLAGS = @CFLAGS@ @SSL_CFLAGS@ -I$(top_srcdir)/sys \ -D_PATH_AFPDPWFILE=\"$(pkgconfdir)/afppasswd\" install-exec-hook: -if USE_DHX +if HAVE_OPENSSL chmod u+s $(DESTDIR)$(bindir)/afppasswd endif diff --git a/configure.in b/configure.in index 8aaec6bd..0f14c3e2 100644 --- a/configure.in +++ b/configure.in @@ -1,4 +1,4 @@ -dnl $Id: configure.in,v 1.205 2006-09-09 04:30:01 didg Exp $ +dnl $Id: configure.in,v 1.206 2008-11-22 12:07:26 didg Exp $ dnl configure.in for netatalk AC_INIT(etc/afpd/main.c) @@ -919,7 +919,10 @@ if test x"$this_os" = "xtru64"; then sysv_style=tru64 fi -dnl -- look for openssl +dnl -- check for libgcrypt, if found enables DHX UAM +NETATALK_CHECK_LIBGCRYPT + +dnl -- look for openssl, if found enables DHX UAM and Randnum UAM AC_PATH_SSL dnl -- check for crypt @@ -931,7 +934,7 @@ AC_MSG_CHECKING([whether the PGP UAM should be build]) AC_ARG_ENABLE(pgp-uam, [ --enable-pgp-uam enable build of PGP UAM module],[ if test "$enableval" = "yes"; then - if test "$compile_ssl" = "yes"; then + if test "x$neta_cv_have_openssl" = "xyes"; then AC_DEFINE(UAM_PGP, 1, [Define if the PGP UAM module should be compiled]) compile_pgp=yes AC_MSG_RESULT([yes]) @@ -1005,12 +1008,17 @@ AC_SUBST(OVERWRITE_CONFIG) AM_CONDITIONAL(SOLARIS_MODULE, test x$solaris_module = xyes) AM_CONDITIONAL(COMPILE_TIMELORD, test x$compile_timelord = xyes) AM_CONDITIONAL(COMPILE_A2BOOT, test x$compile_a2boot = xyes) -AM_CONDITIONAL(USE_DHX, test x$compile_ssl = xyes) +AM_CONDITIONAL(HAVE_LIBGCRYPT, test x$neta_cv_have_libgcrypt = xyes) +AM_CONDITIONAL(HAVE_OPENSSL, test x$neta_cv_have_openssl = xyes) +AM_CONDITIONAL(USE_DHX, test x$neta_cv_compile_dhx = xyes) +AM_CONDITIONAL(USE_DHX2, test x$neta_cv_compile_dhx2 = xyes) +AM_CONDITIONAL(USE_RANDNUM, test x$neta_cv_have_openssl = xyes) AM_CONDITIONAL(USE_KERBEROS, test x$compile_kerberos = xyes) AM_CONDITIONAL(USE_PAM_SO, test x$use_pam_so = xyes) AM_CONDITIONAL(USE_PAM, test x$netatalk_cv_install_pam = xyes) AM_CONDITIONAL(BUILD_PAM, test x$compile_pam = xyes) AM_CONDITIONAL(USE_PGP, test x$compile_pgp = xyes) +AM_CONDITIONAL(DEFAULT_HOOK, test x$neta_cv_have_libgcrypt != xyes && test x$neta_cv_have_openssl != xyes) AM_CONDITIONAL(USE_COBALT, test x$sysv_style = xcobalt) AM_CONDITIONAL(USE_NETBSD, test x$sysv_style = xnetbsd) AM_CONDITIONAL(USE_REDHAT, test x$sysv_style = xredhat) diff --git a/etc/uams/Makefile.am b/etc/uams/Makefile.am index d3e35108..46446a37 100644 --- a/etc/uams/Makefile.am +++ b/etc/uams/Makefile.am @@ -7,20 +7,55 @@ SUBDIRS = uams_krb4 # UAMS_GENERIC = uams_guest.la uams_passwd.la +uams_LINKS = uams_clrtxt.so +dhx_exec_hook = echo +dhx2_exec_hook = echo + +# these are complex: check if DHX and DHX2 module should be build if USE_DHX -UAMS_DHX_GENERIC = uams_randnum.la uams_dhx_passwd.la +if HAVE_OPENSSL +uams_LINKS += uams_dhx.so +UAMS_DHX = uams_dhx_passwd.la +if BUILD_PAM +UAMS_DHX_PAM = uams_dhx_pam.la +endif +if USE_PAM_SO +dhx_exec_hook += && $(LN_S) uams_dhx_pam.so uams_dhx.so +else +dhx_exec_hook += && $(LN_S) uams_dhx_passwd.so uams_dhx.so endif -if USE_PGP -UAMS_PGP = uams_pgp.la +endif endif +if USE_DHX2 +if HAVE_LIBGCRYPT +uams_LINKS += uams_dhx2.so +UAMS_DHX2 = uams_dhx2_passwd.la +if BUILD_PAM +UAMS_DHX2_PAM = uams_dhx2_pam.la +endif +if USE_PAM_SO +dhx2_exec_hook += && $(LN_S) uams_dhx2_pam.so uams_dhx2.so +else +dhx2_exec_hook += && $(LN_S) uams_dhx2_passwd.so uams_dhx2.so +endif +endif +endif + +# these are simple, though some the last three depend on OpenSSL + if BUILD_PAM UAMS_PAM = uams_pam.la -if USE_DHX -UAMS_DHX_PAM = uams_dhx_pam.la endif + +if USE_RANDNUM +UAMS_RANDNUM = uams_randnum.la +endif + +if USE_PGP +UAMS_PGP = uams_pgp.la endif if USE_GSSAPI @@ -28,12 +63,11 @@ UAMS_GSSAPI = uams_gss.la endif # links + if USE_PAM_SO UAMS_CLRTXT_LINK = uams_pam.so -UAMS_DHX_LINK = uams_dhx_pam.so else UAMS_CLRTXT_LINK = uams_passwd.so -UAMS_DHX_LINK = uams_dhx_passwd.so endif # @@ -47,6 +81,8 @@ uams_pam_la_SOURCES = uams_pam.c uams_pgp_la_SOURCES = uams_pgp.c uams_dhx_passwd_la_SOURCES = uams_dhx_passwd.c uams_dhx_pam_la_SOURCES = uams_dhx_pam.c +uams_dhx2_passwd_la_SOURCES = uams_dhx2_passwd.c +uams_dhx2_pam_la_SOURCES = uams_dhx2_pam.c uams_gss_la_SOURCES = uams_gss.c # @@ -54,7 +90,7 @@ uams_gss_la_SOURCES = uams_gss.c # # do that on behalf of the brokeness of automake 1.4 -AM_CFLAGS = @CFLAGS@ @SSL_CFLAGS@ +AM_CFLAGS = @CFLAGS@ @SSL_CFLAGS@ @LIBGCRYPT_CFLAGS@ uams_guest_la_CFLAGS = @CFLAGS@ uams_randnum_la_CFLAGS = @CFLAGS@ @SSL_CFLAGS@ @@ -63,6 +99,8 @@ uams_pam_la_CFLAGS = @CFLAGS@ @PAM_CFLAGS@ uams_pgp_la_CFLAGS = @CFLAGS@ @SSL_CFLAGS@ uams_dhx_passwd_la_CFLAGS = @CFLAGS@ @SSL_CFLAGS@ uams_dhx_pam_la_CFLAGS = @CFLAGS@ @SSL_CFLAGS@ @PAM_CFLAGS@ +uams_dhx2_passwd_la_CFLAGS = @CFLAGS@ @LIBGCRYPT_CFLAGS@ +uams_dhx2_pam_la_CFLAGS = @CFLAGS@ @LIBGCRYPT_CFLAGS@ @PAM_CFLAGS@ uams_gss_la_CFLAGS = @CFLAGS@ @GSSAPI_CFLAGS@ uams_guest_la_LDFLAGS = -module -avoid-version @@ -71,7 +109,9 @@ uams_passwd_la_LDFLAGS = -module -avoid-version @CRYPT_LIBS@ uams_pam_la_LDFLAGS = -module -avoid-version @PAM_LIBS@ uams_pgp_la_LDFLAGS = -module -avoid-version @SSL_LIBS@ uams_dhx_passwd_la_LDFLAGS = -module -avoid-version @CRYPT_LIBS@ @SSL_LIBS@ -uams_dhx_pam_la_LDFLAGS = -module -avoid-version @SSL_LIBS@ @PAM_LIBS@ +uams_dhx_pam_la_LDFLAGS = -module -avoid-version @CRYPT_LIBS@ @SSL_LIBS@ @PAM_LIBS@ +uams_dhx2_passwd_la_LDFLAGS = -module -avoid-version @CRYPT_LIBS@ @LIBGCRYPT_LIBS@ +uams_dhx2_pam_la_LDFLAGS = -module -avoid-version @LIBGCRYPT_LIBS@ @PAM_LIBS@ uams_gss_la_LDFLAGS = -module -avoid-version @GSSAPI_LIBS@ # @@ -83,37 +123,28 @@ uams_LTLIBRARIES = \ $(UAMS_GENERIC) \ $(UAMS_PGP) \ $(UAMS_PAM) \ - $(UAMS_DHX_GENERIC) \ + $(UAMS_RANDNUM) \ + $(UAMS_DHX) \ $(UAMS_DHX_PAM) \ + $(UAMS_DHX2) \ + $(UAMS_DHX2_PAM) \ $(UAMS_GSSAPI) # # link creation # -uams_LINKS = - -if USE_DHX install-exec-hook: (cd $(DESTDIR)$(uamsdir) && \ - rm -f uams_clrtxt.so uams_dhx.so && \ + rm -f $(uams_LINKS) && \ $(LN_S) $(UAMS_CLRTXT_LINK) uams_clrtxt.so && \ - $(LN_S) $(UAMS_DHX_LINK) uams_dhx.so \ + $(dhx_exec_hook) && \ + $(dhx2_exec_hook) \ ) -uninstall-hook: - (cd $(DESTDIR)$(uamsdir) && \ - rm -f uams_clrtxt.so uams_dhx.so \ - ) -else -install-exec-hook: - (cd $(DESTDIR)$(uamsdir) && \ - rm -f uams_clrtxt.so && \ - $(LN_S) $(UAMS_CLRTXT_LINK) uams_clrtxt.so \ - ) + uninstall-hook: (cd $(DESTDIR)$(uamsdir) && \ - rm -f uams_clrtxt.so \ + rm -f $(uams_LINKS) \ ) -endif diff --git a/etc/uams/uams_dhx2_pam.c b/etc/uams/uams_dhx2_pam.c new file mode 100644 index 00000000..bcd858cb --- /dev/null +++ b/etc/uams/uams_dhx2_pam.c @@ -0,0 +1,951 @@ +/* + * $Id: uams_dhx2_pam.c,v 1.1 2008-11-22 12:07:26 didg Exp $ + * + * Copyright (c) 1990,1993 Regents of The University of Michigan. + * Copyright (c) 1999 Adrian Sun (asun@u.washington.edu) + * All Rights Reserved. See COPYRIGHT. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#if defined (USE_PAM) && defined (UAM_DHX2) +#include +#include +#include +#include + +#ifdef HAVE_UNISTD_H +#include +#endif /* HAVE_UNISTD_H */ +#include +#ifdef HAVE_SECURITY_PAM_APPL_H +#include +#endif +#ifdef HAVE_PAM_PAM_APPL_H +#include +#endif + + +#ifdef HAVE_LIBGCRYPT +#include +#endif /* HAVE_LIBGCRYPT */ + +#include +#include +#include "../afpd/globals.h" + +/* Number of bits for p which we generate. Everybode out there uses 512, so we beet them */ +#define PRIMEBITS 1024 + +/* hash a number to a 16-bit quantity */ +#define dhxhash(a) ((((unsigned long) (a) >> 8) ^ \ + (unsigned long) (a)) & 0xffff) + +/* Some parameters need be maintained across calls */ +static gcry_mpi_t p, Ra; +static gcry_mpi_t serverNonce; +static char *K_MD5hash = NULL; +static int K_hash_len; +static u_int16_t ID; + +/* The initialization vectors for CAST128 are fixed by Apple. */ +static unsigned char dhx_c2siv[] = { 'L', 'W', 'a', 'l', 'l', 'a', 'c', 'e' }; +static unsigned char dhx_s2civ[] = { 'C', 'J', 'a', 'l', 'b', 'e', 'r', 't' }; + +/* Static variables used to communicate between the conversation function + * and the server_login function */ +static pam_handle_t *pamh = NULL; +static char *PAM_username; +static char *PAM_password; +static struct passwd *dhxpwd; + +/********************************************************* + * Crypto helper func to generate p and g for use in DH. + * libgcrpyt doesn't provide one directly. + * Algorithm taken from GNUTLS:gnutls_dh_primes.c + *********************************************************/ + +/** + * This function will generate a new pair of prime and generator for use in + * the Diffie-Hellman key exchange. + * The bits value should be one of 768, 1024, 2048, 3072 or 4096. + **/ + +static int +dh_params_generate (gcry_mpi_t *ret_p, gcry_mpi_t *ret_g, unsigned int bits) { + + int result, times = 0, qbits; + + gcry_mpi_t g = NULL, prime = NULL; + gcry_mpi_t *factors = NULL; + gcry_error_t err; + + /* Version check should be the very first call because it + makes sure that important subsystems are intialized. */ + if (!gcry_check_version (GCRYPT_VERSION)) { + LOG(log_info, logtype_uams, "PAM DHX2: libgcrypt versions mismatch. Need: %u", GCRYPT_VERSION); + result = AFPERR_MISC; + goto error; + } + + if (bits < 256) + qbits = bits / 2; + else + qbits = (bits / 40) + 105; + + if (qbits & 1) /* better have an even number */ + qbits++; + + /* find a prime number of size bits. */ + do { + if (times) { + gcry_mpi_release (prime); + gcry_prime_release_factors (factors); + } + err = gcry_prime_generate (&prime, bits, qbits, &factors, NULL, NULL, + GCRY_STRONG_RANDOM, GCRY_PRIME_FLAG_SPECIAL_FACTOR); + if (err != 0) { + result = AFPERR_MISC; + goto error; + } + err = gcry_prime_check (prime, 0); + times++; + } while (err != 0 && times < 10); + + if (err != 0) { + result = AFPERR_MISC; + goto error; + } + + /* generate the group generator. */ + err = gcry_prime_group_generator (&g, prime, factors, NULL); + if (err != 0) { + result = AFPERR_MISC; + goto error; + } + + gcry_prime_release_factors (factors); + factors = NULL; + + if (ret_g) + *ret_g = g; + else + gcry_mpi_release (g); + if (ret_p) + *ret_p = prime; + else + gcry_mpi_release (prime); + + return 0; + +error: + gcry_prime_release_factors (factors); + gcry_mpi_release (g); + gcry_mpi_release (prime); + + return result; +} + + +/* PAM conversation function + * Here we assume (for now, at least) that echo on means login name, and + * echo off means password. + */ +static int PAM_conv (int num_msg, + const struct pam_message **msg, + struct pam_response **resp, + void *appdata_ptr _U_) { + int count = 0; + struct pam_response *reply; + +#define COPY_STRING(s) (s) ? strdup(s) : NULL + + errno = 0; + + if (num_msg < 1) { + /* Log Entry */ + LOG(log_info, logtype_uams, "PAM DHX2 Conversation Err -- %s", + strerror(errno)); + /* Log Entry */ + return PAM_CONV_ERR; + } + + reply = (struct pam_response *) + calloc(num_msg, sizeof(struct pam_response)); + + if (!reply) { + /* Log Entry */ + LOG(log_info, logtype_uams, "PAM DHX2: Conversation Err -- %s", + strerror(errno)); + /* Log Entry */ + return PAM_CONV_ERR; + } + + for (count = 0; count < num_msg; count++) { + char *string = NULL; + + switch (msg[count]->msg_style) { + case PAM_PROMPT_ECHO_ON: + if (!(string = COPY_STRING(PAM_username))) { + /* Log Entry */ + LOG(log_info, logtype_uams, "PAM DHX2: username failure -- %s", + strerror(errno)); + /* Log Entry */ + goto pam_fail_conv; + } + break; + case PAM_PROMPT_ECHO_OFF: + if (!(string = COPY_STRING(PAM_password))) { + /* Log Entry */ + LOG(log_info, logtype_uams, "PAM DHX2: passwd failure: --: %s", + strerror(errno)); + /* Log Entry */ + goto pam_fail_conv; + } + break; + case PAM_TEXT_INFO: +#ifdef PAM_BINARY_PROMPT + case PAM_BINARY_PROMPT: +#endif /* PAM_BINARY_PROMPT */ + /* ignore it... */ + break; + case PAM_ERROR_MSG: + default: + LOG(log_info, logtype_uams, "PAM DHX2: Binary_Prompt -- %s", strerror(errno)); + goto pam_fail_conv; + } + + if (string) { + reply[count].resp_retcode = 0; + reply[count].resp = string; + string = NULL; + } + } + + *resp = reply; + LOG(log_info, logtype_uams, "PAM DHX2: PAM Success"); + return PAM_SUCCESS; + +pam_fail_conv: + for (count = 0; count < num_msg; count++) { + if (!reply[count].resp) + continue; + switch (msg[count]->msg_style) { + case PAM_PROMPT_ECHO_OFF: + case PAM_PROMPT_ECHO_ON: + free(reply[count].resp); + break; + } + } + free(reply); + /* Log Entry */ + LOG(log_info, logtype_uams, "PAM DHX2: Conversation Err -- %s", + strerror(errno)); + /* Log Entry */ + return PAM_CONV_ERR; +} + +static struct pam_conv PAM_conversation = { + &PAM_conv, + NULL +}; + + +static int dhx2_setup(void *obj, char *ibuf, int ibuflen _U_, + char *rbuf, int *rbuflen) +{ + int i, ret; + + unsigned int g_uint; + gcry_mpi_t g, Ma; + char *Ra_binary = NULL; + gcry_cipher_hd_t ctx; + gcry_error_t ctxerror; + + const int g_len = 4; + size_t len; + size_t nwritten; + + *rbuflen = 0; + + p = gcry_mpi_new(0); + g = gcry_mpi_new(0); + Ra = gcry_mpi_new(0); + Ma = gcry_mpi_new(0); + + /* Generate p and g for DH */ + ret = dh_params_generate( &p, &g, PRIMEBITS); + if (ret != 0) { + LOG(log_info, logtype_uams, "DHX2: Couldn't generate p and g"); + ret = AFPERR_MISC; + goto error; + } + + /* Generate our random number Ra. */ + Ra_binary = calloc(1, PRIMEBITS/8); + if (Ra_binary == NULL) { + ret = AFPERR_MISC; + goto error; + } + gcry_randomize(Ra_binary, PRIMEBITS/8, GCRY_STRONG_RANDOM); + gcry_mpi_scan(&Ra, GCRYMPI_FMT_USG, Ra_binary, PRIMEBITS/8, NULL); + free(Ra_binary); + Ra_binary = NULL; + + /* Ma = g^Ra mod p. This is our "public" key */ + gcry_mpi_powm(Ma, g, Ra, p); + + /* ------- DH Init done ------ */ + /* Start building reply packet */ + + /* Session ID first */ + ID = dhxhash(obj); + *(u_int16_t *)rbuf = htons(ID); + rbuf += 2; + *rbuflen += 2; + + /* g is next */ + gcry_mpi_print( GCRYMPI_FMT_USG, rbuf, 4, &nwritten, g); + if (nwritten < 4) { + memmove( rbuf+4-nwritten, rbuf, nwritten); + memset( rbuf, 0, 4-nwritten); + } + rbuf += 4; + *rbuflen += 4; + + /* len = length of p = PRIMEBITS/8 */ + *(u_int16_t *)rbuf = htons((u_int16_t) PRIMEBITS/8); + rbuf += 2; + *rbuflen += 2; + + /* p */ + gcry_mpi_print( GCRYMPI_FMT_USG, rbuf, PRIMEBITS/8, NULL, p); + rbuf += PRIMEBITS/8; + *rbuflen += PRIMEBITS/8; + + /* Ma */ + gcry_mpi_print( GCRYMPI_FMT_USG, rbuf, PRIMEBITS/8, &len, Ma); + if (len < PRIMEBITS/8) { + memmove(rbuf + (PRIMEBITS/8) - len, rbuf, len); + memset(rbuf, 0, (PRIMEBITS/8) - len); + } + rbuf += PRIMEBITS/8; + *rbuflen += PRIMEBITS/8; + + ret = AFPERR_AUTHCONT; + +error: /* We exit here anyway */ + /* We will only need p and Ra later, but mustn't forget to release it ! */ + gcry_mpi_release(g); + gcry_mpi_release(Ma); + return ret; +} + +/* -------------------------------- */ +static int login(void *obj, char *username, int ulen, struct passwd **uam_pwd _U_, + char *ibuf, int ibuflen, + char *rbuf, int *rbuflen) +{ + if (( dhxpwd = uam_getname(obj, username, ulen)) == NULL ) { + LOG(log_info, logtype_uams, "DHX2: unknown username"); + return AFPERR_PARAM; + } + + PAM_username = username; + LOG(log_info, logtype_uams, "DHX2 login: %s", username); + return dhx2_setup(obj, ibuf, ibuflen, rbuf, rbuflen); +} + +/* -------------------------------- */ +/* dhx login: things are done in a slightly bizarre order to avoid + * having to clean things up if there's an error. */ +static int pam_login(void *obj, struct passwd **uam_pwd, + char *ibuf, int ibuflen, + char *rbuf, int *rbuflen) +{ + char *username; + int len, ulen; + + *rbuflen = 0; + + /* grab some of the options */ + if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &ulen) < 0) { + LOG(log_info, logtype_uams, "DHX2: uam_afpserver_option didn't meet uam_option_username -- %s", + strerror(errno)); + return AFPERR_PARAM; + } + + len = (unsigned char) *ibuf++; + if ( len > ulen ) { + LOG(log_info, logtype_uams, "DHX2: Signature Retieval Failure -- %s", + strerror(errno)); + return AFPERR_PARAM; + } + + memcpy(username, ibuf, len ); + ibuf += len; + username[ len ] = '\0'; + + if ((unsigned long) ibuf & 1) /* pad to even boundary */ + ++ibuf; + + return (login(obj, username, ulen, uam_pwd, ibuf, ibuflen, rbuf, rbuflen)); +} + +/* ----------------------------- */ +static int pam_login_ext(void *obj, char *uname, struct passwd **uam_pwd, + char *ibuf, int ibuflen, + char *rbuf, int *rbuflen) +{ + char *username; + int len, ulen; + u_int16_t temp16; + + *rbuflen = 0; + + /* grab some of the options */ + if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &ulen) < 0) { + LOG(log_info, logtype_uams, "DHX2: uam_afpserver_option didn't meet uam_option_username -- %s", + strerror(errno)); + return AFPERR_PARAM; + } + + if (*uname != 3) + return AFPERR_PARAM; + uname++; + memcpy(&temp16, uname, sizeof(temp16)); + len = ntohs(temp16); + + if ( !len || len > ulen ) { + LOG(log_info, logtype_uams, "DHX2: Signature Retrieval Failure -- %s", + strerror(errno)); + return AFPERR_PARAM; + } + memcpy(username, uname +2, len ); + username[ len ] = '\0'; + + return (login(obj, username, ulen, uam_pwd, ibuf, ibuflen, rbuf, rbuflen)); +} + +/* -------------------------------- */ + +static int logincont1(void *obj, char *ibuf, int ibuflen, char *rbuf, int *rbuflen) +{ + u_int16_t retID; + size_t nwritten; + int ret; + *rbuflen = 0; + + gcry_mpi_t Mb, K, clientNonce; + char *K_bin = NULL; + char serverNonce_bin[16]; + gcry_cipher_hd_t ctx; + gcry_error_t ctxerror; + + Mb = gcry_mpi_new(0); + K = gcry_mpi_new(0); + clientNonce = gcry_mpi_new(0); + serverNonce = gcry_mpi_new(0); + + /* Packet size should be: Session ID + Ma + Encrypted client nonce */ + if (ibuflen != 2 + PRIMEBITS/8 + 16) { + LOG(log_error, logtype_uams, "DHX2: Paket length not correct"); + ret = AFPERR_PARAM; + goto error_noctx; + } + + /* Skip session id */ + ibuf += 2; + + /* Extract Mb, client's "public" key */ + gcry_mpi_scan(&Mb, GCRYMPI_FMT_USG, ibuf, PRIMEBITS/8, NULL); + ibuf += PRIMEBITS/8; + + /* Now finally generate the Key: K = Mb^Ra mod p */ + gcry_mpi_powm(K, Mb, Ra, p); + + /* We need K in binary form in order to ... */ + K_bin = calloc(1, PRIMEBITS/8); + if (K_bin == NULL) { + ret = AFPERR_MISC; + goto error_noctx; + } + gcry_mpi_print(GCRYMPI_FMT_USG, K_bin, PRIMEBITS/8, &nwritten, K); + if (nwritten < PRIMEBITS/8) { + memmove(K_bin + PRIMEBITS/8 - nwritten, K_bin, nwritten); + memset(K_bin, 0, PRIMEBITS/8 - nwritten); + } + + /* ... generate the MD5 hash of K. K_MD5hash is what we actually use ! */ + K_MD5hash = calloc(1, K_hash_len = gcry_md_get_algo_dlen(GCRY_MD_MD5)); + if (K_MD5hash == NULL) { + ret = AFPERR_MISC; + goto error_noctx; + } + gcry_md_hash_buffer(GCRY_MD_MD5, K_MD5hash, K_bin, PRIMEBITS/8); + free(K_bin); + K_bin = NULL; + + /* FIXME: To support the Reconnect UAM, we need to store this key somewhere */ + + /* Set up our encryption context. */ + ctxerror = gcry_cipher_open( &ctx, GCRY_CIPHER_CAST5, GCRY_CIPHER_MODE_CBC, 0); + if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) { + ret = AFPERR_MISC; + goto error_ctx; + } + /* Set key */ + ctxerror = gcry_cipher_setkey(ctx, K_MD5hash, K_hash_len); + if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) { + ret = AFPERR_MISC; + goto error_ctx; + } + /* Set the initialization vector for client->server transfer. */ + ctxerror = gcry_cipher_setiv(ctx, dhx_c2siv, sizeof(dhx_c2siv)); + if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) { + ret = AFPERR_MISC; + goto error_ctx; + } + /* Finally: decrypt client's md5_K(client nonce, C2SIV) inplace */ + ctxerror = gcry_cipher_decrypt(ctx, ibuf, 16, NULL, 0); + if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) { + ret = AFPERR_MISC; + goto error_ctx; + } + /* Pull out clients nonce */ + gcry_mpi_scan(&clientNonce, GCRYMPI_FMT_USG, ibuf, 16, NULL); + /* Increment nonce */ + gcry_mpi_add_ui(clientNonce, clientNonce, 1); + + /* Generate our nonce and remember it for Logincont2 */ + gcry_create_nonce(serverNonce_bin, 16); /* We'll use this here */ + gcry_mpi_scan(&serverNonce, GCRYMPI_FMT_USG, serverNonce_bin, 16, NULL); /* For use in Logincont2 */ + + /* ---- Start building reply packet ---- */ + + /* Session ID + 1 first */ + *(u_int16_t *)rbuf = htons(ID+1); + rbuf += 2; + *rbuflen += 2; + + /* Client nonce + 1 */ + gcry_mpi_print(GCRYMPI_FMT_USG, rbuf, PRIMEBITS/8, NULL, clientNonce); + /* Server nonce */ + memcpy(rbuf+16, serverNonce_bin, 16); + + /* Set the initialization vector for server->client transfer. */ + ctxerror = gcry_cipher_setiv(ctx, dhx_s2civ, sizeof(dhx_s2civ)); + if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) { + ret = AFPERR_MISC; + goto error_ctx; + } + /* Encrypt md5_K(clientNonce+1, serverNonce) inplace */ + ctxerror = gcry_cipher_encrypt(ctx, rbuf, 32, NULL, 0); + if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) { + ret = AFPERR_MISC; + goto error_ctx; + } + rbuf += 32; + *rbuflen += 32; + + ret = AFPERR_AUTHCONT; + goto exit; + +error_ctx: + gcry_cipher_close(ctx); +error_noctx: + gcry_mpi_release(serverNonce); + free(K_MD5hash); + K_MD5hash=NULL; +exit: + gcry_mpi_release(K); + gcry_mpi_release(Mb); + gcry_mpi_release(Ra); + gcry_mpi_release(p); + gcry_mpi_release(clientNonce); + return ret; +} + +static int logincont2(void *obj, struct passwd **uam_pwd, + char *ibuf, int ibuflen, + char *rbuf, int *rbuflen) +{ + int ret; + int PAM_error; + char *hostname = NULL; + gcry_mpi_t retServerNonce; + gcry_cipher_hd_t ctx; + gcry_error_t ctxerror; + + *rbuflen = 0; + + /* Packet size should be: Session ID + ServerNonce + Passwd buffer */ + if (ibuflen != 2 + 16 + 256) { + LOG(log_error, logtype_uams, "DHX2: Paket length not correct"); + ret = AFPERR_PARAM; + goto error_noctx; + } + + retServerNonce = gcry_mpi_new(0); + + /* For PAM */ + uam_afpserver_option(obj, UAM_OPTION_CLIENTNAME, (void *) &hostname, NULL); + + /* Set up our encryption context. */ + ctxerror = gcry_cipher_open( &ctx, GCRY_CIPHER_CAST5, GCRY_CIPHER_MODE_CBC, 0); + if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) { + ret = AFPERR_MISC; + goto error_ctx; + } + /* Set key */ + ctxerror = gcry_cipher_setkey(ctx, K_MD5hash, K_hash_len); + if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) { + ret = AFPERR_MISC; + goto error_ctx; + } + /* Set the initialization vector for client->server transfer. */ + ctxerror = gcry_cipher_setiv(ctx, dhx_c2siv, sizeof(dhx_c2siv)); + if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) { + ret = AFPERR_MISC; + goto error_ctx; + } + + /* Skip Session ID */ + ibuf += 2; + + /* Finally: decrypt client's md5_K(serverNonce+1, passwor, C2SIV) inplace */ + ctxerror = gcry_cipher_decrypt(ctx, ibuf, 16+256, NULL, 0); + if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) { + ret = AFPERR_MISC; + goto error_ctx; + } + /* Pull out nonce. Should be serverNonce+1 */ + gcry_mpi_scan(&retServerNonce, GCRYMPI_FMT_USG, ibuf, 16, NULL); + gcry_mpi_sub_ui(retServerNonce, retServerNonce, 1); + if ( gcry_mpi_cmp( serverNonce, retServerNonce) != 0) { + /* We're hacked! */ + ret = AFPERR_NOTAUTH; + goto error_ctx; + } + ibuf += 16; + + LOG(log_info, logtype_uams, "DHX2: logincont2 alive!"); + + /* ---- Start authentication with PAM --- */ + + /* Set these things up for the conv function */ + PAM_password = ibuf; + + ret = AFPERR_NOTAUTH; + PAM_error = pam_start("netatalk", PAM_username, &PAM_conversation, &pamh); + if (PAM_error != PAM_SUCCESS) { + LOG(log_info, logtype_uams, "DHX2: PAM_Error: %s", + pam_strerror(pamh,PAM_error)); + goto error_ctx; + } + + /* solaris craps out if PAM_TTY and PAM_RHOST aren't set. */ + pam_set_item(pamh, PAM_TTY, "afpd"); + pam_set_item(pamh, PAM_RHOST, hostname); + PAM_error = pam_authenticate(pamh, 0); + if (PAM_error != PAM_SUCCESS) { + if (PAM_error == PAM_MAXTRIES) + ret = AFPERR_PWDEXPR; + LOG(log_info, logtype_uams, "DHX2: PAM_Error: %s", + pam_strerror(pamh, PAM_error)); + goto error_ctx; + } + + PAM_error = pam_acct_mgmt(pamh, 0); + if (PAM_error != PAM_SUCCESS ) { + LOG(log_info, logtype_uams, "DHX2: PAM_Error: %s", + pam_strerror(pamh, PAM_error)); + if (PAM_error == PAM_NEW_AUTHTOK_REQD) /* password expired */ + ret = AFPERR_PWDEXPR; +#ifdef PAM_AUTHTOKEN_REQD + else if (PAM_error == PAM_AUTHTOKEN_REQD) + ret = AFPERR_PWDCHNG; +#endif + else + goto error_ctx; + } + +#ifndef PAM_CRED_ESTABLISH +#define PAM_CRED_ESTABLISH PAM_ESTABLISH_CRED +#endif + PAM_error = pam_setcred(pamh, PAM_CRED_ESTABLISH); + if (PAM_error != PAM_SUCCESS) { + LOG(log_info, logtype_uams, "DHX2: PAM_Error: %s", + pam_strerror(pamh, PAM_error)); + goto error_ctx; + } + + PAM_error = pam_open_session(pamh, 0); + if (PAM_error != PAM_SUCCESS) { + LOG(log_info, logtype_uams, "DHX2: PAM_Error: %s", + pam_strerror(pamh, PAM_error)); + goto error_ctx; + } + + memset(ibuf, 0, 256); /* zero out the password */ + *uam_pwd = dhxpwd; + LOG(log_info, logtype_uams, "DHX2: PAM Auth OK!"); + if ( ret == AFPERR_PWDEXPR) + return ret; + ret = AFP_OK; + +error_ctx: + gcry_cipher_close(ctx); +error_noctx: + free(K_MD5hash); + K_MD5hash=NULL; + gcry_mpi_release(serverNonce); + gcry_mpi_release(retServerNonce); + return ret; +} + +static int pam_logincont(void *obj, struct passwd **uam_pwd, + char *ibuf, int ibuflen, + char *rbuf, int *rbuflen) +{ + u_int16_t retID; + int ret; + + /* check for session id */ + retID = ntohs(*(u_int16_t *)ibuf); + if (retID == ID) + ret = logincont1(obj, ibuf, ibuflen, rbuf, rbuflen); + else if (retID == ID+1) + ret = logincont2(obj, uam_pwd, ibuf,ibuflen, rbuf, rbuflen); + else { + LOG(log_info, logtype_uams, "DHX2: Session ID Mismatch"); + ret = AFPERR_PARAM; + } + return ret; +} + + +/* logout */ +static void pam_logout() { + pam_close_session(pamh, 0); + pam_end(pamh, 0); + pamh = NULL; +} + +/**************************** + * --- Change pwd stuff --- */ + +static int changepw_1(void *obj, char *uname, + char *ibuf, int ibuflen, char *rbuf, int *rbuflen) +{ + unsigned int len; + *rbuflen = 0; + + /* Remember it now, use it in changepw_3 */ + PAM_username = uname; + LOG(log_error, logtype_uams, "DHX2 ChangePW: packet 1 processin for user: %s",PAM_username); + + return( dhx2_setup(obj, ibuf, ibuflen, rbuf, rbuflen) ); +} + +static int changepw_2(void *obj, + char *ibuf, int ibuflen, char *rbuf, int *rbuflen) +{ + LOG(log_error, logtype_uams, "DHX2 ChangePW: packet 2 processing"); + return( logincont1(obj, ibuf, ibuflen, rbuf, rbuflen) ); +} + +static int changepw_3(void *obj _U_, + char *ibuf, int ibuflen _U_, + char *rbuf _U_, int *rbuflen _U_) +{ + int ret; + int PAM_error; + uid_t uid; + pam_handle_t *lpamh; + char *hostname = NULL; + gcry_mpi_t retServerNonce; + gcry_cipher_hd_t ctx; + gcry_error_t ctxerror; + + *rbuflen = 0; + + LOG(log_error, logtype_uams, "DHX2 ChangePW: packet 3 processing"); + + /* Packet size should be: Session ID + ServerNonce + 2*Passwd buffer */ + if (ibuflen != 2 + 16 + 2*256) { + LOG(log_error, logtype_uams, "DHX2: Paket length not correct"); + ret = AFPERR_PARAM; + goto error_noctx; + } + + retServerNonce = gcry_mpi_new(0); + + /* For PAM */ + uam_afpserver_option(obj, UAM_OPTION_CLIENTNAME, (void *) &hostname, NULL); + + /* Set up our encryption context. */ + ctxerror = gcry_cipher_open( &ctx, GCRY_CIPHER_CAST5, GCRY_CIPHER_MODE_CBC, 0); + if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) { + ret = AFPERR_MISC; + goto error_ctx; + } + /* Set key */ + ctxerror = gcry_cipher_setkey(ctx, K_MD5hash, K_hash_len); + if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) { + ret = AFPERR_MISC; + goto error_ctx; + } + + /* Set the initialization vector for client->server transfer. */ + ctxerror = gcry_cipher_setiv(ctx, dhx_c2siv, sizeof(dhx_c2siv)); + if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) { + ret = AFPERR_MISC; + goto error_ctx; + } + + /* Skip Session ID */ + ibuf += 2; + + /* Finally: decrypt client's md5_K(serverNonce+1, 2*password, C2SIV) inplace */ + ctxerror = gcry_cipher_decrypt(ctx, ibuf, 16+2*256, NULL, 0); + if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) { + ret = AFPERR_MISC; + goto error_ctx; + } + /* Pull out nonce. Should be serverNonce+1 */ + gcry_mpi_scan(&retServerNonce, GCRYMPI_FMT_USG, ibuf, 16, NULL); + gcry_mpi_sub_ui(retServerNonce, retServerNonce, 1); + if ( gcry_mpi_cmp( serverNonce, retServerNonce) != 0) { + /* We're hacked! */ + ret = AFPERR_NOTAUTH; + goto error_ctx; + } + ibuf += 16; + + /* ---- Start pwd changing with PAM --- */ + ibuf[255] = '\0'; /* For safety */ + ibuf[511] = '\0'; + + LOG(log_info, logtype_uams, "DHX2 Chgpwd: new pwd \'%s\'",ibuf); + LOG(log_info, logtype_uams, "DHX2 Chgpwd: old pwd \'%s\'",ibuf+256); + + /* check if new and old password are equal */ + if (memcmp(ibuf, ibuf + 256, 255) == 0) { + LOG(log_info, logtype_uams, "DHX2 Chgpwd: new and old password are equal"); + ret = AFPERR_PWDSAME; + goto error_ctx; + } + + /* Set these things up for the conv function. PAM_username was set in changepw_1 */ + PAM_password = ibuf + 256; + PAM_error = pam_start("netatalk", PAM_username, &PAM_conversation, &lpamh); + if (PAM_error != PAM_SUCCESS) { + LOG(log_info, logtype_uams, "DHX2 Chgpwd: PAM error in pam_start"); + ret = AFPERR_PARAM; + goto error_ctx; + } + pam_set_item(lpamh, PAM_TTY, "afpd"); + uam_afpserver_option(obj, UAM_OPTION_CLIENTNAME, (void *) &hostname, NULL); + pam_set_item(lpamh, PAM_RHOST, hostname); + uid = geteuid(); + seteuid(0); + PAM_error = pam_authenticate(lpamh,0); + if (PAM_error != PAM_SUCCESS) { + LOG(log_info, logtype_uams, "DHX2 Chgpwd: error authenticating with PAM"); + seteuid(uid); + pam_end(lpamh, PAM_error); + ret = AFPERR_NOTAUTH; + goto error_ctx; + } + PAM_password = ibuf; + PAM_error = pam_chauthtok(lpamh, 0); + seteuid(uid); /* un-root ourselves. */ + memset(ibuf, 0, 512); + if (PAM_error != PAM_SUCCESS) { + LOG(log_info, logtype_uams, "DHX2 Chgpwd: error changing pw with PAM"); + pam_end(lpamh, PAM_error); + ret = AFPERR_ACCESS; + goto error_ctx; + } + pam_end(lpamh, 0); + ret = AFP_OK; + +error_ctx: + gcry_cipher_close(ctx); +error_noctx: +exit: + free(K_MD5hash); + K_MD5hash=NULL; + gcry_mpi_release(serverNonce); + gcry_mpi_release(retServerNonce); + return ret; +} + +static int dhx2_changepw(void *obj _U_, char *uname, + struct passwd *pwd _U_, char *ibuf, int ibuflen _U_, + char *rbuf _U_, int *rbuflen _U_) +{ + /* We use this to serialize the three incoming FPChangePassword calls */ + static int dhx2_changepw_status = 1; + + int ret; + + LOG(log_error, logtype_uams, "DHX2 ChangePW: Start!"); + + switch (dhx2_changepw_status) { + case 1: + ret = changepw_1( obj, uname, ibuf, ibuflen, rbuf, rbuflen); + if ( ret == AFPERR_AUTHCONT) + dhx2_changepw_status += 1; + break; + case 2: + ret = changepw_2( obj, ibuf, ibuflen, rbuf, rbuflen); + if ( ret == AFPERR_AUTHCONT) + dhx2_changepw_status += 1; + else + dhx2_changepw_status = 1; + break; + case 3: + ret = changepw_3( obj, ibuf, ibuflen, rbuf, rbuflen); + dhx2_changepw_status = 1; /* Whether is was succesfull or not: we + restart anyway !*/ + break; + } + return ret; +} + +static int uam_setup(const char *path) +{ + if (uam_register(UAM_SERVER_LOGIN_EXT, path, "DHX2", pam_login, + pam_logincont, pam_logout, pam_login_ext) < 0) + return -1; + if (uam_register(UAM_SERVER_CHANGEPW, path, "DHX2", dhx2_changepw) < 0) + return -1; + return 0; +} + +static void uam_cleanup(void) +{ + uam_unregister(UAM_SERVER_LOGIN, "DHX2"); + uam_unregister(UAM_SERVER_CHANGEPW, "DHX2"); +} + + +UAM_MODULE_EXPORT struct uam_export uams_dhx2 = { + UAM_MODULE_SERVER, + UAM_MODULE_VERSION, + uam_setup, uam_cleanup +}; + + +UAM_MODULE_EXPORT struct uam_export uams_dhx2_pam = { + UAM_MODULE_SERVER, + UAM_MODULE_VERSION, + uam_setup, uam_cleanup +}; + +#endif /* USE_PAM && UAM_DHX2 */ + diff --git a/etc/uams/uams_dhx2_passwd.c b/etc/uams/uams_dhx2_passwd.c new file mode 100644 index 00000000..925c7eec --- /dev/null +++ b/etc/uams/uams_dhx2_passwd.c @@ -0,0 +1,647 @@ +/* + * $Id: uams_dhx2_passwd.c,v 1.1 2008-11-22 12:07:26 didg Exp $ + * + * Copyright (c) 1990,1993 Regents of The University of Michigan. + * Copyright (c) 1999 Adrian Sun (asun@u.washington.edu) + * All Rights Reserved. See COPYRIGHT. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#ifdef UAM_DHX2 + +#include +#include +#include +#include +#include + +#ifdef NETBSD +#define _XOPEN_SOURCE 500 /* for crypt() */ +#endif +#ifdef FREEBSD +#define _XOPEN_SOURCE /* for crypt() */ +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif + +#ifdef HAVE_CRYPT_H +#include +#endif + +#ifdef HAVE_SYS_TIME_H +#include +#endif + +#ifdef HAVE_TIME_H +#include +#endif + +#ifdef SHADOWPW +#include +#endif + +#ifdef HAVE_LIBGCRYPT +#include +#endif + +#include +#include +#include + +/* Number of bits for p which we generate. Everybode out there uses 512, so we beet them */ +#define PRIMEBITS 1024 + +/* hash a number to a 16-bit quantity */ +#define dhxhash(a) ((((unsigned long) (a) >> 8) ^ \ + (unsigned long) (a)) & 0xffff) + +/* Some parameters need be maintained across calls */ +static gcry_mpi_t p, Ra; +static gcry_mpi_t serverNonce; +static char *K_MD5hash = NULL; +static int K_hash_len; +static u_int16_t ID; + +/* The initialization vectors for CAST128 are fixed by Apple. */ +static unsigned char dhx_c2siv[] = { 'L', 'W', 'a', 'l', 'l', 'a', 'c', 'e' }; +static unsigned char dhx_s2civ[] = { 'C', 'J', 'a', 'l', 'b', 'e', 'r', 't' }; + +/* Static variables used to communicate between the conversation function + * and the server_login function */ +static struct passwd *dhxpwd; + +/********************************************************* + * Crypto helper func to generate p and g for use in DH. + * libgcrpyt doesn't provide one directly. + * Algorithm taken from GNUTLS:gnutls_dh_primes.c + *********************************************************/ + +/** + * This function will generate a new pair of prime and generator for use in + * the Diffie-Hellman key exchange. + * The bits value should be one of 768, 1024, 2048, 3072 or 4096. + **/ + +static int +dh_params_generate (gcry_mpi_t *ret_p, gcry_mpi_t *ret_g, unsigned int bits) { + + int result, times = 0, qbits; + + gcry_mpi_t g = NULL, prime = NULL; + gcry_mpi_t *factors = NULL; + gcry_error_t err; + + /* Version check should be the very first call because it + makes sure that important subsystems are intialized. */ + if (!gcry_check_version (GCRYPT_VERSION)) { + LOG(log_info, logtype_uams, "PAM DHX2: libgcrypt versions mismatch. Need: %u", GCRYPT_VERSION); + result = AFPERR_MISC; + goto error; + } + + if (bits < 256) + qbits = bits / 2; + else + qbits = (bits / 40) + 105; + + if (qbits & 1) /* better have an even number */ + qbits++; + + /* find a prime number of size bits. */ + do { + if (times) { + gcry_mpi_release (prime); + gcry_prime_release_factors (factors); + } + err = gcry_prime_generate (&prime, bits, qbits, &factors, NULL, NULL, + GCRY_STRONG_RANDOM, GCRY_PRIME_FLAG_SPECIAL_FACTOR); + if (err != 0) { + result = AFPERR_MISC; + goto error; + } + err = gcry_prime_check (prime, 0); + times++; + } while (err != 0 && times < 10); + + if (err != 0) { + result = AFPERR_MISC; + goto error; + } + + /* generate the group generator. */ + err = gcry_prime_group_generator (&g, prime, factors, NULL); + if (err != 0) { + result = AFPERR_MISC; + goto error; + } + + gcry_prime_release_factors (factors); + factors = NULL; + + if (ret_g) + *ret_g = g; + else + gcry_mpi_release (g); + if (ret_p) + *ret_p = prime; + else + gcry_mpi_release (prime); + + return 0; + +error: + gcry_prime_release_factors (factors); + gcry_mpi_release (g); + gcry_mpi_release (prime); + + return result; +} + +static int dhx2_setup(void *obj, char *ibuf, int ibuflen _U_, + char *rbuf, int *rbuflen) +{ + int i, ret; + + unsigned int g_uint; + gcry_mpi_t g, Ma; + char *Ra_binary = NULL; + gcry_cipher_hd_t ctx; + gcry_error_t ctxerror; + + const int g_len = 4; + size_t len; + size_t nwritten; +#ifdef SHADOWPW + struct spwd *sp; +#endif /* SHADOWPW */ + *rbuflen = 0; + + /* Initialize passwd/shadow */ +#ifdef SHADOWPW + if (( sp = getspnam( dhxpwd->pw_name )) == NULL ) { + LOG(log_info, logtype_uams, "no shadow passwd entry for this user"); + return AFPERR_NOTAUTH; + } + dhxpwd->pw_passwd = sp->sp_pwdp; +#endif /* SHADOWPW */ + + if (!dhxpwd->pw_passwd) + return AFPERR_NOTAUTH; + + /* Initialize DH params */ + + p = gcry_mpi_new(0); + g = gcry_mpi_new(0); + Ra = gcry_mpi_new(0); + Ma = gcry_mpi_new(0); + + /* Generate p and g for DH */ + ret = dh_params_generate( &p, &g, PRIMEBITS); + if (ret != 0) { + LOG(log_info, logtype_uams, "DHX2: Couldn't generate p and g"); + ret = AFPERR_MISC; + goto error; + } + + /* Generate our random number Ra. */ + Ra_binary = calloc(1, PRIMEBITS/8); + if (Ra_binary == NULL) { + ret = AFPERR_MISC; + goto error; + } + gcry_randomize(Ra_binary, PRIMEBITS/8, GCRY_STRONG_RANDOM); + gcry_mpi_scan(&Ra, GCRYMPI_FMT_USG, Ra_binary, PRIMEBITS/8, NULL); + free(Ra_binary); + Ra_binary = NULL; + + /* Ma = g^Ra mod p. This is our "public" key */ + gcry_mpi_powm(Ma, g, Ra, p); + + /* ------- DH Init done ------ */ + /* Start building reply packet */ + + /* Session ID first */ + ID = dhxhash(obj); + *(u_int16_t *)rbuf = htons(ID); + rbuf += 2; + *rbuflen += 2; + + /* g is next */ + gcry_mpi_print( GCRYMPI_FMT_USG, rbuf, 4, &nwritten, g); + if (nwritten < 4) { + memmove( rbuf+4-nwritten, rbuf, nwritten); + memset( rbuf, 0, 4-nwritten); + } + rbuf += 4; + *rbuflen += 4; + + /* len = length of p = PRIMEBITS/8 */ + *(u_int16_t *)rbuf = htons((u_int16_t) PRIMEBITS/8); + rbuf += 2; + *rbuflen += 2; + + /* p */ + gcry_mpi_print( GCRYMPI_FMT_USG, rbuf, PRIMEBITS/8, NULL, p); + rbuf += PRIMEBITS/8; + *rbuflen += PRIMEBITS/8; + + /* Ma */ + gcry_mpi_print( GCRYMPI_FMT_USG, rbuf, PRIMEBITS/8, &len, Ma); + if (len < PRIMEBITS/8) { + memmove(rbuf + (PRIMEBITS/8) - len, rbuf, len); + memset(rbuf, 0, (PRIMEBITS/8) - len); + } + rbuf += PRIMEBITS/8; + *rbuflen += PRIMEBITS/8; + + ret = AFPERR_AUTHCONT; + +error: /* We exit here anyway */ + /* We will only need p and Ra later, but mustn't forget to release it ! */ + gcry_mpi_release(g); + gcry_mpi_release(Ma); + return ret; +} + +/* -------------------------------- */ +static int login(void *obj, char *username, int ulen, struct passwd **uam_pwd _U_, + char *ibuf, int ibuflen, + char *rbuf, int *rbuflen) +{ + if (( dhxpwd = uam_getname(obj, username, ulen)) == NULL ) { + LOG(log_info, logtype_uams, "DHX2: unknown username"); + return AFPERR_PARAM; + } + + LOG(log_info, logtype_uams, "DHX2 login: %s", username); + return dhx2_setup(obj, ibuf, ibuflen, rbuf, rbuflen); +} + +/* -------------------------------- */ +/* dhx login: things are done in a slightly bizarre order to avoid + * having to clean things up if there's an error. */ +static int passwd_login(void *obj, struct passwd **uam_pwd, + char *ibuf, int ibuflen, + char *rbuf, int *rbuflen) +{ + char *username; + int len, ulen; + + *rbuflen = 0; + + /* grab some of the options */ + if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &ulen) < 0) { + LOG(log_info, logtype_uams, "DHX2: uam_afpserver_option didn't meet uam_option_username -- %s", + strerror(errno)); + return AFPERR_PARAM; + } + + len = (unsigned char) *ibuf++; + if ( len > ulen ) { + LOG(log_info, logtype_uams, "DHX2: Signature Retieval Failure -- %s", + strerror(errno)); + return AFPERR_PARAM; + } + + memcpy(username, ibuf, len ); + ibuf += len; + username[ len ] = '\0'; + + if ((unsigned long) ibuf & 1) /* pad to even boundary */ + ++ibuf; + + return (login(obj, username, ulen, uam_pwd, ibuf, ibuflen, rbuf, rbuflen)); +} + +/* ----------------------------- */ +static int passwd_login_ext(void *obj, char *uname, struct passwd **uam_pwd, + char *ibuf, int ibuflen, + char *rbuf, int *rbuflen) +{ + char *username; + int len, ulen; + u_int16_t temp16; + + *rbuflen = 0; + + /* grab some of the options */ + if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &ulen) < 0) { + LOG(log_info, logtype_uams, "DHX2: uam_afpserver_option didn't meet uam_option_username -- %s", + strerror(errno)); + return AFPERR_PARAM; + } + + if (*uname != 3) + return AFPERR_PARAM; + uname++; + memcpy(&temp16, uname, sizeof(temp16)); + len = ntohs(temp16); + + if ( !len || len > ulen ) { + LOG(log_info, logtype_uams, "DHX2: Signature Retrieval Failure -- %s", + strerror(errno)); + return AFPERR_PARAM; + } + memcpy(username, uname +2, len ); + username[ len ] = '\0'; + + return (login(obj, username, ulen, uam_pwd, ibuf, ibuflen, rbuf, rbuflen)); +} + +/* -------------------------------- */ + +static int logincont1(void *obj, struct passwd **uam_pwd, + char *ibuf, int ibuflen, + char *rbuf, int *rbuflen) +{ + u_int16_t retID; + size_t nwritten; + int ret; + *rbuflen = 0; + + gcry_mpi_t Mb, K, clientNonce; + char *K_bin = NULL; + char serverNonce_bin[16]; + gcry_cipher_hd_t ctx; + gcry_error_t ctxerror; + + Mb = gcry_mpi_new(0); + K = gcry_mpi_new(0); + clientNonce = gcry_mpi_new(0); + serverNonce = gcry_mpi_new(0); + + /* Packet size should be: Session ID + Ma + Encrypted client nonce */ + if (ibuflen != 2 + PRIMEBITS/8 + 16) { + LOG(log_error, logtype_uams, "DHX2: Paket length not correct"); + ret = AFPERR_PARAM; + goto error_noctx; + } + + /* Skip session id */ + ibuf += 2; + + /* Extract Mb, client's "public" key */ + gcry_mpi_scan(&Mb, GCRYMPI_FMT_USG, ibuf, PRIMEBITS/8, NULL); + ibuf += PRIMEBITS/8; + + /* Now finally generate the Key: K = Mb^Ra mod p */ + gcry_mpi_powm(K, Mb, Ra, p); + + /* We need K in binary form in order to ... */ + K_bin = calloc(1, PRIMEBITS/8); + if (K_bin == NULL) { + ret = AFPERR_MISC; + goto error_noctx; + } + gcry_mpi_print(GCRYMPI_FMT_USG, K_bin, PRIMEBITS/8, &nwritten, K); + if (nwritten < PRIMEBITS/8) { + memmove(K_bin + PRIMEBITS/8 - nwritten, K_bin, nwritten); + memset(K_bin, 0, PRIMEBITS/8 - nwritten); + } + + /* ... generate the MD5 hash of K. K_MD5hash is what we actually use ! */ + K_MD5hash = calloc(1, K_hash_len = gcry_md_get_algo_dlen(GCRY_MD_MD5)); + if (K_MD5hash == NULL) { + ret = AFPERR_MISC; + goto error_noctx; + } + gcry_md_hash_buffer(GCRY_MD_MD5, K_MD5hash, K_bin, PRIMEBITS/8); + free(K_bin); + K_bin = NULL; + + /* FIXME: To support the Reconnect UAM, we need to store this key somewhere */ + + /* Set up our encryption context. */ + ctxerror = gcry_cipher_open( &ctx, GCRY_CIPHER_CAST5, GCRY_CIPHER_MODE_CBC, 0); + if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) { + ret = AFPERR_MISC; + goto error_ctx; + } + /* Set key */ + ctxerror = gcry_cipher_setkey(ctx, K_MD5hash, K_hash_len); + if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) { + ret = AFPERR_MISC; + goto error_ctx; + } + /* Set the initialization vector for client->server transfer. */ + ctxerror = gcry_cipher_setiv(ctx, dhx_c2siv, sizeof(dhx_c2siv)); + if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) { + ret = AFPERR_MISC; + goto error_ctx; + } + /* Finally: decrypt client's md5_K(client nonce, C2SIV) inplace */ + ctxerror = gcry_cipher_decrypt(ctx, ibuf, 16, NULL, 0); + if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) { + ret = AFPERR_MISC; + goto error_ctx; + } + /* Pull out clients nonce */ + gcry_mpi_scan(&clientNonce, GCRYMPI_FMT_USG, ibuf, 16, NULL); + /* Increment nonce */ + gcry_mpi_add_ui(clientNonce, clientNonce, 1); + + /* Generate our nonce and remember it for Logincont2 */ + gcry_create_nonce(serverNonce_bin, 16); /* We'll use this here */ + gcry_mpi_scan(&serverNonce, GCRYMPI_FMT_USG, serverNonce_bin, 16, NULL); /* For use in Logincont2 */ + + /* ---- Start building reply packet ---- */ + + /* Session ID + 1 first */ + *(u_int16_t *)rbuf = htons(ID+1); + rbuf += 2; + *rbuflen += 2; + + /* Client nonce + 1 */ + gcry_mpi_print(GCRYMPI_FMT_USG, rbuf, PRIMEBITS/8, NULL, clientNonce); + /* Server nonce */ + memcpy(rbuf+16, serverNonce_bin, 16); + + /* Set the initialization vector for server->client transfer. */ + ctxerror = gcry_cipher_setiv(ctx, dhx_s2civ, sizeof(dhx_s2civ)); + if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) { + ret = AFPERR_MISC; + goto error_ctx; + } + /* Encrypt md5_K(clientNonce+1, serverNonce) inplace */ + ctxerror = gcry_cipher_encrypt(ctx, rbuf, 32, NULL, 0); + if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) { + ret = AFPERR_MISC; + goto error_ctx; + } + rbuf += 32; + *rbuflen += 32; + + ret = AFPERR_AUTHCONT; + goto exit; + +error_ctx: + gcry_cipher_close(ctx); +error_noctx: + gcry_mpi_release(serverNonce); + free(K_MD5hash); + K_MD5hash=NULL; +exit: + gcry_mpi_release(K); + gcry_mpi_release(Mb); + gcry_mpi_release(Ra); + gcry_mpi_release(p); + gcry_mpi_release(clientNonce); + return ret; +} + +static int logincont2(void *obj, struct passwd **uam_pwd, + char *ibuf, int ibuflen, + char *rbuf, int *rbuflen) +{ +#ifdef SHADOWPW + struct spwd *sp; +#endif /* SHADOWPW */ + size_t nwritten; + int ret; + char *p; + *rbuflen = 0; + gcry_mpi_t retServerNonce; + gcry_cipher_hd_t ctx; + gcry_error_t ctxerror; + + /* Packet size should be: Session ID + ServerNonce + Passwd buffer */ + if (ibuflen != 2 + 16 + 256) { + LOG(log_error, logtype_uams, "DHX2: Paket length not correct"); + ret = AFPERR_PARAM; + goto error_noctx; + } + + retServerNonce = gcry_mpi_new(0); + + /* Set up our encryption context. */ + ctxerror = gcry_cipher_open( &ctx, GCRY_CIPHER_CAST5, GCRY_CIPHER_MODE_CBC, 0); + if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) { + ret = AFPERR_MISC; + goto error_ctx; + } + /* Set key */ + ctxerror = gcry_cipher_setkey(ctx, K_MD5hash, K_hash_len); + if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) { + ret = AFPERR_MISC; + goto error_ctx; + } + /* Set the initialization vector for client->server transfer. */ + ctxerror = gcry_cipher_setiv(ctx, dhx_c2siv, sizeof(dhx_c2siv)); + if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) { + ret = AFPERR_MISC; + goto error_ctx; + } + + /* Skip Session ID */ + ibuf += 2; + + /* Finally: decrypt client's md5_K(serverNonce+1, passwor, C2SIV) inplace */ + ctxerror = gcry_cipher_decrypt(ctx, ibuf, 16+256, NULL, 0); + if (gcry_err_code(ctxerror) != GPG_ERR_NO_ERROR) { + ret = AFPERR_MISC; + goto error_ctx; + } + /* Pull out nonce. Should be serverNonce+1 */ + gcry_mpi_scan(&retServerNonce, GCRYMPI_FMT_USG, ibuf, 16, NULL); + gcry_mpi_sub_ui(retServerNonce, retServerNonce, 1); + if ( gcry_mpi_cmp( serverNonce, retServerNonce) != 0) { + /* We're hacked! */ + ret = AFPERR_NOTAUTH; + goto error_ctx; + } + ibuf += 16; /* ibuf now point to passwd in cleartext */ + + /* ---- Start authentication --- */ + ret = AFPERR_NOTAUTH; + + p = crypt( ibuf, dhxpwd->pw_passwd ); + memset(ibuf, 0, 255); + if ( strcmp( p, dhxpwd->pw_passwd ) == 0 ) { + *uam_pwd = dhxpwd; + ret = AFP_OK; + } + +#ifdef SHADOWPW + if (( sp = getspnam( dhxpwd->pw_name )) == NULL ) { + LOG(log_info, logtype_uams, "no shadow passwd entry for %s", dhxpwd->pw_name); + ret = AFPERR_NOTAUTH; + goto exit; + } + + /* check for expired password */ + if (sp && sp->sp_max != -1 && sp->sp_lstchg) { + time_t now = time(NULL) / (60*60*24); + int32_t expire_days = sp->sp_lstchg - now + sp->sp_max; + if ( expire_days < 0 ) { + LOG(log_info, logtype_uams, "password for user %s expired", dhxpwd->pw_name); + ret = AFPERR_PWDEXPR; + goto exit; + } + } +#endif /* SHADOWPW */ + +error_ctx: + gcry_cipher_close(ctx); +error_noctx: +exit: + free(K_MD5hash); + K_MD5hash=NULL; + gcry_mpi_release(serverNonce); + gcry_mpi_release(retServerNonce); + return ret; +} + +static int passwd_logincont(void *obj, struct passwd **uam_pwd, + char *ibuf, int ibuflen, + char *rbuf, int *rbuflen) +{ + u_int16_t retID; + int ret; + + /* check for session id */ + retID = ntohs(*(u_int16_t *)ibuf); + if (retID == ID) + ret = logincont1(obj, uam_pwd, ibuf, ibuflen, rbuf, rbuflen); + else if (retID == ID+1) + ret = logincont2(obj, uam_pwd, ibuf,ibuflen, rbuf, rbuflen); + else { + LOG(log_info, logtype_uams, "DHX2: Session ID Mismatch"); + ret = AFPERR_PARAM; + } + return ret; +} + +static int uam_setup(const char *path) +{ + if (uam_register(UAM_SERVER_LOGIN_EXT, path, "DHX2", passwd_login, + passwd_logincont, NULL, passwd_login_ext) < 0) + return -1; + return 0; +} + +static void uam_cleanup(void) +{ + uam_unregister(UAM_SERVER_LOGIN, "DHX2"); +} + + +UAM_MODULE_EXPORT struct uam_export uams_dhx2 = { + UAM_MODULE_SERVER, + UAM_MODULE_VERSION, + uam_setup, uam_cleanup +}; + + +UAM_MODULE_EXPORT struct uam_export uams_dhx2_pam = { + UAM_MODULE_SERVER, + UAM_MODULE_VERSION, + uam_setup, uam_cleanup +}; + +#endif /* UAM_DHX2 */ + diff --git a/macros/libgcrpyt.m4 b/macros/libgcrpyt.m4 new file mode 100644 index 00000000..599869a9 --- /dev/null +++ b/macros/libgcrpyt.m4 @@ -0,0 +1,51 @@ +AC_DEFUN([NETATALK_CHECK_LIBGCRYPT], [ + AC_ARG_ENABLE(gcrypt, [ --disable-gcrypt build without gcrypt (limits supported UAMs)]) + if test "x$enable_gcrypt" != "xno" ; then + ac_save_CFLAGS="$CFLAGS" + ac_save_LIBS="$LIBS" + CFLAGS="" + LIBS="" + AC_CHECK_LIB([gcrypt], [gcry_cipher_open],, [neta_cv_have_libgcrypt=no]) + case $host in + *-*-darwin*) + dnl -- Darwin is a special case: check if version > 1.4.0 + AC_MSG_CHECKING([for correct gcrypt version]) + AC_RUN_IFELSE( + [AC_LANG_PROGRAM([ + #include + #include ],[ + char*p= GCRYPT_VERSION; + unsigned int vers; + vers=atoi(p)*10000; + p=strchr(p,'.')+1; + vers+=atoi(p)*100; + p=strchr(p,'.')+1; + vers+=atoi(p); + if (vers<10400) return 1; + ])], + [AC_MSG_RESULT([yes])], + [ + AC_MSG_ERROR([version is < 1.4.0]) + neta_cv_have_libgcrypt=no + ]) + ;; + *) + ;; + esac + if test x$neta_cv_have_libgcrypt != xno ; then + AC_MSG_RESULT([Enabling UAM: DHX2]) + AC_DEFINE(LIBGCRYPT_DHX, 1, [Define if the DHX modules should be built with libgcrypt]) + AC_DEFINE(UAM_DHX2, 1, [Define if the DHX UAM modules should be compiled]) + neta_cv_have_libgcrypt=yes + neta_cv_compile_dhx2=yes + LIBGCRYPT_CFLAGS="$CFLAGS" + LIBGCRYPT_LIBS="$LIBS" + CFLAGS_REMOVE_USR_INCLUDE(LIBGCRYPT_CFLAGS) + LIB_REMOVE_USR_LIB(LIBGCRYPT_LIBS) + AC_SUBST(LIBGCRYPT_CFLAGS) + AC_SUBST(LIBGCRYPT_LIBS) + fi + CFLAGS="$ac_save_CFLAGS" + LIBS="$ac_save_LIBS" + fi +]) \ No newline at end of file diff --git a/macros/ssl-check.m4 b/macros/ssl-check.m4 index 6ff42425..9de28019 100644 --- a/macros/ssl-check.m4 +++ b/macros/ssl-check.m4 @@ -1,4 +1,4 @@ -dnl $Id: ssl-check.m4,v 1.13 2005-04-28 20:50:05 bfernhomberg Exp $ +dnl $Id: ssl-check.m4,v 1.14 2008-11-22 12:07:26 didg Exp $ dnl Autoconf macro to check for SSL or OpenSSL AC_DEFUN([AC_CRYPT], [ @@ -38,7 +38,8 @@ AC_DEFUN([AC_PATH_SSL], [ SSL_LIBS="" saved_LIBS=$LIBS saved_CFLAGS=$CFLAGS - compile_ssl=no + neta_cv_have_openssl=no +dnl compile_ssl=no dnl make sure atalk_libname is defined beforehand [[ -n "$atalk_libname" ]] || AC_MSG_ERROR([internal error, atalk_libname undefined]) @@ -64,13 +65,14 @@ dnl FIXME: The following looks crude and probably doesn't work properly. AC_DEFINE(OPENSSL_DHX, 1, [Define if the OpenSSL DHX modules should be built]) AC_DEFINE(UAM_DHX, 1, [Define if the DHX UAM modules should be compiled]) - compile_ssl=yes + neta_cv_have_openssl=yes + neta_cv_compile_dhx=yes CFLAGS=$saved_CFLAGS LIBS=$saved_LIBS break fi done - if test "x$compile_ssl" = "xno"; then + if test "x$neta_cv_have_openssl" = "xno"; then AC_MSG_RESULT([no]) fi fi diff --git a/macros/summary.m4 b/macros/summary.m4 index 5f25f915..3f32834e 100644 --- a/macros/summary.m4 +++ b/macros/summary.m4 @@ -1,4 +1,4 @@ -dnl $Id: summary.m4,v 1.2 2005-04-28 20:50:05 bfernhomberg Exp $ +dnl $Id: summary.m4,v 1.3 2008-11-22 12:07:26 didg Exp $ dnl Autoconf macros, display configure summary AC_DEFUN([AC_NETATALK_CONFIG_SUMMARY], [ @@ -26,8 +26,13 @@ AC_DEFUN([AC_NETATALK_CONFIG_SUMMARY], [ if test "x$netatalk_cv_use_shadowpw" = "xyes"; then uams_using_options="$uams_using_options SHADOW" fi - if test "x$compile_ssl" = "xyes"; then + if test "x$neta_cv_compile_dhx" = "xyes"; then AC_MSG_RESULT([ DHX ($uams_using_options)]) + fi + if test "x$neta_cv_compile_dhx2" = "xyes"; then + AC_MSG_RESULT([ DHX2 ($uams_using_options)]) + fi + if test "x$neta_cv_have_openssl" = "xyes"; then AC_MSG_RESULT([ RANDNUM ($uams_using_options)]) fi if test x"$netatalk_cv_build_krb5_uam" = x"yes"; then @@ -73,11 +78,16 @@ AC_DEFUN([AC_NETATALK_LIBS_SUMMARY], [ AC_MSG_RESULT([Using libraries:]) AC_MSG_RESULT([ LIBS = $LIBS]) AC_MSG_RESULT([ CFLAGS = $CFLAGS]) - if test x"$compile_ssl" = x"yes"; then + if test x"$neta_cv_have_openssl" = x"yes"; then AC_MSG_RESULT([ SSL:]) AC_MSG_RESULT([ LIBS = $SSL_LIBS]) AC_MSG_RESULT([ CFLAGS = $SSL_CFLAGS]) fi + if test x"$neta_cv_have_libgcrypt" = x"yes"; then + AC_MSG_RESULT([ LIBGCRYPT:]) + AC_MSG_RESULT([ LIBS = $LIBGCRYPT_LIBS]) + AC_MSG_RESULT([ CFLAGS = $LIBGCRYPT_CFLAGS]) + fi if test x"$netatalk_cv_use_pam" = x"yes"; then AC_MSG_RESULT([ PAM:]) AC_MSG_RESULT([ LIBS = $PAM_LIBS]) -- 2.39.2