Files
ncpfs/contrib/pam/pam_ncp_auth.c
2026-04-28 20:56:03 +02:00

5048 lines
143 KiB
C

/* $Header: /usr/src/redhat/BUILD/Linux-PAM-0.59/modules/pam_ncp/RCS/pam_ncp_auth.c,v 1.3 1998/03/04 02:52:07 dwmw2 Exp $ */
/*
* Copyright David Woodhouse, 1998. All rights reserved.
*
* 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.
*/
/*
* $Log: pam_ncp_auth.c,v $
*
* Revision 1.28 2002/10/02 PP
* 1) default zenflag is now 0 and not anymore ZF_CREATE_NWCLIENT | ZF_AUTOMOUNT_NWHOME | ZF_CREATE_NWINFOS
* that set trouble with some users not familiar with C coding. they turned these off in NDS
* and they were still applied.
*
* Revision 1.27 2002/10/02 PP
* Experimental code:
* 1) We experienced some stange behavior with contexless login. If user miss his password and retry to
* login nw_search_ctx is called again and freeze...
* 2) trying to find why console login do not work in RH73 ... this module says OK but session is closed
* immediatly (?)
*
* New Code:
* 1) Some users complained that this module consumes 2 connections to NDS (one for the authentication
* and one for the autmounting of the Netware home). Actually only one should stay permanent, to be
* later reused by mounting of extra Netware ressource via ncpmap without having to provide again
* login/password. Since the authenticating connection is not in /etc/mtab, so it cannot be "reused"
* by ncpmap anyway, so we must get rid of it at end of session opening part.
* 2) Paul Berger (bergerp@breedtech.com) signaled me that in some environnement, home directories paths
* are coded in NwAdmin as dir1/dir2/%LOGIN_NAME or dir1/dir2/%CN (with or without a terminating %) and
* the mapping of the home is done within the context login script, with automatic substitution
* of %LOGIN_NAME by the user's cn truncated to 8 or of %CN by the full user's CN. Some provisions are
* added to do the substitution here when automounting home and writing data in the ~/.nwinfos file.
* This is a crude code that chop off whatever starts with a % and append the user's name. We do not
* support dir1/dir2/%LOGIN_NAME/dir3 !!!! See nds_pp_home_directory function.
*
* Revision 1.26 2002/05/24 PP
* Extended -Z option to allow some zenflags to be turned OFF on that workstation.
* With -Z all zenflags are off, but with -ZABCD, only flags ABCD are off.
*
* Some support is given for the case of the same CN in different contexts. In that case, if the
* password is good but some other login errors is reported ( account disabled, too many connexions...)
* we report error and abort.
* But if the password is bad, then we try to find another user with the same CN in another context ( if any).
* TODO: since the original code, expired password error is ignored and user is allowed to login...(?)
* Added the service su to the table of allowed service.
* still problems with impersonnating ( operation not permitted)
* authentication and access is granted but pam still fails ( check /etc/pam.d/su )
*
* Revision 1.25 2002/05/10 PP
* The modifications required for operations in a pure IP network have been added.
* These modifications were proposed by Jean-François Burdet <jean-francois.burdet@adm.unige.ch>
* and are described at http://lnxjfb.unige.ch/grpnov/article.php3?id_article=81
* They include:
* adding a -A that trigger adding -A server option to ncpmount
* adding logic for context searches in case no list has been provided on the command line
*
* Merging of group zenflags is now done before automatic group creation, so if creation fails
* (due to a name too long for example) zenflags are still unherited
* Revision 1.24 2002/05/09 PP
* EXPERIMENTAL CODE
* NEW CODE is marked with #ifdef INPERSONATE
* Provisions have been added for home directories on shared NFS servers
* In that case, new user home directory already exists and will be mounted via autofs
* so
* 1) this module should stop complaining that user's home already exists !!!
* 2) the useradd command should be called with -M option ( DO NOT create HOME) !
* instead of -m (DO create home)
* 3) actually the module should complains if the home DO NOT EXIST (not created on NFS server
* or NFS server down).
* This is the exact reverse behaviour of the previous versions !!!!
* In order not to break things a flag (-n) has been added to the command line
* When present the module do not attempt to autocreate a local home; it expects the home to be present
* either locally or more likely as a NFS mounted ressource.
*
* This leads to an extra security problem. The PAM module is ran with root privileges and requires
* - read privileges to check existence of nwhome in current user home
* - write privileges to the NFS mounted user's home to create a directory (nwhome) and some files
* (.forward, .nwclient and .nwinfos).
* Thus the NFS directories must be exported with the no_root_squash option which is quite unsafe.
*
* This means that if somebody get root access on a local workstation, he can access any home directory on the NFS server
* with root privileges !!!
* And any student in the campus that is root on his own machine have full rights on the NFS directories !
*
* PARTIAL FIX: We do not set the no_root_squash option on NFS servers; As soon as this module has determined
* the current user's Unix UID ( either server required or calculated), and
* eventually autocreated it on the local machine, it impersonnates to that UID at every access to his home
*
* MORE SERIOUS: if NFS directory is mounted with no_root_squash, internally called ncpmount fails with "mount point not found"
* since this suid program has no rights in NFS remote directories.
* Same problems with ncplogin, ncpmap that have no rights to "autocreate" the mounting point ~/ncp/SERVER/VOLUME !
*
* so when the -n parameter is present we mount Netware home in a local /mnt/ncp/$USER directory chmoded to 0700
* and pass this directory to ncpmount...(this is the new behaviour of ncplogin/ncpmap with the -l option)
*
* Revision 1.23 2002/02/21 pp
* Fixed possible buffer overflow in mount_nwhome in verbose mode
* Fixed remote access granting table for samba >2.07 ( pam_tty=samba and pam_rhost=hostname)
* was null, null in samba 2.06
*
* Revision 1.22 2002/02/03 vana
* Fix couple of possible and real buffer overflows in the code.
* It still needs review, but buffer overflows exploitable by malicious
* NDS administrators should be fixed now. It is true that if you
* trust them enough to manage logins for your box, they do not have
* need for setting user's home directories to 4KB strings, but
* if they do, it should not crash PAM authentication module.
* I believe that no buffer overflow was exploitable by mere user.
* Reformatted some functions. We use tab size 8, not 4, not 6, not 7.5...
*
* Revision 1.21 2002/01/20 PP
* kscreensaver (from KDE) calls again PAM when tring to unlock the screen.
* currently only the authenticate service is called (not session) But in that case
* current user environnment is used, so it is quite likely that a default context will be red from there.
* ncp_auth_tree has been modified to take this in account.
* furthermore, user's info and zen processing should not be done again . So another flag has been added
* this skip these parts if the PAM service name contains "saver". I hope it does include other
* screensavers
*
* Revision 1.20 2002/01/14 PP
* as suggested by vana, changed code in exechelper() to simplify use of su. rather than calling su -l username ....
*
*
* Revision 1.19 2002/01/10 PP
* automatic ncpmounting is now run by this module as the current user and not as root
* this has two major consequenes:
* 1) nwhome directory now get group permissions for the group users the group root. This is a cosmetic detail
* since permissions are set to 700
* 2) more important. If ncpmount is done by root, user cannot read back later its private key data for this connection.
* ncp_get_private_data grant access to the private data but returns a length of 0 for the private data (???).
* Note that if root requests these private data (using contrib/testing/pp/nwgetconnlist -a), he get a EACCESS (13)
* error as expected. Private IS private!
* so this initial connection was not flagged as authenticated for this user and cannot be used later by ncpmap to mount
* additionnal netware volumes of the same tree with the same credentials.
*
* changed mount_nwhome to call /bin/su -l unixname -c "/usr/sbin/ncpmount -S sss -V vvvv -U uuu ... /home/user/nwhome"
* changed process zenscripts to do the same : for security reasons zenscripts are now run with current user's permissions
* and not anymore as root.
*
* added a new account flag NDS_IS_NEW_USER in .nwinfos to ease zenscript processing
* is account is new, we can further personnalize config files such as kmailrc, liprefs.js ...
*
* Revision 1.18 2002/01/09 PP
* With redHat 7.2 the RHOST value passed to PAM with X remote access is emty end not NULL
* the kde entry in the zen table has been duplicated
* added -o "symlinks" to options of ncpmount.
*
* Revision 1.17 2001/12/12 PP
* With redHat 7.2 the return value of pam_set-cred and pam_get_cred that was PAM_IGNORE
* is really used, so authentication fails !
* Changed to PAM_SUCCESS
*
* Revision 1.16 2001/11/01 PP
* added the new entries needed by nwnet.c v >1.17 in ~/.nwclient
* must be after the usual server/user pawwd line since many script use 'read server passwd <~/.nwclient'
*
* Revision 1.15 2001/10/31 pp
* added support for Server AND contextless authentication upon request of Steve Flynn <sflyn@tpg.com.au>
* --> the server command line now accept a list of context to search ndsserver=SERVER_NAME:Ctx1,Ctx2,...Ctxn/group
* idem for password changing
*
* Revision 1.15 2001/11/01 PP
* added the new entries needed by nwnet.c v >1.17 in ~/.nwclient
* must be after the usual server/user pawwd line since many script use 'read server passwd <~/.nwclient'
*
* Revision 1.14 2001/10/27 PP
* Made the -m "empty" feature more reliable:
* 1) if empty the Netware home directory is mounted as /home/user but we
* still have problem with chown and kde on a mounted netware
* 2) parsemntpoint() did not scan the argument to the end, so with option
* -mnwhome, it was called twice ; once for "nwhome" and once for "me" !
* result is that the mounting point was ~/e !!! Corrected in pam_sm_athenticate.
* Due to this feature in -m option:
* we moved the creation of the .forward file during the authentication section.
* so: if user is not currently logged in, system will still find the .forward file in local /home/user ( it cannot mount user's netware
* home to find it)...
* We do it twice (before and after automounting) so if user IS logged in system will
* find the .forward file... This second time could be done in a zenscript ...
*
* Revision 1.13 2001/10/24 pp
* added support for Tree & contextless authentication
* --> new command line tree=TREE_NAME:Ctx1,Ctx2,...Ctxn/group
* --> reorganized authentication code for the group verification part to be common
* to auth_server and auth_tree
* --> modified nw_auto_mount_home to pass a FQDN to ncpmount
* idem for password changing
*
* Revision 1.12 2001/10/24 pp
* added support for ssh remote access ( under the same ZEN flag as Telnet access)
* due to current implementation of OpenSSH, user must have an account & home on the local machine
* for authentication by Netware to work. sshd peeks first in user's home to find data files...
* so automagic creation does not work with ssh
*
* Revision 1.11 2001/02/24 pp
* Added some support for reading bindery properties
* IDENTIFICATION --> Gecos field
* HOME_DIRECTORY --> nwhome ( must be on the same server as the "autenticator"
*
* Revision 1.10 2001/02/21 12:34:56 vana
* Cleaned warnings, fixed bindery login.
*
* Revision 1.9 2001/01/26 16:50:00 pp
* Added Zenux Flags concept for tuning user's account on Linux
* for more documentation see http://cipcinsa.insa-lyon.fr/ppollet/pamncp/
*
* Revision 1.8 2001/01/15 19:30:00 pp
* Added optional automounting of Netware Home directory
* in ~/nwhome, and dismounting upon session ending
*
* Revision 1.7 2001/01/06 10:50:00 pp
* Added support for pré DS8 network using either Location
* attribute or Postal Adress
*
* Revision 1.6 2000/07/06 14:40:00 vana
* Added DCV_DEREF_ALIASES where needed
*
* Revision 1.5 2000/06/02 17:40:00 vana
* Tons of new code - passwd, changed auth code
*
* Revision 1.4 1999/10/31 15:44:44 vana
* Couple of new options, new check for group membership
*
* Revision 1.3 1998/03/04 02:52:07 dwmw2
* Oops - licensing has to be GPL as it's using the ncpfs libraries and headers.
*
* Revision 1.2 1998/03/04 02:39:12 dwmw2
* Tidied up (a bit) for initial release.
*
* Revision 1.1 1998/03/04 02:21:07 dwmw2
* Initial revision
*
*
*/
#define _GNU_SOURCE
#define _BSD_SOURCE
#define inline __inline__
#define NCP_OBSOLETE
#include <ncp/nwcalls.h>
#include <ncp/nwnet.h>
#include <ncp/nwclient.h>
#include <features.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <ctype.h>
#include "support.h"
static const char rcsid[] = "$Id: pam_ncp_auth.c,v 1.9 2001/21/26 16:52:07 PP Exp $ pam_ncp authentication functions. Dave@imladris.demon.co.uk,vandrove@vc.cvut.cz,patrick.pollet@insa-lyon.fr ";
/* Define function prototypes */
static void
nw_cleanup_conn(UNUSED(pam_handle_t *pamh),
void *data,
UNUSED(int error_status))
{
NWCCODE err;
err = ncp_close((struct ncp_conn*)data);
syslog(LOG_NOTICE, "pam closing authenticating connection: %s", strnwerror(err));
}
#define QFC_FIRST_UNUSED 0
#define QFC_NEXT_UNUSED 1
#define QFC_PREFFER_SERVER 2
#define QFC_REQUIRE_SERVER 4
// caldera open linux does not likes the -G parameter in useradd !!!PP
// so we will have a specific flag for these and use a second call to usermod -G
#define NO_PROCESS_GROUPS 1
#define QFMU_GID 0x0001
#define QFMU_GECOS 0x0002
#define QFMU_DIR 0x0004
#define QFMU_SHELL 0x0008
/* reactivate if you prefer NDS property Postal Address
rather than Location for pre-DS 8 systems*/
#if 0
#define USE_POSTAL_ADDRESS
#endif
// from the snapin C code
// names of NDS attributes used
// USE_DUMMY_ATTRibutes must be set in the Makefile
#ifndef USING_DUMMY_ATTRIBUTES
// the real ones
#define ATTR_UID "UNIX:UID"
#define ATTR_PGNAME "UNIX:Primary GroupName"
#define ATTR_PGID "UNIX:Primary GroupID"
#define ATTR_GID "UNIX:GID"
#define ATTR_SHELL "UNIX:Login Shell"
#define ATTR_COM "UNIX:Comments"
#define ATTR_HOME "UNIX:Home Directory"
#else
// dummy attributes for testing
// created with Schemax with the same syntax
// and associated to user class and group class
#define ATTR_UID "LINUX:UID"
#define ATTR_PGNAME "LINUX:Primary GroupName"
#define ATTR_PGID "LINUX:Primary GroupID"
#define ATTR_GID "LINUX:GID"
#define ATTR_SHELL "LINUX:Login Shell"
#define ATTR_COM "LINUX:Comments"
#define ATTR_HOME "LINUX:Home Directory"
#endif
// the attribute used to test presence of NDS8
// either real or dummy (not used yet)
#define ATTR_NDS8 ATTR_UID
// other attributes used
// absent NDS8 attributes are searched in L attribute
// also new properties ( Zenux Flags, Other group...)
#define ATTR_LOCATION "L"
#define ATTR_LOGIN_SCRIPT "Login Script"
#define ATTR_GRP_MBS "Group Membership"
#define ATTR_HOST_SERVER "Host Server"
#define ATTR_HOST_RN "Host Resource Name"
#define ATTR_SMTP_EMAIL "Email Address"
#define ATTR_LDAP_EMAIL "Internet Email Address"
#define ATTR_FULL_NAME "Full Name"
#define ATTR_SURNAME "Surname"
#define ATTR_GIVEN_NAME "Given Name"
#define ATTR_POSTAL_ADDRESS "Postal Address"
#define ATTR_HOME_NW "Home Directory"
#define ATTR_MESSAGE_SERVER "Message Server"
// the proper naming attribute may be customized here (must be a CI_STRING )
#define ATTR_GECOS ATTR_FULL_NAME
// syntaxes of the used attributes
#define SYN_LOCATION SYN_CI_STRING
#define SYN_LOGIN_SCRIPT SYN_STREAM
#define SYN_UID SYN_INTEGER
#define SYN_PGNAME SYN_DIST_NAME
#define SYN_PGID SYN_INTEGER
#define SYN_GID SYN_INTEGER
#define SYN_SHELL SYN_CE_STRING
#define SYN_COM SYN_CI_STRING
#define SYN_HOME SYN_CE_STRING
#define SYN_GRP_MBS SYN_DIST_NAME
/* user account tuning flags red from the NDS (ZenFlag) */
// zenFlag processing can be turned off by a command line -Z
// it is on by default
#define NDS_ZF_AUTOMOUNT_NWHOME 'A'
#define NDS_ZF_BROADCAST_ALL 'B'
#define NDS_ZF_BROADCAST_CONSOLE 'C'
#define NDS_ZF_D 'D'
#define NDS_ZF_E 'E'
#define NDS_ZF_ALLOW_FTP_ACCESS 'F'
#define NDS_ZF_G 'G'
#define NDS_ZF_ALLOW_RSH_ACCESS 'H'
#define NDS_ZF_CREATE_NWINFOS 'I'
#define NDS_ZF_J 'J'
#define NDS_ZF_K 'K'
#define NDS_ZF_L 'L'
#define NDS_ZF_FORWARD_MAIL 'M'
#define NDS_ZF_CREATE_NWCLIENT 'N'
#define NDS_ZF_OVERWRITE_NWCLIENT 'O'
#define NDS_ZF_PASSWD_IN_NWCLIENT 'P'
#define NDS_ZF_Q 'Q'
#define NDS_ZF_ALLOW_RLOGIN_ACCESS 'R'
#define NDS_ZF_ALLOW_SAMBA_ACCESS 'S'
#define NDS_ZF_ALLOW_TELNET_ACCESS 'T'
#define NDS_ZF_U 'U'
#define NDS_ZF_VOLATILE_ACCOUNT 'V'
// I reserve this letter, but I still don't know
// how auth_nds_mod of Apache works ( TODO !)
#define NDS_ZF_ALLOW_WEB_ACCESS 'W'
#define NDS_ZF_ALLOW_X_ACCESS 'X'
#define NDS_ZF_Y 'Y'
#define NDS_ZF_Z 'Z'
// three admin defined scripts that can be launched at session opening
// these scripts WILL source the file ~/.nwinfos to get user's data
// if more than one will be executed in this order
#define NDS_ZF_0 '0'
#define NDS_ZF_1 '1'
#define NDS_ZF_2 '2'
// three admin defined scripts that can be launched at session closing
// these scripts WILL source the file ~/.nwinfos to get user's data
// if more than one will be executed in this order
#define NDS_ZF_3 '3'
#define NDS_ZF_4 '4'
#define NDS_ZF_5 '5'
/*corresponding binary flag*/
#define ZF_UNSUPPORTED 0x00000000
#define ZF_AUTOMOUNT_NWHOME 0x00000001
#define ZF_BROADCAST_ALL 0x00000002
#define ZF_BROADCAST_CONSOLE 0x00000004
#define ZF_D 0x00000008
#define ZF_E 0x00000010
#define ZF_ALLOW_FTP_ACCESS 0x00000020
#define ZF_G 0x00000040
#define ZF_ALLOW_RSH_ACCESS 0x00000080
#define ZF_CREATE_NWINFOS 0x00000100
#define ZF_J 0x00000200
#define ZF_K 0x00000400
#define ZF_L 0x00000800
#define ZF_FORWARD_MAIL 0x00001000
#define ZF_CREATE_NWCLIENT 0x00002000
#define ZF_OVERWRITE_NWCLIENT 0x00004000
#define ZF_PASSWD_IN_NWCLIENT 0x00008000
#define ZF_Q 0x00010000
#define ZF_ALLOW_RLOGIN_ACCESS 0x00020000
#define ZF_ALLOW_SAMBA_ACCESS 0x00040000
#define ZF_ALLOW_TELNET_ACCESS 0x00080000
#define ZF_U 0x00100000
#define ZF_V 0x00200000
#define ZF_ALLOW_WEB_ACCESS 0x00400000
#define ZF_ALLOW_X_ACCESS 0x00800000
#define ZF_Y 0x01000000
#define ZF_Z 0x02000000
#define ZF_0 0x04000000
#define ZF_1 0x08000000
#define ZF_2 0x10000000
#define ZF_3 0x20000000
#define ZF_4 0x40000000
#define ZF_5 0x80000000
#define ZF_OPENING_SCRIPTS (ZF_0 | ZF_1 | ZF_2)
#define ZF_CLOSING_SCRIPTS (ZF_3 | ZF_4 | ZF_5)
//#define ZF_DEFAULTS ZF_CREATE_NWCLIENT | ZF_AUTOMOUNT_NWHOME | ZF_CREATE_NWINFOS
#define ZF_DEFAULTS 0
// where must be the scripts
// TODO: let PAM finds them somewhere in its path
// (if it has one ?)
#define ZEN_SCRIPT_0 "/usr/local/bin/zenscript0"
#define ZEN_SCRIPT_1 "/usr/local/bin/zenscript1"
#define ZEN_SCRIPT_2 "/usr/local/bin/zenscript2"
#define ZEN_SCRIPT_3 "/usr/local/bin/zenscript3"
#define ZEN_SCRIPT_4 "/usr/local/bin/zenscript4"
#define ZEN_SCRIPT_5 "/usr/local/bin/zenscript5"
#define NWINFOS_FILE ".nwinfos"
#define NWCLIENT_FILE ".nwclient"
#define DEF_MNT_PNT "nwhome"
struct zenElement {
char letter;
long value;
};
/* only the flag processed in this version are !=ZF_UNSUPPORTED */
static struct zenElement zenTable[]={
{ NDS_ZF_AUTOMOUNT_NWHOME, ZF_AUTOMOUNT_NWHOME },
{ NDS_ZF_BROADCAST_ALL, ZF_BROADCAST_ALL },
{ NDS_ZF_BROADCAST_CONSOLE, ZF_BROADCAST_CONSOLE },
{ NDS_ZF_D, ZF_UNSUPPORTED },
{ NDS_ZF_E, ZF_UNSUPPORTED },
{ NDS_ZF_ALLOW_FTP_ACCESS, ZF_ALLOW_FTP_ACCESS },
{ NDS_ZF_G, ZF_UNSUPPORTED },
{ NDS_ZF_ALLOW_RSH_ACCESS, ZF_ALLOW_RSH_ACCESS },
{ NDS_ZF_CREATE_NWINFOS, ZF_CREATE_NWINFOS },
{ NDS_ZF_J, ZF_UNSUPPORTED },
{ NDS_ZF_K, ZF_UNSUPPORTED },
{ NDS_ZF_L, ZF_UNSUPPORTED },
{ NDS_ZF_FORWARD_MAIL, ZF_FORWARD_MAIL },
{ NDS_ZF_CREATE_NWCLIENT, ZF_CREATE_NWCLIENT },
{ NDS_ZF_OVERWRITE_NWCLIENT, ZF_OVERWRITE_NWCLIENT },
{ NDS_ZF_PASSWD_IN_NWCLIENT, ZF_PASSWD_IN_NWCLIENT },
{ NDS_ZF_Q, ZF_UNSUPPORTED },
{ NDS_ZF_ALLOW_RLOGIN_ACCESS, ZF_ALLOW_RLOGIN_ACCESS },
{ NDS_ZF_ALLOW_SAMBA_ACCESS, ZF_ALLOW_SAMBA_ACCESS },
{ NDS_ZF_ALLOW_TELNET_ACCESS, ZF_ALLOW_TELNET_ACCESS },
{ NDS_ZF_U, ZF_UNSUPPORTED },
{ NDS_ZF_VOLATILE_ACCOUNT, ZF_UNSUPPORTED },
{ NDS_ZF_ALLOW_WEB_ACCESS, ZF_UNSUPPORTED },
{ NDS_ZF_ALLOW_X_ACCESS, ZF_ALLOW_X_ACCESS },
{ NDS_ZF_Y, ZF_UNSUPPORTED },
{ NDS_ZF_Z, ZF_UNSUPPORTED },
// zen scripts should source ~/.nwinfos file, so make sure
// it is created
{ NDS_ZF_0, ZF_0 | ZF_CREATE_NWINFOS },
{ NDS_ZF_1, ZF_1 | ZF_CREATE_NWINFOS },
{ NDS_ZF_2, ZF_2 | ZF_CREATE_NWINFOS },
{ NDS_ZF_3, ZF_3 | ZF_CREATE_NWINFOS },
{ NDS_ZF_4, ZF_4 | ZF_CREATE_NWINFOS },
{ NDS_ZF_5, ZF_5 | ZF_CREATE_NWINFOS },
{ 0, 0 }
};
static long
decodeZenFlag(const char **optp)
{
long r = 0;
char c;
const char *ZF = *optp;
while ((c = *ZF++) != 0) {
struct zenElement *z;
c = toupper(c);
for (z = zenTable; z->letter; z++) {
if (c == z->letter) {
r |= z->value;
break;
}
}
}
*optp = ZF;
return r;
}
struct pam_ncp_state {
struct {
uid_t min;
uid_t max;
int flags;
int modflags;
} uid;
struct {
gid_t min;
gid_t max;
int flags;
} gid;
};
// concat2a (give a local string that is destroyed at the end of the function)
#define concat2a(one,two) ({ \
size_t lone = strlen(one); \
size_t ltwo = strlen(two); \
char* buf = alloca(lone + ltwo + 1); \
memcpy(buf, one, lone); \
memcpy(buf + lone, two, ltwo + 1); \
buf; })
// concat2m (give a "global" string that is NOT destroyed at the end of the function)
#define concat2m(one,two) ({ \
size_t lone = strlen(one); \
size_t ltwo = strlen(two); \
char* buf = malloc(lone + ltwo + 1); \
memcpy(buf, one, lone); \
memcpy(buf + lone, two, ltwo + 1); \
buf; })
#define concat3a(one,med,two) ({ \
size_t lone = strlen(one); \
size_t ltwo = strlen(two); \
char* buf = alloca(lone + 1+ ltwo + 1); \
memcpy(buf, one, lone); \
buf[lone] = med; \
memcpy(buf + lone + 1, two, ltwo + 1); \
buf; })
static int getnumber(int* val, const char** str) {
const char *p = *str;
char *z;
if (!*p)
return 1;
if (*p == ',') {
*str = p + 1;
return 1;
}
*val = strtoul(p, &z, 0);
if (p == z)
return -1;
if (*z == ',')
z++;
*str = z;
return 0;
}
// flip Dos antislash to Unix
// converts to uppercase for ncpmount to work with ROOT options ON
static void
unixifyPathUC(char *dosPath)
{
size_t i;
for (i = 0; i < strlen(dosPath); i++) {
if (dosPath[i] == '\\')
dosPath[i] = '/';
}
str_upper(dosPath);
}
// remove all spaces from a NDS name
static void
trim(char *string)
{
char *aux = string;
char c;
while ((c = *string++) != 0) {
if (c != ' ')
*aux++ = c;
}
*aux = 0;
}
#define QF_VERBOSE 0x0001
#define QF_DEBUG 0x0002
#define QF_NOSU 0x0004
#define QF_NOSUEQ 0x0008
#define QF_AUTOCREATE 0x0010
#define QF_AUTOMODIFY 0x0020
#define QF_BINDERY 0x0040
#define QF_NO_PEER_CHECKS 0x0080
#define QF_USE_NETWARE_IP 0x1000
// version 1.24. automatically create home directory on local machine for new users
// it is on by default
// should be turned off by -n option if homes are on a remote NFS server (automounted)
#define QF_MOUNTLOCALLY 0x2000
#define QF_CREATEHOME 0x4000
// test v 1.25 impersonnating to current user when accessing his NFS mounted home
#define IMPERSONNATE 1
static int
is_member_of_bindery_group(NWCONN_HANDLE conn, const char *user, const char *group)
{
int err;
err = NWIsObjectInSet(conn, user, NCP_BINDERY_USER, "GROUPS_I'M_IN", group, NCP_BINDERY_UGROUP);
if (!err) {
err = NWIsObjectInSet(conn, group, NCP_BINDERY_UGROUP, "GROUP_MEMBERS", user, NCP_BINDERY_USER);
if (!err) {
return 0;
} else {
syslog(LOG_WARNING, "inconsistent bindery database for user %s and group %s: %s\n", user, group, strnwerror(err));
}
} else {
syslog(LOG_WARNING, "user %s is not member of %s: %s\n", user, group, strnwerror(err));
}
return -1;
}
static int
is_member_of_nds_group(NWDSContextHandle ctx, NWCONN_HANDLE conn, NWObjectID oid, const char *group)
{
int eval = 0;
Buf_T *buf = NULL;
NWDSCCODE dserr;
nbool8 match;
dserr = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &buf);
if (dserr) {
syslog(LOG_WARNING, "NWDSAllocBuf() failed with %s\n", strnwerror(dserr));
eval = 120;
goto bailout;
}
dserr = NWDSInitBuf(ctx, DSV_COMPARE, buf);
if (dserr) {
syslog(LOG_WARNING, "NWDSInitBuf() failed with %s\n", strnwerror(dserr));
eval = 121;
goto bailout;
}
dserr = NWDSPutAttrName(ctx, buf, "Group Membership");
if (dserr) {
syslog(LOG_WARNING, "NWDSPutAttrName() failed with %s\n", strnwerror(dserr));
eval = 122;
goto bailout;
}
dserr = NWDSPutAttrVal(ctx, buf, SYN_DIST_NAME, group);
if (dserr) {
syslog(LOG_WARNING, "NWDSPutAttrVal() failed with %s\n", strnwerror(dserr));
eval = 123;
goto bailout;
}
dserr = __NWDSCompare(ctx, conn, oid, buf, &match);
if (dserr) {
syslog(LOG_WARNING, "__NWDSCompare() failed with %s(oid=%x)\n", strnwerror(dserr), oid);
eval = 124;
goto bailout;
}
if (!match) {
eval = 125;
}
bailout:;
if (buf)
NWDSFreeBuf(buf);
return eval;
}
static NWCCODE
nw_create_conn_to_server(NWCONN_HANDLE * conn, const char *server, const char *user, const char *pwd, int qflag)
{
struct ncp_bindery_object uinfo;
unsigned char ncp_key[8];
NWCONN_HANDLE cn;
char *pwd2;
long err;
if (qflag & QF_DEBUG)
syslog(LOG_DEBUG, "Trying to contact %s/%s\n", server, user);
pwd2 = strdup(pwd);
if (!pwd2) {
syslog(LOG_WARNING, "Not enough memory when uppercasing password\n");
err = PAM_TRY_AGAIN;
goto fail2;
}
str_upper(pwd2);
err = NWCCOpenConnByName(NULL, server, NWCC_NAME_FORMAT_BIND, 0, NWCC_RESERVED, &cn);
if (err) {
syslog(LOG_WARNING, "%s when trying to open connection\n", strnwerror(err));
err = PAM_TRY_AGAIN;
goto fail2;
}
if (!(qflag & QF_BINDERY) && NWIsDSServer(cn, NULL)) {
err = nds_login_auth(cn, user, pwd2);
} else {
err = ncp_get_encryption_key(cn, ncp_key);
if (err) {
syslog(LOG_WARNING, "%s when trying to get encryption key. Doing unencrypted\n", strnwerror(err));
err = ncp_login_unencrypted(cn, NCP_BINDERY_USER, user, pwd2);
} else {
err = ncp_get_bindery_object_id(cn, NCP_BINDERY_USER, user, &uinfo);
if (err) {
syslog(LOG_WARNING, "%s when trying to get object ID\n", strnwerror(err));
err = PAM_USER_UNKNOWN;
goto fail;
}
err = ncp_login_encrypted(cn, &uinfo, ncp_key, pwd2);
}
}
if (err && err != NWE_PASSWORD_EXPIRED) {
syslog(LOG_WARNING, "%s when trying to login\n", strnwerror(err));
switch (err) {
case ERR_NO_SUCH_ENTRY:
case NWE_SERVER_UNKNOWN:
err = PAM_USER_UNKNOWN;
break;
case NWE_LOGIN_MAX_EXCEEDED:
case NWE_LOGIN_UNAUTHORIZED_TIME:
case NWE_LOGIN_UNAUTHORIZED_STATION:
case NWE_ACCT_DISABLED:
err = PAM_AUTH_ERR;
break;
default:
err = PAM_AUTH_ERR;
break;
}
goto fail;
}
// PP: I don't understand why we do not report this error ???
#if 0
if (err)
err = PAM_NEW_AUTHTOK_REQD;
#else
err = 0;
#endif
if (qflag & QF_DEBUG)
syslog(LOG_DEBUG, "User %s/%s was successfully authorized\n", server, user);
*conn = cn;
return err;
fail:;
NWCCCloseConn(cn);
fail2:;
*conn = NULL;
return err;
}
static int
nw_get_nwid(NWCONN_HANDLE conn, NWObjectID * id, UNUSED(int qflag))
{
NWCCODE err;
err = NWCCGetConnInfo(conn, NWCC_INFO_USER_ID, sizeof (*id), id);
if (err) {
syslog(LOG_WARNING, "%s when retrieving object ID\n", strnwerror(err));
return PAM_SYSTEM_ERR;
}
return PAM_SUCCESS;
}
/* old server centric code called by server=XXX/group cmd line parameter */
static int
nw_create_verify_conn_to_server(NWCONN_HANDLE * conn, NWObjectID * id,
const char *server, const char *user, const char *pwd,
int qflag, const char *group)
{
NWCONN_HANDLE cn;
NWCCODE err;
NWObjectID oid;
err = nw_create_conn_to_server(&cn, server, user, pwd, qflag);
if (err && err != PAM_NEW_AUTHTOK_REQD)
return err;
err = nw_get_nwid(cn, &oid, qflag);
if (err) {
syslog(LOG_WARNING, "Error %s retrieving user ID for %s\n", strnwerror(err), user);
goto bailout;
}
if (qflag & QF_NOSU) {
if (oid == 0x00000001) {
err = PAM_AUTH_ERR;
syslog(LOG_WARNING, "Access denied for %s/%s because of it is supervisor\n", server, user);
goto bailout;
}
if (qflag & QF_DEBUG)
syslog(LOG_DEBUG, "User %s/%s passed supervisor check\n", server, user);
}
if (qflag & QF_NOSUEQ) {
nuint8 level;
err = NWGetBinderyAccessLevel(cn, &level, NULL);
if (err) {
syslog(LOG_WARNING, "Access denied for %s/%s because of I/O error during object rights verification\n", server, user);
err = PAM_AUTH_ERR;
goto bailout;
}
if ((level >= 0x30) || ((level & 0xF) >= 3)) {
syslog(LOG_WARNING, "Access denied for %s/%s because of it is supervisor equivalent\n", server, user);
err = PAM_AUTH_ERR;
goto bailout;
}
}
if (group) {
if (!(qflag & QF_BINDERY)) {
NWDSContextHandle ctx;
nuint32 c;
err = NWDSCreateContextHandle(&ctx);
if (err) {
syslog(LOG_WARNING, "NWDSCreateContextHandle() failed with %s\n", strnwerror(err));
err = PAM_SYSTEM_ERR;
goto bailout;
}
c = DCV_XLATE_STRINGS | DCV_TYPELESS_NAMES | DCV_DEREF_ALIASES;
err = NWDSSetContext(ctx, DCK_FLAGS, &c);
if (err) {
syslog(LOG_WARNING, "NWDSSetContext() failed with %s\n", strnwerror(err));
err = PAM_SYSTEM_ERR;
NWDSFreeContext(ctx);
goto bailout;
}
err = is_member_of_nds_group(ctx, cn, oid, group);
NWDSFreeContext(ctx);
if (err) {
syslog(LOG_WARNING, "%s is not member of NDS %s\n", user, group);
err = PAM_AUTH_ERR;
goto bailout;
}
} else { /* bindery testing */
if (is_member_of_bindery_group(cn, user, group)) {
syslog(LOG_WARNING, "%s is not member of BINDERY %s\n", user, group);
err = PAM_AUTH_ERR;
goto bailout;
}
}
}
if (id)
*id = oid;
if (conn)
*conn = cn;
else
NWCCCloseConn(cn);
return PAM_SUCCESS;
bailout:;
/* Close the connection. */
NWCCCloseConn(cn);
return err;
}
// this code was contributed by Jean Francois Burdet <jean.francois.burdet@adm.unige.ch>
// to implement a contextless login if a list of context to search is not provided in the command line
// The parameter ndserver was replaced by a conn since it is now called from nw_create_verify_conn_to_tree
// with an open connection to a tree or a ndsserver
// extra errors checkings and buffer overflow tested
//static void nw_ctx_search(const char * user_cn, const char * nds_server, char * contexts) {
static NWDSCCODE
nw_ctx_search(const char *user_cn, NWCONN_HANDLE conn, char *contexts, size_t maxsize)
{
NWDSContextHandle context;
NWDSCCODE ccode;
nuint32 iterationHandle;
nuint32 countObjectsSearched;
nuint32 objCntr;
nuint32 objCount;
char objectName[MAX_DN_CHARS + 1];
size_t ctxp;
nuint32 contextFlags;
// buffers
pBuf_T searchFilter; // search filter
pBuf_T retBuf; // result buffer for NWDSSearch
Filter_Cursor_T *cur; // search expression tree temporary buffer
if (!contexts || maxsize < MAX_DN_CHARS + 1)
return EINVAL;
ccode = NWDSCreateContextHandle(&context);
if (ccode) {
syslog(LOG_NOTICE, "nw_ctx_search:NWDSCreateContextHandle (DCK_FLAGS) failed, returns %d\n", ccode);
goto Exit1;
}
ccode = NWDSSetContext(context, DCK_NAME_CONTEXT, "[Root]");
if (ccode) {
syslog(LOG_NOTICE, "nw_ctx_search:NWDSGetContext (DCK_FLAGS) failed, returns %d\n", ccode);
goto Exit2;
}
ccode = NWDSGetContext(context, DCK_FLAGS, &contextFlags);
if (ccode) {
syslog(LOG_NOTICE, "nw_ctx_search:NWDSGetContext (DCK_FLAGS) failed, returns %d\n", ccode);
goto Exit2;
}
contextFlags |= DCV_TYPELESS_NAMES;
ccode = NWDSSetContext(context, DCK_FLAGS, &contextFlags);
if (ccode) {
syslog(LOG_NOTICE, "nw_ctx_search:NWDSSetContext (DCK_FLAGS DCV_TYPELESS_NAMES) failed, returns %d\n", ccode);
goto Exit2;
}
ccode = NWDSAddConnection(context, conn);
if (ccode) {
syslog(LOG_NOTICE, "nw_ctx_search:NWDSAddConnection failed, returns %d\n", ccode);
goto Exit2;
}
/**********************************************************************
* In order to search, we need: *
* A Filter Cursor (to build the search expression) *
* A Filter Buffer (to store the expression; used by NWDSSearch) *
* A Buffer to store which attributes we need information on *
* A Result Buffer (to store the search results) *
**********************************************************************/
/* Allocate Filter buffer and Cursor and populate */
ccode = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &searchFilter);
if (ccode) {
syslog(LOG_NOTICE, "nw_ctx_search:NWDSAllocBuf returned: %d\n", ccode);
goto Exit3;
}
// Initialize the searchFilter buffer
ccode = NWDSInitBuf(context, DSV_SEARCH_FILTER, searchFilter);
if (ccode) {
syslog(LOG_NOTICE, "nw_ctx_search:NWDSInitBuf returned: %d\n", ccode);
goto Exit6;
}
// Allocate a filter cursor to put the search expression
ccode = NWDSAllocFilter(&cur);
if (ccode) {
syslog(LOG_NOTICE, "nw_ctx_search:NWDSAllocFilter returned: %d\n", ccode);
goto Exit6;
}
// Build the expression tree in cur, then place into searchFilter
// Object Class = User AND CN = user_cn
ccode = NWDSAddFilterToken(cur, FTOK_ANAME, "Object Class", SYN_CLASS_NAME);
ccode = NWDSAddFilterToken(cur, FTOK_EQ, NULL, 0);
ccode = NWDSAddFilterToken(cur, FTOK_AVAL, "User", SYN_CLASS_NAME);
ccode = NWDSAddFilterToken(cur, FTOK_AND, NULL, 0);
ccode = NWDSAddFilterToken(cur, FTOK_ANAME, "CN", SYN_CI_STRING);
ccode = NWDSAddFilterToken(cur, FTOK_EQ, NULL, 0);
ccode = NWDSAddFilterToken(cur, FTOK_AVAL, user_cn, SYN_CI_STRING);
ccode = NWDSAddFilterToken(cur, FTOK_END, NULL, 0);
ccode = NWDSPutFilter(context, searchFilter, cur, NULL);
if (ccode) {
syslog(LOG_NOTICE, "nw_ctx_search:NWDSPutFilter returned: %d\n", ccode);
goto Exit5;
} else
cur = NULL;
ccode = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &retBuf);
if (ccode) {
syslog(LOG_NOTICE, "nw_ctx_search:NWDSAllocBuf returned: %d\n", ccode);
goto Exit5;
}
ctxp = 0;
iterationHandle = NO_MORE_ITERATIONS;
// while NWDSSearch still can get some objects...
do {
ccode = NWDSSearch(context, "[Root]", DS_SEARCH_SUBTREE, 0, // don't dereference aliases
searchFilter, 0, // we want attributes and values
0, // only want information in attrNames
NULL, &iterationHandle, 0, // reserved
&countObjectsSearched, retBuf);
if (ccode) {
syslog(LOG_NOTICE, "nw_ctx_search:NWDSSearch returned: %s\n", strnwerror(ccode));
goto Exit4;
}
// count the object returned in the buffer
ccode = NWDSGetObjectCount(context, retBuf, &objCount);
if (ccode) {
syslog(LOG_NOTICE, "nw_ctx_search:NWDSGetObjectCount returned: %d\n", ccode);
goto Exit4;
}
// for the number of objects returned...
for (objCntr = 0; objCntr < objCount; objCntr++) {
char *p;
size_t ln;
// get an object name
ccode = NWDSGetObjectName(context, retBuf, objectName, NULL, NULL);
if (ccode) {
syslog(LOG_NOTICE, "nw_ctx_search:NWDSGetObjectName returned: %d\n", ccode);
goto Exit4;
}
// now, we get the user context wich starts at the first . occurence in the string
p = strchr(objectName, '.');
if (!p) {
break;
}
ln = strlen(p + 1);
if (ctxp + ln >= maxsize) {
break;
}
if (ctxp) {
*contexts++ = ',';
maxsize--;
}
memcpy(contexts, p + 1, ln);
contexts += ln;
maxsize -= ln;
ctxp = 1;
} // objCntr
} while (iterationHandle != NO_MORE_ITERATIONS);
Exit4:;
if (iterationHandle != NO_MORE_ITERATIONS) {
// let's keep the final dserr as the 'out of memory error' from ptr->getval()
NWDSCloseIteration(context, DSV_SEARCH_FILTER, iterationHandle);
}
//lets remove trailing ':'
if (ctxp)
*contexts = 0;
else
ccode = PAM_USER_UNKNOWN; // not found...
if (retBuf)
NWDSFreeBuf(retBuf);
Exit5:;
if (cur)
NWDSFreeFilter(cur, NULL);
Exit6:;
if (searchFilter)
NWDSFreeBuf(searchFilter);
Exit3:;
Exit2:;
NWDSFreeContext(context);
Exit1:;
return ccode;
}
/* new code called by tree=XXX:ctx1,ctx2...,ctxn/group cmd line parameter */
/* or by ndsserver =XXX:ctx1.../Group */
static int
nw_create_verify_conn_to_tree(NWCONN_HANDLE * conn, NWObjectID * id,
const char *tree, const char *user, const char *contexts, const char *pwd,
int qflag, const char *group, nuint nameFormat)
{
NWCONN_HANDLE cn = NULL;
NWCCODE err;
NWObjectID oid;
NWDSContextHandle ctx = NULL;
nuint32 c;
char fqdn[MAX_DN_CHARS + 1];
const char *ctxStart;
const char *ctxEnd;
char ctxbuf[4096]; // buffer to hold possible matches in a contexless login
err = NWCCOpenConnByName(NULL, tree, nameFormat, NWCC_OPEN_NEW_CONN, NWCC_RESERVED, &cn);
if (err) {
if (nameFormat == NWCC_NAME_FORMAT_NDS_TREE)
syslog(LOG_WARNING, "%s when trying to open connection to tree %s \n", strnwerror(err), tree);
else
syslog(LOG_WARNING, "%s when trying to open connection to NDS server !%s! \n", strnwerror(err), tree);
return PAM_TRY_AGAIN;
}
err = PAM_SYSTEM_ERR;
if (NWDSCreateContextHandle(&ctx)) {
syslog(LOG_WARNING, "NWDSCreateContextHandle() failed with %s\n", strnwerror(err));
goto bailout;
}
if (NWDSGetContext(ctx, DCK_FLAGS, &c)) {
syslog(LOG_WARNING, "NWDSGetContext(DCK_FLAGS) failed with %s\n", strnwerror(err));
goto bailout;
}
c |= DCV_XLATE_STRINGS | DCV_TYPELESS_NAMES | DCV_DEREF_ALIASES;
c &= ~DCV_CANONICALIZE_NAMES;
if (NWDSSetContext(ctx, DCK_FLAGS, &c)) {
syslog(LOG_WARNING, "NWDSSetContext(DCK_FLAGS) failed with %s\n", strnwerror(err));
goto bailout;
}
if (NWDSAddConnection(ctx, cn)) {
syslog(LOG_WARNING, "NWDSAddConnection failed with %s\n", strnwerror(err));
goto bailout;
}
/* test 1.21* WITH PAM first login context is [Root] (pam has no $HOME)
but with xscreensaver, context is red by ncpfs from a nwclient file in user's home
and resolution fails !
same problem with nds_group checking OK with PAM since both value are realtive to [root]
by with xscreensaver , the ctx is set to default, so comparing is done to the wrong group name
if (NWDSGetContext(ctx,DCK_NAME_CONTEXT,fqdn))
syslog(LOG_WARNING, "NWDSGetContext (DCK_NAME_CONTEXT) failed with %s\n", strnwerror(err));
else
syslog(LOG_WARNING, "Default context is %s :\n", fqdn);
*/
// rev 1.25 , implement contextless login if no contexts are provided in the contexts list
if (!contexts) {
if (qflag & QF_DEBUG)
syslog(LOG_DEBUG, "Trying contexless login for %s on %s\n", tree, user);
if (nw_ctx_search(user, cn, ctxbuf, sizeof (ctxbuf))) {
err = PAM_AUTH_ERR;
goto bailout;
}
contexts = ctxbuf;
if (qflag & QF_DEBUG)
syslog(LOG_DEBUG, "Found %s for %s\n", ctxbuf, user);
}
/* scan the search contexts list */
ctxStart = contexts;
do {
size_t ln;
err = PAM_AUTH_ERR;
ln = strlen(user);
if (ln >= sizeof (fqdn)) {
syslog(LOG_ERR, "Buffer overflow attack attempted at %s:%u (%s)\n", __FILE__, __LINE__, __FUNCTION__);
goto bailout;
}
memcpy(fqdn, user, ln + 1);
if (ctxStart) {
size_t appln;
ctxEnd = strchr(ctxStart, ',');
if (ctxEnd) {
appln = ctxEnd - ctxStart;
ctxEnd++;
} else {
appln = strlen(ctxStart);
}
if (ln + 1 + appln >= sizeof (fqdn)) {
syslog(LOG_ERR, "Buffer overflow attack attempted at %s:%u (%s)\n", __FILE__, __LINE__, __FUNCTION__);
goto bailout;
}
fqdn[ln] = '.';
memcpy(fqdn + ln + 1, ctxStart, appln);
fqdn[ln + 1 + appln] = 0;
ctxStart = ctxEnd;
}
if (qflag & QF_DEBUG)
syslog(LOG_NOTICE, "nw_create_verify_conn_to_tree: trying to resolve %s\n", fqdn);
/* rev 1.26 : some support for the same CN in different contexts
the password makes the difference */
err = NWDSMapNameToID(ctx, cn, fqdn, &oid);
if (!err) {
if (qflag & QF_DEBUG)
syslog(LOG_WARNING, "trying to login as %s\n", fqdn);
err = nds_login_auth(cn, fqdn, pwd);
if (err && err != NWE_PASSWORD_EXPIRED) {
syslog(LOG_WARNING, "%s when trying to login\n", strnwerror(err));
switch (err) {
/* case 1:good password but some other problem, report failure */
case NWE_LOGIN_MAX_EXCEEDED:
case NWE_LOGIN_UNAUTHORIZED_TIME:
case NWE_LOGIN_UNAUTHORIZED_STATION:
case NWE_ACCT_DISABLED:
err = PAM_AUTH_ERR;
goto bailout;
/* case 2: one day we should really do something about this ? */
case NWE_PASSWORD_EXPIRED:
err = PAM_NEW_AUTHTOK_REQD;
goto bailout;
/*case 3: wrong password or other errors. let's try in another context */
case ERR_NO_SUCH_ENTRY:
case NWE_SERVER_UNKNOWN:
err = PAM_USER_UNKNOWN;
break;
default:
err = PAM_AUTH_ERR;
break;
}
//goto bailout; not anymore
} else
break; /* everything is Ok, except maybe password expired */
} else {
syslog(LOG_WARNING, "NWDSMapNameToID for %s failed with %s\n", fqdn, strnwerror(err));
}
} while (ctxStart);
if (err) {
err = PAM_AUTH_ERR;
goto bailout;
}
/* PP:: not very sure this code is STILL relevant against NDS tree */
err = nw_get_nwid(cn, &oid, qflag); // needed again ?
if (err) {
syslog(LOG_WARNING, "Error %s retrieving user ID for %s\n", strnwerror(err), user);
goto bailout;
}
if (qflag & QF_NOSU) {
if (oid == 0x00000001) {
err = PAM_AUTH_ERR;
syslog(LOG_WARNING, "Access denied for %s/%s because of it is supervisor\n", tree, user);
goto bailout;
}
if (qflag & QF_DEBUG)
syslog(LOG_DEBUG, "User %s/%s passed supervisor check\n", tree, user);
}
if (qflag & QF_NOSUEQ) {
nuint8 level;
err = NWGetBinderyAccessLevel(cn, &level, NULL);
if (err) {
syslog(LOG_WARNING, "Access denied for %s/%s because of I/O error during object rights verification\n", tree, user);
err = PAM_AUTH_ERR;
goto bailout;
}
if ((level >= 0x30) || ((level & 0xF) >= 3)) {
syslog(LOG_WARNING, "Access denied for %s/%s because of it is supervisor equivalent\n", tree, user);
err = PAM_AUTH_ERR;
goto bailout;
}
}
if (qflag & QF_DEBUG) {
char aux[512];
aux[0] = 0;
if (nameFormat == NWCC_NAME_FORMAT_NDS_TREE) {
NWCCGetConnInfo(cn, NWCC_INFO_SERVER_NAME, sizeof (aux), aux);
syslog(LOG_DEBUG, "User %s was successfully authorized on tree %s by server %s \n", fqdn, tree, aux);
} else {
NWCCGetConnInfo(cn, NWCC_INFO_TREE_NAME, sizeof (aux), aux);
syslog(LOG_DEBUG, "User %s was successfully authorized by NDS server %s on tree %s\n", fqdn, tree, aux);
}
}
if (group) {
/* v 1.21 */
err = is_member_of_nds_group(ctx, cn, oid, group);
if (err) {
syslog(LOG_WARNING, "%s is not member of NDS %s\n", user, group);
err = PAM_AUTH_ERR;
goto bailout;
}
}
if (id)
*id = oid;
if (conn)
*conn = cn;
else
NWCCCloseConn(cn);
NWDSFreeContext(ctx);
return PAM_SUCCESS;
bailout:;
/* code to be executed upon any error */
if (ctx)
NWDSFreeContext(ctx);
if (cn)
NWCCCloseConn(cn);
return err;
}
static int
nw_attempt_auth_server(pam_handle_t * pamh, const char *server, const char *user, const char *pwd, int qflag, const char *group)
{
int err;
NWCONN_HANDLE conn;
err = nw_create_verify_conn_to_server(&conn, NULL, server, user, pwd, qflag, group);
if (err)
return err;
pam_set_data(pamh, "pam.ncpfs.passwd.conn", conn, nw_cleanup_conn);
return PAM_SUCCESS;
}
/* trial code server is a Tree or a NDS server... and contexts is a comma separated list of CTX to search */
/* rev 1.25: if contexts is NULL, contextless login is attempted */
static int
nw_attempt_auth_tree(pam_handle_t * pamh,
const char *tree, const char *user, const char *contexts, const char *pwd,
int qflag, const char *group, nuint nameFormat) // either NWCC_NAME_FORMAT_NDS_TREE, NWCC_NAME_FORMAT_BIND
{
int err;
NWCONN_HANDLE conn;
err = nw_create_verify_conn_to_tree(&conn, NULL, tree, user, contexts, pwd, qflag, group, nameFormat);
if (err)
return err;
pam_set_data(pamh, "pam.ncpfs.passwd.conn", conn, nw_cleanup_conn);
return PAM_SUCCESS;
}
/********************************************************* part II NDS properties reading *******************/
struct nw_group_info {
struct nw_group_info *next;
char *name;
gid_t gid;
long zenFlag; //PP
};
// thes infos will be "dumped" in a ~/nwinfos file that can be "sourced" by the 6 "Zen scripts"
// or any future scripts / applications. (see the pam_session_opening part for the used format
// and script variables names
struct nw_user_info {
char *name;
char *gecos;
char *shell;
char *dir;
uid_t uid;
gid_t gid;
struct nw_group_info *groups;
pam_handle_t *pamh;
struct pam_ncp_state state;
int qflag;
char *fqdn; /* User's FQDN from NDS */
char *nwhomeServer; //PP CN of the server extracted from NDS prop Home Directory
char *nwhomeVolume; //PP real name of the volume (SYS...)extracted from NDS prop Home Directory
char *nwhomePath; //PP "unixified" and "uppercased" extracted from NDS prop Home Directory
char *nwhomeMntPnt; //PP defaut =nwhome , can be changed by -m option
char *emailSMTP; //PP NDS property (obsolete but still used)
char *emailLDAP; //PP NDS property
char *messageServer; //PP CN of the NDS property maybe != authenticating server used by PAM
char *defaultTree; //PP the tree of the server that authenticated him
char *defaultNameCtx; //PP from his canonical name
long zenFlag; //PP see above
long zenFlagOFF; //PP rev 1.26 turn off some zenflags on that workstation
int isNewUser; // PP rev 1.19
int isScreenSaverRelogin; // PP rev 1.21
};
static void
init_nw_user_info(struct nw_user_info *ui)
{
ui->pamh = NULL;
ui->qflag = 0;
ui->zenFlag = ZF_DEFAULTS; //PP
ui->zenFlagOFF = 0;
ui->name = NULL;
ui->gecos = NULL;
ui->shell = NULL;
ui->dir = NULL;
ui->uid = (uid_t) -1;
ui->gid = (gid_t) -1;
ui->groups = NULL;
ui->fqdn = NULL;
ui->nwhomeServer = NULL; //PP
ui->nwhomeVolume = NULL; //PP
ui->nwhomePath = NULL; //PP
ui->nwhomeMntPnt = NULL; //PP
ui->emailSMTP = NULL; //PP
ui->emailLDAP = NULL; //PP
ui->messageServer = NULL; //PP
ui->defaultTree = NULL; //PP
ui->defaultNameCtx = NULL; //PP
ui->isNewUser = 0; //PP
}
static void
free_nw_user_info(struct nw_user_info *ui)
{
struct nw_group_info *gi;
struct nw_group_info *bkgi;
#define FREEFIELD(x) do if (ui->x) { free(ui->x); ui->x = NULL; } while (0)
FREEFIELD(name);
FREEFIELD(gecos);
FREEFIELD(shell);
FREEFIELD(dir);
FREEFIELD(fqdn);
FREEFIELD(nwhomeServer);
FREEFIELD(nwhomeVolume);
FREEFIELD(nwhomePath);
FREEFIELD(nwhomeMntPnt);
FREEFIELD(emailSMTP);
FREEFIELD(emailLDAP);
FREEFIELD(messageServer);
FREEFIELD(defaultTree);
FREEFIELD(defaultNameCtx);
#undef FREEFIELD
for (gi = ui->groups; gi; gi = bkgi) {
bkgi = gi->next;
free(gi->name);
free(gi);
}
ui->groups = NULL;
}
// PP:called by PAM at pam_end
// There is a possible problem storing the user_data in PAM during all the session
// If an admin changes something to that user, the data is out on sync
// so we should use this data ONLY in a "PAM_open_session" call back
// that happens just after the successful login
static void
cleanup_user_info(UNUSED(pam_handle_t * pamh), void *data, UNUSED(int error_status))
{
free_nw_user_info((struct nw_user_info *) data);
}
static int
nw_retrieve_bindery_user_info(struct nw_user_info *ui, NWCONN_HANDLE conn, UNUSED(NWObjectID oid))
{
/* we have to give some support here since:
1) writing the .nwclient file in user's home (flags NOP) requires that
either ui.messageServer or ui.nwHomeServer to be not NULL
2) we need user's home from bindery to mount it (flag H)
warning: the home MUST be on the authenticating server else
the bindery property is likely to be empty
*/
char serverName[128];
char userName[128];
char *v;
NWCCODE err;
struct nw_property p;
const char me[] = "nw_retrieve_bindery_user_info";
err = NWCCGetConnInfo(conn, NWCC_INFO_USER_NAME, sizeof (userName), userName);
if (err) {
syslog(LOG_WARNING, "%s:unable to get back user name from connection.\n", me);
return err;
}
err = NWCCGetConnInfo(conn, NWCC_INFO_SERVER_NAME, sizeof (serverName), serverName);
if (err) {
syslog(LOG_WARNING, "%s:unable to get back server name from connection.\n", me);
return err;
}
v = strdup(serverName);
if (!v) {
syslog(LOG_WARNING, "%s:Not enough memory for strdup()\n", me);
return ENOMEM;
}
ui->messageServer = v;
memset(&p, 0, sizeof (p));
err = ncp_read_property_value(conn, NCP_BINDERY_USER, userName, 1, "IDENTIFICATION", &p);
if (!err && p.value[0]) {
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "%s:got a full name %s for %s\n ", me, p.value, userName);
v = strdup((char*)p.value);
if (!v) {
syslog(LOG_WARNING, "%s:Not enough memory for strdup()\n", me);
return ENOMEM;
}
ui->gecos = v;
}
/*do not rely too much on this with NW4 servers:
in my place they do not answer correctly to this call even if
the user DO have his home on that server.
in contrast, one NW5 server, were the user DO NOT has his home on it
DID reply correctly ???
*/
memset(&p, 0, sizeof (p));
err = ncp_read_property_value(conn, NCP_BINDERY_USER, userName, 1, "HOME_DIRECTORY", &p);
if (!err && p.value[0]) {
char *v1;
char *mark = strchr((char*)p.value, ':');
if (mark && *(mark + 1)) {
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "%s:got a home directory %s for %s\n ", me, p.value, userName);
*mark = 0;
v = strdup((char*)p.value);
if (!v) {
syslog(LOG_WARNING, "%s:Not enough memory for strdup()\n", me);
return ENOMEM;
}
v1 = strdup(mark + 1);
if (!v1) {
syslog(LOG_WARNING, "%s:Not enough memory for strdup()\n", me);
return ENOMEM;
}
ui->nwhomeServer = strdup(ui->messageServer);
ui->messageServer = NULL;
ui->nwhomeVolume = v;
unixifyPathUC(v1);
ui->nwhomePath = v1;
}
}
return 0;
}
/******************************************** NDS_Reading ********************/
struct attrop {
const NWDSChar *attrname;
NWDSCCODE (*getval)(NWDSContextHandle, const void *val, void *arg);
enum SYNTAX synt;
};
static NWDSCCODE
nds_read_attrs(NWDSContextHandle ctx, const NWDSChar * objname, void *arg, const struct attrop *atlist)
{
Buf_T *attrlist;
Buf_T *info;
NWDSCCODE dserr;
const struct attrop *ptr;
nuint32 iterHandle;
dserr = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &attrlist);
if (dserr) {
syslog(LOG_WARNING, "NWDSAllocBuf() failed with %s\n", strnwerror(dserr));
goto bailout;
}
dserr = NWDSInitBuf(ctx, DSV_READ, attrlist);
if (dserr) {
syslog(LOG_WARNING, "NWDSInitBuf() failed with %s\n", strnwerror(dserr));
goto bailoutbuf1;
}
for (ptr = atlist; ptr->attrname; ptr++) {
dserr = NWDSPutAttrName(ctx, attrlist, ptr->attrname);
if (dserr) {
syslog(LOG_WARNING, "NWDSPutAttrName(%s) failed with %s\n", ptr->attrname, strnwerror(dserr));
goto bailoutbuf1;
}
}
dserr = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &info);
if (dserr) {
syslog(LOG_WARNING, "NWDSAllocBuf() failed with %s\n", strnwerror(dserr));
goto bailoutbuf1;
}
iterHandle = NO_MORE_ITERATIONS;
do {
NWObjectCount attrs;
dserr = NWDSRead(ctx, objname, DS_ATTRIBUTE_VALUES, 0, attrlist, &iterHandle, info);
if (dserr) {
if (dserr == ERR_NO_SUCH_ATTRIBUTE)
dserr = 0;
else
syslog(LOG_WARNING, "NWDSRead() failed with %s\n", strnwerror(dserr));
goto bailoutbuf2;
}
dserr = NWDSGetAttrCount(ctx, info, &attrs);
if (dserr) {
syslog(LOG_WARNING, "NWDSGetAttrCount() failed with %s\n", strnwerror(dserr));
goto bailoutcloit;
}
while (attrs--) {
NWDSChar attrname[MAX_SCHEMA_NAME_BYTES];
enum SYNTAX synt;
NWObjectCount vals;
dserr = NWDSGetAttrName(ctx, info, attrname, &vals, &synt);
if (dserr) {
syslog(LOG_WARNING, "NWDSGetAttrName() failed with %s\n", strnwerror(dserr));
goto bailoutcloit;
}
while (vals--) {
size_t sz;
void *val;
dserr = NWDSComputeAttrValSize(ctx, info, synt, &sz);
if (dserr) {
syslog(LOG_WARNING, "NWDSComputeAttrValSize() failed with %s\n", strnwerror(dserr));
goto bailoutcloit;
}
val = malloc(sz);
if (!val) {
syslog(LOG_WARNING, "malloc() failed with %s\n", strnwerror(ENOMEM));
goto bailoutcloit;
}
dserr = NWDSGetAttrVal(ctx, info, synt, val);
if (dserr) {
free(val);
syslog(LOG_WARNING, "NWDSGetAttrVal() failed with %s\n", strnwerror(dserr));
goto bailoutcloit;
}
for (ptr = atlist; ptr->attrname; ptr++) {
if (!strcasecmp(ptr->attrname, attrname))
break;
}
if (ptr->getval) {
if (ptr->synt != synt) {
syslog(LOG_WARNING, "Incompatible tree schema, %s has syntax %d instead of %d\n", attrname, synt, ptr->synt);
} else {
// ajout PP dserr= !!! en cas de pb mémoire
dserr = ptr->getval(ctx, val, arg);
}
}
free(val);
if (dserr) {
goto bailoutcloit;
}
}
}
} while (iterHandle != NO_MORE_ITERATIONS);
bailoutcloit:;
if (iterHandle != NO_MORE_ITERATIONS) {
// PP let's keep the final dserr as the 'out of memory error' from ptr->getval()
NWDSCCODE dserr2 = NWDSCloseIteration(ctx, DSV_READ, iterHandle);
if (dserr2) {
syslog(LOG_WARNING, "NWDSCloseIteration() failed with %s\n", strnwerror(dserr2));
}
}
bailoutbuf2:;
NWDSFreeBuf(info);
bailoutbuf1:;
NWDSFreeBuf(attrlist);
bailout:;
return dserr;
}
/*PP ************************************************* splitting home directory attribute*/
struct nw_home_info {
char *hs;
char *hrn;
};
static NWDSCCODE
nds_pp_host_server(UNUSED(NWDSContextHandle ctx), const void *val, void *arg)
{
struct nw_home_info *hi = (struct nw_home_info *) arg;
const char *src = val;
size_t len;
char *dot;
char *v;
dot = strchr(src, '.');
if (dot) {
len = dot - src;
} else {
len = strlen(src);
}
v = malloc((len + 1) * sizeof (char));
if (!v) {
syslog(LOG_WARNING, "Not enough memory for strdup()\n");
return ENOMEM;
}
memcpy(v, src, len);
v[len] = 0;
hi->hs = v;
return 0;
}
static NWDSCCODE
nds_pp_host_resource_name(UNUSED(NWDSContextHandle ctx), const void *val, void *arg)
{
struct nw_home_info *hi = (struct nw_home_info *) arg;
char *v = strdup((const char *) val);
if (!v) {
syslog(LOG_WARNING, "Not enough memory for strdup()\n");
return ENOMEM;
}
hi->hrn = v;
return 0;
}
static NWDSCCODE
nds_home_info(NWDSContextHandle ctx, const NWDSChar * objname, struct nw_home_info *hi)
{
static const struct attrop atlist[] = {
{ATTR_HOST_SERVER, nds_pp_host_server, SYN_DIST_NAME},
{ATTR_HOST_RN, nds_pp_host_resource_name, SYN_CI_STRING},
{NULL, NULL, SYN_UNKNOWN}
};
return nds_read_attrs(ctx, objname, hi, atlist);
}
/******************************************************************************/
/** gather Unix groups informations */
static NWDSCCODE
nds_ga_group_unixgid(UNUSED(NWDSContextHandle ctx), const void *val, void *arg)
{
struct nw_group_info *gi = (struct nw_group_info *) arg;
if (gi->gid == (gid_t) -1) {
gi->gid = *(const Integer_T *) val;
}
return 0;
}
// PP: id no NDS8 is present, collect the group Unix ID from one of the location
// string with the format G:nnn
// This is relevant ONLY if the flag QFC_REQUIRE_SERVER has been set in the command line
// can also used to specify a name of unix group different of the NDS'one
// eg. everyone --> users
// staff --> root
static NWDSCCODE
nds_pp_group_location(UNUSED(NWDSContextHandle ctx), const void *val, void *arg)
{
struct nw_group_info *gi = (struct nw_group_info *) arg;
const char *pt = (const char *) val;
int n;
//syslog(LOG_NOTICE, "start of NW group location got %s\n ",pt);
if (strlen(pt) > 2 && pt[1] == ':') {
const char *cur_pt = pt + 2;
switch (*pt) {
case 'g':
case 'G':
if (gi->gid == (gid_t) -1) {
switch (getnumber(&n, &cur_pt)) {
case 0:
gi->gid = n;
break;
default:
syslog(LOG_ERR, "Invalid group GID %s for %s\n", pt, gi->name);
}
}
break;
case 'n':
case 'N': // unix equivalent name
if (!gi->name) {
char *v = strdup(cur_pt);
if (!v) {
syslog(LOG_WARNING, "Not enough memory for strdup()\n");
return ENOMEM;
}
gi->name = v;
}
break;
/* interesting feature: since we OR them, a group may have
serveral Z:xxxx strings in the location property */
case 'z':
case 'Z':
gi->zenFlag |= decodeZenFlag(&cur_pt);
break;
}
}
return 0;
}
static NWDSCCODE
nds_group_info(NWDSContextHandle ctx, const NWDSChar * objname, struct nw_group_info *gi)
{
static const struct attrop atlist[] = {
{ATTR_GID, nds_ga_group_unixgid, SYN_INTEGER},
{ATTR_LOCATION, nds_pp_group_location, SYN_CI_STRING},
{NULL, NULL, SYN_UNKNOWN}
};
gi->gid = (gid_t) -1;
gi->zenFlag = 0;
gi->name = NULL;
return nds_read_attrs(ctx, objname, gi, atlist);
}
// PP:called only if no "Unix name has been found in NDS for that group
// a string N:nnnnn in L attribute
static NWDSCCODE
nds_group_name(UNUSED(NWDSContextHandle ctx), const NWDSChar * objname, struct nw_group_info *gi)
{
char *buff;
const unsigned char *f;
int c;
/* Worst case: output is Z, two chars per one in input, and zero terminating byte */
gi->name = buff = malloc(strlen(objname) * 2 + 2);
if (!buff) {
syslog(LOG_WARNING, "Not enough memory for strdup()\n");
return ENOMEM;
}
f = (const unsigned char*)objname;
for (; (c = *f++) != 0; *buff++ = c) {
if (c >= 'a' && c <= 'z')
continue;
if (c >= 'A' && c <= 'Z') {
c += 'a' - 'A';
continue;
}
if (gi->name == buff)
*buff++ = 'Z';
// PP: why ? a group starting by a digit is not legal?
// 24/01/2001 I know why now !!! grpadd will add user to group 1, 2 ... (bin, daemon !!!)
if (c >= '0' && c <= '9')
continue;
if (c == '_' || c == ' ') {
c = '_';
continue;
}
if (c == '.') { //stop at first dot
if (1)
break;
c = 'U';
continue;
}
*buff++ = 'A' + (c >> 4);
c = 'A' + (c & 0xF);
}
*buff = 0;
return 0;
}
static int nw_update_group_info(struct nw_user_info *ui, struct nw_group_info *gi);
static int
rqgroup(struct nw_user_info *ui, NWDSContextHandle ctx, const NWDSChar * objname, int primary)
{
struct nw_group_info gi;
struct group *grp;
struct nw_group_info *i;
int err;
//syslog(LOG_NOTICE, "call of rggroup for %s\n ",objname);
err = nds_group_info(ctx, objname, &gi);
if (err)
return err;
ui->zenFlag |= gi.zenFlag; // merge group zenFlag to user's (rev 1.25 now, so it is done even if group cannot be autocreated)
if (!gi.name) // no alias sent by NDS
if (nds_group_name(ctx, objname, &gi)) // make a name
return -1;
setgrent(); //PP
grp = getgrnam(gi.name);
endgrent(); //PP
if (grp) {
gi.gid = grp->gr_gid;
} else {
err = nw_update_group_info(ui, &gi);
if (err)
return err;
}
if (primary) {
ui->gid = gi.gid;
}
// PP no duplicates in group list. may happen with an alias sent by NDS
for (i = ui->groups; i; i = i->next) {
if (!strcmp(gi.name, i->name))
break;
}
if (!i) {
i = malloc(sizeof (*i));
if (!i)
return ENOMEM;
i->name = gi.name;
i->gid = gi.gid;
i->next = ui->groups;
i->zenFlag = gi.zenFlag;
ui->groups = i;
}
return 0;
}
static int
build_groups_list(const struct nw_user_info *ui, char **list)
{
const struct nw_group_info *gi;
size_t ln;
char *p;
*list = NULL;
ln = 1;
for (gi = ui->groups; gi; gi = gi->next)
ln += strlen(gi->name) + 1;
if (ln == 1)
return 0;
p = (char *) malloc(ln);
if (!p)
return ENOMEM;
*list = p;
for (gi = ui->groups; gi; gi = gi->next) {
ln = strlen(gi->name);
memcpy(p, gi->name, ln);
p += ln;
*p++ = ',';
}
*--p = 0;
//syslog(LOG_NOTICE, "end of build group list got %s\n ",*list);
return 0;
}
/************************************ helper functions to extract some user's properties **/
static NWDSCCODE
nds_ga_unixuid(UNUSED(NWDSContextHandle ctx), const void *val, void *arg)
{
struct nw_user_info *ui = (struct nw_user_info *) arg;
if (ui->uid == (uid_t) -1) {
ui->uid = *(const Integer_T *) val;
// talk a bit (real NDS8 attribute or dummy ?)
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "got a Unix ID %d from %s\n ", ui->uid, ATTR_UID);
}
return 0;
}
static NWDSCCODE
nds_ga_unixpgid(UNUSED(NWDSContextHandle ctx), const void *val, void *arg)
{
struct nw_user_info *ui = (struct nw_user_info *) arg;
if (ui->gid == (gid_t) -1) {
ui->gid = *(const Integer_T *) val;
// talk a bit (real NDS8 attribute or dummy ?)
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "got a Unix PGID %d from %s\n ", ui->gid, ATTR_PGID);
}
return 0;
}
// PP this is the same founction as below ???
// does Netware has two synonyms for the same property (UNIX:GID"
// and UNIX:Primary GroupID???
static NWDSCCODE
nds_ga_unixgid(UNUSED(NWDSContextHandle ctx), const void *val, void *arg)
{
struct nw_user_info *ui = (struct nw_user_info *) arg;
if (ui->gid == (gid_t) -1) {
ui->gid = *(const Integer_T *) val;
// talk a bit (real NDS8 attribute or dummy ?)
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "got a Unix GID %d from %s\n ", ui->gid, ATTR_GID);
}
return 0;
}
static NWDSCCODE
nds_ga_unixhome(UNUSED(NWDSContextHandle ctx), const void *val, void *arg)
{
struct nw_user_info *ui = (struct nw_user_info *) arg;
if (!ui->dir) {
char *v = strdup((const char *) val);
if (!v) {
syslog(LOG_WARNING, "Not enough memory for strdup()\n");
return ENOMEM;
}
ui->dir = v;
// talk a bit (real NDS8 attribute or dummy ?)
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "got a Unix Home %s from %s\n ", ui->dir, ATTR_HOME);
}
return 0;
}
static NWDSCCODE
nds_ga_unixshell(UNUSED(NWDSContextHandle ctx), const void *val, void *arg)
{
struct nw_user_info *ui = (struct nw_user_info *) arg;
if (!ui->shell) {
char *v = strdup((const char *) val);
if (!v) {
syslog(LOG_WARNING, "Not enough memory for strdup()\n");
return ENOMEM;
}
ui->shell = v;
// talk a bit (real NDS8 attribute or dummy ?)
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "got a Unix shell %s from %s\n ", ui->shell, ATTR_SHELL);
}
return 0;
}
static NWDSCCODE
nds_update_gecos(struct nw_user_info *ui, const char *str)
{
char *v;
size_t sadd = strlen(str) + 1;
if (ui->gecos) { // already got the name
size_t sold = strlen(ui->gecos);
v = realloc(ui->gecos, sold + 1 + sadd);
if (!v) {
syslog(LOG_WARNING, "Not enough memory for strdup()\n");
return ENOMEM;
}
v[sold] = ',';
memcpy(v + 1, str, sadd);
} else {
v = malloc(sadd);
if (!v) {
syslog(LOG_WARNING, "Not enough memory for strdup()\n");
return ENOMEM;
}
memcpy(v, str, sadd);
}
ui->gecos = v;
return 0;
}
// PP we append the Comment after the full name, separated by a comma
static NWDSCCODE
nds_ga_unixcomment(UNUSED(NWDSContextHandle ctx), const void *val, void *arg)
{
struct nw_user_info *ui = (struct nw_user_info *) arg;
// talk a bit (real NDS8 attribute or dummy ?)
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "got a Unix Comment %s from %s\n ", (const char *) val, ATTR_COM);
return nds_update_gecos(ui, (const char *) val);
}
// PP can be any naming attribute returning a SYN_CI_STRING see define before nds_user_info()
// PP we add the name before any comment that can be there
static NWDSCCODE
nds_ga_gecos(UNUSED(NWDSContextHandle ctx), const void *val, void *arg)
{
struct nw_user_info *ui = (struct nw_user_info *) arg;
NWDSCCODE err;
if (ui->qflag & QF_DEBUG) {
syslog(LOG_NOTICE, "before full name gecos is %s\n ", ui->gecos ? : "(null)");
}
err = nds_update_gecos(ui, (const char *) val);
if (err) {
return err;
}
if (ui->qflag & QF_DEBUG) {
syslog(LOG_NOTICE, "after full name gecos is %s\n ", ui->gecos);
}
return 0;
}
static NWDSCCODE
nds_ga_unixpgname(NWDSContextHandle ctx, const void *val, void *arg)
{
struct nw_user_info *ui = (struct nw_user_info *) arg;
// talk a bit (real NDS8 attribute or dummy ?)
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "got a Unix PGroup Name %s from %s\n ", (const char *) val, ATTR_PGNAME);
if (ui->gid == (gid_t) -1) {
rqgroup(ui, ctx, val, 1);
}
return 0;
}
static NWDSCCODE
nds_ga_group_membership(NWDSContextHandle ctx, const void *val, void *arg)
{
struct nw_user_info *ui = (struct nw_user_info *) arg;
rqgroup(ui, ctx, val, 0);
return 0;
}
#ifdef USE_POSTAL_ADDRESS
// PP: id no NDS8 is present, collect the user's basic Unix informations from
// 6 fields of the Postal Address attribute
// This is relevant ONLY if the flag QFC_REQUIRE_SERVER has been set in the command line
// and if zenFlag processing is activated.
// some people may prefer the postal address instead of the Location property
// used here by default.
// advantages: can be directty edited.No need to prepend X:
// disadvantages: limited to 30 chars and 6 strings
// The location attribute is more convenient
// ( no limit of number of strings and 128 chars rather than 30).
static NWDSCCODE
nds_pp_postal_address(NWDSContextHandle ctx, const void *val, void *arg)
{
struct nw_user_info *ui = (struct nw_user_info *) arg;
Postal_Address_T *pa = (Postal_Address_T *) val;
int i, n;
const char *pt;
//rqgroup(ui, ctx, val, 0);
// adress 0 = unix UID leading spaces not significant
// adress 1= unix primary GID leading spaces not significant
// adress 2= unix home dir spaces significant
// adress 3= unix shell spaces significant
// adress 4= other group name spaces turned to _
// ONLY ONE: do not use a comma separated list group1,group2,.....
// adress 5= Zen flags spaces ignored
/* note that this attribute is not mandatory
so if it does not exists that "call back" is not called */
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "start of PO address\n");
for (i = 0; i < 6; i++) {
pt = (const char *) (*pa)[i];
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "reading po_ad %d %s ", i, pt);
// caution it may be there but empty (pointing to à 0) !!!
if (pt && strlen(pt))
switch (i) {
case 0:
if (ui->uid == (uid_t) -1) { // do not overwrite a DS 8 answer
const char *cur_pt = pt;
switch (getnumber(&n, &cur_pt)) {
case 0:
ui->uid = n;
break;
default:
syslog(LOG_ERR, "Invalid user ID %s for users %s\n", *pt, ui->name);
}
}
break;
case 1:
if (ui->gid == (gid_t) -1) { // do not overwrite a DS 8 answer
const char *cur_pt = pt;
switch (getnumber(&n, &cur_pt)) {
case 0:
ui->gid = n;
break;
default:
syslog(LOG_ERR, "Invalid primary user GID %s for user %s\n", *pt, ui->name);
}
}
break;
case 2:
if (!ui->dir) { // do not overwrite a DS 8 answer
char *v = strdup(pt);
if (!v) {
syslog(LOG_WARNING, "Not enough memory for strdup()\n");
return ENOMEM;
}
ui->dir = v;
}
break;
case 3:
if (!ui->shell) { // do not overwrite a DS 8 answer
char *v = strdup(pt);
if (!v) {
syslog(LOG_WARNING, "Not enough memory for strdup()\n");
return ENOMEM;
}
ui->shell = v;
}
break;
case 4: // other group name: only one may be specified, unless we
// separe them by comma and parse. but limited to 30 chars
//(one of the reasons why we dropped Postal Address as a substitue for NDS8)
rqgroup(ui, ctx, val, 0);
break;
case 5: // ZenFlag per user ABCDEFGHIJKLMNOPQRSTUVWXYZ (30 max!!!)
ui->zenFlag |= decodeZenFlag(&pt);
break;
}
}
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "end of PO address\n");
return 0;
}
#else
// PP: id no NDS8 is present, collect the user's basic Unix informations from the location
// strings with the format X:nnnnnnnn , X = [U,G,H,S,P,O,C,Z] upper of lower case
// This is relevant ONLY if the flag QFC_REQUIRE_SERVER has been set in the command line
// Of course, even if NDS8 IS present, we still look at these, just in case the migration
// is not complete and to look for the user's ZENFLAG
// feature I: since zenflags are ORED, a user can have SEVERAL z:xxxx strings in his location strings
// better than deleting and recreating it !!!
// feature II: several O:groups strings are possible ( secondary groups defintion)
// DO NOT use o:group1,group2.....
static NWDSCCODE
nds_pp_location(NWDSContextHandle ctx, const void *val, void *arg)
{
struct nw_user_info *ui = (struct nw_user_info *) arg;
const char *pt = (const char *) val;
char *v;
int n;
int err;
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "start of NW location got %s\n ", pt);
if (strlen(pt) > 2 && pt[1] == ':') {
const char *cur_pt = pt + 2;
switch (*pt) {
case 'u': //user ID leading spaces not significant
case 'U':
if (ui->uid == (uid_t) -1) { // do not overwrite a DS 8 answer
switch (getnumber(&n, &cur_pt)) {
case 0:
ui->uid = n;
break;
default:
syslog(LOG_ERR, "Invalid user ID %s\n", pt);
}
}
break;
case 'g': // primary group number GID leading spaces not significant
case 'G':
if (ui->gid == (gid_t) -1) { // do not overwrite a DS 8 answer
switch (getnumber(&n, &cur_pt)) {
case 0:
ui->gid = n;
break;
default:
syslog(LOG_ERR, "Invalid primary user GID %s\n", pt);
}
}
break;
case 'p': // primary group name, illegal chars converted by nds_group_name
case 'P':
if (ui->gid == (gid_t) -1) // do not overwrite a DS 8 answer
rqgroup(ui, ctx, cur_pt, 1);
break;
case 'h': // home Unix all spaces significant (must have none ?)
case 'H':
if (!ui->dir) { // do not overwrite a DS 8 answer
v = strdup(cur_pt);
if (!v) {
syslog(LOG_WARNING, "Not enough memory for strdup()\n");
return ENOMEM;
}
ui->dir = v;
}
break;
case 's': //shell Unix all spaces significant (must have none ?)
case 'S':
if (!ui->shell) { // do not overwrite a DS 8 answer
v = strdup(cur_pt);
if (!v) {
syslog(LOG_WARNING, "Not enough memory for strdup()\n");
return ENOMEM;
}
ui->shell = v;
}
break;
case 'c': // comment all spaces significant. Will be appended to the gecos naming
case 'C': // attribute with a comma and set by calling chfn -f xxxx -o xxxx
// if comma are present in the string chfn will fails
if (ui->qflag & QF_DEBUG) {
syslog(LOG_NOTICE, "before comment gecos is %s\n ", ui->gecos);
}
err = nds_update_gecos(ui, cur_pt);
if (err) {
return err;
}
if (ui->qflag & QF_DEBUG) {
syslog(LOG_NOTICE, "gecos %s\n ", ui->gecos);
}
break;
case 'o': // other group names
case 'O': // wze can have several entries o:group (but not o:group1,group2...)
rqgroup(ui, ctx, cur_pt, 0);
break;
case 'z': // ZenFlag per user ABCDEFGHIJKLMNOPQRSTUVWXYZ012345 (32 max)
case 'Z':
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "before decode ZF is %s\n ", cur_pt);
ui->zenFlag |= decodeZenFlag(&cur_pt);
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "after decode ZF is %lx\n ", ui->zenFlag);
break;
}
}
return 0;
}
#endif
static NWDSCCODE nds_pp_home_directory(NWDSContextHandle ctx,
const void* val,
void* arg) {
struct nw_user_info *ui = (struct nw_user_info *) arg;
const Path_T *pa = (const Path_T *) val;
NWDSCCODE dserr;
struct nw_home_info hi = { NULL, NULL };
char *v;
char * p;
if (ui->qflag & QF_DEBUG) syslog(LOG_NOTICE, "start of NW home dir got %s %s \n",pa->volumeName,pa->path);
dserr = nds_home_info(ctx, (char *) pa->volumeName, &hi);
if (dserr)
return dserr;
if (ui->qflag & QF_DEBUG) syslog(LOG_NOTICE, "got %s %s ",hi.hs,hi.hrn);
ui->nwhomeServer = hi.hs;
ui->nwhomeVolume = hi.hrn;
// revision 1.27. Do something smart ;-) if %LOGIN_ or %CN in the path to home.
if ((p = strstr(pa->path,"%CN")) != NULL) {
*p=0; // chop off %CN and replace by user's name
v = concat2m(pa->path,ui->name);
} else if ((p = strstr(pa->path, "%LOG")) != NULL) { // chop off %LOGIN_NAME or and replace by user's name up to 8 chars
char aux[9];
*p=0;
strncpy(aux, ui->name, 8);
aux[8] = 0;
v = concat2m(pa->path, aux);
} else {
v = strdup(pa->path);
}
if (!v) {
syslog(LOG_WARNING, "Not enough memory for strdup()\n");
return ENOMEM;
}
unixifyPathUC(v);
ui->nwhomePath = v;
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "end of NW home dir: final path UNIX %s \n",ui->nwhomePath);
return 0;
}
static NWDSCCODE nds_pp_smtp_email_address(UNUSED(NWDSContextHandle ctx),
const void* val,
void* arg) {
struct nw_user_info *ui = (struct nw_user_info *) arg;
const EMail_Address_T *em = (const EMail_Address_T *) val;
char *v;
if (ui->qflag & QF_DEBUG) syslog(LOG_NOTICE, "start of NW smtp email got %u %s\n",em->type,em->address);
// pick up only the first with type=0 (SMTP) !
if ((!ui->emailSMTP && em->type == 0) && (!strncmp(em->address, "SMTP:", 5))) {
// skip header SMTP: must be if type is 0 ?
v = strdup(em->address + 5);
if (!v) {
syslog(LOG_WARNING, "Not enough memory for strdup()\n");
return ENOMEM;
}
ui->emailSMTP = v;
}
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "end of NW smtp email GOT %s\n", ui->emailSMTP);
return 0;
}
static NWDSCCODE
nds_pp_ldap_email_address(UNUSED(NWDSContextHandle ctx), const void *val, void *arg)
{
struct nw_user_info *ui = (struct nw_user_info *) arg;
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "start of NW ldap email got %s\n", (const char *) val);
// pick up only the first of a multi-valued attribute !
if (!ui->emailLDAP) {
char *v = strdup((const char *) val);
if (!v) {
syslog(LOG_WARNING, "Not enough memory for strdup()\n");
return ENOMEM;
}
trim(v); // remove the leading space added by other PP utilities
ui->emailLDAP = v;
}
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "end of NW ldap email GOT [%s]\n", ui->emailLDAP);
return 0;
}
static NWDSCCODE
nds_pp_message_server(UNUSED(NWDSContextHandle ctx), const void *val, void *arg)
{
struct nw_user_info *ui = (struct nw_user_info *) arg;
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "start of NW message server got %s\n", (const char *) val);
if (!ui->messageServer) {
char *v;
const char *dot;
const char *str = val;
size_t ln;
dot = strchr(str, '.');
if (dot) {
ln = dot - str;
} else {
ln = strlen(str);
}
v = malloc(ln + 1);
if (!v) {
syslog(LOG_WARNING, "Not enough memory for strdup()\n");
return ENOMEM;
}
memcpy(v, str, ln);
v[ln] = 0;
ui->messageServer = v;
}
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "end of NW message server GOT [%s]\n", ui->messageServer);
return 0;
}
/*****************************************************GET ALL USER INFO FROM NDS *************/
static int
nds_user_info(NWDSContextHandle ctx, const NWDSChar * objname, struct nw_user_info *ui)
{
static const struct attrop atlist[] = {
{ATTR_UID, nds_ga_unixuid, SYN_INTEGER},
{ATTR_PGNAME, nds_ga_unixpgname, SYN_DIST_NAME},
{ATTR_PGID, nds_ga_unixpgid, SYN_INTEGER},
{ATTR_GID, nds_ga_unixgid, SYN_INTEGER},
{ATTR_HOME, nds_ga_unixhome, SYN_CE_STRING},
{ATTR_SHELL, nds_ga_unixshell, SYN_CE_STRING},
{ATTR_COM, nds_ga_unixcomment, SYN_CI_STRING},
{ATTR_GECOS, nds_ga_gecos, SYN_CI_STRING},
{ATTR_GRP_MBS, nds_ga_group_membership, SYN_DIST_NAME},
/*PP*/ {ATTR_HOME_NW, nds_pp_home_directory, SYN_PATH},
/*PP*/ {ATTR_SMTP_EMAIL, nds_pp_smtp_email_address, SYN_EMAIL_ADDRESS},
/*PP*/ {ATTR_LDAP_EMAIL, nds_pp_ldap_email_address, SYN_CI_STRING},
/*PP*/ {ATTR_MESSAGE_SERVER, nds_pp_message_server, SYN_DIST_NAME},
{NULL, NULL, SYN_UNKNOWN}
};
static const struct attrop atlist2[] = {
#ifdef USE_POSTAL_ADDRESS
/*PP*/ {ATTR_POSTAL_ADDRESS, nds_pp_postal_address, SYN_PO_ADDRESS},
#else
/*PP*/ {ATTR_LOCATION, nds_pp_location, SYN_CI_STRING},
#endif
{NULL, NULL, SYN_UNKNOWN}
};
int err;
// we must do TWO NDS queries since NDS does not return attributes in this order
// studies of /var/log/secure showed that L attribute usually come out before the NDS8 ones !
err = nds_read_attrs(ctx, objname, ui, atlist);
if (err)
return err;
if (ui->qflag & QF_DEBUG) {
#ifdef USE_POSTAL_ADDRESS
syslog(LOG_NOTICE, "using postal address attribute\n");
#else
syslog(LOG_NOTICE, "using location attribute\n");
#endif
}
return nds_read_attrs(ctx, objname, ui, atlist2);
}
/****************** main NDS reading function ********************************************/
static int
nw_retrieve_nds_user_info(struct nw_user_info *ui, NWCONN_HANDLE conn, NWObjectID oid)
{
NWDSContextHandle ctx;
nuint32 c;
NWDSChar username[MAX_DN_BYTES];
int err;
err = NWDSCreateContextHandle(&ctx);
if (err) {
syslog(LOG_WARNING, "NWDSCreateContextHandle() failed with %s\n", strnwerror(err));
err = PAM_SYSTEM_ERR;
goto bailout;
}
c = DCV_XLATE_STRINGS | DCV_TYPELESS_NAMES | DCV_DEREF_ALIASES;
err = NWDSSetContext(ctx, DCK_FLAGS, &c);
if (err) {
syslog(LOG_WARNING, "NWDSSetContext() failed with %s\n", strnwerror(err));
err = PAM_SYSTEM_ERR;
goto bailoutctx;
}
NWDSAddConnection(ctx, conn);
err = NWDSMapIDToName(ctx, conn, oid, username);
if (err) {
syslog(LOG_WARNING, "NWDSMapIDToName() failed with %s\n", strnwerror(err));
err = PAM_USER_UNKNOWN;
goto bailoutctx;
}
if (ui->qflag & QF_DEBUG)
syslog(LOG_DEBUG, "User has DN %s\n", username);
err = nds_user_info(ctx, username, ui);
if (err) {
syslog(LOG_NOTICE, "Could not retrieve nds user info: %s\n", strnwerror(err));
err = PAM_AUTHTOK_ERR;
goto bailoutctx;
}
{
ui->fqdn = strdup(username);
if (!ui->fqdn) {
syslog(LOG_WARNING, "Not enough memory for strdup()\n");
err = PAM_SYSTEM_ERR;
goto bailoutctx;
}
}
//PP a good spot to retrieve user's defaultNameCtx and defaultTree
{
char *p = username;
char tn[MAX_TREE_NAME_CHARS + 1];
while (*p && (*p != '.'))
p++; //luckily we are in XLATE_STRINGS[TYPELESS_NAMES !!!
if (*p && *(p + 1)) { // skip also the first dot
ui->defaultNameCtx = strdup(p + 1);
if (!ui->defaultNameCtx) {
syslog(LOG_WARNING, "Not enough memory for strdup()\n");
err = ENOMEM;
}
}
if (!(err = NWCCGetConnInfo(conn, NWCC_INFO_TREE_NAME, sizeof (tn), tn))) {
ui->defaultTree = strdup(tn);
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "NWCCGetConnInfo(NWCC_INFO_TREE_NAME) returned %s\n", tn);
if (!ui->defaultTree) {
syslog(LOG_WARNING, "Not enough memory for strdup()\n");
err = ENOMEM;
}
} else {
if (ui->qflag & QF_DEBUG)
syslog(LOG_WARNING, "NWCCGetConnInfo(NWCC_INFO_TREE_NAME) returned %x\n", err);
err = 0; // not lethal !
}
}
//end PP
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "end of retrieve nds user info code: %s\n", strnwerror(err));
bailoutctx:;
NWDSFreeContext(ctx);
bailout:;
if (!err & (ui->qflag & QF_DEBUG))
syslog(LOG_NOTICE, "%u %u %s %s %s\n", ui->uid, ui->gid, ui->dir, ui->gecos, ui->shell);
return err;
}
/******************************************************************************************/
static int
nw_retrieve_user_info(struct nw_user_info *ui)
{
struct ncp_conn *conn;
long err;
NWObjectID oid;
err = my_pam_get_data(ui->pamh, "pam.ncpfs.passwd.conn", &conn);
if (err)
return err;
err = nw_get_nwid(conn, &oid, ui->qflag);
if (err)
return err;
if (ui->qflag & QF_BINDERY) {
return nw_retrieve_bindery_user_info(ui, conn, oid);
} else {
return nw_retrieve_nds_user_info(ui, conn, oid);
}
}
static int
exechelper(const char *program, const char *argv[], const char *username)
{
int i;
int err;
i = fork();
if (i < 0) {
err = errno;
syslog(LOG_ERR, "Cannot fork: %s\n", strerror(err));
return err;
}
if (i) {
int status;
switch (waitpid(i, &status, 0)) {
case (pid_t)-1:
syslog(LOG_ERR, "waitpid unexpectedly terminated: %s\n", strerror(errno));
return -1;
case 0:
syslog(LOG_ERR, "waitpid: Fatal: No child processes\n");
return -1;
default:
if (!WIFEXITED(status)) {
syslog(LOG_ERR, "%s killed by signal\n", program);
return -1;
}
if (WEXITSTATUS(status)) {
syslog(LOG_ERR, "%s finished with error %d\n", program, WEXITSTATUS(status));
return -1;
}
}
return 0;
} else {
int fd;
fd = open("/dev/null", O_RDWR);
if (fd == -1) {
err = errno;
syslog(LOG_ERR, "Cannot open /dev/null: %s\n", strerror(err));
exit(126);
}
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
argv[0] = program;
if (username) { // run child process as username
struct passwd *pwd = getpwnam(username);
//syslog(LOG_ERR, " as user %s ..",username);
if (!pwd) {
syslog(LOG_ERR, "Oops, something wicked happened, user %s does not exist...", username);
exit(111);
}
if (initgroups(username, pwd->pw_gid)) {
/* initgroups() fails on shutdown as we
do not run as 'root' at shutdown time...
Just ignore error from initgroups,
but treat setgid/setuid problems still
as fatal. */
if (errno != EPERM) {
syslog(LOG_ERR, "Oops, initgroups failed for user %s: %s\n", username, strerror(errno));
exit(112);
}
}
if (setgid(pwd->pw_gid)) {
syslog(LOG_ERR, "Oops, setgid failed for user %s: %s\n", username, strerror(errno));
exit(113);
}
if (setuid(pwd->pw_uid)) {
syslog(LOG_ERR, "Oops, setuid failed for user %s: %s\n", username, strerror(errno));
exit(114);
}
}
execv(program, (char *const *) (unsigned long) argv);
err = errno;
syslog(LOG_ERR, "Cannot execute %s: %s\n", program, strerror(err));
exit(127);
}
}
static int
groupadd(const char *name, gid_t gid, int verbose)
{
char gidstr[30];
const char *argv[10];
int err;
sprintf(gidstr, "%u", gid);
argv[1] = "-g";
argv[2] = gidstr;
argv[3] = name;
argv[4] = NULL;
//TODO: it would be nice to have the name of the adding group utility
// in some config file (/etc/pam_ncp.conf)
// so we could give more flexibility to the group creation ( a good shell script)
err = exechelper("/usr/sbin/groupadd", argv, NULL);
if (err)
return err;
if (verbose)
syslog(LOG_NOTICE, "Group %s(%u) created\n", name, gid);
return 0;
}
static int
do_chfn(const char *uname, const char *gecos, int verbose)
{
char *gecosbuf;
char *p;
const char *argv[10];
int err;
int i = 1;
gecosbuf = strdup(gecos);
if (!gecosbuf) {
syslog(LOG_WARNING, "Not enough memory for gecos buffer\n");
return ENOMEM;
}
p = strchr(gecosbuf, ',');
if (p) {
*p++ = '\0';
} else {
p = (char*)"";
}
argv[i++] = "-f";
argv[i++] = gecosbuf;
argv[i++] = "-o";
argv[i++] = p;
argv[i++] = uname;
argv[i++] = NULL;
/******/
if (verbose) {
int j;
char s[8192];
char *pos = s;
size_t space = sizeof (s) - 1;
for (j = 1; j < i; j++) {
size_t ln = strlen(argv[j]);
if (ln > space) {
ln = space;
}
memcpy(pos, argv[j], ln);
pos += ln;
space -= ln;
if (space) {
*pos++ = ' ';
space--;
}
}
*pos = 0;
syslog(LOG_WARNING, "%s", s);
}
//TODO: it would be nice to have the name of the chfn utility
// in some config file (/etc/pam_ncp.conf)
err = exechelper("/usr/bin/chfn", argv, NULL);
if (verbose)
syslog(LOG_NOTICE, "chfn (%s) for user %s ended with error code %d\n", gecos, uname, err);
return err;
}
static int
usermod(const char *uname, gid_t gid, const char *gecos, const char *dir, const char *shell,
/*PP added */ const char *grplist, int verbose)
{
char gidstr[30];
const char *argv[30];
int err;
int i, j;
int usechfn = 0;
i = 1;
if (gid != (gid_t) -1) {
sprintf(gidstr, "%u", gid);
argv[i++] = "-g";
argv[i++] = gidstr;
}
if (gecos) { // donne un erreur 3 !!!
if (!strchr(gecos, ',')) {
argv[i++] = "-c";
argv[i++] = gecos;
} else {
usechfn = 1;
}
}
if (dir) {
argv[i++] = "-d";
argv[i++] = dir;
}
if (shell) {
argv[i++] = "-s";
argv[i++] = shell;
}
/******************** OK with COL 24 usermod !!! ****/
if (grplist) {
argv[i++] = "-G";
argv[i++] = grplist;
}
argv[i++] = uname;
argv[i] = NULL;
// temp debug
if (verbose) {
for (j = 1; j < i; j++)
syslog(LOG_NOTICE, "usermod %u %s", j, argv[j]);
}
//TODO: it would be nice to have the name of the modif user utility
// in some config file (/etc/pam_ncp.conf)
// so we could give more flexibility to the user modification ( a good shell script)
err = exechelper("/usr/sbin/usermod", argv, NULL);
if (err)
return err;
if (usechfn) {
if (verbose)
syslog(LOG_NOTICE, "User %s has a comma in his gecos %s\n", uname, gecos);
/*err= */ do_chfn(uname, gecos, verbose);
//don't return err, it is not lethal
}
if (verbose)
syslog(LOG_NOTICE, "User %s modified\n", uname);
return 0;
}
#define concat2a(one,two) ({ \
size_t lone = strlen(one); \
size_t ltwo = strlen(two); \
char* buf = alloca(lone + ltwo + 1); \
memcpy(buf, one, lone); \
memcpy(buf + lone, two, ltwo + 1); \
buf; })
#define concat3a(one,med,two) ({ \
size_t lone = strlen(one); \
size_t ltwo = strlen(two); \
char* buf = alloca(lone + 1+ ltwo + 1); \
memcpy(buf, one, lone); \
buf[lone] = med; \
memcpy(buf + lone + 1, two, ltwo + 1); \
buf; })
static int
useradd(const char *uname, uid_t uid, gid_t gid, const char *gecos, const char *dir, const char *shell, const char *grplist, int process_groups_later, int create_local_home, int verbose)
{
/* rev PP
-Caldera Open Linux does not like the -G parameter in useradd, but it is OK in usermod ?
- so we call usermod at the end if needed...
-add some defaut if nothing has been retrieved from NDS and we are in SERVER_REQUIRED MODE
Il dir is empty change it to /home/username
if shell is empty change it to /bin/bash
if guid is empty change it to 100
so only UNIX_UID is REALLY SERVER_REQUIRED ( others get default values)
*/
char gidstr[30];
char uidstr[30];
const char *argv[30];
int err;
int i;
int usechfn = 0;
i = 1;
if (gid == (gid_t) -1) {
gid = 100;
}
sprintf(gidstr, "%u", gid);
argv[i++] = "-g";
argv[i++] = gidstr;
//PP
/******************** Not with COL 24 useradd****/
if (!process_groups_later && grplist) {
argv[i++] = "-G";
argv[i++] = grplist;
}
//PP
if (!gecos) {
gecos = uname;
}
// may have a comma in it ( full name,comment...)
if (!strchr(gecos, ',')) {
argv[i++] = "-c";
argv[i++] = gecos;
} else {
usechfn = 1;
}
// PP modified for a default value and still testing for no existence
if (!dir) {
dir = concat2a("/home/", uname);
}
{
struct stat statbuf;
argv[i++] = "-d";
argv[i++] = dir;
if (create_local_home && !lstat(dir, &statbuf)) { // rev 1.24
syslog(LOG_ERR, "Will not create %s because of home directory %s already exist\n", uname, dir);
return -1;
}
}
/*
Currently if the home is remote and the NFS server is down, the home WILL not be created locally
So console login will not find the home and fall to / directory, and graphical login will fails
*/
if (!shell) {
shell = "/bin/bash";
}
argv[i++] = "-s";
argv[i++] = shell;
// end PP
sprintf(uidstr, "%u", uid);
argv[i++] = "-u";
argv[i++] = uidstr;
argv[i++] = create_local_home ? "-m" : "-M"; // rev 1.24
argv[i++] = uname;
argv[i] = NULL;
if (verbose) {
syslog(LOG_NOTICE, "useradd %s %s %s %s %s %s", argv[1], argv[2], argv[3], argv[4], argv[5], argv[6]);
syslog(LOG_NOTICE, "useradd %s %s %s %s %s %s", argv[7], argv[8], argv[9], argv[10], argv[11], argv[12]);
}
//TODO: it would be nice to have the name of the adding user utility
// in some config file (/etc/pam_ncp.conf)
// so we could give more flexibility to the user autocreation ( a good shell script)
err = exechelper("/usr/sbin/useradd", argv, NULL);
if (err)
return err;
if (verbose)
syslog(LOG_NOTICE, "User %s(%u) added\n", uname, uid);
if (usechfn) {
if (verbose)
syslog(LOG_NOTICE, "User %s has a comma in his gecos %s\n", uname, gecos);
/*err= */ do_chfn(uname, gecos, verbose);
// not lethal if it fails
}
// PP: use a call to usermod to process the other groups lists. Needed at least with Caldera OpenLinux
if (process_groups_later && grplist) {
return usermod(uname, (gid_t) -1, NULL, NULL, NULL, grplist, verbose);
}
return 0;
}
static int
uidcmp(const void *p1, const void *p2)
{
if (*(const uid_t *) p1 < *(const uid_t *) p2)
return -1;
if (*(const uid_t *) p1 > *(const uid_t *) p2)
return 1;
return 0;
}
static int
gidcmp(const void *p1, const void *p2)
{
if (*(const gid_t *) p1 < *(const gid_t *) p2)
return -1;
if (*(const gid_t *) p1 > *(const gid_t *) p2)
return 1;
return 0;
}
static int
allocategid(const struct pam_ncp_state *state, gid_t *gid)
{
struct group *grp;
gid_t now;
now = state->gid.min;
setgrent();
if (state->gid.flags & QFC_NEXT_UNUSED) {
while ((grp = getgrent()) != NULL) {
if (grp->gr_gid < state->gid.min)
continue;
if (grp->gr_gid >= state->gid.max)
continue;
if (now <= grp->gr_gid)
now = grp->gr_gid + 1;
}
} else {
struct {
gid_t *array;
size_t used;
size_t alloc;
} gids;
gid_t *p;
gids.array = NULL;
gids.alloc = 0;
gids.used = 0;
while ((grp = getgrent()) != NULL) {
if (grp->gr_gid < state->gid.min)
continue;
if (grp->gr_gid >= state->gid.max)
continue;
if (gids.used >= gids.alloc) {
gid_t *np;
size_t ns;
if (gids.array) {
ns = gids.alloc * 2;
np = (gid_t *) realloc(gids.array, ns * sizeof (gid_t));
} else {
ns = 16;
np = (gid_t *) malloc(ns * sizeof (gid_t));
}
if (!np) {
syslog(LOG_WARNING, "Not enough memory\n");
if (gids.array)
free(gids.array);
return -1;
}
gids.array = np;
gids.alloc = ns;
}
gids.array[gids.used++] = grp->gr_gid;
}
qsort(gids.array, gids.used, sizeof (*gids.array), gidcmp);
p = gids.array;
while (gids.used-- && (now == *p)) {
now++;
p++;
}
free(gids.array);
}
if (now >= state->gid.max) {
return -1;
}
endgrent();
*gid = now;
return 0;
}
static int
allocateuid(const struct pam_ncp_state *state, uid_t *uid)
{
struct passwd *pwd;
uid_t now;
now = state->uid.min;
setpwent();
if (state->uid.flags & QFC_NEXT_UNUSED) {
while ((pwd = getpwent()) != NULL) {
if (pwd->pw_uid < state->uid.min)
continue;
if (pwd->pw_uid >= state->uid.max)
continue;
if (now <= pwd->pw_uid)
now = pwd->pw_uid + 1;
}
} else {
struct {
uid_t *array;
size_t used;
size_t alloc;
} uids;
uid_t *p;
uids.array = NULL;
uids.alloc = 0;
uids.used = 0;
while ((pwd = getpwent()) != NULL) {
if (pwd->pw_uid < state->uid.min)
continue;
if (pwd->pw_uid >= state->uid.max)
continue;
if (uids.used >= uids.alloc) {
uid_t *np;
size_t ns;
if (uids.array) {
ns = uids.alloc * 2;
np = (uid_t *) realloc(uids.array, ns * sizeof (uid_t));
} else {
ns = 16;
np = (uid_t *) malloc(ns * sizeof (uid_t));
}
if (!np) {
syslog(LOG_WARNING, "Not enough memory\n");
if (uids.array)
free(uids.array);
return -1;
}
uids.array = np;
uids.alloc = ns;
}
uids.array[uids.used++] = pwd->pw_uid;
}
qsort(uids.array, uids.used, sizeof (*uids.array), uidcmp);
p = uids.array;
while (uids.used-- && (now == *p)) {
now++;
p++;
}
free(uids.array);
}
if (now >= state->uid.max) {
return -1;
}
endpwent();
*uid = now;
return 0;
}
/*
-10 : 100% match with server required, but no UNIX:GID or
gid or name already in use
-11 : no more free gids
-12 : group creation failed
*/
static int
nw_update_group_info(struct nw_user_info *ui, struct nw_group_info *gi)
{
struct group *grp;
// PP: added endgrent()
// no more return but goto bailout.
int err = PAM_SUCCESS;
setgrent();
// check again for grp name, so if an alias has been sent by NDS,
// Location string of a NDS group with N:UnixAlias, we will find it here.
// i.e. everyone -> users or staff -> root
grp = getgrnam(gi->name);
if (!grp) {
while (ui->state.gid.flags & (QFC_PREFFER_SERVER | QFC_REQUIRE_SERVER)) {
if (gi->gid == (gid_t) -1) {
if ((ui->qflag & QF_DEBUG) && (ui->state.gid.flags & QFC_REQUIRE_SERVER))
syslog(LOG_DEBUG, "Will not create group %s: no UNIX:GID present\n", gi->name);
break;
}
grp = getgrgid(gi->gid);
if (grp) {
if ((ui->qflag & QF_DEBUG) && (ui->state.gid.flags & QFC_REQUIRE_SERVER))
syslog(LOG_DEBUG, "Will not create group %s: gid %u is already used by %s\n", gi->name, gi->gid, grp->gr_name);
break;
}
if (groupadd(gi->name, gi->gid, ui->qflag & QF_DEBUG))
break; // trouble
goto bailout; // OK
}
if (ui->state.gid.flags & QFC_REQUIRE_SERVER) {
err = -10;
goto bailout;
//return -10;
}
if (allocategid(&ui->state, &gi->gid)) {
syslog(LOG_WARNING, "Cannot allocate gid for group %s\n", gi->name);
err = -11;
goto bailout;
//return -11;
}
if (groupadd(gi->name, gi->gid, ui->qflag & QF_DEBUG)) {
syslog(LOG_WARNING, "Could not create group %s\n", gi->name);
err = -12;
goto bailout;
//return -12;
}
} else {
gi->gid = grp->gr_gid;
}
bailout:;
endgrent(); //PP
return err;
}
static int
nw_update_user_info(struct nw_user_info *ui)
{
struct passwd *pwd;
char *glist;
const char *gecos;
// PP: added endpwent() and freeing of malloced glist
// no more return but goto bailout.
int err = PAM_SUCCESS;
if (!ui->name) {
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "NW_UPDATE_USER:failed user has no name %u\n", ui->uid);
return -1;
}
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "NW_UPDATE_USER: %u %u %s %s %s\n", ui->uid, ui->gid, ui->dir, ui->gecos, ui->shell);
if (build_groups_list(ui, &glist)) {
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "NW_UPDATE_USER:failed to build group list for %s\n", ui->name);
return -1;
}
setpwent();
pwd = getpwnam(ui->name);
//PP
gecos = ui->gecos ? : "";
// end PP
if (!pwd) {
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "NW_UPDATE_USER:creating %u %u %s %s %s\n", ui->uid, ui->gid, ui->dir, ui->gecos, ui->shell);
while (ui->state.uid.flags & (QFC_PREFFER_SERVER | QFC_REQUIRE_SERVER)) {
if (ui->uid == (uid_t) -1) {
if ((ui->qflag & QF_DEBUG) && (ui->state.uid.flags & QFC_REQUIRE_SERVER))
syslog(LOG_DEBUG, "Will not create user %s: no UNIX:UID present\n", ui->name);
break;
}
pwd = getpwuid(ui->uid);
if (pwd) {
if ((ui->qflag & QF_DEBUG) && (ui->state.uid.flags & QFC_REQUIRE_SERVER))
syslog(LOG_DEBUG, "Will not create user %s: uid %u is already used by %s\n", ui->name, ui->uid, pwd->pw_name);
break;
}
err = useradd(ui->name, ui->uid, ui->gid, gecos, ui->dir, ui->shell, glist, NO_PROCESS_GROUPS, ui->qflag & QF_CREATEHOME, ui->qflag & QF_DEBUG);
if (err)
break;
ui->isNewUser = 1; // rev 1.19
err = PAM_SUCCESS;
goto bailout;
}
if (ui->state.uid.flags & QFC_REQUIRE_SERVER) {
syslog(LOG_WARNING, "Cannot create uid %u required by server for user %s\n", ui->uid, ui->name);
err = -10;
goto bailout;
//return -10;
}
if (allocateuid(&ui->state, &ui->uid)) {
syslog(LOG_WARNING, "Cannot allocate uid for user %s\n", ui->name);
err = -11;
goto bailout;
//return -11;
}
if (useradd(ui->name, ui->uid, ui->gid, gecos, ui->dir, ui->shell, glist, NO_PROCESS_GROUPS, ui->qflag & QF_CREATEHOME, ui->qflag & QF_DEBUG)) {
syslog(LOG_WARNING, "Cannot create user %s\n", ui->name);
err = -12;
goto bailout;
//return -12;
}
} else {
int diff;
ui->uid = pwd->pw_uid;
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "NW_UPDATE_USER:updating %u %u %s %s %s\n", ui->uid, ui->gid, ui->dir, ui->gecos, ui->shell);
diff = 0;
if ((ui->gid != (gid_t) -1) && ui->gid != pwd->pw_gid)
diff |= QFMU_GID;
if (*gecos && (!pwd->pw_gecos || strcmp(gecos, pwd->pw_gecos)))
diff |= QFMU_GECOS;
if (ui->dir && (!pwd->pw_dir || strcmp(ui->dir, pwd->pw_dir)))
diff |= QFMU_DIR;
if (ui->shell && (!pwd->pw_shell || strcmp(ui->shell, pwd->pw_shell)))
diff |= QFMU_SHELL;
diff &= ui->state.uid.modflags;
// FIXME: glist is not processed if it is the only change ! 26/01/2001
if (diff) {
err = usermod(ui->name, (diff & QFMU_GID) ? ui->gid : (gid_t) -1, (diff & QFMU_GECOS) ? gecos : NULL, //PP
(diff & QFMU_DIR) ? ui->dir : NULL, (diff & QFMU_SHELL) ? ui->shell : NULL, glist, // process groups //PP
ui->qflag & QF_DEBUG);
}
}
bailout:;
endpwent();
if (glist)
free(glist);
if (ui->qflag & QF_DEBUG)
syslog(LOG_DEBUG, "return value of update_user_info %d for %s\n", err, ui->name);
return err;
}
static int
getcflag(int *val, const char **str)
{
const char *p = *str;
int valm;
int c;
*val = 0;
valm = 1;
if (!*p)
return 1;
while ((c = *p++) != 0) {
if (c == ',')
break;
switch (c) {
case 'f':
case 'N':
*val &= ~QFC_NEXT_UNUSED;
break;
case 'n':
case 'F':
*val |= QFC_NEXT_UNUSED;
break;
case 'P':
*val &= ~QFC_PREFFER_SERVER;
break;
case 'p':
*val |= QFC_PREFFER_SERVER;
break;
case 'R':
*val &= ~QFC_REQUIRE_SERVER;
break;
case 'r':
*val |= QFC_REQUIRE_SERVER;
break;
default:
return -1;
}
valm = 0;
}
if (!c)
p--;
*str = p;
return valm;
}
static int
getmodflag(int *val, const char **str)
{
const char *p = *str;
int valm;
int c;
*val = 0;
valm = 1;
if (!*p)
return 1;
while ((c = *p++) != 0) {
if (c == ',')
break;
switch (c) {
case 'G':
*val &= ~QFMU_GID;
break;
case 'g':
*val |= QFMU_GID;
break;
case 'C':
*val &= ~QFMU_GECOS;
break;
case 'c':
*val |= QFMU_GECOS;
break;
case 'D':
*val &= ~QFMU_DIR;
break;
case 'd':
*val |= QFMU_DIR;
break;
case 'S':
*val &= ~QFMU_SHELL;
break;
case 's':
*val |= QFMU_SHELL;
break;
default:
return -1;
}
valm = 0;
}
if (!c)
p--;
*str = p;
return valm;
}
static int
parseuid(struct nw_user_info *ui, const char **str)
{
int val;
switch (getnumber(&val, str)) {
case 0:
ui->state.uid.min = val;
break;
case 1:
break;
default:
syslog(LOG_ERR, "Unknown min uid value %s\n", *str);
return -1;
}
switch (getnumber(&val, str)) {
case 0:
ui->state.uid.max = val;
break;
case 1:
break;
default:
syslog(LOG_ERR, "Unknown max uid value %s\n", *str);
return -1;
}
switch (getcflag(&val, str)) {
case 0:
ui->state.uid.flags = val;
ui->qflag |= QF_AUTOCREATE;
break;
case 1:
break;
default:
syslog(LOG_ERR, "Unknown uid flags value %s\n", *str);
return -1;
}
switch (getmodflag(&val, str)) {
case 0:
ui->state.uid.modflags = val;
ui->qflag |= QF_AUTOMODIFY;
break;
case 1:
break;
default:
syslog(LOG_ERR, "Unknown uid modflags value %s\n", *str);
return -1;
}
return 0;
}
static int
parsegid(struct nw_user_info *ui, const char **str)
{
int val;
switch (getnumber(&val, str)) {
case 0:
ui->state.gid.min = val;
break;
case 1:
break;
default:
syslog(LOG_ERR, "Unknown min gid value %s\n", *str);
return -1;
}
switch (getnumber(&val, str)) {
case 0:
ui->state.gid.max = val;
break;
case 1:
break;
default:
syslog(LOG_ERR, "Unknown max gid value %s\n", *str);
return -1;
}
switch (getcflag(&val, str)) {
case 0:
ui->state.gid.flags = val;
break;
case 1:
break;
default:
syslog(LOG_ERR, "Unknown gid flags value %s\n", *str);
return -1;
}
return 0;
}
// ajout PP 2000/12/19 session_on et off
// will mount Netware home directory in ~/nwhome
static int
parsemntpnt(struct nw_user_info *ui, const char **str)
{
// found another value that default nwhome in the command line (-mPATH (no space!)
// and cannot have space in it ;-)
// v 1.14: if the argument of -m is empty user's Netware home will be mounted as Unix home
ui->nwhomeMntPnt = strdup(*str);
if (ui->qflag & QF_DEBUG)
syslog(LOG_DEBUG, "new default mounting point is \"%s\"", ui->nwhomeMntPnt);
return 0;
}
// found a default zenFlag value on this machine
// apply it to user instead of the ZF_DEFAULTS
static int
parsezenflag_on(struct nw_user_info *ui, const char **str)
{
ui->zenFlag = decodeZenFlag(str);
return 0;
}
// found a zenFlags not allowed on this machine
// apply it to user
static int
parsezenflag_off(struct nw_user_info *ui, const char **str)
{
if (**str)
ui->zenFlagOFF = decodeZenFlag(str);
else
ui->zenFlagOFF = ~0UL; // all are off
return 0;
}
static void
mergeZenFlags(struct nw_user_info *ui)
{
ui->zenFlag &= ~ui->zenFlagOFF;
// make sure .nwinfos file is created if any zenscript is required
if (ui->zenFlag & (ZF_OPENING_SCRIPTS | ZF_CLOSING_SCRIPTS))
ui->zenFlag |= ZF_CREATE_NWINFOS;
}
// rev 1.19 we now run the following command
// su -l USER -c "ncpmount ..... "
static int
mount_nwhome(const char *unixname, const char *uname, // nw FQDN name
const char *pwd, const char *server, const char *volume,
const char *path, const char *mntpoint, int uid, UNUSED(int zenFlag), int useIP, int verbose)
{
const char *argv[30];
int err;
int i = 1, j = 0;
char uidstr[30];
/* -B flag not yet supported by ncpmount */
#ifdef SET_BCAST
int bMode = 0;
char bModeStr[128];
switch (zenFlag & (ZF_BROADCAST_ALL | ZF_BROADCAST_CONSOLE)) {
case ZF_BROADCAST_ALL:
bMode = NWCC_BCAST_PERMIT_ALL;
break;
case ZF_BROADCAST_CONSOLE:
bMode = NWCC_BCAST_PERMIT_SYSTEM;
break;
default:
bMode = NWCC_BCAST_PERMIT_NONE;
}
sprintf(bModeStr, "%u", bMode);
argv[i++] = "-B";
argv[i++] = bModeStr;
#endif
if (uname) {
argv[i++] = "-U";
argv[i++] = uname;
}
if (pwd) {
argv[i++] = "-P";
argv[i++] = pwd;
}
if (server) {
argv[i++] = "-S";
argv[i++] = server;
// added for JFB
if (useIP) {
argv[i++] = "-A";
argv[i++] = server;
}
}
if (volume) {
#ifdef NCP_IOC_GETROOT
if (path) { // already in Unix & uppercase
char *fullPathToHome;
fullPathToHome = concat3a(volume, ':', path);
argv[i++] = "-V";
argv[i++] = fullPathToHome;
} else
#endif
{
argv[i++] = "-V";
argv[i++] = volume;
}
}
argv[i++] = "-o";
argv[i++] = "symlinks,exec";
sprintf(uidstr, "%u", uid); // current user is owner
argv[i++] = "-u";
argv[i++] = uidstr;
argv[i++] = "-c";
argv[i++] = uidstr;
argv[i++] = "-d";
argv[i++] = "0700"; // current user is owner and only him
if (mntpoint) // better be ;-)
argv[i++] = mntpoint;
argv[i] = NULL;
if (verbose) {
char s[4096]; /* buf... buf... buffer overflow... */
// do not log a clear password in /var/log/secure !
sprintf(s, "running as %s \"%s", unixname, NCPMOUNT_PATH);
for (j = 1; j < i; j++) {
if (strlen(s) + strlen(argv[j]) >= sizeof (s) - 2)
break; /* fix the possible buffer overflow */
if (strcmp(argv[j], "-P")) {
strcat(s, " ");
strcat(s, argv[j]);
} else {
j++;
}
}
strcat(s, "\"");
syslog(LOG_WARNING, "%s", s);
}
err = exechelper(NCPMOUNT_PATH, argv, unixname);
if (verbose) {
if (err)
syslog(LOG_DEBUG, "user %s had trouble mounting %s/%s on %s", uname, server, volume, mntpoint);
else
syslog(LOG_NOTICE, "User %s has mounted %s/%s as %s\n", uname, server, volume, mntpoint);
}
return err;
}
// called by pam_end_session
static int
umount_nwhome(const char *unixname, const char *mntpoint, int verbose)
{
const char *argv[5];
int err;
int i;
i = 1;
if (mntpoint) { // better be ;-)
argv[i++] = mntpoint;
}
argv[i] = NULL;
err = exechelper(NCPUMOUNT_PATH, argv, unixname); // umount as user
if (verbose) {
if (err)
syslog(LOG_DEBUG, "user %s had trouble unmounting %s", unixname, mntpoint);
else
syslog(LOG_NOTICE, "User %s has unmounted %s\n", unixname, mntpoint);
}
return err;
}
static int
nw_automount_home(const char *uname, struct nw_user_info *ui, const struct passwd *pwd, const char *pass)
{
long err;
struct stat stt;
char *mountpnt;
const char *fqdn;
int perm_err;
if (ui->qflag & QF_DEBUG)
syslog(LOG_DEBUG, "start of nw_auto_mount_home \n");
// few sanity checks !!!
if (!ui->nwhomeServer || !ui->nwhomeVolume)
return -1;
// no value red from the command line, use default ~/nwhome
if (!ui->nwhomeMntPnt)
ui->nwhomeMntPnt = strdup(DEF_MNT_PNT);
if (pwd == NULL) {
syslog(LOG_DEBUG, "/etc/passwd/%s not found !\n", uname);
return PAM_USER_UNKNOWN;
}
if (ui->qflag & QF_MOUNTLOCALLY) { // rev 1.24
# define TOP_OF_ALL_MOUNTS "/mnt/ncp"
if (stat(TOP_OF_ALL_MOUNTS, &stt))
if (mkdir(TOP_OF_ALL_MOUNTS, 0711)) {
syslog(LOG_DEBUG, "Unable to create common mounting point %s \n", TOP_OF_ALL_MOUNTS);
return PAM_USER_UNKNOWN;
}
mountpnt = concat3a(TOP_OF_ALL_MOUNTS, '/', pwd->pw_name);
if (stat(mountpnt, &stt)) {
perm_err = mkdir(mountpnt, 0700) || chown(mountpnt, pwd->pw_uid, pwd->pw_gid);
if (perm_err) {
syslog(LOG_DEBUG, "Unable to create local mounting point %s \n", mountpnt);
return PAM_USER_UNKNOWN;
}
}
mountpnt = concat3a(mountpnt, '/', ui->nwhomeMntPnt);
} else {
if (stat(pwd->pw_dir, &stt) != 0) {
syslog(LOG_DEBUG, "Unix home %s not found !\n", pwd->pw_dir);
return PAM_USER_UNKNOWN;
}
mountpnt = concat3a(pwd->pw_dir, '/', ui->nwhomeMntPnt);
}
if (stat(mountpnt, &stt) != 0) {
if (ui->qflag & QF_DEBUG)
syslog(LOG_DEBUG, "Netware home mounting point %s not found. Will create it \n", mountpnt);
perm_err = mkdir(mountpnt, 0700) || chown(mountpnt, pwd->pw_uid, pwd->pw_gid);
if (perm_err) {
syslog(LOG_DEBUG, "error creating Netware home mounting point %s :%d (%s)\n", mountpnt, perm_err, strerror(errno));
return PAM_USER_UNKNOWN;
}
}
//rev 1.24 :remember full path to Netware home instead of simply 'nwhome'
// will be written in $HOME/.nwinfos file and red again at PAM end session
free(ui->nwhomeMntPnt);
ui->nwhomeMntPnt = strdup(mountpnt);
/* v 1.13 if we allow contextless login, we must pass to ncpmount a FQDN and not a CN ! */
if (ui->fqdn) {
/* Add leading dot to denote absolute DN... */
fqdn = concat2a(".", ui->fqdn);
} else {
/* Bindery... */
fqdn = uname;
}
err = mount_nwhome(uname, // unix name for su
fqdn, // NW name for ncpmount
pass, ui->nwhomeServer, ui->nwhomeVolume, ui->nwhomePath, mountpnt, pwd->pw_uid, ui->zenFlag, ui->qflag & QF_USE_NETWARE_IP, ui->qflag & QF_DEBUG);
if (!err)
err = PAM_SUCCESS;
return err;
}
/* create the 600 .nwclient file in user's home
this file is used by others ncp utilities
it contains the line SERVER/USER password or SERVER/USER
depending of ZF_PASSWD_IN_NWCLIENT */
static int
nw_create_nwclient(const char *uname, const struct nw_user_info *ui, const struct passwd *pwd, const char *pass)
{
char *nwclient;
const char *server;
FILE *f;
#ifdef IMPERSONNATE
uid_t currentUID = getuid();
#endif
int perm_err;
if (pwd == NULL) {
syslog(LOG_DEBUG, "/etc/passwd/%s not found !\n", uname);
return PAM_USER_UNKNOWN;
}
// I have a problem here. They may be different . If the Netware home is automounted
// the connection to the homeServer (not to the messageServer) will be marked as permanent.
server = (ui->messageServer ? ui->messageServer : ui->nwhomeServer);
if (!server)
return PAM_SUCCESS;
nwclient = concat3a(pwd->pw_dir, '/', NWCLIENT_FILE);
#ifdef IMPERSONNATE
// v 1.24 NFS mounted homes are mounted with no_root_squash option , so we must change UID to current user
if (seteuid(pwd->pw_uid)) {
syslog(LOG_DEBUG, "Cannot impersonnate to %s: %s\n", pwd->pw_name, strerror(errno));
return -1;
}
#endif
if (ui->zenFlag & ZF_OVERWRITE_NWCLIENT)
f = fopen(nwclient, "w"); // erase it every time
else
f = fopen(nwclient, "a"); // I should append ?
if (f) {
// must have a CR/LF
if (ui->zenFlag & ZF_PASSWD_IN_NWCLIENT)
fprintf(f, "%s/%s %s\n", server, uname, pass);
else
fprintf(f, "%s/%s \n", server, uname);
/* rev 1.15 */
if (ui->defaultTree) {
fprintf(f, "\n[Requester]\n");
fprintf(f, "Default Tree Name=%s\n", ui->defaultTree);
if (ui->defaultNameCtx)
fprintf(f, "Default Name Context=%s\n", ui->defaultNameCtx);
}
fclose(f);
// must be 600 and owner by current user, else ncpfs apps disregard it
//perm_err=chmod(nwclient,0600) || chown(nwclient,pwd->pw_uid,pwd->pw_gid);
perm_err = chmod(nwclient, 0600);
#ifdef IMPERSONNATE
seteuid(currentUID);
#endif
if (perm_err)
syslog(LOG_DEBUG, "problem %d (%s)changing permissions of %s (%d %d)\n", perm_err, strerror(errno), nwclient, pwd->pw_uid, pwd->pw_gid);
else {
if (ui->qflag & QF_DEBUG)
syslog(LOG_DEBUG, "DONE writing to %s\n", nwclient);
return PAM_SUCCESS; // OK OK
}
} else {
#ifdef IMPERSONNATE
seteuid(currentUID);
#endif
syslog(LOG_DEBUG, "problem writing to %s\n", nwclient);
}
return -1; // trouble somewhere (should not be lethal)
}
static int
nw_process_forward_file(UNUSED(const char *uname), const struct nw_user_info *ui, const struct passwd *pwd)
{
// what we do here it to setup a forward to any email address found in NDS
// we do it during the authentication part so if the Netware home directory is mounted
// as /home/user ( -m with no argument) the .forward file is on the local machine if user is not currently logged in ...
//We do it again in the pam_session part ( if the argument of -m is empty) so if user IS logged in,
// the local /home/user is "masqued off" and system will still find this file in the mounted Netware home ...
FILE *f;
char *p;
char *forward;
#ifdef IMPERSONNATE
uid_t currentUID = getuid();
#endif
int perm_err;
if (ui->emailLDAP) {
p = ui->emailLDAP; // priority to the "new format"
} else if (ui->emailSMTP) {
p = ui->emailSMTP;
} else {
return 0; // too bad
}
forward = concat2a(pwd->pw_dir, "/.forward");
#ifdef IMPERSONNATE
// v 1.25 NFS mounted homes are mounted with no_root_squash option , so we must change UID to current user
if (seteuid(pwd->pw_uid)) {
syslog(LOG_DEBUG, "Cannot inpersonnate to %s: %s\n", pwd->pw_name, strerror(errno));
return -1;
}
#endif
f = fopen(forward, "w"); // erase it every time
if (f) {
// must have a CR/LF ??
fprintf(f, "%s\n", p);
fclose(f);
// makes no harm to restrict reading to user ?
//perm_err= chmod(forward,0600) || chown(forward,pwd->pw_uid,pwd->pw_gid);
perm_err = chmod(forward, 0600);
#ifdef IMPERSONNATE
seteuid(currentUID);
#endif
if (perm_err)
syslog(LOG_DEBUG, "problem %d (%s)changing permissions of %s\n", perm_err, strerror(errno), forward);
} else {
#ifdef IMPERSONNATE
seteuid(currentUID);
#endif
syslog(LOG_DEBUG, "Cannot open %s: %s\n", forward, strerror(errno));
}
return 0;
}
/******************************* check for allowed remote access by zen ***/
#if 0
static void
report(int err, const char *what, const char *info)
{
if (err != PAM_SUCCESS) {
syslog(LOG_NOTICE, "error reading pam item %s \n", what);
} else {
if (!info)
syslog(LOG_NOTICE, "pam item %s is missing \n", what);
else if (*info == '\0')
syslog(LOG_NOTICE, "pam item %s is empty \n", what);
else
syslog(LOG_NOTICE, "pam item %s value is %s\n", what, info);
}
}
static void __attribute__((unused))
test_pam_items(const char *me, pam_handle_t * pamh)
{
const char *info;
int err;
syslog(LOG_NOTICE, "%s testing for PAM items \n", me);
err = my_pam_get_item(pamh, PAM_RUSER, &info);
report(err, "PAM_RUSER", info);
err = my_pam_get_item(pamh, PAM_SERVICE, &info);
report(err, "PAM_SERVICE", info);
err = my_pam_get_item(pamh, PAM_RHOST, &info);
report(err, "PAM_RHOST", info);
err = my_pam_get_item(pamh, PAM_TTY, &info);
report(err, "PAM_TTY", info);
}
#endif
struct pam_auth_element {
const char *service;
const char *rhost;
const char *tty;
const char *ruser;
int zenFlag;
int result;
int cutZen; // turn off incompatible flags
};
// this table was built on Caldera openLinux 2.4 and Redhat 7.1
// using the call to test_pam_items() to determine the correct values received by PAM
// for each service...
// you may have to adapt it to your distribution
// use the very same trick to add other remote access
// STRANGE: with samba pam_rhost is missing ( but pam_tty) also
// fixed with samba >=2.07 pam_rhost=hostname pam_tty=samba
// FIXME: with ftp : my ftpd does not call pam session, so mounted nwhome stays mounted
// I am sure I have the line in /etc/pam.d/ftp "session required pam_ncp_auth.so -d"
// SAME with samba
// so my current solution is to turn off automounting !!!
// ssh RHOST=IP TTY=ssh SERVICE=sshd RUSER=missing
// X access RHOST=missing TTY=hostname only SERVICE=kde is tested, others pass through ?
// TESTME: rlogin, rsh (I never used these utilities ;-))
// TODO LATER: Web authorization
static struct pam_auth_element pam_auth_table[] = {
{ "login", "*", "*", NULL, ZF_ALLOW_TELNET_ACCESS, PAM_SUCCESS, 0},
{ "ftp", "*", NULL, NULL, ZF_ALLOW_FTP_ACCESS, PAM_SUCCESS, ZF_AUTOMOUNT_NWHOME},
{ "samba", "*", "samba", NULL, ZF_ALLOW_SAMBA_ACCESS, PAM_SUCCESS, ZF_AUTOMOUNT_NWHOME}, //samba >2.07
{ "samba", NULL, NULL, NULL, ZF_ALLOW_SAMBA_ACCESS, PAM_SUCCESS, ZF_AUTOMOUNT_NWHOME},
{ "kde", NULL, "*", NULL, ZF_ALLOW_X_ACCESS, PAM_SUCCESS, 0}, // RedHat 7.1 send NULL RHOST
{ "kde", "", "*", NULL, ZF_ALLOW_X_ACCESS, PAM_SUCCESS, 0}, // RedHat 7.2 send empty RHOST
{ "rlogin", "*", "*", "*", ZF_ALLOW_RLOGIN_ACCESS, PAM_SUCCESS, 0},
{ "rsh", "*", "*", "*", ZF_ALLOW_RSH_ACCESS, PAM_SUCCESS, 0},
{ "sshd", "*", "ssh", NULL, ZF_ALLOW_TELNET_ACCESS, PAM_SUCCESS, 0},
{ "su", NULL, NULL, NULL, ZF_ALLOW_TELNET_ACCESS, PAM_SUCCESS, 0},
{ NULL, NULL, NULL, NULL, 0, PAM_AUTH_ERR, 0}
};
static int
process_zenflag_remote(pam_handle_t * pamh, UNUSED(const char *user), struct nw_user_info *ui)
{
int err;
char *service;
char *rhost;
char *ruser;
char *tty;
// zenflags restricting remote access are LETHAL
//reactivate to test values of ruser, tty, rhost and service for a new service
// see output in /var/log/secure
#if 0
test_pam_items(user, pamh);
#endif
if (ui->qflag & QF_NO_PEER_CHECKS) {
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "Remote host and tty port are not checked\n");
return PAM_SUCCESS;
}
err = my_pam_get_item(pamh, PAM_TTY, &tty);
if (err != PAM_SUCCESS)
return PAM_SYSTEM_ERR;
// local access
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "testing remote acces PAM_TTY is %s", tty);
// PP not really sure this is good enough
// JFB added tty="vc/" for Mandrake 8.2
if (tty) {
if (!memcmp(tty, "/dev/", 5)) {
tty += 5;
}
if (!memcmp(tty, "tty", 3) || !memcmp(tty, ":0", 2) || !memcmp(tty, "vc/", 2)) {
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "local acces OK to %s", tty);
return PAM_SUCCESS;
}
}
err = my_pam_get_item(pamh, PAM_RHOST, &rhost);
if (err != PAM_SUCCESS) {
syslog(LOG_WARNING, "error getting PAM_RHOST");
return PAM_SYSTEM_ERR;
}
err = my_pam_get_item(pamh, PAM_RUSER, &ruser);
if (err != PAM_SUCCESS) {
syslog(LOG_WARNING, "error getting PAM_RUSER");
return PAM_SYSTEM_ERR;
}
err = my_pam_get_item(pamh, PAM_SERVICE, &service);
if (err != PAM_SUCCESS) {
syslog(LOG_WARNING, "error getting PAM_SERVICE");
return PAM_SYSTEM_ERR;
}
{
struct pam_auth_element *z;
err = PAM_AUTH_ERR;
for (z = pam_auth_table; z->service; z++) {
if (!strcmp(z->service, service))
if ((rhost && z->rhost) || (!rhost && !z->rhost))
if ((tty && z->tty) || (!tty && !z->tty))
if ((ruser && z->ruser) || (!ruser && !z->ruser)) {
if (!(z->zenFlag & ui->zenFlag)) {
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "remote acces for service %s rejected by Zen for %s", service, ui->name);
return PAM_AUTH_ERR;
} else {
// turn OFF incompatible FLAGS ( automounting ?)
ui->zenFlag &= ~z->cutZen;
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "remote acces for service %s granted by Zen for %s", service, ui->name);
err = PAM_SUCCESS;
break;
}
}
#if 0
else
syslog(LOG_NOTICE, "Mismatching ruser found: %s expecting:%s", ruser, z->ruser);
else
syslog(LOG_NOTICE, "Mismatching tty found: %s expecting:%s", tty, z->tty);
else
syslog(LOG_NOTICE, "Mismatching rhost found: %s expecting:%s", rhost, z->rhost);
#endif
}
}
#if 0
syslog(LOG_NOTICE, "final return on remote check %d", err);
#endif
return err; //PAM_SUCCESS;
}
/***************************************************************************************************/
// must be done at this stage since they requires the user's current passwd
static int
process_zenflag_auth(UNUSED(pam_handle_t * pamh), const char *user, struct nw_user_info *ui, const struct passwd *pwd, const char *userpass)
{
int err; // ignore errors for now
/* must be done BEFORE mounting Netware home since local /home/user
can become unavailable if the Netware home directory is mounted as /home/user
using the -m option with NO ARGUMENT */
if (ui->zenFlag & ZF_FORWARD_MAIL)
err = nw_process_forward_file(user, ui, pwd);
if (ui->zenFlag & ZF_AUTOMOUNT_NWHOME) {
err = nw_automount_home(user, ui, pwd, userpass);
/* do it AGAIN after mounting Netware home, so the .forward file is still available
when user is logged in */
if (!err && ui->zenFlag & ZF_FORWARD_MAIL)
if (ui->nwhomeMntPnt && ui->nwhomeMntPnt[0] == 0)
err = nw_process_forward_file(user, ui, pwd);
}
if (ui->zenFlag & (ZF_CREATE_NWCLIENT | ZF_OVERWRITE_NWCLIENT))
nw_create_nwclient(user, ui, pwd, userpass); // ignore errors for now
return PAM_SUCCESS;
}
/* The code - what there is of it :) */
PAM_EXTERN int
pam_sm_authenticate(pam_handle_t * pamh, int flags, int argc, const char **argv)
{
int retval;
const char *name;
char *p;
int c;
struct nw_user_info inf;
struct passwd *pwd_entry;
openlog("pam_ncp_auth", LOG_PID, LOG_AUTHPRIV);
init_nw_user_info(&inf);
inf.pamh = pamh;
inf.qflag = QF_VERBOSE | QF_CREATEHOME;
inf.state.uid.min = 1000;
inf.state.uid.max = 60000;
inf.state.uid.flags = 0;
inf.state.uid.modflags = 0;
inf.state.gid.min = 1000;
inf.state.gid.max = 60000;
inf.state.gid.flags = QFC_NEXT_UNUSED | QFC_PREFFER_SERVER;
/* Get options */
for (c = 0; c < argc; c++) {
const char *chrp = argv[c];
if (*chrp++ == '-') {
int chr;
while ((chr = *chrp++) != 0) {
switch (chr) {
case 'v':
inf.qflag |= QF_VERBOSE;
break; /* verbose */
case 'q':
inf.qflag &= ~QF_VERBOSE;
break; /* quiet */
case 'd':
inf.qflag |= QF_DEBUG;
break; /* debug */
case 's':
inf.qflag |= QF_NOSU;
break; /* no supervisor */
case 'S':
inf.qflag |= QF_NOSUEQ;
break; /* no supervisor equivalent */
case 'a':
inf.qflag |= QF_AUTOCREATE;
break; /* create account automagically */
case 'b':
inf.qflag |= QF_BINDERY;
break; /* use bindery access */
case 'u':
parseuid(&inf, &chrp);
break;
case 'g':
parsegid(&inf, &chrp);
break;
case 'm':
parsemntpnt(&inf, &chrp);
while ((chr = *chrp++) != 0) ; // bug corrected v 1.14
break; //PP
case 'z':
parsezenflag_on(&inf, &chrp);
break; //PP
case 'Z':
parsezenflag_off(&inf, &chrp);
break; //PP
case 'n':
inf.qflag &= ~QF_CREATEHOME;
break; // PP v 1.24 (DO NOT create home for new users!)
case 'l':
inf.qflag |= QF_MOUNTLOCALLY;
break; // PP v 1.24 (homes are NFS mounted !)
case 'A':
inf.qflag |= QF_USE_NETWARE_IP; // PP v 1.24
break;
case 'L':
inf.qflag |= QF_NO_PEER_CHECKS;
break;
default:; /* just silently ignore unknown option... */
}
}
}
}
//if (inf.qflag &QF_DEBUG)
// test_pam_items ("authenticate",pamh);
/* Get username */
if ((retval = pam_get_user(pamh, &name, "login: ")) != PAM_SUCCESS)
goto quit;
/* Get password */
my_pam_get_item(pamh, PAM_AUTHTOK, &p);
if (!p) {
retval = _set_auth_tok(pamh, flags);
if (retval != PAM_SUCCESS)
goto quit;
}
my_pam_get_item(pamh, PAM_AUTHTOK, &p);
/* v 1.22 check the service is not a screensaver */
{
const char *service;
if ((retval = my_pam_get_item(pamh, PAM_SERVICE, &service)) != PAM_SUCCESS)
goto quit;
inf.isScreenSaverRelogin = strstr(service, "saver") != NULL;
// report (retval, "PAM_SERVICE",service);
if (inf.isScreenSaverRelogin && inf.qflag & QF_DEBUG)
syslog(LOG_NOTICE, "relogin of %s from the screen saver %s ", name, service);
}
/* Find the server name in the configuration. */
for (c = 0; c < argc; c++) {
if (!strncmp("server=", argv[c], 7)) {
const char *server;
const char *group;
char sbuf[256];
server = argv[c] + 7;
group = strchr(server, '/');
if (group) {
if ((size_t) (group - server) < sizeof (sbuf) - 1) {
memcpy(sbuf, server, group - server);
sbuf[group - server] = 0;
server = sbuf;
group = group + 1;
} else {
syslog(LOG_ALERT, "Error in configuration file: server name too long!\n");
continue;
}
}
retval = nw_attempt_auth_server(pamh, server, name, p, inf.qflag, group);
if (retval == PAM_SUCCESS)
goto success;
}
if (!strncmp("tree=", argv[c], 5)) {
const char *tree;
const char *contexts;
const char *group;
char tbuf[512];
tree = argv[c] + 5;
if (strlen(tree) >= sizeof (tbuf)) {
syslog(LOG_ALERT, "Error in configuration file: tree argument too long!\n");
continue;
}
strcpy(tbuf, tree);
tree = tbuf;
group = strchr(tree, '/');
if (group) {
tbuf[group - tree] = 0;
group++;
}
contexts = strchr(tree, ':');
if (contexts) {
tbuf[contexts - tree] = 0;
contexts++;
}
if (inf.qflag & QF_DEBUG)
syslog(LOG_NOTICE, "using tree %s ctxs %s group %s", tree, contexts, group);
retval = nw_attempt_auth_tree(pamh, tree, name, contexts, p, inf.qflag, group, NWCC_NAME_FORMAT_NDS_TREE);
if (retval == PAM_SUCCESS)
goto success;
}
if (!strncmp("ndsserver=", argv[c], 10)) {
const char *server;
const char *contexts;
const char *group;
char tbuf[512];
server = argv[c] + 10;
if (strlen(server) >= sizeof (tbuf)) {
syslog(LOG_ALERT, "Error in configuration file: ndsserver argument too long!\n");
continue;
}
strcpy(tbuf, server);
server = tbuf;
group = strchr(server, '/');
if (group) {
tbuf[group - server] = 0;
group++;
}
contexts = strchr(server, ':');
if (contexts) {
tbuf[contexts - server] = 0;
contexts++;
}
if (inf.qflag & QF_DEBUG)
syslog(LOG_NOTICE, "using server %s ctxs %s group %s", server, contexts, group);
retval = nw_attempt_auth_tree(pamh, server, name, contexts, p, inf.qflag, group, NWCC_NAME_FORMAT_BIND);
if (retval == PAM_SUCCESS)
goto success;
}
}
retval = PAM_AUTH_ERR;
goto quit;
success:;
if (inf.qflag & QF_DEBUG)
syslog(LOG_DEBUG, "Auth OK\n");
setpwent();
pwd_entry = getpwnam(name);
endpwent();
retval = PAM_SUCCESS; //moved here
if (!inf.isScreenSaverRelogin && inf.qflag & (QF_AUTOCREATE | QF_AUTOMODIFY)) { // v 1.22
if ((!pwd_entry && (inf.qflag & QF_AUTOCREATE)) || (pwd_entry && (inf.qflag & QF_AUTOMODIFY))) {
int err;
inf.name = strdup(name);
if (!inf.name) {
syslog(LOG_WARNING, "Not enough memory for strdup()\n");
err = -10; // added PP
} else {
// NDS error means NO LOGIN
err = nw_retrieve_user_info(&inf);
// remote access denied = NO LOGIN (and no update/creation!)
if (inf.qflag & QF_DEBUG)
syslog(LOG_DEBUG, "FOUND ZF %lx in NDS \n", inf.zenFlag);
if (inf.qflag & QF_DEBUG)
syslog(LOG_DEBUG, "APPLYING ZF %lx OFF \n", inf.zenFlagOFF);
mergeZenFlags(&inf); // rev 1.26 turn off prohibited flags
if (inf.qflag & QF_DEBUG)
syslog(LOG_DEBUG, "USING ZF %lx \n", inf.zenFlag);
if (!err)
err = process_zenflag_remote(pamh, name, &inf);
// in this version creation error is lethal, not update
if (!err)
err = nw_update_user_info(&inf);
#if 0
err = 0; // testing
#endif
}
retval = err ? PAM_AUTH_ERR : PAM_SUCCESS; // added here PP
}
// feature: if you do not allow autocreation, automodification, NDS is not RED
// so no Zen, no remote access restrictions...
}
quit:;
//PP keep the nw_user_info into PAM for a later use in sm_open_session
// be Zen, no password saved yet ;-)
if (retval == PAM_SUCCESS && !inf.isScreenSaverRelogin) {
void *sav;
//read again pwd_entry ( was NULL if it is a new account !)
setpwent();
pwd_entry = getpwnam(name);
endpwent();
mergeZenFlags(&inf); // rev 1.26 turn off prohibited flags
retval = process_zenflag_auth(pamh, name, &inf, pwd_entry, p);
if (retval == PAM_SUCCESS) { // some ZeN_FLAGS may stop the authentification process !
sav = malloc(sizeof (inf));
if (sav) {
if (inf.qflag & QF_DEBUG)
syslog(LOG_NOTICE, "saving user_info\n");
memcpy(sav, &inf, sizeof (inf));
pam_set_data(pamh, "pam.ncpfs.user_info", sav, cleanup_user_info);
} else {
if (inf.qflag & QF_DEBUG)
syslog(LOG_NOTICE, "Out of memory. NOT saving user_info\n");
free_nw_user_info(&inf); // forget about it
// I feel bad about the pam-session parts, but they should just quit gracefully
}
}
}
if (inf.qflag & QF_DEBUG)
syslog(LOG_NOTICE, "final PAM retval %u\n", retval);
//PP: may be a good spot to close and get rid on the still opened connection ???
closelog();
return retval;
}
/*
* Does nothing. for now
*/
PAM_EXTERN int
pam_sm_setcred(UNUSED(pam_handle_t * pamh),
UNUSED(int flags),
UNUSED(int argc),
UNUSED(const char **argv))
{
//return PAM_IGNORE;
return PAM_SUCCESS;
}
/*
* Does nothing. for now
*/
PAM_EXTERN int
pam_sm_acct_mgmt(UNUSED(pam_handle_t * pamh),
UNUSED(int flags),
UNUSED(int argc),
UNUSED(const char **argv))
{
//return PAM_IGNORE;
return PAM_SUCCESS;
}
/* old code used with server=XXX cmd line argument */
static int
nw_attempt_passwd_prelim_server(pam_handle_t * pamh,
const char *server, const char *user, const char *oldpwd,
int qflag, const char *group, UNUSED(int flags))
{
struct ncp_conn *conn;
int err;
err = nw_create_verify_conn_to_server(&conn, NULL, server, user, oldpwd, qflag, group);
if (err)
return err;
pam_set_data(pamh, "pam.ncpfs.passwd.conn", conn, nw_cleanup_conn);
return 0;
}
/*new code for tree=argument */
static int
nw_attempt_passwd_prelim_tree(pam_handle_t * pamh,
const char *tree, const char *user, const char *contexts, const char *oldpwd,
int qflag, const char *group, UNUSED(int flags), nuint nameFormat)
{
struct ncp_conn *conn;
int err;
err = nw_create_verify_conn_to_tree(&conn, NULL, tree, user, contexts, oldpwd, qflag, group, nameFormat);
if (err)
return err;
pam_set_data(pamh, "pam.ncpfs.passwd.conn", conn, nw_cleanup_conn);
return 0;
}
static int
nw_attempt_passwd_post(pam_handle_t * pamh, const char *pwd, const char *oldpwd, int qflag, int flags)
{
struct ncp_conn *conn;
long err;
NWObjectID oid;
char *oldpwdup;
char *pwdup;
err = PAM_AUTHTOK_ERR;
if (!(flags & PAM_UPDATE_AUTHTOK))
goto bailout_nf;
oldpwdup = strdup(oldpwd);
if (!oldpwd) {
err = ENOMEM;
goto bailout_nf;
}
str_upper(oldpwdup);
pwdup = strdup(pwd);
if (!pwd) {
err = ENOMEM;
goto bailout_nf2;
}
str_upper(pwdup);
err = my_pam_get_data(pamh, "pam.ncpfs.passwd.conn", &conn);
if (err)
goto bailout;
err = nw_get_nwid(conn, &oid, qflag);
if (err)
goto bailout;
if (qflag & QF_BINDERY) {
struct ncp_bindery_object uinfo;
struct ncp_bindery_object u0;
unsigned char ncp_key[8];
err = ncp_get_bindery_object_name(conn, oid, &u0);
if (err) {
syslog(LOG_WARNING, "%s when trying to get object name\n", strnwerror(err));
err = PAM_USER_UNKNOWN;
goto bailout;
}
err = ncp_get_encryption_key(conn, ncp_key);
if (err) {
syslog(LOG_WARNING, "%s when trying to get encryption key\n", strnwerror(err));
err = PAM_AUTHTOK_ERR;
goto bailout;
} else {
err = ncp_get_bindery_object_id(conn, u0.object_type, u0.object_name, &uinfo);
if (err) {
syslog(LOG_WARNING, "%s when trying to get object ID\n", strnwerror(err));
err = PAM_USER_UNKNOWN;
goto bailout;
}
err = ncp_change_login_passwd(conn, &uinfo, ncp_key, oldpwdup, pwdup);
if (err) {
syslog(LOG_WARNING, "%s when trying to change password\n", strnwerror(err));
err = PAM_AUTHTOK_ERR;
goto bailout;
}
}
} else {
NWDSContextHandle ctx;
nuint32 c;
char username[MAX_DN_BYTES];
err = NWDSCreateContextHandle(&ctx);
if (err) {
syslog(LOG_WARNING, "NWDSCreateContextHandle() failed with %s\n", strnwerror(err));
err = PAM_SYSTEM_ERR;
goto bailout;
}
c = DCV_XLATE_STRINGS | DCV_TYPELESS_NAMES | DCV_DEREF_ALIASES;
err = NWDSSetContext(ctx, DCK_FLAGS, &c);
if (err) {
syslog(LOG_WARNING, "NWDSSetContext() failed with %s\n", strnwerror(err));
err = PAM_SYSTEM_ERR;
goto bailoutctx;
}
NWDSAddConnection(ctx, conn);
err = NWDSMapIDToName(ctx, conn, oid, username);
if (err) {
syslog(LOG_WARNING, "NWDSMapIDToName() failed with %s\n", strnwerror(err));
err = PAM_USER_UNKNOWN;
goto bailoutctx;
}
if (qflag & QF_DEBUG)
syslog(LOG_DEBUG, "User has DN %s\n", username);
err = NWDSChangeObjectPassword(ctx, NDS_PASSWORD, username, oldpwdup, pwdup);
if (err) {
syslog(LOG_NOTICE, "NWDSChangeObjectPassword() failed with %s\n", strnwerror(err));
err = PAM_AUTHTOK_ERR;
goto bailoutctx;
}
if (qflag & QF_DEBUG)
syslog(LOG_DEBUG, "User %s has succesfully changed its NW pasword\n", username);
bailoutctx:;
NWDSFreeContext(ctx);
}
bailout:;
// set it to null so nw_clean_up_conn will do no harm
err = pam_set_data(pamh, "pam.ncpfs.passwd.conn", NULL, NULL);
if (qflag & QF_DEBUG)
syslog (LOG_NOTICE,"pam end of PWD:setting conn to NULL %lx",err);
free(pwdup);
bailout_nf2:;
free(oldpwdup);
bailout_nf:;
return err;
}
//PP: untested code ..
// one question.
//
PAM_EXTERN int
pam_sm_chauthtok(pam_handle_t * pamh, int flags, int argc, const char **argv)
{
int retval;
const char *name;
char *p;
char *oldpasswd;
int c;
int qflag = QF_VERBOSE;
openlog("pam_ncp_auth", LOG_PID, LOG_AUTHPRIV);
/* Get options */
for (c = 0; c < argc; c++) {
if (argv[c][0] == '-') {
int i;
for (i = 1; argv[c][i]; i++) {
switch (argv[c][i]) {
case 'v':
qflag |= QF_VERBOSE;
break; /* verbose */
case 'q':
qflag &= ~QF_VERBOSE;
break; /* quiet */
case 'd':
qflag |= QF_DEBUG;
break; /* debug */
case 's':
qflag |= QF_NOSU;
break; /* no supervisor */
case 'S':
qflag |= QF_NOSUEQ;
break; /* no supervisor equivalent */
case 'b':
qflag |= QF_BINDERY;
break; /* use bindery access */
default:; /* just silently ignore unknown option... */
}
}
}
}
//if (qflag &QF_DEBUG)
// test_pam_items ("chauthtok",pamh);
/* Get username */
if ((retval = pam_get_user(pamh, &name, "passwd: ")) != PAM_SUCCESS)
goto quit;
pam_get_item(pamh, PAM_OLDAUTHTOK, (void *) &oldpasswd);
if (!oldpasswd) {
retval = _set_oldauth_tok(pamh, flags);
if (retval != PAM_SUCCESS)
goto quit;
pam_get_item(pamh, PAM_OLDAUTHTOK, (void *) &oldpasswd);
}
if (flags & PAM_PRELIM_CHECK) {
/* Find the server name in the configuration. */
for (c = 0; c < argc; c++) {
if (!strncmp("server=", argv[c], 7)) {
const char *server;
const char *group;
char sbuf[256];
server = argv[c] + 7;
group = strchr(server, '/');
if (group) {
if ((size_t) (group - server) < sizeof (sbuf) - 1) {
memcpy(sbuf, server, group - server);
sbuf[group - server] = 0;
server = sbuf;
group = group + 1;
} else {
syslog(LOG_ALERT, "Error in configuration file: server name too long!\n");
continue;
}
}
retval = nw_attempt_passwd_prelim_server(pamh, server, name, oldpasswd, qflag, group, flags);
if (retval == PAM_SUCCESS)
goto quit;
}
if (!strncmp("tree=", argv[c], 5)) {
const char *tree;
const char *contexts;
const char *group;
char tbuf[512];
tree = argv[c] + 5;
if (strlen(tree) >= sizeof (tbuf)) {
syslog(LOG_ALERT, "Error in configuration file: tree argument too long!\n");
continue;
}
strcpy(tbuf, tree);
tree = tbuf;
group = strchr(tree, '/');
if (group) {
tbuf[group - tree] = 0;
group++;
}
contexts = strchr(tree, ':');
if (contexts) {
tbuf[contexts - tree] = 0;
contexts++;
}
if (qflag & QF_DEBUG)
syslog(LOG_NOTICE, "using tree %s ctxs %s group %s", tree, contexts, group);
retval = nw_attempt_passwd_prelim_tree(pamh, tree, name, contexts, oldpasswd, qflag, group, flags, NWCC_NAME_FORMAT_NDS_TREE);
if (retval == PAM_SUCCESS)
goto quit;
}
if (!strncmp("ndsserver=", argv[c], 10)) {
const char *tree;
const char *contexts;
const char *group;
char tbuf[512];
tree = argv[c] + 10;
if (strlen(tree) >= sizeof (tbuf)) {
syslog(LOG_ALERT, "Error in configuration file: NDS server argument too long!\n");
continue;
}
strcpy(tbuf, tree);
tree = tbuf;
group = strchr(tree, '/');
if (group) {
tbuf[group - tree] = 0;
group++;
}
contexts = strchr(tree, ':');
if (contexts) {
tbuf[contexts - tree] = 0;
contexts++;
}
if (qflag & QF_DEBUG)
syslog(LOG_NOTICE, "using NDS server %s ctxs %s group %s", tree, contexts, group);
retval = nw_attempt_passwd_prelim_tree(pamh, tree, name, contexts, oldpasswd, qflag, group, flags, NWCC_NAME_FORMAT_BIND);
if (retval == PAM_SUCCESS)
goto quit;
}
}
retval = PAM_AUTHTOK_ERR;
goto quit;
}
if (flags & PAM_UPDATE_AUTHTOK) {
/* Get password */
pam_get_item(pamh, PAM_AUTHTOK, (void *) &p);
if (!p) {
retval = _read_new_pwd(pamh, flags);
if (retval != PAM_SUCCESS)
return retval;
pam_get_item(pamh, PAM_AUTHTOK, (void *) &p);
}
retval = nw_attempt_passwd_post(pamh, p, oldpasswd, qflag, flags);
} else {
retval = PAM_SYSTEM_ERR;
}
quit:;
closelog();
return retval;
}
// ajout PP 2000/12/19
// will process session on and session end zenFlag
static int
nw_process_nwinfos_file(UNUSED(const char *uname), const struct nw_user_info *ui, const struct passwd *pwd)
{
// create a ~/.nwinfos file that can be "sourced" in future scripts
FILE *f;
char *nwinfos;
#ifdef IMPERSONNATE
uid_t currentUID = getuid();
#endif
int perm_err;
nwinfos = concat3a(pwd->pw_dir, '/', NWINFOS_FILE);
#ifdef IMPERSONNATE
// v 1.24 NFS mounted homes are mounted with no_root_squash option , so we must change UID to current user
if (seteuid(pwd->pw_uid)) {
syslog(LOG_DEBUG, "Cannot inpersonnate to %s: %s\n", pwd->pw_name, strerror(errno));
return 1;
}
#endif
f = fopen(nwinfos, "w");
if (f) {
if (ui->qflag & QF_DEBUG)
syslog(LOG_DEBUG, "writing a new %s file\n", nwinfos);
// either NDS or local values
fprintf(f, "NDS_USER=%s\n", ui->name ? ui->name : pwd->pw_name);
fprintf(f, "NDS_GECOS=\"%s\"\n", ui->gecos ? ui->gecos : pwd->pw_gecos); //may have space in it
fprintf(f, "NDS_SHELL=%s\n", ui->shell ? ui->shell : pwd->pw_shell);
fprintf(f, "NDS_HOME=%s\n", ui->dir ? ui->dir : pwd->pw_dir);
fprintf(f, "NDS_UID=%u\n", (ui->uid != (uid_t) -1) ? ui->uid : pwd->pw_uid);
fprintf(f, "NDS_GID=%u\n", (ui->gid != (gid_t) -1) ? ui->gid : pwd->pw_gid);
// no local alernatives
fprintf(f, "NDS_QFLAG=%x\n", ui->qflag);
if (ui->nwhomeServer)
fprintf(f, "NDS_HOME_SERVER=%s\n", ui->nwhomeServer);
if (ui->nwhomeVolume)
fprintf(f, "NDS_HOME_VOLUME=%s\n", ui->nwhomeVolume);
if (ui->nwhomePath)
fprintf(f, "NDS_HOME_PATH=%s\n", ui->nwhomePath);
if (ui->nwhomeMntPnt)
fprintf(f, "NDS_HOME_MNT_PNT=%s\n", ui->nwhomeMntPnt);
if (ui->emailSMTP)
fprintf(f, "NDS_EMAIL=%s\n", ui->emailSMTP);
if (ui->emailLDAP)
fprintf(f, "NDS_EMAIL=%s\n", ui->emailLDAP); // overwrite it
if (ui->messageServer)
fprintf(f, "NDS_PREFERRED_SERVER=%s\n", ui->messageServer);
if (ui->defaultTree)
fprintf(f, "NDS_PREFERRED_TREE=%s\n", ui->defaultTree);
if (ui->defaultNameCtx)
fprintf(f, "NDS_PREFERRED_NAME_CTX=%s\n", ui->defaultNameCtx);
// v 1.19 added a new account flag to ease zenscript processing
// is account is new, we can further personnalize config files such as kmailrc, liprefs.js ...
fprintf(f, "NDS_IS_NEW_USER=%s\n", (ui->isNewUser) ? "1" : "0");
fprintf(f, "NDS_ZEN_FLAG=0x%lx\n", ui->zenFlag);
switch (ui->zenFlag && (ZF_BROADCAST_ALL + ZF_BROADCAST_CONSOLE)) {
case ZF_BROADCAST_ALL:
fprintf(f, "NDS_BCAST=2\n");
break;
case ZF_BROADCAST_CONSOLE:
fprintf(f, "NDS_BCAST=1\n");
break;
default:
fprintf(f, "NDS_BCAST=0\n");
break;
}
fclose(f);
//perm_err=chmod(nwinfos,0600) || chown(nwinfos,pwd->pw_uid,pwd->pw_gid);
perm_err = chmod(nwinfos, 0600);
seteuid(currentUID);
if (perm_err)
syslog(LOG_DEBUG, "problem %d (%s)changing permissions to %s\n", perm_err, strerror(errno), nwinfos);
if (ui->qflag & QF_DEBUG)
syslog(LOG_DEBUG, "done writing %s \n", nwinfos);
} else {
seteuid(currentUID);
syslog(LOG_DEBUG, "Cannot open %s: %s\n", nwinfos, strerror(errno));
}
return 0;
}
// starting at rev. 1.19 zencripts are launched via su -l user for security reasons !
static int
exechelper2(const char *program, // script name just to test if the command exist
const char *argv[], // extras arguments to -c program
const char *unixname, // user to su to
int verbose)
{
struct stat stt;
if (verbose) {
if (unixname) {
syslog(LOG_NOTICE, "launching %s %s %s as %s\n", program, argv[1], argv[2], unixname);
} else {
syslog(LOG_NOTICE, "launching %s %s %s as root\n", program, argv[1], argv[2]);
}
}
if (stat(program, &stt) != 0) {
if (verbose)
syslog(LOG_NOTICE, "%s not found\n", program);
return -1;
}
return exechelper(program, argv, unixname);
}
static int
process_zenflag_session_opening(const char *user, const struct nw_user_info *ui, const struct passwd *pwd)
{
int err;
if (ui->qflag & QF_DEBUG)
syslog(LOG_DEBUG, "APPLYING ZF %lx\n", ui->zenFlag);
if (ui->zenFlag & ZF_CREATE_NWINFOS)
err = nw_process_nwinfos_file(user, ui, pwd);
if (ui->zenFlag & ZF_OPENING_SCRIPTS) {
const char *argv[5];
argv[1] = pwd->pw_dir; // argument 1 = location of nwinfos file to source in
argv[2] = NWINFOS_FILE; // actual file name
argv[3] = NULL;
// argument 1 = location of nwinfos file to source in (=user's home dir)
//argument 2 = actual file name (default=.nwinfos)
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "running opening scripts.\n");
if (ui->zenFlag & ZF_0)
err = exechelper2(ZEN_SCRIPT_0, argv, user, ui->qflag & QF_DEBUG);
if (ui->zenFlag & ZF_1)
err = exechelper2(ZEN_SCRIPT_1, argv, user, ui->qflag & QF_DEBUG);
if (ui->zenFlag & ZF_2)
err = exechelper2(ZEN_SCRIPT_2, argv, user, ui->qflag & QF_DEBUG);
}
#if 1
err = 0; // ignore errors for now
#endif
return err;
}
PAM_EXTERN int
pam_sm_open_session(pam_handle_t * pamh, UNUSED(int flags), int argc, const char **argv)
{
/* NO WAY to mount netware home here
passord is lost, we are in the pam_session now.
I could have stored it in nw-user_info struct saved in PAM data
but I am not sure this is really safe !!!
we only process here some ZENFLAGS red from NDS
and stored in the PAM item "pam.ncpfs.user_info"
*/
struct ncp_conn *conn;
struct nw_user_info *ui;
const struct passwd *pwd;
struct stat stt;
const char *user;
int err, c;
int qflag = QF_VERBOSE;
openlog("pam_ncp_auth", LOG_PID, LOG_AUTHPRIV);
/* Get options */
for (c = 0; c < argc; c++) {
if (argv[c][0] == '-') {
int i;
for (i = 1; argv[c][i]; i++) {
switch (argv[c][i]) {
case 'v':
qflag |= QF_VERBOSE;
break; /* verbose */
case 'q':
qflag &= ~QF_VERBOSE;
break; /* quiet */
case 'd':
qflag |= QF_DEBUG;
break; /* debug */
default:; /* just silently ignore unknown option... */
}
}
}
}
/*****************************************************
if (qflag & QF_DEBUG) {
test_pam_items ("session closing",pamh);
}
*****************************************************/
if (qflag & QF_DEBUG) {
syslog(LOG_NOTICE, "start of session \n");
}
// just in case user has been removed ???
err = my_pam_get_item(pamh, PAM_USER, &user);
if (err != PAM_SUCCESS || user == NULL || *user == '\0')
goto doneok;
setpwent();
pwd = getpwnam(user);
endpwent();
if (pwd == NULL) {
syslog(LOG_DEBUG, "%s not found\n", user);
goto doneok;
}
if (stat(pwd->pw_dir, &stt) != 0) {
syslog(LOG_DEBUG, "Unix home of %s not found !\n", user);
goto doneok;
}
// get back user's info stored in PAM by the authentification section
// no password there !
err = my_pam_get_data(pamh, "pam.ncpfs.user_info", &ui);
if (!err) {
if (qflag & QF_DEBUG)
syslog(LOG_NOTICE, "got user info back %u", ui->uid);
process_zenflag_session_opening(user, ui, pwd);
} else {
if (qflag & QF_DEBUG)
syslog(LOG_DEBUG, "failure reading back pam.ncpfs.user_info %u\n", err);
}
// no problem, he is authenticated but "zen failed"
doneok:
// revision 1.27 we get rid of the authenticating connection .
// it is not in /etc/mtab, so it caoonot be "reused" by ncpmap anyway
err = my_pam_get_data(pamh, "pam.ncpfs.passwd.conn", &conn);
if (!err && conn) {
err = pam_set_data(pamh, "pam.ncpfs.passwd.conn", NULL, NULL);
if (qflag & QF_DEBUG)
syslog (LOG_NOTICE,"pam start of session :setting internal conn to NULL %x",err);
}
return PAM_SUCCESS;
}
// starting at rev. 1.19 zencripts are lauched via su -l user for security reasons !
static int
process_zenflag_session_closing(const char *user, const struct nw_user_info *ui, const struct passwd *pwd)
{
int err = 0;
// first zenScripts in case they opeate on the mounted nwhome !
if (ui->zenFlag & ZF_CLOSING_SCRIPTS) {
const char *argv[5];
argv[1] = pwd->pw_dir; // argument 1 = location of nwinfos file to source in
argv[2] = NWINFOS_FILE; // actual file name
argv[3] = NULL;
// argument 1 = location of nwinfos file to source in (=user's home dir)
// argument 2 = actual file name (default=.nwinfos)
if (ui->qflag & QF_DEBUG)
syslog(LOG_NOTICE, "running closing scripts.\n");
if (ui->zenFlag & ZF_3)
err = exechelper2(ZEN_SCRIPT_3, argv, user, ui->qflag & QF_DEBUG);
if (ui->zenFlag & ZF_4)
err = exechelper2(ZEN_SCRIPT_4, argv, user, ui->qflag & QF_DEBUG);
if (ui->zenFlag & ZF_5)
err = exechelper2(ZEN_SCRIPT_5, argv, user, ui->qflag & QF_DEBUG);
}
if (ui->zenFlag & ZF_AUTOMOUNT_NWHOME) {
err = umount_nwhome(user, ui->nwhomeMntPnt, ui->qflag & QF_DEBUG);
}
#if 1
err = 0; // ignore error
#endif
return err;
}
static int
pam_do_close_session(const char *user, const struct nw_user_info *ui, const struct passwd *pwd)
{
return process_zenflag_session_closing(user, ui, pwd);
}
PAM_EXTERN int
pam_sm_close_session(pam_handle_t * pamh, UNUSED(int flags), int argc, const char **argv)
// logout fom Netware at session end
// no complains
{
struct nw_user_info *ui;
long err;
const char *user;
const struct passwd *pwd;
struct stat stt;
int qflag = QF_VERBOSE;
int c;
openlog("pam_ncp_auth", LOG_PID, LOG_AUTHPRIV);
/* Get options */
for (c = 0; c < argc; c++) {
if (argv[c][0] == '-') {
int i;
for (i = 1; argv[c][i]; i++) {
switch (argv[c][i]) {
case 'v':
qflag |= QF_VERBOSE;
break; /* verbose */
case 'q':
qflag &= ~QF_VERBOSE;
break; /* quiet */
case 'd':
qflag |= QF_DEBUG;
break; /* debug */
default:; /* just silently ignore unknown option... */
}
}
}
}
/*****************************************************
if (qflag & QF_DEBUG) {
test_pam_items ("session closing",pamh);
}
*****************************************************/
if (qflag & QF_DEBUG) {
syslog(LOG_NOTICE, "end of session\n");
}
// just in case user has been removed ???
err = my_pam_get_item(pamh, PAM_USER, &user);
if (err != PAM_SUCCESS || user == NULL || *user == '\0')
goto doneok;
setpwent();
pwd = getpwnam(user);
endpwent();
if (pwd == NULL) {
syslog(LOG_NOTICE, "%s not found\n", user);
goto doneok;
}
if (stat(pwd->pw_dir, &stt) != 0) {
syslog(LOG_NOTICE, "Unix home of %s not found !\n", user);
goto doneok;
}
// read back all user data
err = my_pam_get_data(pamh, "pam.ncpfs.user_info", &ui);
if (!err) {
if (qflag & QF_DEBUG)
syslog(LOG_NOTICE, "got it back %u", ui->uid);
} else {
if (qflag & QF_DEBUG)
syslog(LOG_DEBUG, "failed reading pam.ncpfs.user_info %lu\n", err);
goto doneok;
}
pam_do_close_session(user, ui, pwd);
free_nw_user_info(ui); // forget about it
doneok:
closelog();
return PAM_SUCCESS;
}
/* static module data */
#ifdef PAM_STATIC
struct pam_module _pam_ncp_auth_modstruct = {
"pam_ncp_auth",
pam_sm_authenticate,
pam_sm_setcred,
pam_sm_acct_mgmt,
pam_sm_open_session, // session IN was NULL PP
pam_sm_close_session, // session OUT was NULL PP
pam_sm_chauthtok,
};
#endif