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