lists.arthurdejong.org
RSS feed

nss-pam-ldapd branch master updated. 0.8.12-156-g62a409c

[Date Prev][Date Next] [Thread Prev][Thread Next]

nss-pam-ldapd branch master updated. 0.8.12-156-g62a409c



This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "nss-pam-ldapd".

The branch, master has been updated
       via  62a409cb43b441c32692f414a1867176d37034ac (commit)
       via  012b18554e5e6a408a11a7157a30c5d068f2d3d1 (commit)
       via  d0482fb56654037d01feb0f7a27206aefacf7112 (commit)
       via  f1895f98a36d0f29b88ed61edf2e978dc520caed (commit)
       via  8fb5eb16ccc7fe3d119d7365537ca7cd82d496b8 (commit)
      from  aae36cfcfb6ec00776f6da1e0d1fd5f90a72f2dd (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
http://arthurdejong.org/git/nss-pam-ldapd/commit/?id=62a409cb43b441c32692f414a1867176d37034ac

commit 62a409cb43b441c32692f414a1867176d37034ac
Merge: aae36cf 012b185
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sat Mar 30 23:10:34 2013 +0100

    Implement used modification functionality
    
    This adds user information modification functionality to nslcd and pynslcd 
and
    implements a chsh.ldap utility that can be used to change the login shell 
of a
    user (similar to the normal chsh command).
    
    The user modification functionality should allow for generic modifications 
of
    user information. More utility commands to perform modifications remain to 
be
    implemented.


http://arthurdejong.org/git/nss-pam-ldapd/commit/?id=012b18554e5e6a408a11a7157a30c5d068f2d3d1

commit 012b18554e5e6a408a11a7157a30c5d068f2d3d1
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sat Mar 30 22:59:57 2013 +0100

    Initial version of a chsh.ldap utility

diff --git a/.gitignore b/.gitignore
index d079ba9..d3a7ecc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -37,6 +37,8 @@ stamp-*
 /splint.txt
 
 # /man/
+/man/chsh.ldap.1
+/man/chsh.ldap.1.html
 /man/getent.ldap.1
 /man/getent.ldap.1.html
 /man/nslcd.8
diff --git a/man/Makefile.am b/man/Makefile.am
index 8975eec..5b8d7f4 100644
--- a/man/Makefile.am
+++ b/man/Makefile.am
@@ -18,7 +18,7 @@
 # 02110-1301 USA
 
 PAM_MANS = pam_ldap.8
-UTILS_MANS = getent.ldap.1
+UTILS_MANS = getent.ldap.1 chsh.ldap.1
 NSLCD_MANS = nslcd.conf.5 nslcd.8
 PYNSLCD_MANS = nslcd.conf.5 pynslcd.8
 ALL_MANS = $(PAM_MANS) $(UTILS_MANS) $(NSLCD_MANS) $(PYNSLCD_MANS)
diff --git a/man/chsh.ldap.1.xml b/man/chsh.ldap.1.xml
new file mode 100644
index 0000000..c5927d8
--- /dev/null
+++ b/man/chsh.ldap.1.xml
@@ -0,0 +1,148 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"
+                   "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd";>
+
+<!--
+   chsh.ldap.1.xml - docbook manual page for chsh.ldap
+
+   Copyright (C) 2013 Arthur de Jong
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, write to the Free Software
+   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301 USA
+-->
+
+<refentry id="chshldap1">
+
+ <refentryinfo>
+  <author>
+   <firstname>Arthur</firstname>
+   <surname>de Jong</surname>
+  </author>
+ </refentryinfo>
+
+ <refmeta>
+  <refentrytitle>chsh.ldap</refentrytitle>
+  <manvolnum>1</manvolnum>
+  <refmiscinfo class="version">Version 0.8.11</refmiscinfo>
+  <refmiscinfo class="manual">User Commands</refmiscinfo>
+  <refmiscinfo class="date">Oct 2012</refmiscinfo>
+ </refmeta>
+
+ <refnamediv id="name">
+  <refname>chsh.ldap</refname>
+  <refpurpose>change login shell in LDAP</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv id="synopsis">
+  <cmdsynopsis>
+   <command>chsh.ldap</command>
+   <arg choice="opt"><replaceable>options</replaceable></arg>
+   <arg choice="opt"><replaceable>LOGIN</replaceable></arg>
+  </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1 id="description">
+  <title>Description</title>
+  <para>
+   The <command>chsh.ldap</command> command can be used to change user's
+   login shell (command interpreter).
+  </para>
+  <para>
+   The actual change in <acronym>LDAP</acronym> is performed by the
+   <command>nslcd</command> daemon and is subject to the access controls
+   configured in the <acronym>LDAP</acronym> server.
+  </para>
+ </refsect1>
+
+ <refsect1 id="options">
+  <title>Options</title>
+  <para>
+   The options that may be specified to the <command>chsh.ldap</command>
+   command are:
+  </para>
+  <variablelist remap="TP">
+
+   <varlistentry id="shell">
+    <term>
+     <option>-s</option>, <option>--shell</option>
+     <replaceable>SHELL</replaceable>
+    </term>
+    <listitem>
+     <para>
+      The name of the user's new login shell.
+      Setting this field to blank causes the system to select the default
+      login shell.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry id="listshells">
+    <term>
+     <option>-l</option>, <option>--list-shells</option>
+    </term>
+    <listitem>
+     <para>
+      Print the list of shells found in <file>/etc/shells</file> and exit.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry id="help">
+    <term>
+     <option>-h</option>, <option>--help</option>
+    </term>
+    <listitem>
+     <para>Display short help and exit.</para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry id="version">
+    <term>
+     <option>-V, --version</option>
+    </term>
+    <listitem>
+     <para>Output version information and exit.</para>
+    </listitem>
+   </varlistentry>
+
+  </variablelist>
+  <para>
+    If no option is specified <command>chsh.ldap</command> will prompt the
+    user to enter a value for the shell.
+  </para>
+ </refsect1>
+
+ <refsect1 id="files">
+  <title>Files</title>
+  <para>
+   <filename>/etc/shells</filename> - list of valid login shells
+  </para>
+ </refsect1>
+
+ <refsect1 id="see_also">
+  <title>See Also</title>
+  <para>
+   
<citerefentry><refentrytitle>chsh</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+   
<citerefentry><refentrytitle>shells</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+   
<citerefentry><refentrytitle>nslcd</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+  </para>
+ </refsect1>
+
+ <refsect1 id="author">
+  <title>Author</title>
+  <para>This manual was written by Arthur de Jong 
&lt;arthur@arthurdejong.org&gt;.</para>
+ </refsect1>
+
+</refentry>
diff --git a/utils/Makefile.am b/utils/Makefile.am
index e9233d8..a10e61d 100644
--- a/utils/Makefile.am
+++ b/utils/Makefile.am
@@ -19,7 +19,7 @@
 
 utilsdir = $(datadir)/nslcdutils
 
-utils_PYTHON = cmdline.py getent.py nslcd.py
+utils_PYTHON = cmdline.py nslcd.py getent.py chsh.py shells.py users.py
 nodist_utils_PYTHON = constants.py
 CLEANFILES = $(nodist_utils_PYTHON)
 
@@ -36,7 +36,7 @@ constants.py: ../pynslcd/constants.py
 # create symbolic links to the commands and fix permissions
 install-data-hook:
        $(MKDIR_P) $(DESTDIR)$(bindir)
-       set -ex; for cmd in getent ; do \
+       set -ex; for cmd in getent chsh ; do \
          chmod a+rx $(DESTDIR)$(utilsdir)/$$cmd.py ; \
          [ -L $(DESTDIR)$(bindir)/$$cmd.ldap ] || $(LN_S) $(utilsdir)/$$cmd.py 
$(DESTDIR)$(bindir)/$$cmd.ldap ; \
        done
diff --git a/utils/chsh.py b/utils/chsh.py
new file mode 100755
index 0000000..30c5c12
--- /dev/null
+++ b/utils/chsh.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+# coding: utf-8
+
+# chsh.py - program for changing the login shell using nslcd
+#
+# Copyright (C) 2013 Arthur de Jong
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA
+
+import argparse
+
+from cmdline import VersionAction, ListShellsAction
+import constants
+import nslcd
+import shells
+import users
+
+
+# set up command line parser
+parser = argparse.ArgumentParser(
+        description='Change the user login shell in LDAP.',
+        epilog='Report bugs to <%s>.' % constants.PACKAGE_BUGREPORT
+    )
+parser.add_argument('-V', '--version', action=VersionAction)
+parser.add_argument('-s', '--shell', help='login shell for the user account')
+parser.add_argument('-l', '--list-shells', action=ListShellsAction)
+parser.add_argument('username', metavar='USER', nargs='?',
+    help="the user who's shell to change")
+
+
+def ask_shell(oldshell):
+    """Ask the user to provide a shell."""
+    shell = raw_input('  Login Shell [%s]: ' % oldshell)
+    return shell or oldshell
+
+
+# parse arguments
+args = parser.parse_args()
+# check username part
+user = users.User(args.username)
+user.check()
+# check the command line shell if one was provided (to fail early)
+shell = args.shell
+if shell is not None:
+    shells.check(shell, user.asroot)
+# prompt for a password if required
+password = user.get_passwd()
+# prompt for a shell if it was not specified on the command line
+if shell is None:
+    print 'Enter the new value, or press ENTER for the default'
+    shell = ask_shell(user.shell)
+    shells.check(shell, user.asroot)
+# perform the modification
+result = nslcd.usermod(user.username, user.asroot, password, {
+        constants.NSLCD_USERMOD_SHELL: shell,
+    })
+# TODO: print proper response
diff --git a/utils/cmdline.py b/utils/cmdline.py
index eb84fe3..3d7d58f 100644
--- a/utils/cmdline.py
+++ b/utils/cmdline.py
@@ -48,3 +48,21 @@ class VersionAction(argparse.Action):
     def __call__(self, parser, namespace, values, option_string=None):
         print version_string
         parser.exit()
+
+
+class ListShellsAction(argparse.Action):
+
+    def __init__(self, option_strings, dest,
+                 help='list the shells found in /etc/shells'):
+        super(ListShellsAction, self).__init__(
+            option_strings=option_strings,
+            dest=argparse.SUPPRESS,
+            default=argparse.SUPPRESS,
+            nargs=0,
+            help=help)
+
+    def __call__(self, parser, namespace, values, option_string=None):
+        import shells
+        for shell in shells.list_shells():
+            print shell
+        parser.exit()
diff --git a/utils/nslcd.py b/utils/nslcd.py
index 06165cc..65e5822 100644
--- a/utils/nslcd.py
+++ b/utils/nslcd.py
@@ -111,3 +111,26 @@ class NslcdClient(object):
 
     def __del__(self):
         self.close()
+
+
+def usermod(username, asroot=False, password=None, args=None):
+    # open a connection to nslcd
+    con = NslcdClient(constants.NSLCD_ACTION_USERMOD)
+    # write the request information
+    con.write_string(username)
+    con.write_int32(1 if asroot else 0)
+    con.write_string(password)
+    for k, v in args.items():
+        con.write_int32(k)
+        con.write_string(v)
+    con.write_int32(constants.NSLCD_USERMOD_END)
+    # read the response
+    assert con.get_response() == constants.NSLCD_RESULT_BEGIN
+    response = {}
+    while True:
+        key = con.read_int32()
+        if key == constants.NSLCD_USERMOD_END:
+            break
+        response[key] = con.read_string()
+    # return the response
+    return response
diff --git a/utils/shells.py b/utils/shells.py
new file mode 100644
index 0000000..cc3fca1
--- /dev/null
+++ b/utils/shells.py
@@ -0,0 +1,64 @@
+# coding: utf-8
+
+# shells.py - functions for validating user shells
+#
+# Copyright (C) 2013 Arthur de Jong
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA
+
+import ctypes
+import ctypes.util
+import os
+import sys
+
+
+def list_shells():
+    """List the shells from /etc/shells."""
+    libc = ctypes.CDLL(ctypes.util.find_library("c"))
+    libc.setusershell()
+    while True:
+        shell = ctypes.c_char_p(libc.getusershell()).value
+        if not shell:
+            break
+        yield shell
+    libc.endusershell()
+
+
+def shellexists(shell):
+    """Check if the provided shell exists and is executable."""
+    return os.path.isfile(shell) and os.access(shell, os.X_OK)
+
+
+def check(shell, asroot=False):
+    """Check if the specified shell is valid and exit if it isn't."""
+    # if the shell is listed in /etc/shells, everything should be OK
+    if shell in list_shells():
+        return
+    # if we are not root, bail out
+    if not asroot:
+        if not shell:
+            # FIXME: print to stderr
+            print '%s: empty shell not allowed' % sys.argv[0]
+        else:
+            # FIXME: print to stderr
+            print '%s: %s is an invalid shell' % (sys.argv[0], shell)
+        sys.exit(1)
+    # warn if something seems wrong
+    if not shell:
+        # FIXME: print to stderr
+        print '%s: Warning: setting empty shell' % sys.argv[0]
+    elif not shellexists(shell):
+        print '%s: Warning: %s does not exist' % (sys.argv[0], shell)
diff --git a/utils/users.py b/utils/users.py
new file mode 100644
index 0000000..02216d6
--- /dev/null
+++ b/utils/users.py
@@ -0,0 +1,60 @@
+# coding: utf-8
+
+# users.py - functions for validating the user to change information for
+#
+# Copyright (C) 2013 Arthur de Jong
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA
+
+import getpass
+import os
+import pwd
+import sys
+
+
+class User(object):
+
+    def __init__(self, username):
+        self.myuid = os.getuid()
+        if username:
+            userinfo = pwd.getpwnam(username)
+        else:
+            self.asroot = False
+            userinfo = pwd.getpwuid(self.myuid)
+        (self.username, ignore, self.uid, self.gid, self.gecos, self.homedir,
+            self.shell) = userinfo
+        # if we are trying to modify another user we should be root
+        self.asroot = self.myuid != self.uid
+
+    def check(self):
+        """Check if the user we want to modify is an LDAP user and whether
+        we may modify the user information."""
+        if self.asroot and self.myuid != 0:
+            print "%s: you may not modify user '%s'.\n" % \
+                    (sys.argv[0], self.username)
+            sys.exit(1)
+        # FIXME: check if the user is an LDAP user
+
+    def get_passwd(self):
+        """Ask and return a password that is required to change the user."""
+        # FIXME: only ask the password if we require it
+        # (e.g. when root and nslcd has userpwmoddn we don't need to)
+        return getpass.getpass(
+                'LDAP administrator password: '
+                if self.asroot else
+                'LDAP password for %s: ' % self.username
+            )
+        # FIXME: check if the provided password is valid

http://arthurdejong.org/git/nss-pam-ldapd/commit/?id=d0482fb56654037d01feb0f7a27206aefacf7112

commit d0482fb56654037d01feb0f7a27206aefacf7112
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sat Mar 30 22:44:26 2013 +0100

    Handle user modification requests in pynslcd
    
    Similar to the nslcd implementation, this currently only covers modifying 
the
    homeDirectory and loginShell attributes.

diff --git a/pynslcd/pynslcd.py b/pynslcd/pynslcd.py
index eedab78..35ecb08 100755
--- a/pynslcd/pynslcd.py
+++ b/pynslcd/pynslcd.py
@@ -187,6 +187,7 @@ handlers.update(common.get_handlers('protocol'))
 handlers.update(common.get_handlers('rpc'))
 handlers.update(common.get_handlers('service'))
 handlers.update(common.get_handlers('shadow'))
+handlers.update(common.get_handlers('usermod'))
 
 
 def acceptconnection(session):
diff --git a/pynslcd/usermod.py b/pynslcd/usermod.py
new file mode 100644
index 0000000..c957b97
--- /dev/null
+++ b/pynslcd/usermod.py
@@ -0,0 +1,131 @@
+
+# usermod.py - functions for modifying user information
+#
+# Copyright (C) 2013 Arthur de Jong
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA
+
+import ctypes
+import ctypes.util
+import logging
+import os
+import os.path
+
+import ldap
+
+import cache
+import cfg
+import common
+import constants
+import pam
+import passwd
+
+
+def list_shells():
+    """List the shells from /etc/shells."""
+    libc = ctypes.CDLL(ctypes.util.find_library("c"))
+    libc.setusershell()
+    while True:
+        shell = ctypes.c_char_p(libc.getusershell()).value
+        if not shell:
+            break
+        yield shell
+    libc.endusershell()
+
+
+class UserModRequest(pam.PAMRequest):
+
+    action = constants.NSLCD_ACTION_USERMOD
+
+    def read_parameters(self, fp):
+        username = fp.read_string()
+        asroot = fp.read_int32()
+        password = fp.read_string()
+        mods = {}
+        while True:
+            key = fp.read_int32()
+            if key == constants.NSLCD_USERMOD_END:
+                break
+            mods[key] = fp.read_string()
+        return dict(username=username,
+                    asroot=asroot,
+                    password=password,
+                    mods=mods)
+
+    def write_result(self, mod, message):
+        self.fp.write_int32(mod)
+        self.fp.write_string(message)
+
+    def handle_request(self, parameters):
+        # fill in any missing userdn, etc.
+        self.validate(parameters)
+        is_root = (self.calleruid == 0) and parameters['asroot']
+        mods = []
+        # check if the the user passed the rootpwmoddn
+        if parameters['asroot']:
+            binddn = cfg.rootpwmoddn
+            # check if rootpwmodpw should be used
+            if not parameters['password'] and is_root and cfg.rootpwmodpw:
+                password = cfg.rootpwmodpw
+            else:
+                password = parameters['password']
+        else:
+            binddn = parameters['userdn']
+            password = parameters['password']
+        # write response header
+        self.fp.write_int32(constants.NSLCD_RESULT_BEGIN)
+        # check home directory modification
+        homedir = parameters['mods'].get(constants.NSLCD_USERMOD_HOMEDIR)
+        if homedir:
+            if is_root:
+                mods.append((ldap.MOD_REPLACE, passwd.attmap['homeDirectory'], 
[homedir]))
+            elif not os.path.isabs(homedir):
+                self.write_result(constants.NSLCD_USERMOD_HOMEDIR,
+                    'should be an absolute path')
+            elif not os.path.isdir(homedir):
+                self.write_result(constants.NSLCD_USERMOD_HOMEDIR,
+                    'not a directory')
+            else:
+                mods.append((ldap.MOD_REPLACE, passwd.attmap['homeDirectory'], 
[homedir]))
+        # check login shell modification
+        shell = parameters['mods'].get(constants.NSLCD_USERMOD_SHELL)
+        if shell:
+            if is_root:
+                mods.append((ldap.MOD_REPLACE, passwd.attmap['loginShell'], 
[shell]))
+            elif shell not in list_shells():
+                self.write_result(constants.NSLCD_USERMOD_SHELL,
+                    'unlisted shell')
+            elif not os.path.isfile(shell) or not os.access(shell, os.X_OK):
+                self.write_result(constants.NSLCD_USERMOD_SHELL,
+                    'not an executable')
+            else:
+                mods.append((ldap.MOD_REPLACE, passwd.attmap['loginShell'], 
[shell]))
+        # get a connection and perform the modification
+        if mods:
+            try:
+                conn, authz, msg = pam.authenticate(binddn, password)
+                conn.modify_s(parameters['userdn'], mods)
+                logging.info('changed information for %s', 
parameters['userdn'])
+            except (ldap.INVALID_CREDENTIALS, ldap.INSUFFICIENT_ACCESS), e:
+                try:
+                    msg = e[0]['desc']
+                except:
+                    msg = str(e)
+                logging.debug('modification failed: %s', msg)
+                self.write_result(constants.NSLCD_USERMOD_RESULT, msg)
+        # write closing statement
+        self.fp.write_int32(constants.NSLCD_USERMOD_END)
+        self.fp.write_int32(constants.NSLCD_RESULT_END)

http://arthurdejong.org/git/nss-pam-ldapd/commit/?id=f1895f98a36d0f29b88ed61edf2e978dc520caed

commit f1895f98a36d0f29b88ed61edf2e978dc520caed
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sat Mar 30 21:09:44 2013 +0100

    Handle user modification requests in nslcd
    
    This is currently limited to supporting modification of the homeDirectory 
and
    loginShell attributes.
    
    Modifications as root currently use the rootpwmoddn and rootpwmodpw options.

diff --git a/nslcd/Makefile.am b/nslcd/Makefile.am
index d901d64..60560f8 100644
--- a/nslcd/Makefile.am
+++ b/nslcd/Makefile.am
@@ -31,8 +31,8 @@ nslcd_SOURCES = nslcd.c ../nslcd.h ../common/nslcd-prot.h \
                 cfg.c cfg.h \
                 attmap.c attmap.h \
                 nsswitch.c nscd.c \
-                alias.c config.c ether.c group.c host.c netgroup.c network.c \
-                passwd.c protocol.c rpc.c service.c shadow.c pam.c
+                config.c alias.c ether.c group.c host.c netgroup.c network.c \
+                passwd.c protocol.c rpc.c service.c shadow.c pam.c usermod.c
 nslcd_LDADD = ../common/libtio.a ../common/libdict.a \
               ../common/libexpr.a ../compat/libcompat.a \
               @nslcd_LIBS@ @PTHREAD_LIBS@
diff --git a/nslcd/common.h b/nslcd/common.h
index 2965de6..a6c2c4d 100644
--- a/nslcd/common.h
+++ b/nslcd/common.h
@@ -240,6 +240,7 @@ int nslcd_pam_authz(TFILE *fp, MYLDAP_SESSION *session);
 int nslcd_pam_sess_o(TFILE *fp, MYLDAP_SESSION *session);
 int nslcd_pam_sess_c(TFILE *fp, MYLDAP_SESSION *session);
 int nslcd_pam_pwmod(TFILE *fp, MYLDAP_SESSION *session, uid_t calleruid);
+int nslcd_usermod(TFILE *fp, MYLDAP_SESSION *session, uid_t calleruid);
 
 /* macros for generating service handling code */
 #define NSLCD_HANDLE(db, fn, action, readfn, mkfilter, writefn)             \
diff --git a/nslcd/nslcd.c b/nslcd/nslcd.c
index 5f70963..9e22682 100644
--- a/nslcd/nslcd.c
+++ b/nslcd/nslcd.c
@@ -408,6 +408,7 @@ static void handleconnection(int sock, MYLDAP_SESSION 
*session)
     case NSLCD_ACTION_PAM_SESS_O:       (void)nslcd_pam_sess_o(fp, session); 
break;
     case NSLCD_ACTION_PAM_SESS_C:       (void)nslcd_pam_sess_c(fp, session); 
break;
     case NSLCD_ACTION_PAM_PWMOD:        (void)nslcd_pam_pwmod(fp, session, 
uid); break;
+    case NSLCD_ACTION_USERMOD:          (void)nslcd_usermod(fp, session, uid); 
break;
     default:
       log_log(LOG_WARNING, "invalid request id: 0x%08x", (unsigned int)action);
       break;
diff --git a/nslcd/usermod.c b/nslcd/usermod.c
new file mode 100644
index 0000000..61f6bbe
--- /dev/null
+++ b/nslcd/usermod.c
@@ -0,0 +1,297 @@
+/*
+   usermod.c - routines for changing user information such as full name,
+               login shell, etc
+
+   Copyright (C) 2013 Arthur de Jong
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, write to the Free Software
+   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301 USA
+*/
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif /* HAVE_STDINT_H */
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "common.h"
+#include "log.h"
+#include "myldap.h"
+#include "cfg.h"
+#include "attmap.h"
+
+/* ensure that both userdn and username are filled in from the entry,
+   returns an LDAP result code */
+static MYLDAP_ENTRY *validate_user(MYLDAP_SESSION *session,
+                                   char *username, int *rcp)
+{
+  int rc;
+  MYLDAP_ENTRY *entry = NULL;
+  /* check username for validity */
+  if (!isvalidname(username))
+  {
+    log_log(LOG_WARNING, "request denied by validnames option");
+    *rcp = LDAP_NO_SUCH_OBJECT;
+    return NULL;
+  }
+  /* get the user entry based on the username */
+  entry = uid2entry(session, username, &rc);
+  if (entry == NULL)
+  {
+    if (rc == LDAP_SUCCESS)
+      rc = LDAP_NO_SUCH_OBJECT;
+    log_log(LOG_DEBUG, "\"%s\": user not found: %s", username, 
ldap_err2string(rc));
+    *rcp = rc;
+    return NULL;
+  }
+  return entry;
+}
+
+static int is_valid_homedir(const char *homedir)
+{
+  struct stat sb;
+  /* should be absolute path */
+  if (homedir[0] != '/')
+    return 0;
+  /* get directory status */
+  if (stat(homedir, &sb))
+  {
+    log_log(LOG_DEBUG, "cannot stat() %s: %s", homedir, strerror(errno));
+    return 0;
+  }
+  /* check if a directory */
+  if (!S_ISDIR(sb.st_mode))
+  {
+    log_log(LOG_DEBUG, "%s: not a directory", homedir);
+    return 0;
+  }
+  /* FIXME: check ownership */
+  return 1;
+}
+
+static int is_valid_shell(const char *shell)
+{
+  int valid = 0;
+  char *l;
+  setusershell();
+  while ((l = getusershell()) != NULL)
+  {
+    if (strcmp(l, shell) == 0)
+    {
+      valid = 1;
+      break;
+    }
+  }
+  endusershell();
+  return valid;
+}
+
+static MYLDAP_SESSION *get_session(const char *binddn, const char *userdn,
+                                   const char *password, int *rcp)
+{
+  MYLDAP_SESSION *session;
+  char buffer[256];
+  /* set up a new connection */
+  session = myldap_create_session();
+  if (session == NULL)
+  {
+    *rcp = LDAP_UNAVAILABLE;
+    return NULL;
+  }
+  /* set up credentials for the session */
+  myldap_set_credentials(session, binddn, password);
+  /* perform search for own object (just to do any kind of search to set
+     up the connection with fail-over) */
+  if ((lookup_dn2uid(session, userdn, rcp, buffer, sizeof(buffer)) == NULL) ||
+      (*rcp != LDAP_SUCCESS))
+  {
+    myldap_session_close(session);
+    return NULL;
+  }
+  return session;
+}
+
+#define ADD_MOD(attribute, value)                                           \
+  if ((value != NULL) && (attribute[0] != '"'))                             \
+  {                                                                         \
+    strvals[i * 2] = (char *)value;                                         \
+    strvals[i * 2 + 1] = NULL;                                              \
+    mods[i].mod_op = LDAP_MOD_REPLACE;                                      \
+    mods[i].mod_type = (char *)attribute;                                   \
+    mods[i].mod_values = strvals + (i * 2);                                 \
+    modsp[i] = mods + i;                                                    \
+    i++;                                                                    \
+  }
+
+static int change(MYLDAP_SESSION *session, const char *userdn,
+                  const char *homedir, const char *shell)
+{
+  #define NUMARGS 2
+  char *strvals[(NUMARGS + 1) * 2];
+  LDAPMod mods[(NUMARGS + 1)], *modsp[(NUMARGS + 1)];
+  int i = 0;
+  /* build the list of modifications */
+  ADD_MOD(attmap_passwd_homeDirectory, homedir);
+  ADD_MOD(attmap_passwd_loginShell, shell);
+  /* terminate the list of modifications */
+  modsp[i] = NULL;
+  /* execute the update */
+  return myldap_modify(session, userdn, modsp);
+}
+
+int nslcd_usermod(TFILE *fp, MYLDAP_SESSION *session, uid_t calleruid)
+{
+  int32_t tmpint32;
+  int rc = LDAP_SUCCESS;
+  char username[256];
+  int asroot, isroot;
+  char password[64];
+  int32_t param;
+  char buffer[4096];
+  size_t buflen = sizeof(buffer);
+  size_t bufptr = 0;
+  const char *value = NULL;
+  const char *fullname = NULL, *roomnumber = NULL, *workphone = NULL;
+  const char *homephone = NULL, *other = NULL, *homedir = NULL;
+  const char *shell = NULL;
+  const char *binddn = NULL; /* the user performing the modification */
+  MYLDAP_ENTRY *entry;
+  MYLDAP_SESSION *newsession;
+  char errmsg[1024];
+  /* read request parameters */
+  READ_STRING(fp, username);
+  READ_INT32(fp, asroot);
+  READ_STRING(fp, password);
+  /* read the usermod parameters */
+  while (1)
+  {
+    READ_INT32(fp, param);
+    if (param == NSLCD_USERMOD_END)
+      break;
+    READ_BUF_STRING(fp, value);
+    switch (param)
+    {
+      case NSLCD_USERMOD_FULLNAME:   fullname = value; break;
+      case NSLCD_USERMOD_ROOMNUMBER: roomnumber = value; break;
+      case NSLCD_USERMOD_WORKPHONE:  workphone = value; break;
+      case NSLCD_USERMOD_HOMEPHONE:  homephone = value; break;
+      case NSLCD_USERMOD_OTHER:      other = value; break;
+      case NSLCD_USERMOD_HOMEDIR:    homedir = value; break;
+      case NSLCD_USERMOD_SHELL:      shell = value; break;
+      default: /* other parameters are silently ignored */ break;
+    }
+  }
+  /* log call */
+  log_setrequest("usermod=\"%s\"", username);
+  log_log(LOG_DEBUG, "nslcd_usermod(\"%s\",%s,\"%s\")",
+          username, asroot ? "asroot" : "asuser", *password ? "***" : "");
+  if (fullname != NULL)
+    log_log(LOG_DEBUG, "nslcd_usermod(fullname=\"%s\")", fullname);
+  if (roomnumber != NULL)
+    log_log(LOG_DEBUG, "nslcd_usermod(roomnumber=\"%s\")", roomnumber);
+  if (workphone != NULL)
+    log_log(LOG_DEBUG, "nslcd_usermod(workphone=\"%s\")", workphone);
+  if (homephone != NULL)
+    log_log(LOG_DEBUG, "nslcd_usermod(homephone=\"%s\")", homephone);
+  if (other != NULL)
+    log_log(LOG_DEBUG, "nslcd_usermod(other=\"%s\")", other);
+  if (homedir != NULL)
+    log_log(LOG_DEBUG, "nslcd_usermod(homedir=\"%s\")", homedir);
+  if (shell != NULL)
+    log_log(LOG_DEBUG, "nslcd_usermod(shell=\"%s\")", shell);
+  /* write the response header */
+  WRITE_INT32(fp, NSLCD_VERSION);
+  WRITE_INT32(fp, NSLCD_ACTION_USERMOD);
+  /* validate request */
+  entry = validate_user(session, username, &rc);
+  if (entry == NULL)
+  {
+    /* for user not found we just say no result, otherwise break the protocol 
*/
+    if (rc == LDAP_NO_SUCH_OBJECT)
+    {
+      WRITE_INT32(fp, NSLCD_RESULT_END);
+    }
+    return -1;
+  }
+  /* check if it is a modification as root */
+  isroot = (calleruid == 0) && asroot;
+  if (asroot)
+  {
+    if (nslcd_cfg->rootpwmoddn == NULL)
+    {
+      log_log(LOG_NOTICE, "rootpwmoddn not configured");
+      /* we break the protocol */
+      return -1;
+    }
+    binddn = nslcd_cfg->rootpwmoddn;
+    /* check if rootpwmodpw should be used */
+    if ((*password == '\0') && isroot && (nslcd_cfg->rootpwmodpw != NULL))
+    {
+      if (strlen(nslcd_cfg->rootpwmodpw) >= sizeof(password))
+      {
+        log_log(LOG_ERR, "nslcd_pam_pwmod(): rootpwmodpw will not fit in 
password");
+        return -1;
+      }
+      strcpy(password, nslcd_cfg->rootpwmodpw);
+    }
+  }
+  else
+    binddn = myldap_get_dn(entry);
+  WRITE_INT32(fp, NSLCD_RESULT_BEGIN);
+  /* home directory change requires either root or valid directory */
+  if ((homedir != NULL) && (!isroot) && !is_valid_homedir(homedir))
+  {
+    log_log(LOG_NOTICE, "invalid directory: %s", homedir);
+    WRITE_INT32(fp, NSLCD_USERMOD_HOMEDIR);
+    WRITE_STRING(fp, "invalid directory");
+    homedir = NULL;
+  }
+  /* shell change requires either root or a valid shell */
+  if ((shell != NULL) && (!isroot) && !is_valid_shell(shell))
+  {
+    log_log(LOG_NOTICE, "invalid shell: %s", shell);
+    WRITE_INT32(fp, NSLCD_USERMOD_SHELL);
+    WRITE_STRING(fp, "invalid shell");
+    shell = NULL;
+  }
+  /* perform requested changes */
+  newsession = get_session(binddn, myldap_get_dn(entry), password, &rc);
+  if (newsession != NULL)
+  {
+    rc = change(newsession, myldap_get_dn(entry), homedir, shell);
+    myldap_session_close(newsession);
+  }
+  /* return response to caller */
+  if (rc != LDAP_SUCCESS)
+  {
+    log_log(LOG_WARNING, "%s: modification failed: %s",
+            myldap_get_dn(entry), ldap_err2string(rc));
+    mysnprintf(errmsg, sizeof(errmsg) - 1, "change failed: %s", 
ldap_err2string(rc));
+    WRITE_INT32(fp, NSLCD_USERMOD_RESULT);
+    WRITE_STRING(fp, errmsg);
+    WRITE_INT32(fp, NSLCD_USERMOD_END);
+    WRITE_INT32(fp, NSLCD_RESULT_END);
+    return 0;
+  }
+  log_log(LOG_NOTICE, "changed information for %s", myldap_get_dn(entry));
+  WRITE_INT32(fp, NSLCD_USERMOD_END);
+  WRITE_INT32(fp, NSLCD_RESULT_END);
+  return 0;
+}

http://arthurdejong.org/git/nss-pam-ldapd/commit/?id=8fb5eb16ccc7fe3d119d7365537ca7cd82d496b8

commit 8fb5eb16ccc7fe3d119d7365537ca7cd82d496b8
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sat Mar 30 21:09:29 2013 +0100

    Define a NSLCD_ACTION_USERMOD request
    
    The modification can either be requested by root or by the user itself.
    
    Modifications by the user should be done by connecting to the LDAP server 
with
    the user-supplied credentials. It is expected that access controls in the 
LDAP
    server prevent unwanted modifications. The nslcd process is expected to 
check
    whether supplied values are sensible.

diff --git a/nslcd.h b/nslcd.h
index 456e2df..c9857d6 100644
--- a/nslcd.h
+++ b/nslcd.h
@@ -251,6 +251,36 @@
      STRING  error message */
 #define NSLCD_ACTION_PAM_PWMOD         0x000d0005
 
+/* User information change request. This request allows one to change
+   their full name and other information. The request parameters for this
+   request are:
+     STRING  user name
+     INT32   asroot: 0=passwd is user passwd, 1=passwd is root passwd
+     STRING  password
+   followed by one or more of the below, terminated by NSLCD_USERMOD_END
+     INT32   NSLCD_USERMOD_*
+     STRING  new value
+   the response consists of one or more of the entries below, terminated
+   by NSLCD_USERMOD_END:
+     INT32   NSLCD_USERMOD_*
+     STRING  response
+   (if the response is blank, the change went OK, otherwise the string
+   contains an error message)
+   */
+#define NSLCD_ACTION_USERMOD           0x000e0001
+
+/* These are the possible values for the NSLCD_ACTION_USERMOD operation
+   above. */
+#define NSLCD_USERMOD_END        0 /* end of change values */
+#define NSLCD_USERMOD_RESULT     1 /* global result value */
+#define NSLCD_USERMOD_FULLNAME   2 /* full name */
+#define NSLCD_USERMOD_ROOMNUMBER 3 /* room number */
+#define NSLCD_USERMOD_WORKPHONE  4 /* office phone number */
+#define NSLCD_USERMOD_HOMEPHONE  5 /* home phone number */
+#define NSLCD_USERMOD_OTHER      6 /* other info */
+#define NSLCD_USERMOD_HOMEDIR    7 /* home directory */
+#define NSLCD_USERMOD_SHELL      8 /* login shell */
+
 /* Request result codes. */
 #define NSLCD_RESULT_BEGIN 1
 #define NSLCD_RESULT_END   2

-----------------------------------------------------------------------

Summary of changes:
 .gitignore                             |    2 +
 man/Makefile.am                        |    2 +-
 man/{pynslcd.8.xml => chsh.ldap.1.xml} |   77 ++++----
 nslcd.h                                |   30 ++++
 nslcd/Makefile.am                      |    4 +-
 nslcd/common.h                         |    1 +
 nslcd/nslcd.c                          |    1 +
 nslcd/usermod.c                        |  297 ++++++++++++++++++++++++++++++++
 pynslcd/pynslcd.py                     |    1 +
 pynslcd/usermod.py                     |  131 ++++++++++++++
 utils/Makefile.am                      |    4 +-
 utils/chsh.py                          |   70 ++++++++
 utils/cmdline.py                       |   18 ++
 utils/nslcd.py                         |   23 +++
 utils/shells.py                        |   64 +++++++
 utils/users.py                         |   60 +++++++
 16 files changed, 742 insertions(+), 43 deletions(-)
 copy man/{pynslcd.8.xml => chsh.ldap.1.xml} (53%)
 create mode 100644 nslcd/usermod.c
 create mode 100644 pynslcd/usermod.py
 create mode 100755 utils/chsh.py
 create mode 100644 utils/shells.py
 create mode 100644 utils/users.py


hooks/post-receive
-- 
nss-pam-ldapd
-- 
To unsubscribe send an email to
nss-pam-ldapd-commits-unsubscribe@lists.arthurdejong.org or see
http://lists.arthurdejong.org/nss-pam-ldapd-commits/