Knowledgebase
Linux kerberos & active directory authentication
Posted by Jason Gerfen on 05 May 2010 06:49 AM

PAM_KRB5+LDAP

The pam_krb5+ldap is a modification of the recent release of the pam_krb5 Redhat Kerberos Authentication Module.

This module was modified to include remote user support utilizing Active Directory w/ Unix extensions and/or OpenLDAP. Some more details regarding the account lookup, passwordless account creation as well as options to build the users home directory is listed below:

Installation:

  • Verify required libraries are installed
    %> wget http://web.mit.edu/kerberos/dist/krb5/1.6/krb5-1.6.3-signed.tar
    %> wget ftp://ftp.openldap.org/pub/OpenLDAP/openldap-stable/openldap-stable-20071118.tgz
    %> wget http://www.kernel.org/pub/linux/libs/pam/library/Linux-PAM-1.0.1.tar.gz
    
    *You will need to install each package
  • Download the pam_krb5+ldap source
    %> wget http://pam_krb5_ldap.scl.utah.edu/downloads/pam_krb5-2.3.1-3+ldap.tgz
    
  • Extract the source code
    %> tar zxvf pam_krb5-2.3.1-3+ldap.tgz
    
  • Run the configure command for module
    %> ./configure --with-ldap
    
  • Perform installation
    %> make && make install
    
  • Configuration

  • Edit configuration options for module within the '/etc/krb5.conf' file
    [libdefaults]
            default_realm = UTAH.EDU
            clockskew = 300
    
    [realms]
    UTAH.EDU = {
            kdc = 155.99.1.95
            default_domain = scl.utah.edu
            admin_server = 155.99.1.95
    }
    
    [logging]
            kdc = FILE:/var/log/krb5kdc.log
            admin_server = FILE:/var/log/kadmin.log
            default = FILE:/var/log/krb5lib.log
    [domain_realm]
            .scl.utah.edu = UTAH.EDU
    
    [appdefaults]
    pam = {
            ticket_lifetime = 1d
            renew_lifetime = 1d
            forwardable = true
            proxiable = false
            retain_after_close = false
            minimum_uid = 2
            try_first_pass = true
            ignore_root = true
    
            schema = ad
            ldapservs = 155.97.16.130 155.97.15.2
            ldapport = 389
            binddn = uid=ldapaccess,ou=Users,dc=scl,dc=utah,dc=edu
            basedn = ou=campus,dc=scl,dc=utah,dc=edu
            ldapuser = [readonly-username]
            ldappass = [readonly-password]
            
            passwd = /etc/passwd
            shadow = /etc/shadow
            groups = /etc/group
    
            groups_list = audio,cdrom,cdrw,usb,plugdev,video,games
    
            # If you define these they will
            # over write anything obtained from
            # ldap/active directory
            homedir = /home
            defshell = /bin/bash
    }
    
  • Add module to PAM authentication stack
    #%PAM-1.0
    
    auth            requisite       pam_lockout.so user=^gp*
    auth            required       pam_env.so
    auth            sufficient      pam_krb5.so
    auth            sufficient      pam_unix.so try_first_pass likeauth nullok
    auth            required       pam_deny.so
    
    account         required        pam_unix.so
    
    password        required        pam_cracklib.so difok=2 minlen=8 dcredit=2 ocredit=2 retry=3
    password        sufficient      pam_krb5.so
    password        sufficient      pam_unix.so try_first_pass use_authtok nullok sha512 shadow
    password        required        pam_deny.so
    
    session         required        pam_limits.so
    session         required        pam_env.so
    session         optional        pam_krb5.so
    session         required        pam_unix.so
    session         required        pam_mkhomedir.so silent skel=/etc/skel/ umask=0022
    session         optional        pam_permit.so
    
  • Alternate Installation

  • An alternate method is to patch the latest version (instructions)
    1. Download latest source for pam_krb5
      %> wget http://people.redhat.com/nalin/pam_krb5/pam_krb5-.tar.gz
    2. Next simply extract the source code
      %> tar zxvf pam_krb5-.tar.gz
    3. Get a copy of the patch to include OpenLDAP/Active Directory support
      %> wget http://pam_krb5_ldap.scl.utah.edu/downloads/pam_krb5-2.3.1-1+ldap-08112009.patch
    4. Now simply patch the source like so
      %> patch -p0 < pam_krb5-2.3.1-1+ldap-08112009.patch
    5. Now you can simply execute the configure command with the new --with-ldap option
      %> ./configure --with-ldap && make && make install
    6. If you run into problems or errors when trying to run ./configure you may need to run the following:
      %> aclocal && autoconf && automake && libtoolize --copy --force
  • Details of patch file

  • The patch if new revisions of the original pam_krb5 are released

    diff -Naur pam_krb5-2.3.1-1/README.ldap pam_krb5-2.3.1-2+ldap/README.ldap
    --- pam_krb5-2.3.1-1/README.ldap	1969-12-31 17:00:00.000000000 -0700
    +++ pam_krb5-2.3.1-2+ldap/README.ldap	2010-01-06 07:19:36.000000000 -0700
    @@ -0,0 +1,75 @@
    +OpenLDAP/Active Directory Functionality
    +
    +Assists with UID/GID mapping for users that are not stored
    +locally, but within an OpenLDAP or Active Directory.
    +
    +In order to enable this functionality simply run the configure
    +command with the --with-ldap argument like so.
    +
    +%> ./configure --with-ldap
    +
    +This will enable the linking against the required ldap.h and 
    +-lldap libraries.
    +
    +In addition to enabling this feature a simple configuration is
    +required within the krb5.conf appdefaults section. An example
    +is listed here:
    +
    +[appdefaults]
    +pam = {
    +        ticket_lifetime = 1d
    +        renew_lifetime = 1d
    +        forwardable = true
    +        proxiable = false
    +        retain_after_close = false
    +        minimum_uid = 2
    +        try_first_pass = true
    +        ignore_root = true
    +
    +        schema = ad # ad | ldap to meet your requirements
    +        ldapservs = ad.domain.com ldap.domain.com
    +        ldapport = 389
    +        binddn = uid=read-only-user,ou=Users,dc=ldap,dc=domain,dc=com
    +        basedn = ou=campus,dc=ad,dc=domain,dc=com
    +        ldapuser = [readonly-username]
    +        ldappass = [readonly-password]
    +        
    +        passwd = /etc/passwd
    +        shadow = /etc/shadow
    +
    +	# If you wish to add the user to groups
    +	# a comma separted list should be used
    +	group = /etc/group
    +	groups = audio,cdrom,cdrw,usb,plugdev,video,games
    +
    +        # If you define these they will
    +        # over write anything obtained from
    +        # ldap/active directory
    +        #homedir = /home
    +        #defshell = /bin/bash
    +}
    +
    +You will also need to configure pam to utilize
    +the krb5+ldap authentication. An example of the
    +/etc/pam.d/system-auth file is listed here.
    +
    +auth   	    required   	    pam_env.so 
    +auth        sufficient	    pam_krb5.so
    +auth	    sufficient	    pam_unix.so try_first_pass likeauth nullok 
    +auth	    required        pam_deny.so
    +
    +account	    required	    pam_unix.so 
    + 
    +password    required	    pam_cracklib.so difok=2 minlen=8 dcredit=2 ocredit=2 retry=3 
    +password    sufficient	    pam_krb5.so
    +password    sufficient	    pam_unix.so try_first_pass use_authtok nullok sha512 shadow 
    +password    required	    pam_deny.so
    +
    +session	    required	    pam_limits.so 
    +session	    required	    pam_env.so 
    +session     optional	    pam_krb5.so
    +session	    required	    pam_unix.so 
    +session	    required	    pam_mkhomedir.so silent skel=/etc/skel umask=0022
    +session	    optional	    pam_permit.so
    +
    +Please report any bugs to jason.gerfen@gmail.com.
    diff -Naur pam_krb5-2.3.1-1/config.h.in pam_krb5-2.3.1-2+ldap/config.h.in
    --- pam_krb5-2.3.1-1/config.h.in	2008-04-09 16:48:46.000000000 -0600
    +++ pam_krb5-2.3.1-2+ldap/config.h.in	2009-11-12 12:29:56.000000000 -0700
    @@ -401,6 +408,9 @@
     /* Version number of package */
     #undef VERSION
     
    +/* Define if you require OpenLDAP support. */
    +#undef WITH_LDAP
    +
     
      /* Why Y_? Because if we somehow pull in OpenSSL's , it'll
         undefine _ out from under us. */
    diff -Naur pam_krb5-2.3.1-1/configure.ac pam_krb5-2.3.1-2+ldap/configure.ac
    --- pam_krb5-2.3.1-1/configure.ac	2008-04-09 16:48:26.000000000 -0600
    +++ pam_krb5-2.3.1-2+ldap/configure.ac	2009-12-18 09:13:51.000000000 -0700
    @@ -52,6 +52,33 @@
     	NO_MAN_KRB4=""
     fi
     
    +AC_ARG_WITH(ldap,
    +AC_HELP_STRING(--with-ldap,[Enable OpenLDAP features for dynamic UID/GID TKT verification.]),
    +            ldap=$withval,
    +	    ldap=AUTO)
    +if test x$with_ldap = xyes ; then
    +   AC_CHECK_HEADER([ldap.h], [:], [AC_MSG_ERROR([ldap.h not found! Please install the OpenLDAP libs.])])
    +   LIBS="-lldap $LIBS $KRB5_LIBS $LIBSsave"
    +   AC_CHECK_FUNC(ldap_initialization,,[AC_CHECK_LIB(ldap,ldap_initialization)])
    +   AC_CHECK_FUNC(ldap_simple_bind_s,,[AC_CHECK_LIB(ldap,ldap_simple_bind_s)])
    +   AC_CHECK_FUNC(ldap_search_s,,[AC_CHECK_LIB(ldap,ldap_search_s)])
    +   AC_CHECK_FUNC(ldap_first_entry,,[AC_CHECK_LIB(ldap,ldap_first_entry)])
    +   AC_CHECK_FUNC(ldap_get_values,,[AC_CHECK_LIB(ldap,ldap_get_values)])
    +   AC_CHECK_FUNC(ldap_msgfree,,[AC_CHECK_LIB(ldap,ldap_msgfree)])
    +   AC_CHECK_FUNC(ldap_unbind,,[AC_CHECK_LIB(ldap,ldap_unbind)])
    +   AC_DEFINE(WITH_LDAP,1,[Define if you require OpenLDAP support.])
    +   WITH_LDAP="yes"
    +   AC_CHECK_HEADER([pwd.h], [:], [AC_MSG_ERROR([pwd.h not found! Please install the GNU libraries.])])
    +   AC_CHECK_FUNC(putpwent,,[AC_CHECK_LIB(pwd,putpwent)])
    +   AC_CHECK_HEADER([shadow.h], [:], [AC_MSG_ERROR([shadow.h not found! Please install the GNU libraries.])])
    +   AC_CHECK_FUNC(lckpwdf,,[AC_CHECK_LIB(shadow,lckpwdf)])
    +   AC_CHECK_FUNC(ulckpwdf,,[AC_CHECK_LIB(shadow,ulckpwdf)])
    +   AC_CHECK_FUNC(putspent,,[AC_CHECK_LIB(shadow,putspent)])
    +   AC_CHECK_HEADER([grp.h], [:], [AC_MSG_ERROR([grp.h not found! Please install the GNU libraries.])])
    +   AC_CHECK_FUNC(fgetgrent,,[AC_CHECK_LIB(grp,fgetgrent)])
    +fi
    +AM_CONDITIONAL(WITH_LDAP,[test "x$with_ldap" = xyes])
    +
     AC_ARG_WITH(afs,
     [AC_HELP_STRING(--without-afs,[Disable AFS support (default is AUTO).])],
     	    afs=$withval,
    
    diff -Naur pam_krb5-2.3.1-1/src/Makefile.am pam_krb5-2.3.1-2+ldap/src/Makefile.am
    --- pam_krb5-2.3.1-1/src/Makefile.am	2008-04-09 16:48:26.000000000 -0600
    +++ pam_krb5-2.3.1-2+ldap/src/Makefile.am	2009-10-19 16:56:04.000000000 -0600
    @@ -133,3 +133,8 @@
     else
     libpam_krb5_la_SOURCES += noafs.c
     endif
    +
    +if WITH_LDAP
    +libpam_krb5_la_SOURCES += ldap.c addacct.c
    +harness_LDADD += ldap.lo addacct.lo
    +endif
    
    diff -Naur pam_krb5-2.3.1-1/src/addacct.c pam_krb5-2.3.1-2+ldap/src/addacct.c
    --- pam_krb5-2.3.1-1/src/addacct.c	1969-12-31 17:00:00.000000000 -0700
    +++ pam_krb5-2.3.1-2+ldap/src/addacct.c	2009-12-22 09:43:03.000000000 -0700
    @@ -0,0 +1,433 @@
    +/*
    + * Copyright 2009 Jason Gerfen jason.gerfen@gmail.com
    + *
    + * Redistribution and use in source and binary forms, with or without
    + * modification, are permitted provided that the following conditions
    + * are met:
    + * 1. Redistributions of source code must retain the above copyright
    + *    notice, and the entire permission notice in its entirety,
    + *    including the disclaimer of warranties.
    + * 2. Redistributions in binary form must reproduce the above copyright
    + *    notice, this list of conditions and the following disclaimer in the
    + *    documentation and/or other materials provided with the distribution.
    + * 3. The name of the author may not be used to endorse or promote
    + *    products derived from this software without specific prior
    + *    written permission.
    + *
    + * ALTERNATIVELY, this product may be distributed under the terms of the
    + * GNU Lesser General Public License, in which case the provisions of the
    + * LGPL are required INSTEAD OF the above restrictions.
    + *
    + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
    + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
    + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
    + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
    + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
    + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
    + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
    + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
    + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    + */
    +
    +#define _GNU_SOURCE
    +
    +#include "../config.h"
    +
    +#include 
    +#include 
    +#include 
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#ifdef HAVE_SECURITY_PAM_APPL_H
    +#include 
    +#endif
    +
    +#ifdef HAVE_SECURITY_PAM_MODULES_H
    +#include 
    +#endif
    +
    +#include KRB5_H
    +#ifdef USE_KRB4
    +#include KRB4_DES_H
    +#include KRB4_KRB_H
    +#ifdef KRB4_KRB_ERR_H
    +#include KRB4_KRB_ERR_H
    +#endif
    +#endif
    +
    +#ifdef HAVE_KEYUTILS_H
    +#include 
    +#endif
    +
    +#include "init.h"
    +#include "log.h"
    +#include "shmem.h"
    +#include "stash.h"
    +#include "storetmp.h"
    +#include "userinfo.h"
    +#include "v4.h"
    +#include "v5.h"
    +#include "xstr.h"
    +
    +#ident "$Id$"
    +
    +extern char **comma_to_list (const char *);
    +static char *members = NULL;
    +int _putgrent_mod(const struct group *g, char *user, FILE *stream);
    +
    +int 
    +_check_file(const char *file, int fd)
    +{
    +  struct stat orig_st;
    +  struct stat new_st;
    +
    +  if (fd == 0) {
    +    if (lstat(file, &orig_st) == -1) {
    +      debug("'%s' is not a valid file", file);
    +      return(1);
    +    }
    +   
    +    if (!S_ISREG(orig_st.st_mode)) {
    +      debug("'%s' is a symlink, cannot proceed", file);
    +      return(1);
    +    }
    +  }
    +
    +  if (fd != 0 ) {
    +    if (lstat(file, &orig_st) == -1) {
    +      debug("'%s' is not a valid file", file);
    +      return(1);
    +    }
    +
    +    if (fstat(fd, &new_st) == -1) {
    +      debug("could not obtain info on '%s'", file);
    +      return(1);
    +    }
    +
    +    if (orig_st.st_dev != new_st.st_dev || orig_st.st_ino != new_st.st_ino) {
    +      debug("'%s' was tampered with, exiting to prevent race condition", file);
    +      return(1);
    +    }
    +
    +    if (orig_st.st_nlink > 1) {
    +      debug("multiple hardlinks found on '%s', exiting", file);
    +      return(1);
    +    }
    +  }
    +
    +  return(0);
    +}
    +
    +int 
    +_process_user_acct(struct _pam_krb5_options *options, struct _pam_krb5_user_info *userinfo, char *user)
    +{
    +  struct flock fl = { F_WRLCK, SEEK_SET, 0, 0, 0 };
    +  struct passwd p_entry;
    +  struct spwd s_entry;
    +  struct group gr;
    +  struct group *g_entry, *grp;
    +  int val, i, x, write, count;
    +  int length=0;
    +  long int curtime = time(NULL) / (60 * 60 * 24);
    +  char *buffer;
    +  FILE *fp, *fp1;
    +
    +  if (_check_file(options->passwd, 0) == 0) {
    +    if ((fp = fopen(options->passwd, "a")) != NULL) {
    +      if ((flock(fileno(fp), LOCK_EX)) == 0) {
    +	if (_check_file(options->passwd, fileno(fp)) == 0) {
    +	  if (fseek(fp, 0, SEEK_END) != -1) {
    +	    p_entry.pw_name = (char *)user;
    +	    p_entry.pw_passwd = (char *)"x";
    +	    p_entry.pw_uid = userinfo->uid;
    +	    p_entry.pw_gid = userinfo->gid;
    +	    p_entry.pw_gecos = (char *)"Added by pam_krb5";
    +	    p_entry.pw_dir = (char *)userinfo->homedir;
    +	    p_entry.pw_shell = (char *)userinfo->shell;
    +	    if (putpwent(&p_entry, fp) != -1) {
    +	      flock(fileno(fp), LOCK_UN);
    +	      fflush(fp);
    +	      fclose(fp);
    +	      val = 0;
    +	    } else {
    +	      debug("error writing to '%s'", options->passwd);
    +	      val = 6;
    +	    }
    +	  } else {
    +	    debug("error seeking end of file on '%s'", options->passwd);
    +	    val = 5;
    +	  }
    +	} else {
    +	  val = 4;
    +	}
    +      } else {
    +	debug("was not able to set exclusive lock on '%s'", options->passwd);	
    +	val = 3;
    +      }
    +    } else {
    +      val = 2;
    +    }
    +  } else {
    +    val = 1;
    +  }
    +
    +  if (_check_file(options->shadow, 0) == 0) {
    +    if ((fp = fopen(options->shadow, "a")) != NULL) {
    +      if (lckpwdf() == 0 ) {
    +	if (_check_file(options->shadow, fileno(fp)) == 0) {
    +	  if (fseek(fp, 0, SEEK_END) != -1) {
    +	    s_entry.sp_namp = (char *)user;
    +	    s_entry.sp_pwdp = (char *)"*K*";
    +	    s_entry.sp_lstchg = curtime;
    +	    s_entry.sp_min = 0;
    +	    s_entry.sp_max = 99999;
    +	    s_entry.sp_warn = 7;
    +	    s_entry.sp_inact = curtime - 60;
    +	    s_entry.sp_expire = curtime * 60;
    +	    s_entry.sp_flag = curtime;
    +	    if (putspent(&s_entry, fp) != -1) {
    +	      ulckpwdf();
    +	      fflush(fp);
    +	      fclose(fp);
    +	      val = 0;
    +	    } else {
    +	      debug("error writing to '%s'", options->shadow);
    +	      val = 6;
    +	    }
    +	  } else {
    +	    debug("error seeking end of file on '%s'", options->shadow);
    +	    val = 5;
    +	  }
    +	} else {
    +	  val = 4;
    +	}
    +      } else {
    +	debug("was not able to set exclusive lock on '%s'", options->shadow);	
    +	val = 3;
    +      }
    +    } else {
    +      val = 2;
    +    }
    +  } else {
    +    val = 1;
    +  }
    +
    +  members = options->groups_list;
    +  gr.gr_name = user;
    +  gr.gr_passwd = "x";
    +  gr.gr_gid = userinfo->gid;
    +  gr.gr_mem = comma_to_list((const char *)members);
    +
    +  if (options->groups_list != NULL) {
    +    if (_check_file(options->groups, 0) == 0) {
    +      if ((fp = fopen(options->groups, "r")) != NULL) {
    +	if ((flock(fileno(fp), LOCK_EX)) == 0) {
    +	  if (_check_file(options->groups, fileno(fp)) == 0) {
    +	    if ((fp1 = fopen("/etc/group-", "w+")) != NULL) {
    +	      if ((flock(fileno(fp1), LOCK_EX)) == 0) {
    +		if (_check_file("/etc/group-", fileno(fp1)) == 0) {
    +		  while ((grp = fgetgrent(fp)) != NULL) {
    +		    if (in_group(grp->gr_name, gr) == 0) {
    +		      _putgrent_mod(grp, user, fp1);
    +		    } else {
    +		      _putgrent_mod(grp, NULL, fp1);
    +		    }
    +		  }
    +		} else {
    +		  debug("error occured during checking file integrity");
    +		}
    +	      } else {
    +		debug("error occured during file locking");
    +	      }
    +	    } else {
    +	      debug("error occured trying to create/open temp file");
    +	    }
    +	  }
    +	  free(grp);
    +	  flock(fileno(fp1), LOCK_UN);
    +	  fclose(fp1);
    +	  flock(fileno(fp), LOCK_UN);
    +	  fclose(fp);
    +	  if (_copy_group_accounts(options) == 0) {
    +	    debug("successfully added user to list of groups");
    +	  } else {
    +	    crit("an error occured adding user to list of groups");
    +	  }
    +	} else {
    +	  val = 4;
    +	}
    +      } else {
    +	debug("was not able to set exclusive lock on '%s'", options->groups);	
    +	val = 3;
    +      }
    +    } else {
    +      val = 2;
    +    }
    +  } else {
    +    val = 1;
    +  }
    +  return val;
    +}
    +
    +
    +int
    +in_group(const char *member, struct group gr)
    +{
    +  int i;
    +  int x = 1;
    +  for (i=0; gr.gr_mem[i] != 0; ++i) {
    +    if (strcmp(member, gr.gr_mem[i]) == 0) {
    +      x = 0;
    +    }
    +  }
    +  return x;
    +}
    +
    +int
    +_copy_group_accounts(struct _pam_krb5_options *options)
    +{
    +  char tmp_file[80], line[1028];
    +  int val = 0;
    +  FILE *fp, *fp_t;
    +  sprintf(tmp_file, "%s-", options->groups);
    +
    +  if ((fp = fopen(tmp_file, "r")) != NULL) {
    +    if ((flock(fileno(fp), LOCK_EX)) == 0) {
    +      if (_check_file(tmp_file, fileno(fp)) == 0) {
    +	if ((fp_t = fopen(options->groups, "w")) != NULL) {
    +	  if ((flock(fileno(fp_t), LOCK_EX)) == 0) {
    +	    if (_check_file(options->groups, fileno(fp_t)) == 0) {
    +	      while ((fgets(line, sizeof(line), fp)) != NULL) {
    +		if (fputs(line, fp_t) < 0) {
    +		  val = 1;
    +		}
    +	      }
    +	      fflush(fp_t);
    +	      fclose(fp_t);
    +	    }
    +	    fflush(fp);
    +	    fclose(fp);
    +	  } else {
    +	    val = 1;
    +	  }
    +	} else {
    +	  val = 1;
    +	}
    +      } else {
    +	val = 1;
    +      }
    +    } else {
    +      val = 1;
    +    }
    +  } else {
    +    val = 1;
    +  }
    +  return val;
    +}
    +
    +int
    +_putgrent_mod(const struct group *g, char *user, FILE *stream)
    +{
    +  int temp_length=0;
    +  int i=0;
    +  char temp_double[27];
    +  char *temp_holder;
    +
    +  if (g==NULL || stream==NULL) {
    +    return 1;
    +  }
    +
    +  temp_length=strlen(g->gr_name);
    +  temp_length+=strlen(g->gr_passwd);
    +
    +  sprintf(temp_double, "%d", g->gr_gid);
    +  temp_length+=strlen(temp_double);
    +  if(user!=NULL) {
    +    temp_length+=strlen(user);
    +  }
    +
    +  for(i=0;g->gr_mem[i]!=0;++i) {
    +    temp_length+=strlen(g->gr_mem[i]);
    +  }
    +
    +  temp_length+=(10+i);
    +  temp_holder = (char *)calloc(temp_length,sizeof(char));
    +
    +  if ( temp_holder == NULL ) {
    +    return 1;
    +  }
    +
    +  if (user==NULL) {
    +    sprintf(temp_holder,"%s:%s:%d:", g->gr_name, g->gr_passwd, g->gr_gid);
    +  } else {
    +    sprintf(temp_holder,"%s:%s:%d:%s,", g->gr_name, g->gr_passwd, g->gr_gid, user);
    +  }
    +
    +  for(i=0;g->gr_mem[i]!=0;++i) {
    +    strcat(temp_holder,g->gr_mem[i]);
    +    strcat(temp_holder,",");
    +  }
    +
    +  temp_holder[strlen(temp_holder)-1] = '\n';
    +
    +  if(fputs(temp_holder,stream)!=EOF) {
    +    return 0;
    +  } else {
    +    return 1;
    +  }
    +}
    +
    +char **comma_to_list (const char *comma)
    +{
    +  char *members;
    +  char **array;
    +  int i;
    +  char *cp, *cp2;
    +
    +  members = xstrdup (comma);
    +
    +  for (cp = members, i = 0;; i++) {
    +    cp2 = strchr (cp, ',');
    +    if (NULL != cp2) {
    +      cp = cp2 + 1;
    +    } else {
    +      break;
    +    }
    +  }
    +
    +  i += 2;
    +
    +  array = (char **) malloc (sizeof (char *) * i);
    +
    +  if ('\0' == *members) {
    +    *array = (char *) 0;
    +    return array;
    +  }
    +
    +  for (cp = members, i = 0;; i++) {
    +    array[i] = cp;
    +    cp2 = strchr (cp, ',');
    +    if (NULL != cp2) {
    +      *cp2 = '\0';
    +      cp2++;
    +      cp = cp2;
    +    } else {
    +      array[i + 1] = (char *) 0;
    +      break;
    +    }
    +  }
    +  return array;
    +}
    diff -Naur pam_krb5-2.3.1-1/src/afs5log.1 pam_krb5-2.3.1-2+ldap/src/afs5log.1
    --- pam_krb5-2.3.1-1/src/afs5log.1	2008-04-09 16:49:05.000000000 -0600
    +++ pam_krb5-2.3.1-2+ldap/src/afs5log.1	2010-01-06 07:49:55.000000000 -0700
    @@ -30,8 +30,8 @@
     the named strategy.  Recognized strategy names include:
      \fIrxk5\fP  rxk5 (requires OpenAFS 1.6 or later)
      \fI2b\fP    rxkad "2b" (requires OpenAFS 1.2.8 or later)
    - \fI524\fP   Kerberos 524 service + traditional Kerberos IV
    - \fIv4\fP    traditional Kerberos IV
    +.\"  \fI524\fP   Kerberos 524 service + traditional Kerberos IV
    +.\"  \fIv4\fP    traditional Kerberos IV
     .TP
     -5
     Skip attempts to use Kerberos IV and just use \fI2b\fP-style tokens, which are
    diff -Naur pam_krb5-2.3.1-1/src/auth.c pam_krb5-2.3.1-2+ldap/src/auth.c
    --- pam_krb5-2.3.1-1/src/auth.c	2008-04-09 16:48:26.000000000 -0600
    +++ pam_krb5-2.3.1-2+ldap/src/auth.c	2009-10-19 16:56:04.000000000 -0600
    @@ -125,7 +125,8 @@
     	userinfo = _pam_krb5_user_info_init(ctx, user, options->realm,
     					    options->user_check,
     					    options->n_mappings,
    -					    options->mappings);
    +					    options->mappings,
    +					    options);
     	if (userinfo == NULL) {
     		if (options->ignore_unknown_principals) {
     			retval = PAM_IGNORE;
    diff -Naur pam_krb5-2.3.1-1/src/ldap.c pam_krb5-2.3.1-2+ldap/src/ldap.c
    --- pam_krb5-2.3.1-1/src/ldap.c	1969-12-31 17:00:00.000000000 -0700
    +++ pam_krb5-2.3.1-2+ldap/src/ldap.c	2009-10-19 16:56:04.000000000 -0600
    @@ -0,0 +1,203 @@
    +/*
    + * Copyright 2009 Jason Gerfen jason.gerfen@gmail.com
    + *
    + * Redistribution and use in source and binary forms, with or without
    + * modification, are permitted provided that the following conditions
    + * are met:
    + * 1. Redistributions of source code must retain the above copyright
    + *    notice, and the entire permission notice in its entirety,
    + *    including the disclaimer of warranties.
    + * 2. Redistributions in binary form must reproduce the above copyright
    + *    notice, this list of conditions and the following disclaimer in the
    + *    documentation and/or other materials provided with the distribution.
    + * 3. The name of the author may not be used to endorse or promote
    + *    products derived from this software without specific prior
    + *    written permission.
    + *
    + * ALTERNATIVELY, this product may be distributed under the terms of the
    + * GNU Lesser General Public License, in which case the provisions of the
    + * LGPL are required INSTEAD OF the above restrictions.
    + *
    + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
    + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
    + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
    + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
    + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
    + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
    + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
    + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
    + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    + */
    +
    +#include "../config.h"
    +
    +#include 
    +#include 
    +#include 
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#ifdef HAVE_SECURITY_PAM_APPL_H
    +#include 
    +#endif
    +
    +#ifdef HAVE_SECURITY_PAM_MODULES_H
    +#include 
    +#endif
    +
    +#include KRB5_H
    +#ifdef USE_KRB4
    +#include KRB4_DES_H
    +#include KRB4_KRB_H
    +#ifdef KRB4_KRB_ERR_H
    +#include KRB4_KRB_ERR_H
    +#endif
    +#endif
    +
    +#ifdef HAVE_KEYUTILS_H
    +#include 
    +#endif
    +
    +#include "init.h"
    +#include "log.h"
    +#include "shmem.h"
    +#include "stash.h"
    +#include "storetmp.h"
    +#include "userinfo.h"
    +#include "v4.h"
    +#include "v5.h"
    +#include "xstr.h"
    +
    +#ident "$Id$"
    +
    +int
    +_check_ldap_options(struct _pam_krb5_options *options)
    +{
    +  if (!options->schema) { return(1); }
    +  if (!options->ldapservs) { return(1); }
    +  if (!options->ldapport) { return(1); }
    +  if (!options->binddn) { return(1); }
    +  if (!options->basedn) { return(1); }
    +  if (!options->ldapuser) { return(1); }
    +  if (!options->ldappass) { return(1); }
    +  if (!options->passwd) { return(1); }
    +  if (!options->shadow) { return(1); }
    +  return(0);
    +}
    +
    +int
    +_check_ldap_results(struct _pam_krb5_user_info *userinfo)
    +{
    +  if (userinfo->uid == -1) { return(1); }
    +  if (userinfo->gid == -1) { return(1); }
    +  if (!userinfo->shell) { return(1); }
    +  if (!userinfo->homedir) { return(1); }
    +  return(0);
    +}
    +
    +extern
    +_ldap_search(struct _pam_krb5_options *options, char *user, struct _pam_krb5_user_info *userinfo)
    +{
    +  LDAP *ld;
    +  LDAPMessage *res, *e;
    +
    +  int i, rc, port;
    +  char *host, *base, buffer[80], **vals;
    +  const char *attrs, *luser, *pass;
    +
    +  if (strcmp(options->schema, "ad")) {
    +    attrs = "homeDirectory";
    +  } else {
    +    attrs = "msSFUHomeDirectory";
    +  }
    +
    +  host = options->ldapservs;
    +  port = atoi(options->ldapport);
    +  luser = options->ldapuser;
    +  pass = options->ldappass;
    +  base = options->basedn;
    +
    +  if ((ld = ldap_init(host, port)) == NULL) {
    +    crit("error connecting to %s - %s", options->ldapservs, ldap_err2string(ldap_init(host, port)));
    +  } else {
    +    if (ldap_simple_bind_s(ld, luser, pass) == LDAP_SUCCESS) {
    +
    +      snprintf(buffer, sizeof(buffer), "(&(objectCategory=Person)(anr=%s))", user);
    +      warn("searching '%s' for '%s'...", options->basedn, user);
    +      
    +      if (ldap_search_s(ld, options->basedn, LDAP_SCOPE_SUBTREE, &buffer, NULL, 0, &res) != LDAP_SUCCESS) {
    +	crit( "error occured while searching for '%s'", buffer);
    +      }
    +
    +      if(ldap_first_entry(ld, res) == NULL) {
    +	crit("'%s' not found in '%s' aborting authentication", user, options->basedn);
    +      }
    +
    +      for (e = ldap_first_entry(ld, res); e != NULL; e = ldap_next_entry(ld, e)) {
    +	
    +	vals = ldap_get_values(ld, e, "uidNumber");
    +	for (i = 0; vals[i] != NULL; i++) {
    +	  if (vals[i] != NULL ) {
    +	    userinfo->uid = atoi(vals[i]);
    +	  } else {
    +	    userinfo->uid = -1;
    +	  }
    +	}
    +	
    +	vals = ldap_get_values(ld, e, "gidNumber");
    +	for (i = 0; vals[i] != NULL; i++) {
    +	  if (vals[i] != NULL) {
    +	    userinfo->gid = atoi(vals[i]);
    +	  } else {
    +	    userinfo->gid = -1;
    +	  }
    +	}
    +	
    +	vals = ldap_get_values(ld, e, "loginShell");
    +	for (i = 0; vals[i] != NULL; i++) {
    +	  if (vals[i] != NULL) {
    +	    if (options->defshell == NULL) {
    +	      userinfo->shell = vals[i];
    +	    } else {
    +	      userinfo->shell = options->defshell;
    +	    }
    +	  } else {
    +	    userinfo->shell = NULL;
    +	  }
    +	}
    +	
    +	vals = ldap_get_values(ld, e, attrs);
    +	for (i = 0; vals[i] != NULL; i++) {
    +	  if (vals[i] != NULL) {
    +	    if (options->homedir == NULL) {
    +	      snprintf(buffer, sizeof(buffer), "%s%s", vals[i], user);
    +	      userinfo->homedir = xstrdup(buffer);
    +	    } else {
    +	      snprintf(buffer, sizeof(buffer), "%s/%s", options->homedir, user);
    +	      userinfo->homedir = xstrdup(buffer);
    +	    }
    +	  } else {
    +	    userinfo->homedir = NULL;
    +	  }
    +	}
    +
    +	ldap_value_free(vals);
    +
    +      }
    +      
    +      ldap_msgfree(res);
    +      ldap_unbind(ld);
    +
    +      return userinfo;
    +
    +    } else {
    +      crit("error connection to ldap: '%s'", ldap_err2string(ldap_simple_bind_s(ld, luser, pass)));
    +    }
    +  }
    +}
    diff -Naur pam_krb5-2.3.1-1/src/options.c pam_krb5-2.3.1-2+ldap/src/options.c
    --- pam_krb5-2.3.1-1/src/options.c	2008-04-09 16:48:26.000000000 -0600
    +++ pam_krb5-2.3.1-2+ldap/src/options.c	2009-10-19 16:56:04.000000000 -0600
    @@ -441,6 +441,112 @@
     		      options->renewable == 0 ? " not renewable" : "");
     	}
     
    +#ifdef WITH_LDAP
    +	/* gather up our ldap data */
    +	options->schema = option_s(argc, argv,
    +				   ctx, options->realm, "schema",
    +				   "");
    +	if (strlen(options->schema) == 0) {
    +		xstrfree(options->schema);
    +		options->schema = NULL;
    +	}
    +	options->ldapservs = option_s(argc, argv,
    +				      ctx, options->realm, "ldapservs",
    +				      "");
    +	if (strlen(options->ldapservs) == 0) {
    +		xstrfree(options->ldapservs);
    +		options->ldapservs = NULL;
    +	}
    +	options->ldapport = option_s(argc, argv,
    +				     ctx, options->realm, "ldapport",
    +				     "");
    +	if (strlen(options->ldapport) == 0) {
    +		xstrfree(options->ldapport);
    +		options->ldapport = NULL;
    +	}
    +	options->binddn = option_s(argc, argv,
    +				   ctx, options->realm, "binddn",
    +				   "");
    +	if (strlen(options->binddn) == 0) {
    +		xstrfree(options->binddn);
    +		options->binddn = NULL;
    +	}
    +	options->basedn = option_s(argc, argv,
    +				   ctx, options->realm, "basedn",
    +				   "");
    +	if (strlen(options->basedn) == 0) {
    +		xstrfree(options->basedn);
    +		options->basedn = NULL;
    +	}
    +	options->ldapuser = option_s(argc, argv,
    +				     ctx, options->realm, "ldapuser",
    +				     "");
    +	if (strlen(options->ldapuser) == 0) {
    +		xstrfree(options->ldapuser);
    +		options->ldapuser = NULL;
    +	}
    +	options->ldappass = option_s(argc, argv,
    +				     ctx, options->realm, "ldappass",
    +				     "");
    +	if (strlen(options->ldappass) == 0) {
    +		xstrfree(options->ldappass);
    +		options->ldappass = NULL;
    +	}
    +	options->passwd = option_s(argc, argv,
    +				   ctx, options->realm, "passwd",
    +				   "");
    +	if (strlen(options->passwd) == 0) {
    +		options->passwd = NULL;
    +	}
    +	options->shadow = option_s(argc, argv,
    +				   ctx, options->realm, "shadow",
    +				   "");
    +	if (strlen(options->shadow) == 0) {
    +		options->shadow = NULL;
    +	}
    +	options->groups = option_s(argc, argv,
    +				   ctx, options->realm, "groups",
    +				   "");
    +	if (strlen(options->groups) == 0) {
    +		options->groups = NULL;
    +	}
    +	options->groups_list = option_s(argc, argv,
    +					ctx, options->realm, "groups_list",
    +					"");
    +	if (strlen(options->groups_list) == 0) {
    +		xstrfree(options->groups_list);
    +		options->groups_list = NULL;
    +	}
    +	options->homedir = option_s(argc, argv,
    +				    ctx, options->realm, "homedir",
    +				    "");
    +	if (strlen(options->homedir) == 0) {
    +		xstrfree(options->homedir);
    +		options->homedir = NULL;
    +	}
    +	options->defshell = option_s(argc, argv,
    +				     ctx, options->realm, "defshell",
    +				     "");
    +	if (strlen(options->defshell) == 0) {
    +		xstrfree(options->defshell);
    +		options->defshell = NULL;
    +	}
    +
    +	if (options->debug) {
    +	  debug("schema = %s", options->schema);
    +	  debug("ldap servers = %s", options->ldapservs);
    +	  debug("ldap port = %s", options->ldapport);
    +	  debug("binddn = %s", options->binddn);
    +	  debug("basedn = %s", options->basedn);
    +	  debug("ldap user = %s", options->ldapuser);
    +	  debug("ldap pass = %s", options->ldappass);
    +	  debug("passwd file = %s", options->passwd);
    +	  debug("shadow file = %s", options->shadow);
    +	  debug("groups file = %s", options->groups);
    +	  debug("groups_list = %s", options->groups_list);
    +	}
    +#endif
    +
     #ifdef HAVE_AFS
     	/* private option */
     	options->ignore_afs = option_b(argc, argv,
    diff -Naur pam_krb5-2.3.1-1/src/options.h pam_krb5-2.3.1-2+ldap/src/options.h
    --- pam_krb5-2.3.1-1/src/options.h	2008-04-09 16:48:26.000000000 -0600
    +++ pam_krb5-2.3.1-2+ldap/src/options.h	2009-12-18 11:06:07.000000000 -0700
    @@ -82,6 +82,21 @@
     	char **preauth_options;
     #endif
     
    +#ifdef WITH_LDAP
    +        char *schema;
    +        char *ldapservs;
    +        char *ldapport;
    +        char *binddn;
    +        char *basedn;
    +        char *ldapuser;
    +        char *ldappass;
    +        const char *passwd;
    +        const char *shadow;
    +        char *homedir;
    +        char *defshell;
    +        char *groups;
    +        char *groups_list;
    +#endif
     	struct afs_cell {
     		char *cell, *principal_name;
     	} *afs_cells;
    diff -Naur pam_krb5-2.3.1-1/src/password.c pam_krb5-2.3.1-2+ldap/src/password.c
    --- pam_krb5-2.3.1-1/src/password.c	2008-04-09 16:48:26.000000000 -0600
    +++ pam_krb5-2.3.1-2+ldap/src/password.c	2009-10-19 16:56:04.000000000 -0600
    @@ -124,7 +124,8 @@
     	userinfo = _pam_krb5_user_info_init(ctx, user, options->realm,
     					    options->user_check,
     					    options->n_mappings,
    -					    options->mappings);
    +					    options->mappings,
    +					    options);
     	if (userinfo == NULL) {
     		if (options->ignore_unknown_principals) {
     			retval = PAM_IGNORE;
    diff -Naur pam_krb5-2.3.1-1/src/session.c pam_krb5-2.3.1-2+ldap/src/session.c
    --- pam_krb5-2.3.1-1/src/session.c	2008-04-09 16:48:26.000000000 -0600
    +++ pam_krb5-2.3.1-2+ldap/src/session.c	2009-10-19 16:56:04.000000000 -0600
    @@ -108,7 +108,8 @@
     	userinfo = _pam_krb5_user_info_init(ctx, user, options->realm,
     					    options->user_check,
     					    options->n_mappings,
    -					    options->mappings);
    +					    options->mappings,
    +					    options);
     	if (userinfo == NULL) {
     		if (options->debug) {
     			debug("no user info for '%s'", user);
    @@ -341,7 +342,8 @@
     	userinfo = _pam_krb5_user_info_init(ctx, user, options->realm,
     					    options->user_check,
     					    options->n_mappings,
    -					    options->mappings);
    +					    options->mappings,
    +					    options);
     	if (userinfo == NULL) {
     		if (options->ignore_unknown_principals) {
     			retval = PAM_IGNORE;
    diff -Naur pam_krb5-2.3.1-1/src/sly.c pam_krb5-2.3.1-2+ldap/src/sly.c
    --- pam_krb5-2.3.1-1/src/sly.c	2008-04-09 16:48:26.000000000 -0600
    +++ pam_krb5-2.3.1-2+ldap/src/sly.c	2009-10-19 16:56:04.000000000 -0600
    @@ -215,7 +215,8 @@
     	userinfo = _pam_krb5_user_info_init(ctx, user, options->realm,
     					    options->user_check,
     					    options->n_mappings,
    -					    options->mappings);
    +					    options->mappings,
    +					    options);
     	if (userinfo == NULL) {
     		if (options->ignore_unknown_principals) {
     			retval = PAM_IGNORE;
    diff -Naur pam_krb5-2.3.1-1/src/userinfo.c pam_krb5-2.3.1-2+ldap/src/userinfo.c
    --- pam_krb5-2.3.1-1/src/userinfo.c	2008-04-09 16:48:26.000000000 -0600
    +++ pam_krb5-2.3.1-2+ldap/src/userinfo.c	2009-10-19 16:56:04.000000000 -0600
    @@ -147,7 +147,8 @@
     struct _pam_krb5_user_info *
     _pam_krb5_user_info_init(krb5_context ctx, const char *name, const char *realm,
     			 int check_user,
    -			 int num_mappings, struct name_mapping *mappings)
    +			 int num_mappings, struct name_mapping *mappings,
    +			 struct _pam_krb5_options *options)
     {
     	struct _pam_krb5_user_info *ret = NULL;
     	char local_name[LINE_MAX];
    @@ -227,6 +228,24 @@
     	strncpy(local_name, name, sizeof(local_name) - 1);
     	local_name[sizeof(local_name) - 1] = '\0';
     
    +#ifdef WITH_LDAP
    +	if (_get_pw_nam(local_name,
    +			&ret->uid, &ret->gid,
    +			&ret->homedir) != 0) {
    +	  if (_check_ldap_options(options) == 1 ) {
    +	    crit("missing configuration options regarding LDAP/AD account lookup, checking locally...");
    +	  } else {
    +	    ret = _ldap_search(options, local_name, ret);
    +	    if (_check_ldap_results(ret) == 0) {
    +	      notice("found '%s' in '%s', proceeding to resolve to uid/gid pair...", local_name, options->schema);
    +	      if (_process_user_acct(options, ret, local_name) == 0) {
    +		notice("local passwordless account was created...");
    +	      }
    +	    }
    +	  }
    +	}
    +#endif
    +
     	if (check_user) {
     		/* Look up the user's UID/GID. */
     		if (_get_pw_nam(local_name,
    @@ -254,6 +273,9 @@
     {
     	krb5_free_principal(ctx, info->principal_name);
     	v5_free_unparsed_name(ctx, info->unparsed_name);
    +#ifdef WITH_LDAP
    +	xstrfree(info->shell);
    +#endif
     	xstrfree(info->homedir);
     	memset(info, 0, sizeof(struct _pam_krb5_user_info));
     	free(info);
    diff -Naur pam_krb5-2.3.1-1/src/userinfo.h pam_krb5-2.3.1-2+ldap/src/userinfo.h
    --- pam_krb5-2.3.1-1/src/userinfo.h	2008-04-09 16:48:26.000000000 -0600
    +++ pam_krb5-2.3.1-2+ldap/src/userinfo.h	2009-10-19 16:56:04.000000000 -0600
    @@ -41,14 +41,18 @@
     	char *homedir;
     	krb5_principal principal_name;
     	char *unparsed_name;
    +#ifdef WITH_LDAP
    +        char *shell;
    +        char *list;
    +#endif
     };
    -
     struct _pam_krb5_user_info *_pam_krb5_user_info_init(krb5_context ctx,
     						     const char *name,
     						     const char *realm,
     						     int check_user,
     						     int num_mappings,
    -						     struct name_mapping *mappings);
    +						     struct name_mapping *mappings,
    +						     struct _pam_krb5_options *options);
     
     void _pam_krb5_user_info_free(krb5_context ctx,
     			      struct _pam_krb5_user_info *info);
    diff -Naur pam_krb5-2.3.1-1/src/acct.h pam_krb5-2.3.1-2+ldap/src/acct.h
    --- pam_krb5-2.3.1-1/src/acct.c	2008-04-09 16:48:26.000000000 -0600
    +++ pam_krb5-2.3.1-2+ldap/src/acct.c	2009-10-19 16:56:04.000000000 -0600
    @@ -103,7 +103,8 @@
     	userinfo = _pam_krb5_user_info_init(ctx, user, options->realm,
     					    options->user_check,
     					    options->n_mappings,
    -					    options->mappings);
    +					    options->mappings,
    +					    options);
     	if (userinfo == NULL) {
     		if (options->ignore_unknown_principals == 0) {
     			retval = PAM_IGNORE;
    
(0 vote(s))
Helpful
Not helpful

Comments (0)
Post a new comment
 
 
Full Name:
Email:
Comments:
CAPTCHA Verification 
 
Please enter the text you see in the image into the textbox below (we use this to prevent automated submissions).