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