Source code for sudo/LDAP

ANELLOT at Nationwide.com ANELLOT at Nationwide.com
Fri Oct 31 15:02:02 EST 2003


Source code for sudo to incorporate LDAP. This code was created by Aaron
Spangler to address a security issue that I had. I just got the approval to
release it to the open source community.
One of the things that it does not contain is the -l option for listing
allowed user commands. If you guys have a plan for incorporating it please
let me know, we have a interest in watching it grow.

Thanks,
Todd Anello


diff -u sudo-1.6.6.orig/Makefile.in sudo-1.6.6.ldap/Makefile.in
--- sudo-1.6.6.orig/Makefile.in     Thu Apr 18 11:41:30 2002
+++ sudo-1.6.6.ldap/Makefile.in     Mon Jan 27 13:04:19 2003
@@ -114,6 +114,7 @@
        interfaces.c lex.yy.c lsearch.c logging.c parse.c parse.lex \
        parse.yacc set_perms.c sigaction.c snprintf.c strcasecmp.c
strerror.c \
        sudo.c sudo.tab.c testsudoers.c tgetpass.c utime.c visudo.c \
+       ldap.c \
        $(AUTH_SRCS)

 AUTH_SRCS = auth/afs.c auth/aix_auth.c auth/bsdauth.c auth/dce.c
auth/fwtk.c \
@@ -130,6 +131,7 @@

 SUDOBJS = check.o env.o getspwuid.o goodpath.o fileops.o find_path.o \
        interfaces.o logging.o parse.o set_perms.o sudo.o tgetpass.o \
+       ldap.o \
        $(AUTH_OBJS) $(PARSEOBJS)

 VISUDOBJS = visudo.o fileops.o goodpath.o find_path.o $(PARSEOBJS)
@@ -227,6 +229,7 @@
 strcasecmp.o: strcasecmp.c config.h
 strerror.o: strerror.c config.h
 utime.o: utime.c config.h pathnames.h compat.h emul/utime.h
+ldap.o: ldap.c $(SUDODEP) parse.h

 # Authentication functions live in "auth" dir and so need extra care
 sudo_auth.o: $(authdir)/sudo_auth.c $(AUTHDEP) $(INSDEP)
diff -u sudo-1.6.6.orig/README.LDAP sudo-1.6.6.ldap/README.LDAP
--- sudo-1.6.6.orig/README.LDAP     Tue Jan 28 14:32:23 2003
+++ sudo-1.6.6.ldap/README.LDAP     Fri Jan 31 16:48:51 2003
@@ -1,0 +1,260 @@
+This file explains how to use the optional LDAP functionality of SUDO.
+
+LDAP philosophy
+===============
+As times change and servers become cheap, an enterprise can easily have
500+
+UNIX servers.  Using LDAP to synchronize Users, Groups, Hosts, Mounts, and
+others across an enterprise can greatly reduce the administrative
overhead.
+
+Sudo in the past has only used a single local configuration file
/etc/sudoers.
+Some have attempted to workaround this by synchronizing changes via
+RCS/CVS/RSYNC/RDIST/RCP/SCP and even NFS.  Many have asked for a Hesiod,
NIS,
+or LDAP patch for sudo, so here is my attempt at LDAP'izing sudo.
+
+Definitions
+===========
+Many times the word 'Directory' is used in the document to refer to the
LDAP
+server, structure and contents.
+
+Many times 'options' are used in this document to refer to sudoer
'defaults'.
+They are on in the same.
+
+Design Features
+===============
+
+  * Sudo no longer needs to read all sudoers.  Parsing of /etc/sudoers
requires
+      the entire file to be read.  The LDAP feature of sudo uses two
+      (sometimes three) LDAP queries per invocation.  It never reads the
+      all the sudoer entries in the LDAP store.  This makes it
+      especially fast and particularly usable in LDAP environments.
+      The first query is to parse default options (see below).  The second
+      is to match against the username or groups a user belongs to.
+      (The special ALL tag is matched in this query too.)
+      If not match is against the username, the third query pulls the
entries
+      that match against user netgroups to compare back to the user.
+
+  * Sudo no longer blows up if there is a typo.  Parsing of /etc/sudoers
can
+      still blow up when sudo is invoked.  However when using the LDAP
feature
+      of sudo, LDAP syntax rules are applied before the data is uploaded
into
+      the LDAP server, so proper syntax is always guaranteed!
+      One can of course still insert a bogus hostname or username,
+      but sudo will not care.
+
+  * Options inside of entries now override global default options.
+      /etc/sudoers allowed for only default options and limited options
+      associated with user/host/command aliases.  The syntax can be
difficult
+      for the newbie.  The LDAP feature attempts to simplify this and yet
+      still provide maximum flexibility.
+
+      Sudo first looks for an entry called 'cn=default' in the SUDOers
+      container.  If found, the multi-valued sudoOption attribute is
parsed
+      the same way the global 'Defaults' line in /etc/sudoers is parsed.
+
+      If on the second or third query, a response containing a sudoRole
+      which matches agains the user, host, and command, then the matched
+      object is scanned for a additional options to override the toplevel
+      defaults.  See the Example LDAP content below for more information.
+
+  * Visudo is no longer needed.  Visudo provides locking and syntax
checking
+      against the /etc/sudoers file.  Since LDAP updates are atomic,
locking
+      is no longer necessary.  Because syntax is checked when the data is
+      inserted into LDAP, the sudoers syntax check becomes unnecessary.
+
+  * Aliases are no longer needed.  User, Host, and Command Aliases were
setup
+      to allow simplification and readability of the sudoers files.  Since
the
+      LDAP sudoer entry allows multiple values for each of its attributes
and
+      since most LDAP browsers are graphical and easy to work with,
original
+      aliases are no longer needed.
+
+      If you want to specify lots of users into an entry or want to have
+      similar entries with identical users, then use either groups or user
+      netgroups.  Thats what groups and netgroups are for and Sudo handles
+      this well.  Or just paste them all into the LDAP record.
+
+      If you want to specify lots of hosts into an entry, use netgroups or
+      IP address matches (10.2.3.4/255.255.0.0).  Thats what netgroups are
+      for and Sudo handles this well.  Or just past them all into the LDAP
+      record.
+
+      If you want to specify lots of commands, use directories or
wildcards,
+      or just paste them all into LDAP.  That's what it's for.
+
+  * The /etc/sudoers file can be disabled.  Paranoid security
administrators
+      can now disallow parsing of any local /etc/sudoers file by an LDAP
+      sudoOption '!local_sudoers'.  This way all sudoers can be controlled
+      and audited in one place because local entries are not allowed.
+      In the future, this file may not be present.
+      BUG: THIS OPTION IS NOT IMPLEMENTED YET.
+
+  * The sudo binary compiled with LDAP support should be totally
+      backware compatable and be syntactically and source code equivilent
+      to its non ldap-enabled build.
+
+
+Build instructions
+==================
+The most simplest way to build sudo with ldap support is to include the
+'--with-ldap' option.  I recommend including the '--with-pam' option on
those
+system with PAM so that if you decide to use LDAP for authentication, you
won't
+need to recompile sudo.
+
+  $ ./configure --with-ldap --with-pam
+
+If your ldap libraries and headers are in a non standard place, you will
need
+to specify them at configure time.
+
+  $ CPPFLAGS="-I/usr/local/ldapsdk/include" \
+  > LDFLAGS="-L/usr/local/ldapsdk/lib" \
+  > ./configure --with-ldap --with-pam
+
+In early revs of sudo where the '--with-ldap' option is not available, you
+need to manually append '#define HAVE_LDAP 1' to config.h and set
+LIBS='-lldap' in Makefile.
+
+This *should* build against most LDAP libraries such as OpenLDAP,
Netscape,
+Iplanet, Mozilla, SecureWay, and others.  You might have to also include
+'-llber' or '-lldif' in your LIBS depending on your LDAP libraries.
+
+Schema Changes
+==============
+Add the following schema to your LDAP server so that it may contain sudoer
+content.  In openldap, simply place this into a new file and 'include' it
+in your slapd.conf and restart slapd.  For other LDAP servers, provide
this
+to your LDAP Administrator.  Make sure to index the attribute 'sudoUser'.
+
+
+  #
+  #  schema file for sudo
+  #
+
+  attributetype ( 1.3.6.1.4.1.15953.9.1.1
+        NAME 'sudoUser'
+        DESC 'User(s) who may  run sudo'
+        EQUALITY caseExactIA5Match
+        SUBSTR caseExactIA5SubstringsMatch
+        SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+
+  attributetype ( 1.3.6.1.4.1.15953.9.1.2
+        NAME 'sudoHost'
+        DESC 'Host(s) who may run sudo'
+        EQUALITY caseExactIA5Match
+        SUBSTR caseExactIA5SubstringsMatch
+        SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+
+  attributetype ( 1.3.6.1.4.1.15953.9.1.3
+        NAME 'sudoCommand'
+        DESC 'Command(s) to be executed by sudo'
+        EQUALITY caseExactIA5Match
+        SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+
+  attributetype ( 1.3.6.1.4.1.15953.9.1.4
+        NAME 'sudoRunAs'
+        DESC 'User(s) impersonated by sudo'
+        EQUALITY caseExactIA5Match
+        SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+
+  attributetype ( 1.3.6.1.4.1.15953.9.1.5
+        NAME 'sudoOption'
+        DESC 'Options(s) followed by sudo'
+        EQUALITY caseExactIA5Match
+        SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+
+  objectclass ( 1.3.6.1.4.1.15953.9.2.1 NAME 'sudoRole' SUP top STRUCTURAL
+        DESC 'Sudoer Entries'
+        MUST ( cn )
+        MAY ( sudoUser $ sudoHost $ sudoCommand $ sudoRunAs $ sudoOption $
+              description )
+        )
+
+
+Importing /etc/sudoers to LDAP
+==============================
+Importing is a two step process.
+
+Step 1:
+Ask your LDAP Administrator where to create the ou=SUDOers container.
+(An example location is shown below).  Then use the provided script to
convert
+your sudoers file into LDIF format.  The script will also convert any
default
+options.
+
+  # SUDOERS_BASE=ou=SUDOers,dc=example,dc=com
+  # export SUDOERS_BASE
+  # ./sudoers2ldif /etc/sudoers > /tmp/sudoers.ldif
+
+Step 2:
+Import into your directory server.  If you are using OpenLDAP, do the
following
+if you are using another directory, provide the LDIF file to your LDAP
+Administrator.  An example is shown below.
+
+  # ldapadd -f /tmp/sudoers.ldif -h ldapserver \
+  > -D cn=Manager,dc=example,dc=com -W -x
+
+Example sudoers Entries in LDAP
+===============================
+The equivilent of a sudoer in LDAP is a 'sudoRole'.  It contains
sudoUser(s),
+sudoHost, sudoCommand and optional sudoOption(s) and sudoRunAs(s).
+<put an example here>
+
+Managing LDAP entries
+=====================
+Doing a one-time bulk load of your ldap entries is fine.  However what if
you
+need to make minor changes on a daily basis?  It doesn't make sense to
delete
+and re-add objects.  (You can, but this is tedious).
+
+I recommend using any of the following LDAP browsers to administer your
SUDOers.
+  * GQ - The gentleman's LDAP client - Open Source - I use this a lot on
+         linux and since it is Schema aware, I don't need to create a
sudoRole
+      template.
+    http://biot.com/gq/
+
+  * LDAP Browser/Editor - by Jarek Gawor - I use this a lot on Windows
+    and Solaris.  It runs anywhere in a Java Virtual Machine including
+    web pages.  You have to make a template from an existing sudoRole
entry.
+    http://www.iit.edu/~gawojar/ldap
+    http://www.mcs.anl.gov/~gawor/ldap
+    http://ldapmanager.com
+
+  There are dozens of others, some open source, some free, some not.
+
+
+Configure your /etc/ldap.conf
+=============================
+The /etc/ldap.conf file is meant to be shared between sudo, pam_ldap,
nss_ldap
+and other ldap applications and modules.  IBM Secureway unfortunately uses
+the same filename but has a different syntax.  If you need to rename where
+this file is stored, recompile SUDO with the -DLDAP_CONFIG compile option.
+
+Make sure you sudoers_base matches exactly with the location you specified
+when you imported the sudoers.  Below is an example /etc/ldap.conf
+
+  # Either specify a uri or host & port
+  #host          ldapserver
+  #port          389
+  uri            ldap://ldapserver
+  #
+  # must be set or sudo will ignore LDAP
+  sudoers_base   ou=SUDOers,dc=example,dc=com
+  #
+  # verbose sudoers matching from ldap
+  #sudoers_debug 2
+  #
+  # optional proxy credentials
+  #binddn        <who to search as>
+  #bindpw        <password>
+
+Debugging your LDAP configuration
+=================================
+Enable debugging if you think sudo is not parsing LDAP the way you think
it
+it should.  A value of 1 shows moderate debugging.  A value of 2 shows the
+results of the matches themselves.  Make sure to set the value back to
zero
+so that other users don't get confused by the debugging messages.  This
value
+is 'sudoers_debug' in the /etc/ldap.conf.
+
+Configure your /etc/nsswitch.conf
+=================================
+At the time of this writing, sudo does not consult nsswitch.conf for the
+search order.  But if it did, it would look like this:
+This might be implemented in the future.  For now just skip this step.
+
+  sudoers: files ldap
+
Common subdirectories: sudo-1.6.6.orig/auth and sudo-1.6.6.ldap/auth
diff -u sudo-1.6.6.orig/configure.in sudo-1.6.6.ldap/configure.in
--- sudo-1.6.6.orig/configure.in    Thu Apr 18 11:41:30 2002
+++ sudo-1.6.6.ldap/configure.in    Tue Jan 28 13:49:32 2003
@@ -908,6 +908,17 @@
            ;;
 esac])

+AC_ARG_WITH(ldap, [  --with-ldap              enable LDAP support],
+[case $with_ldap in
+    yes)   AC_DEFINE(HAVE_LDAP, 1, [Define if you use LDAP.])
+           AC_MSG_CHECKING(whether to use sudoers from LDAP)
+           AC_MSG_RESULT(yes)
+           ;;
+    no)          ;;
+    *)           AC_MSG_ERROR(["--with-ldap does not take an argument."])
+           ;;
+esac])
+
 dnl include all insult sets on one line
 if test "$insults" = "on"; then
     AC_MSG_CHECKING(which insult sets to include)
@@ -1950,3 +1961,12 @@
 #endif /* __svr4__ */

 #endif /* _SUDO_CONFIG_H */])
+
+/* LDAP support */
+dnl
+dnl LDAP libs
+dnl
+if test "$with_ldap" = "yes"; then
+    SUDO_LIBS="${SUDO_LIBS} -lldap"
+fi
+
diff -u sudo-1.6.6.orig/def_data.c sudo-1.6.6.ldap/def_data.c
--- sudo-1.6.6.orig/def_data.c      Wed Jan 16 16:13:58 2002
+++ sudo-1.6.6.ldap/def_data.c      Wed Mar 19 16:01:31 2003
@@ -174,6 +174,9 @@
      "verifypw", T_PWFLAG,
      "When to require a password for 'verify' pseudocommand: %s"
     }, {
+     "ignore_local_sudoers", T_FLAG,
+     "If LDAP directory is up, do we ignore local sudoers file: %s"
+    }, {
      NULL, 0, NULL
     }
 };
diff -u sudo-1.6.6.orig/def_data.h sudo-1.6.6.ldap/def_data.h
--- sudo-1.6.6.orig/def_data.h      Wed Jan 16 16:13:58 2002
+++ sudo-1.6.6.ldap/def_data.h      Wed Mar 19 16:01:31 2003
@@ -56,3 +56,4 @@
 #define I_VERIFYPW_I            55
 #define I_LISTPW                56
 #define I_VERIFYPW              57
+#define I_IGNORE_LOCAL_SUDOERS  58
diff -u sudo-1.6.6.orig/def_data.in sudo-1.6.6.ldap/def_data.in
--- sudo-1.6.6.orig/def_data.in     Wed Jan 16 16:13:54 2002
+++ sudo-1.6.6.ldap/def_data.in     Wed Mar 19 16:01:16 2003
@@ -180,3 +180,6 @@
 verifypw
      T_PWFLAG
      "When to require a password for 'verify' pseudocommand: %s"
+ignore_local_sudoers
+     T_FLAG
+     "If LDAP directory is up, do we ignore local sudoers file: %s"
Common subdirectories: sudo-1.6.6.orig/emul and sudo-1.6.6.ldap/emul
diff -u sudo-1.6.6.orig/ldap.c sudo-1.6.6.ldap/ldap.c
--- sudo-1.6.6.orig/ldap.c    Fri Jan 24 11:46:55 2003
+++ sudo-1.6.6.ldap/ldap.c    Wed Mar 19 07:28:30 2003
@@ -1,0 +1,727 @@
+/*
+ * Copyright (c) 1996, 1998-2002 Todd C. Miller
<Todd.Miller at courtesan.com>
+ * All rights reserved.
+ *
+ * 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, this list of conditions and the following disclaimer.
+ *
+ * 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.
+ *
+ * 4. Products derived from this software may not be called "Sudo" nor
+ *    may "Sudo" appear in their names without specific prior written
+ *    permission from the author.
+ *
+ * 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 <sys/types.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# ifdef HAVE_STDLIB_H
+#  include <stdlib.h>
+# endif
+#endif /* STDC_HEADERS */
+#ifdef HAVE_STRING_H
+# include <string.h>
+#else
+# ifdef HAVE_STRINGS_H
+#  include <strings.h>
+# endif
+#endif /* HAVE_STRING_H */
+#if defined(HAVE_MALLOC_H) && !defined(STDC_HEADERS)
+# include <malloc.h>
+#endif /* HAVE_MALLOC_H && !STDC_HEADERS */
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif /* HAVE_UNISTD_H */
+#include <ctype.h>
+#include <pwd.h>
+#include <grp.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include "sudo.h"
+#include "parse.h"
+
+/* LDAP code below */
+
+
+#ifdef HAVE_LDAP
+#include <ldap.h>
+
+#ifndef LDAP_CONFIG
+#define LDAP_CONFIG "/etc/ldap.conf"
+#endif
+
+#define BUF_SIZ 1024
+
+/* ldap configuration structure */
+struct ldap_config {
+  char *host;
+  int  port;
+  char *uri;
+  char *binddn;
+  char *bindpw;
+  char *base;
+  int debug;
+} ldap_conf;
+
+/*
+ * Walks through search result and returns true if we have a
+ * netgroup that matches our user
+ */
+
+
+int
+sudo_ldap_check_user_netgroup(ld,entry)
+  LDAP *ld;
+  LDAPMessage *entry;
+{
+  char **v=NULL;
+  char **p=NULL;
+
+  int  ret=0;
+
+  if (!entry) return ret;
+
+  /* get the values from the entry */
+  v=ldap_get_values(ld,entry,"sudoUser");
+
+  /* walk through values */
+  for (p=v; p && *p && !ret;p++)
+  {
+    if (ldap_conf.debug>1) printf("ldap sudoUser netgroup '%s' ...",*p);
+
+    /* match any */
+    if (netgr_matches(*p,NULL,NULL,user_name)) ret=1;
+
+    if (ldap_conf.debug>1) printf(" %s\n",ret ? "MATCH!" : "not");
+  }
+
+  /* cleanup */
+  if (v) ldap_value_free(v);
+
+  /* all done */
+  return ret;
+}
+
+
+/*
+ * Walks through search result and returns true if we have a
+ * host match
+ */
+int
+sudo_ldap_check_host(ld,entry)
+  LDAP *ld;
+  LDAPMessage *entry;
+{
+  char **v=NULL;
+  char **p=NULL;
+
+  int  ret=0;
+
+  if (!entry) return ret;
+
+  /* get the values from the entry */
+  v=ldap_get_values(ld,entry,"sudoHost");
+
+  /* walk through values */
+  for (p=v; p && *p && !ret;p++)
+  {
+    if (ldap_conf.debug>1) printf("ldap sudoHost '%s' ...",*p);
+
+    /* match any or address or netgroup or hostname */
+    if (
+         !strcasecmp(*p,"ALL") ||
+         addr_matches(*p) ||
+         netgr_matches(*p,user_host,user_shost,NULL) ||
+         !hostname_matches(user_shost,user_host,*p)
+       )
+    {
+       ret=1;
+    }
+
+
+    if (ldap_conf.debug>1) printf(" %s\n",ret ? "MATCH!" : "not");
+  }
+
+  /* cleanup */
+  if (v) ldap_value_free(v);
+
+  /* all done */
+  return ret;
+}
+
+/*
+ * Walks through search result and returns true if we have a
+ * runas match.  Since the runas directive in /etc/sudoers is optional,
+ * so is the sudoRunAs attribute.
+ *
+ */
+
+int sudo_ldap_check_runas(ld,entry)
+  LDAP *ld;
+  LDAPMessage *entry;
+{
+  char **v=NULL;
+  char **p=NULL;
+
+  int  ret=0;
+
+  if (!entry) return ret;
+
+  /* get the values from the entry */
+  v=ldap_get_values(ld,entry,"sudoRunAs");
+
+  /* BUG:
+   *
+   * if runas is not specified on the command line, the only information
as
+   * to which user to run as is in the runas_default option.
+   * We should check check to see if we have the local option present.
+   * Unfortunately we don't parse these options until after this routine
+   * says yes * or no.  The query has already returned, so we could peek
at the
+   * attribute values here though.
+   *
+   * For now just require users to always use -u option unless its set
+   * in the global defaults. This behaviour is no different than the
global
+   * /etc/sudoers.
+   *
+   * Sigh - maybe add this feature later
+   *
+   */
+
+  /* If there are no runas entries, then match the runas_default with
+   * whats on the command line
+   */
+  if (!v)
+  {
+    ret=!strcasecmp(*user_runas,def_str(I_RUNAS_DEFAULT));
+  }
+
+  /* what about the case where exactly one runas is specified in
+   * the config and the user forgets the -u option, should we
+   * switch it?  - Probably not
+   */
+
+  /* walk through values returned, looking for a match*/
+  for (p=v; p && *p && !ret;p++)
+  {
+    if (ldap_conf.debug>1) printf("ldap sudoRunAs '%s' ...",*p);
+
+    if (
+         !strcasecmp(*p,*user_runas) ||
+         !strcasecmp(*p,"ALL")
+       )
+    {
+       ret = 1;
+    }
+
+    if (ldap_conf.debug>1) printf(" %s\n",ret ? "MATCH!" : "not");
+  }
+
+  /* cleanup */
+  if (v) ldap_value_free(v);
+
+  /* all done */
+  return ret;
+}
+
+/*
+ * Walks through search result and returns true if we have a
+ * command match
+ */
+int sudo_ldap_check_command(ld,entry)
+  LDAP *ld;
+  LDAPMessage *entry;
+{
+  char **v=NULL;
+  char **p=NULL;
+  char *allowed_cmnd;
+  char *allowed_args;
+  int  ret=0;
+
+  if (!entry) return ret;
+
+  v=ldap_get_values(ld,entry,"sudoCommand");
+
+  /* get_first_entry */
+  for (p=v; p && *p && !ret;p++){
+    if (ldap_conf.debug>1) printf("ldap sudoCommand '%s' ...",*p);
+
+    /* Match against ALL ? */
+    if (!strcasecmp(*p,"ALL")) {
+      ret=1;
+      if (safe_cmnd) free (safe_cmnd);
+      safe_cmnd=estrdup(user_cmnd);
+    }
+
+    /* split optional args away from command */
+    allowed_cmnd=estrdup(*p);
+    allowed_args=strchr(allowed_cmnd,' ');
+    if (allowed_args) *allowed_args++='\0';
+
+    /* check the command like normal */
+    if (command_matches(user_cmnd, user_args,allowed_cmnd,allowed_args))
ret=1;
+
+    /* cleanup */
+    free(allowed_cmnd);
+    if (ldap_conf.debug>1) printf(" %s\n",ret ? "MATCH!" : "not");
+  }
+
+  /* more cleanup */
+  if (v) ldap_value_free(v);
+
+  /* all done */
+  return ret;
+}
+
+/*
+ * Read sudoOption, modify the defaults as we go.
+ * This is used once from the cn=defaults entry
+ * and also once when a final sudoRole is matched.
+ *
+ */
+void
+sudo_ldap_parse_options(ld,entry)
+  LDAP *ld;
+  LDAPMessage *entry;
+{
+  /* used to parse attributes */
+  char **v=NULL;
+  char **p=NULL;
+  char *var;
+  char *val;
+  char op;
+
+  if (!entry) return;
+
+  v=ldap_get_values(ld,entry,"sudoOption");
+
+  /* walk through options */
+  for (p=v; p && *p;p++){
+
+    if (ldap_conf.debug>1) printf("ldap sudoOption: '%s'\n",*p);
+    var=estrdup(*p);
+    /* check for = char */
+    val=strchr(var,'=');
+
+    /* check for equals sign past first char */
+    if (val>var){
+      *val++='\0'; /* split on = and truncate var */
+      op=*(val-2); /* peek for += or -= cases */
+      if (op == '+' || op == '-') {
+        *(val-2)='\0'; /* found, remove extra char */
+        /* case var+=val or var-=val */
+        set_default(var,val,(int)op);
+      } else {
+        /* case var=val */
+        set_default(var,val,TRUE);
+      }
+    } else if (*var=='!'){
+      /* case !var Boolean False */
+      set_default(var+1,NULL,FALSE);
+    }  else {
+      /* case var Boolean True */
+      set_default(var,NULL,TRUE);
+    }
+    free(var);
+
+  }
+
+  if (v) ldap_value_free(v);
+
+}
+
+/*
+ * Like strcat, only prevents buffer overflows
+ */
+void
+_scatn(buf,bufsize,src)
+  char *buf;
+  size_t bufsize;
+  char *src;
+{
+
+  /* make sure we have enough space,plus null at end */
+  /* or silently truncate the string */
+  if (bufsize>strlen(buf)+strlen(src)+1)
+    strcat(buf,src);
+}
+
+/* builds together a filte to check against ldap
+ */
+char *
+sudo_ldap_build_pass1()
+{
+  static char b[1024];
+  struct group *grp;
+  gid_t *grplist=NULL;
+  int ngrps;
+  int i;
+
+  b[0]='\0'; /* empty string */
+
+
+  /* global OR */
+  _scatn(b,sizeof(b),"(|");
+
+  /* build filter sudoUser=user_name */
+  _scatn(b,sizeof(b),"(sudoUser=");
+  _scatn(b,sizeof(b),user_name);
+  _scatn(b,sizeof(b),")");
+
+  /* Append primary group */
+  grp=getgrgid(getgid());
+  if (grp!=NULL){
+    _scatn(b,sizeof(b),"(sudoUser=%");
+    _scatn(b,sizeof(b),grp->gr_name);
+    _scatn(b,sizeof(b),")");
+  }
+
+  /* handle arbitrary number of groups */
+  if (0<(ngrps=getgroups(0,NULL))){
+    grplist=calloc(ngrps,sizeof(gid_t));
+    if (grplist!=NULL && (0<getgroups(ngrps,grplist)))
+      for(i=0;i<ngrps;i++){
+     if((grp=getgrgid(grplist[i]))!=NULL){
+          _scatn(b,sizeof(b),"(sudoUser=%");
+          _scatn(b,sizeof(b),grp->gr_name);
+          _scatn(b,sizeof(b),")");
+     }
+      }
+  }
+
+
+  /* Add ALL to list */
+  _scatn(b,sizeof(b),"(sudoUser=ALL)");
+
+  /* End of OR List */
+  _scatn(b,sizeof(b),")");
+  return b ;
+}
+
+
+int
+sudo_ldap_read_config()
+{
+  FILE *f;
+  char buf[BUF_SIZ];
+  char *c;
+  char *keyword;
+  char *value;
+
+  f=fopen(LDAP_CONFIG,"r");
+  if (!f) return 0;
+  while (f && fgets(buf,sizeof(buf)-1,f)){
+    c=buf;
+    if (*c == '#')  continue; /* ignore comment */
+    if (*c == '\n') continue; /* skip newline */
+    if (!*c)        continue; /* incomplete last line */
+
+    /* skip whitespace before keyword */
+    while (isspace(*c)) c++;
+    keyword=c;
+
+    /* properly terminate keyword string */
+    while (*c && !isspace(*c)) c++;
+    if (*c) {
+      *c='\0'; /* terminate keyword */
+      c++;
+    }
+
+    /* skip whitespace before value */
+    while (isspace(*c)) c++;
+    value=c;
+
+    /* trim whitespace after value */
+    while (*c) c++; /* wind to end */
+    while (--c > value && isspace(*c)) *c='\0';
+
+    /* The following macros make the code much more readable */
+
+#define MATCH_S(x,y) if (!strcasecmp(keyword,x)) \
+    { if (y) free(y); y=estrdup(value); }
+#define MATCH_I(x,y) if (!strcasecmp(keyword,x)) { y=atoi(value); }
+
+
+
+    /* parse values using a continues chain of
+     * if else if else if else if else ... */
+         MATCH_S("host",    ldap_conf.host)
+    else MATCH_I("port",    ldap_conf.port)
+    else MATCH_S("uri",     ldap_conf.uri)
+    else MATCH_S("binddn",  ldap_conf.binddn)
+    else MATCH_S("bindpw",  ldap_conf.bindpw)
+    else MATCH_S("sudoers_base",    ldap_conf.base)
+    else MATCH_I("sudoers_debug",   ldap_conf.debug)
+    else {
+
+    /* The keyword was unrecognized.  Since this config file is shared
+     * by multiple programs, it is appropriate to silently ignore options
this
+     * program does not understand
+     */
+    }
+
+  } /* parse next line */
+
+  if (f) fclose(f);
+
+  /* defaults */
+  if (!ldap_conf.port) ldap_conf.port=389;
+  if (!ldap_conf.host) ldap_conf.host=estrdup("localhost");
+
+
+  if (ldap_conf.debug>1) {
+    printf("LDAP Config Summary\n");
+    printf("===================\n");
+    printf("host         %s\n", ldap_conf.host ?
+                 ldap_conf.host   : "(NONE)");
+    printf("port         %d\n", ldap_conf.port);
+
+    printf("uri          %s\n", ldap_conf.uri ?
+                 ldap_conf.uri    : "(NONE)");
+    printf("sudoers_base %s\n", ldap_conf.base ?
+                 ldap_conf.base : "(NONE) <---Sudo will ignore ldap)");
+    printf("binddn       %s\n", ldap_conf.binddn ?
+                 ldap_conf.binddn : "(anonymous)");
+    printf("bindpw       %s\n", ldap_conf.bindpw ?
+                 ldap_conf.bindpw : "(anonymous)");
+    printf("===================\n");
+  }
+
+  /* if no base is defined, ignore LDAP */
+  if (!ldap_conf.base) return 0;
+  /* All is good */
+  return 1;
+}
+
+/*
+ * like sudoers_lookup() - only LDAP style
+ *
+ */
+
+int
+sudo_ldap_check(pwflag)
+int pwflag;
+{
+
+  LDAP *ld=NULL;
+
+  /* Used for searches */
+  LDAPMessage *result=NULL;
+  LDAPMessage *entry=NULL;
+  /* used to parse attributes */
+  char *f;
+  /* temp/final return values */
+  int rc=0;
+  int ret=0;
+  int pass=0;
+  /* flags */
+  int ldap_user_matches=0;
+  int ldap_host_matches=0;
+
+  if (!sudo_ldap_read_config())  return VALIDATE_ERROR;
+
+
+  /* attempt connect */
+  if (ldap_conf.uri) {
+
+    if (ldap_conf.debug>1) fprintf(stderr,
+           "ldap_initialize(ld,%s)\n",ldap_conf.uri);
+
+    rc=ldap_initialize(&ld,ldap_conf.uri);
+    if(rc){
+      fprintf(stderr, "ldap_initialize()=%d : %s\n",
+           rc,ldap_err2string(rc));
+      return VALIDATE_ERROR;
+    }
+  } else if (ldap_conf.host) {
+
+    if (ldap_conf.debug>1) fprintf(stderr,
+           "ldap_init(%s,%d)\n",ldap_conf.host,ldap_conf.port);
+
+    ld=ldap_init(ldap_conf.host,ldap_conf.port);
+    if (!ld) {
+      fprintf(stderr, "ldap_init(): errno=%d : %s\n",
+                 errno, strerror(errno));
+      return VALIDATE_ERROR;
+    }
+  }
+
+  /* Acutally connect */
+
+  rc=ldap_simple_bind_s(ld,ldap_conf.binddn,ldap_conf.bindpw);
+  if(rc){
+    fprintf(stderr,"ldap_simple_bind_s()=%d : %s\n",
+           rc, ldap_err2string(rc));
+    return VALIDATE_ERROR ;
+  }
+
+  if (ldap_conf.debug) printf("ldap_bind() ok\n");
+
+
+  /* Parse Default Options */
+
+  rc=ldap_search_s(ld,ldap_conf.base,LDAP_SCOPE_ONELEVEL,
+             "cn=defaults",NULL,0,&result);
+  if (!rc) {
+    entry=ldap_first_entry(ld,result);
+    if (ldap_conf.debug) printf("found:%s\n",ldap_get_dn(ld,entry));
+    sudo_ldap_parse_options(ld,entry);
+  } else {
+    if (ldap_conf.debug) printf("no options found\n");
+  }
+
+  if (result) ldap_msgfree(result);
+  result=NULL;
+
+  /*
+   * Okay - time to search for anything that matches this user
+   * Lets limit it to only two queries of the LDAP server
+   *
+   * The first pass will look by the username, groups, and
+   * the keyword ALL.  We will then inspect the results that
+   * came back from the query.  We don't need to inspect the
+   * sudoUser in this pass since the LDAP server already scanned
+   * it for us.
+   *
+   * The second pass will return all the entries that contain
+   * user netgroups.  Then we take the netgroups returned and
+   * try to match them against the username.
+   *
+   */
+
+  for(pass=1;!ret && pass<=2;pass++){
+
+    if (pass==1) {
+      /* Want the entries that match our usernames or groups */
+      f=sudo_ldap_build_pass1();
+    } else { /* pass=2 */
+      /* Want the entries that have user netgroups in them. */
+      f="sudoUser=+*";
+    }
+    if (ldap_conf.debug) printf("ldap search '%s'\n",f);
+    rc=ldap_search_s(ld,ldap_conf.base,LDAP_SCOPE_ONELEVEL,
+               f,NULL,0,&result);
+    if (rc) {
+      if (ldap_conf.debug) printf("nothing found for '%s'\n",f);
+    }
+    /* parse each entry returned from this most recent search */
+    for(
+        entry=rc ? NULL : ldap_first_entry(ld,result);
+        entry!=NULL;
+        entry=ldap_next_entry(ld,entry))
+    {
+      if (ldap_conf.debug) printf("found:%s\n",ldap_get_dn(ld,entry));
+      if (

+          /* first verify user netgroup matches - only if in pass 2 */
+          (pass!=2 || sudo_ldap_check_user_netgroup(ld,entry)) &&
+       /* remember that user matched */
+       (ldap_user_matches=-1) &&
+          /* verify host match */
+          sudo_ldap_check_host(ld,entry) &&
+       /* remember that host matched */
+       (ldap_host_matches=-1) &&
+          /* verify command match */
+          sudo_ldap_check_command(ld,entry) &&
+          /* verify runas match */
+          sudo_ldap_check_runas(ld,entry)
+      )
+      {
+        /* We have a match! */
+        if(ldap_conf.debug) printf("Perfect Matched!\n");
+        /* pick up any options */
+        sudo_ldap_parse_options(ld,entry);
+        /* make sure we dont reenter loop */
+        ret=VALIDATE_OK;
+        /* break from inside for loop */
+        break;
+      }
+
+    }
+    if (result) ldap_msgfree(result);
+    result=NULL;
+
+  }
+
+  /* shut down connection */
+  if (ld) ldap_unbind_s(ld);
+
+
+  if (ldap_conf.debug) printf("user_matches=%d\n",ldap_user_matches);
+  if (ldap_conf.debug) printf("host_matches=%d\n",ldap_host_matches);
+
+  /*  I am not sure of the rest of the logic from here down */
+  if (ret==0) {
+    ret=VALIDATE_NOT_OK;
+    if (!ldap_user_matches) ret|=FLAG_NO_USER;
+    if (!ldap_host_matches) ret|=FLAG_NO_HOST;
+  }
+
+  /* Fixme - is this the right logic? */
+  if (pwflag || !def_flag(I_AUTHENTICATE)) {
+    ret|=FLAG_NOPASS;
+  }
+
+  if (ldap_conf.debug) printf("sudo_ldap_check()=0x%02x\n",ret);
+
+  return ret ;
+}
+
+/*
+ * Explicityly denied
+ * VALIDATE_NOT_OK
+ * VALIDATE_NOT_OK | FLAG_NOPASS
+ * Explicitly Granted
+ * VALIDATE_OK
+ * VALIDATE_OK | FLAG_NOPASS
+ * VALIDATE_OK |  -1 if found
+ *
+ * remove FLAG_NO_HOST
+ * VALIDATE_ERROR  if could not connect to LDAP server
+ *
+ * FLAG_NO_CHECK
+ * FLAG_NO_HOST
+ * FLAG_NO_USER
+ *
+ *
+ * Checked against
+ * |VALIDATE_ERROR - complains of parse and dies
+ * |FLAG_NOPASS - dont ask for password
+ * |VALIDATE_OK - life is good - may be used with |FLAG_NOPASS
+ *
+ * |FLAG_NO_USER or |FLAG_NO_HOST - logs and dies
+ * |VALIDATE_NOT_OK (! FLAG_NO_USER && ! FLAG_NO_HOST)
+ * - command not allowed?
+ *
+ *
+ */
+
+#endif /* HAVE_LDAP */
+
diff -u sudo-1.6.6.orig/sudo.c sudo-1.6.6.ldap/sudo.c
--- sudo-1.6.6.orig/sudo.c    Thu Apr 18 11:35:20 2002
+++ sudo-1.6.6.ldap/sudo.c    Wed Mar 19 16:07:49 2003
@@ -250,11 +250,25 @@

     cmnd_status = init_vars(sudo_mode);

+#ifdef HAVE_LDAP
+    validated = sudo_ldap_check(pwflag);
+    /* if we did not find it in ldap and we are not disallowed from
reading
+     * the local sudoers, then go ahead and read the sudoers file
+     */
+    if (!(validated & VALIDATE_OK)&& (!def_flag(I_IGNORE_LOCAL_SUDOERS)))
+    {
+      /* check local sudoers */
+
+#endif
     check_sudoers();   /* check mode/owner on _PATH_SUDOERS */

     /* Validate the user but don't search for pseudo-commands. */
     validated = sudoers_lookup(pwflag);
+#ifdef HAVE_LDAP
+    }

+#endif
+
     /*
      * If we have POSIX saved uids and the stay_setuid flag was not set,
      * set the real, effective and saved uids to 0 and use
set_perms_fallback()
diff -u sudo-1.6.6.orig/sudo.h sudo-1.6.6.ldap/sudo.h
--- sudo-1.6.6.orig/sudo.h    Wed Jan 16 16:27:09 2002
+++ sudo-1.6.6.ldap/sudo.h    Mon Jan 27 16:24:33 2003
@@ -201,6 +201,9 @@
 void check_user        __P((void));
 void verify_user __P((struct passwd *, char *));
 int sudoers_lookup     __P((int));
+#ifdef HAVE_LDAP
+int sudo_ldap_check    __P((int));
+#endif
 void set_perms_posix   __P((int, int));
 void set_perms_fallback      __P((int, int));
 void remove_timestamp  __P((int));
diff -u sudo-1.6.6.orig/sudoers2ldif sudo-1.6.6.ldap/sudoers2ldif
--- sudo-1.6.6.orig/sudoers2ldif    Tue Jan 28 14:35:42 2003
+++ sudo-1.6.6.ldap/sudoers2ldif    Tue Feb  4 07:36:26 2003
@@ -1,0 +1,108 @@
+#!/usr/bin/env perl
+use strict;
+
+#
+# Converts a sudoers file to LDIF format in prepration for loading into
+# the LDAP server.
+#
+
+# BUGS:
+#   Does not yet handle multiple lines with : in them
+#   Does not yet handle runas (xxx) syntax.
+#   Does not yet remove quotation marks from options
+
+my %UA;
+my %HA;
+my %CA;
+my $base=$ENV{SUDOERS_BASE} or die "$0: Container SUDOERS_BASE
undefined\n";
+my @options=();
+
+my $did_defaults=0;
+
+# parse sudoers one line at a time
+while (<>){
+
+  # remove comment
+  s/#.*//;
+
+  # line continuation
+  $_.=<> while s/\\\s*$//s;
+
+  # cleanup newline
+  chomp;
+
+  # ignore blank lines
+  next if /^\s*$/;
+
+  if (/^Defaults\s+/i) {
+    my $opt=$';
+    $opt=~s/\s+$//; # remove trailing whitespace
+    push @options,$opt;
+  } elsif (/^(\S+)\s+(.+)=\s*(.*)/) {
+
+    # Aliases or Definitions
+    my ($p1,$p2,$p3)=($1,$2,$3);
+    $p2=~s/\s+$//; # remove trailing whitespace
+    $p3=~s/\s+$//; # remove trailing whitespace
+
+    if      ($p1 eq "User_Alias") {
+      $UA{$p2}=$p3;
+    } elsif ($p1 eq "Host_Alias") {
+      $HA{$p2}=$p3;
+    } elsif ($p1 eq "Cmnd_Alias") {
+      $CA{$p2}=$p3;
+    } else {
+      if (!$did_defaults++){
+        # do this once
+        print "dn: cn=defaults,$base\n";
+        print "objectClass: top\n";
+        print "objectClass: sudoRole\n";
+        print "cn: defaults\n";
+     print "description: Default sudoOption's go here\n";
+        print "sudoOption: $_\n" foreach @options;
+        print "\n";
+      }
+      # Definition
+      my @users=split /\s*,\s*/,$p1;
+      my @hosts=split /\s*,\s*/,$p2;
+      my @cmds= split /\s*,\s*/,$p3;
+      @options=();
+      print "dn: cn=$users[0],$base\n";
+      print "objectClass: top\n";
+      print "objectClass: sudoRole\n";
+      print "cn: $users[0]\n";
+      # will clobber options
+      print "sudoUser: $_\n"   foreach expand(\%UA, at users);
+      print "sudoHost: $_\n"   foreach expand(\%HA, at hosts);
+      print "sudoCommand: $_\n" foreach expand(\%CA, at cmds);
+      print "sudoOption: $_\n" foreach @options;
+      print "\n";
+    }
+
+  } else {
+    print "parse error: $_\n";
+  }
+
+}
+
+#
+# recursively expand hash elements
+sub expand{
+  my $ref=shift;
+  my @a=();
+
+  # preen the line a little
+  foreach (@_){
+    # if NOPASSWD: directive found, mark entire entry as not requiring
+    s/NOPASSWD:\s*// && push @options,"!authenticate";
+    s/PASSWD:\s*// && push @options,"authenticate";
+    s/\w+://; # silently remove other directives
+    s/\s+$//; # right trim
+  }
+
+  # do the expanding
+  push @a,$ref->{$_} ? expand($ref,split /\s*,\s*/,$ref->{$_}):$_ foreach
@_;
+  @a;
+}
+
+

----- Forwarded by Todd V Anello/Nationwide/NWIE on 10/31/2003 03:00 PM
-----






More information about the sudo-users mailing list