diff options
author | Andrew G. Morgan <morgan@kernel.org> | 2000-06-20 22:10:38 +0000 |
---|---|---|
committer | Andrew G. Morgan <morgan@kernel.org> | 2000-06-20 22:10:38 +0000 |
commit | ea488580c42e8918445a945484de3c8a5addc761 (patch) | |
tree | c992f3ba699caafedfadc16af38e6359c3c24698 /modules/pam_unix | |
download | pam-ea488580c42e8918445a945484de3c8a5addc761.tar.gz pam-ea488580c42e8918445a945484de3c8a5addc761.tar.bz2 pam-ea488580c42e8918445a945484de3c8a5addc761.zip |
Initial revision
Diffstat (limited to 'modules/pam_unix')
-rw-r--r-- | modules/pam_unix/.cvsignore | 4 | ||||
-rw-r--r-- | modules/pam_unix/CHANGELOG | 55 | ||||
-rw-r--r-- | modules/pam_unix/Makefile | 155 | ||||
-rw-r--r-- | modules/pam_unix/README | 39 | ||||
-rw-r--r-- | modules/pam_unix/bigcrypt.c | 119 | ||||
-rw-r--r-- | modules/pam_unix/lckpwdf.-c | 117 | ||||
-rw-r--r-- | modules/pam_unix/md5.c | 256 | ||||
-rw-r--r-- | modules/pam_unix/md5.h | 35 | ||||
-rw-r--r-- | modules/pam_unix/md5_crypt.c | 149 | ||||
-rwxr-xr-x | modules/pam_unix/need_nsl.sh | 7 | ||||
-rw-r--r-- | modules/pam_unix/pam_unix_acct.c | 210 | ||||
-rw-r--r-- | modules/pam_unix/pam_unix_auth.c | 220 | ||||
-rw-r--r-- | modules/pam_unix/pam_unix_passwd.c | 985 | ||||
-rw-r--r-- | modules/pam_unix/pam_unix_sess.c | 138 | ||||
-rw-r--r-- | modules/pam_unix/support.c | 821 | ||||
-rw-r--r-- | modules/pam_unix/support.h | 139 | ||||
-rw-r--r-- | modules/pam_unix/unix_chkpwd.c | 324 | ||||
-rw-r--r-- | modules/pam_unix/yppasswd.h | 51 | ||||
-rw-r--r-- | modules/pam_unix/yppasswd_xdr.c | 41 |
19 files changed, 3865 insertions, 0 deletions
diff --git a/modules/pam_unix/.cvsignore b/modules/pam_unix/.cvsignore new file mode 100644 index 00000000..64c5ce5c --- /dev/null +++ b/modules/pam_unix/.cvsignore @@ -0,0 +1,4 @@ +dynamic +unix_chkpwd +*.so +*~ diff --git a/modules/pam_unix/CHANGELOG b/modules/pam_unix/CHANGELOG new file mode 100644 index 00000000..1476b579 --- /dev/null +++ b/modules/pam_unix/CHANGELOG @@ -0,0 +1,55 @@ +$Id$ + +* Mon Aug 16 1999 Jan Rêkorajski <baggins@pld.org.pl> +- fixed reentrancy problems + +* Sun Jul 4 21:03:42 PDT 1999 + +- temporarily removed the crypt16 stuff. I'm really paranoid about + crypto stuff and exporting it, and there are a few too many 's-box' + references in the code for my liking.. + +* Wed Jun 30 1999 Steve Langasek <vorlon@netexpress.net> +- further NIS+ fixes + +* Sun Jun 27 1999 Steve Langasek <vorlon@netexpress.net> +- fix to uid-handling code for NIS+ + +* Sat Jun 26 1999 Jan Rêkorajski <baggins@mimuw.edu.pl> +- merged MD5 fix and early failure syslog + by Andrey Vladimirovich Savochkin <saw@msu.ru> +- minor fixes +- added signal handler to unix_chkpwd + +* Fri Jun 25 1999 Stephen Langasek <vorlon@netexpress.net> +- reorganized the code to let it build as separate C files + +* Sun Jun 20 1999 Jan Rêkorajski <baggins@mimuw.edu.pl> +- fixes in pam_unix_auth, it incorrectly saved and restored return + value when likeauth option was used + +* Tue Jun 15 1999 Jan Rêkorajski <baggins@mimuw.edu.pl> +- added NIS+ support + +* Mon Jun 14 1999 Jan Rêkorajski <baggins@mimuw.edu.pl> +- total rewrite based on pam_pwdb module, now there is ONE pam_unix.so + module, it accepts the same options as pam_pwdb - all of them correctly ;) + (pam_pwdb dosn't understand what DISALLOW_NULL_AUTHTOK means) + +* Tue Apr 20 1999 Jan Rêkorajski <baggins@mimuw.edu.pl> +- Arghhh, pam_unix_passwd was not updating /etc/shadow when used with + pam_cracklib. + +* Mon Apr 19 1999 Jan Rêkorajski <baggins@mimuw.edu.pl> +- added "remember=XXX" option that means 'remember XXX old passwords' + Old passwords are stored in /etc/security/opasswd, there can be + maximum of 400 passwords per user. + +* Sat Mar 27 1999 Jan Rêkorajski <baggins@mimuw.edu.pl> +- added crypt16 to pam_unix_auth and pam_unix_passwd (check only, this algorithm + is too lame to use it in real life) + +* Sun Mar 21 1999 Jan Rêkorajski <baggins@mimuw.edu.pl> +- pam_unix_auth now correctly behave when user has NULL AUTHTOK +- pam_unix_auth returns PAM_PERM_DENIED when seteuid fails + diff --git a/modules/pam_unix/Makefile b/modules/pam_unix/Makefile new file mode 100644 index 00000000..89b33cfd --- /dev/null +++ b/modules/pam_unix/Makefile @@ -0,0 +1,155 @@ +# $Id$ +# +# This Makefile controls a build process of the pam_unix modules +# for Linux-PAM. You should not modify this Makefile. +# + +######################################################################## +# some options... uncomment to take effect +######################################################################## + +# do you want cracklib? +ifeq ($(HAVE_CRACKLIB),yes) +USE_CRACKLIB=-D"USE_CRACKLIB" +endif + +# do you want to use lckpwdf? +USE_LCKPWDF=-D"USE_LCKPWDF" + +# do you need to include the locking functions in the source? +#NEED_LCKPWDF=-D"NEED_LCKPWDF" + +ifeq ($(shell ./need_nsl.sh),yes) +LIBNSL = -lnsl +endif + +CHKPWD=unix_chkpwd + +EXTRAS += -DCHKPWD_HELPER=\"$(SUPLEMENTED)/$(CHKPWD)\" + +######################################################################## + +CFLAGS += $(USE_CRACKLIB) $(USE_LCKPWDF) $(NEED_LCKPWDF) $(EXTRAS) +LDLIBS = $(EXTRALS) + +ifdef USE_CRACKLIB +CRACKLIB = -lcrack +endif + + +LIBOBJ = pam_unix_auth.o pam_unix_acct.o pam_unix_sess.o pam_unix_passwd.o \ + support.o +LIBSRC = pam_unix_auth.c pam_unix_acct.c pam_unix_sess.c pam_unix_passwd.c \ + support.c +LIBOBJD = $(addprefix dynamic/,$(LIBOBJ)) +LIBOBJS = $(addprefix static/,$(LIBOBJ)) + +PLUS = md5_good.o md5_broken.o md5_crypt_good.o md5_crypt_broken.o \ + yppasswd_xdr.o bigcrypt.o + +ifdef DYNAMIC +LIBSHARED = pam_unix.so +endif +ifdef STATIC +LIBSTATIC = libpam_unix.o +endif + + +########################### don't edit below ####################### + +all: dirs info $(PLUS) $(LIBSHARED) $(LIBSTATIC) $(CHKPWD) register + +dynamic/%.o : %.c + $(CC) $(CFLAGS) $(DYNAMIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +static/%.o: %.c + $(CC) $(CFLAGS) $(STATIC) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +dummy: + @echo "**** This is not a top-level Makefile " + exit + +info: + @echo + @echo "*** Building pam-unix module of the framework..." + @echo + +dirs: +ifdef DYNAMIC + mkdir -p ./dynamic +endif +ifdef STATIC + mkdir -p ./static +endif + +register: +ifdef STATIC + ( cd .. ; ./register_static pam_unix_auth pam_unix/$(LIBSTATIC) ; \ + ./register_static pam_unix_acct "" ; \ + ./register_static pam_unix_session "" ; \ + ./register_static pam_unix_passwd "" ; \ + ) +endif + +ifdef DYNAMIC +$(LIBOBJD): $(LIBSRC) + +$(LIBSHARED): $(LIBOBJD) + $(LD_D) -o $@ $(LIBOBJD) $(PLUS) $(CRACKLIB) $(LDLIBS) $(LIBNSL) +endif + +ifdef STATIC +$(LIBOBJS): $(LIBSRC) + +$(LIBSTATIC): $(LIBOBJS) + $(LD) -r -o $@ $(LIBOBJS) $(PLUS) $(CRACKLIB) $(LDLIBS) $(LIBNSL) +endif + +$(CHKPWD): unix_chkpwd.o md5_good.o md5_broken.o \ + md5_crypt_good.o md5_crypt_broken.o \ + bigcrypt.o + $(CC) -o $(CHKPWD) $^ $(LDLIBS) + +unix_chkpwd.o: unix_chkpwd.c + $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +md5_good.o: md5.c + $(CC) $(CFLAGS) $(CPPFLAGS) -DHIGHFIRST -D'MD5Name(x)=Good##x' \ + $(TARGET_ARCH) -c $< -o $@ + +md5_broken.o: md5.c + $(CC) $(CFLAGS) $(CPPFLAGS) -D'MD5Name(x)=Broken##x' \ + $(TARGET_ARCH) -c $< -o $@ + +md5_crypt_good.o: md5_crypt.c + $(CC) $(CFLAGS) $(CPPFLAGS) -D'MD5Name(x)=Good##x' \ + $(TARGET_ARCH) -c $< -o $@ + +md5_crypt_broken.o: md5_crypt.c + $(CC) $(CFLAGS) $(CPPFLAGS) -D'MD5Name(x)=Broken##x' \ + $(TARGET_ARCH) -c $< -o $@ + +install: all + mkdir -p $(FAKEROOT)$(SECUREDIR) +ifdef DYNAMIC + install -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR) + ln -sf $(LIBSHARED) $(FAKEROOT)$(SECUREDIR)/pam_unix_auth.so + ln -sf $(LIBSHARED) $(FAKEROOT)$(SECUREDIR)/pam_unix_acct.so + ln -sf $(LIBSHARED) $(FAKEROOT)$(SECUREDIR)/pam_unix_passwd.so + ln -sf $(LIBSHARED) $(FAKEROOT)$(SECUREDIR)/pam_unix_session.so +endif + install $(CHKPWD) $(FAKEROOT)$(SUPLEMENTED) + +remove: + cd $(FAKEROOT)$(SECUREDIR) && rm -f $(LIBSHARED) + rm -f $(FAKEROOT)$(SUPLEMENTED)/$(CHKPWD) + +clean: + rm -f $(LIBOBJD) $(LIBOBJS) $(CHKPWD) *.o *.so core + +extraclean: clean + rm -f *~ *.a *.out *.bak + +.c.o: + $(CC) -c $(CFLAGS) $< + diff --git a/modules/pam_unix/README b/modules/pam_unix/README new file mode 100644 index 00000000..ad4bc35e --- /dev/null +++ b/modules/pam_unix/README @@ -0,0 +1,39 @@ +This is the README for pam_unix in Linux-PAM-0.67. +-------------------------------------------------- + +pam_unix now comes as one module pam_unix.so. + +The following links are left for compatibility with old versions: +pam_unix_auth: authentication module providing + pam_authenticate() and pam_setcred() hooks +pam_unix_sess: session module, providing session logging +pam_unix_acct: account management, providing shadow account + managment features, password aging etc.. +pam_unix_passwd: password updating facilities providing + cracklib password strength checking facilities. + +The following options are recognized: + debug - log more debugging info + audit - a little more extreme than debug + use_first_pass - don 't prompt the user for passwords + take them from PAM_ items instead + try_first_pass - don 't prompt the user for the passwords + unless PAM_(OLD)AUTHTOK is unset + use_authtok - like try_first_pass, but * fail * if the new + PAM_AUTHTOK has not been previously set. + (intended for stacking password modules only) + not_set_pass - don 't set the PAM_ items with the passwords + used by this module. + shadow - try to maintian a shadow based system. + md5 - when a user changes their password next, + encrypt it with the md5 algorithm. + bigcrypt - when a user changes their password next, + excrypt it with the DEC C2 - algorithm(0). + nodelay - used to prevent failed authentication + resulting in a delay of about 1 second. + nis - use NIS RPC for setting new password + remember=X - remember X old passwords, they are kept in + /etc/security/opasswd in MD5 crypted form + + invalid arguments are logged to syslog. + diff --git a/modules/pam_unix/bigcrypt.c b/modules/pam_unix/bigcrypt.c new file mode 100644 index 00000000..b1568d6b --- /dev/null +++ b/modules/pam_unix/bigcrypt.c @@ -0,0 +1,119 @@ +/* + * This function implements the "bigcrypt" algorithm specifically for + * Linux-PAM. + * + * This algorithm is algorithm 0 (default) shipped with the C2 secure + * implementation of Digital UNIX. + * + * Disclaimer: This work is not based on the source code to Digital + * UNIX, nor am I connected to Digital Equipment Corp, in any way + * other than as a customer. This code is based on published + * interfaces and reasonable guesswork. + * + * Description: The cleartext is divided into blocks of SEGMENT_SIZE=8 + * characters or less. Each block is encrypted using the standard UNIX + * libc crypt function. The result of the encryption for one block + * provides the salt for the suceeding block. + * + * Restrictions: The buffer used to hold the encrypted result is + * statically allocated. (see MAX_PASS_LEN below). This is necessary, + * as the returned pointer points to "static data that are overwritten + * by each call", (XPG3: XSI System Interface + Headers pg 109), and + * this is a drop in replacement for crypt(); + * + * Andy Phillips <atp@mssl.ucl.ac.uk> + */ + +#include <string.h> +#include <security/_pam_macros.h> + +char *crypt(const char *key, const char *salt); +char *bigcrypt(const char *key, const char *salt); + +/* + * Max cleartext password length in segments of 8 characters this + * function can deal with (16 segments of 8 chars= max 128 character + * password). + */ + +#define MAX_PASS_LEN 16 +#define SEGMENT_SIZE 8 +#define SALT_SIZE 2 +#define KEYBUF_SIZE ((MAX_PASS_LEN*SEGMENT_SIZE)+SALT_SIZE) +#define ESEGMENT_SIZE 11 +#define CBUF_SIZE ((MAX_PASS_LEN*ESEGMENT_SIZE)+SALT_SIZE+1) + +char *bigcrypt(const char *key, const char *salt) +{ + static char dec_c2_cryptbuf[CBUF_SIZE]; /* static storage area */ + + unsigned long int keylen, n_seg, j; + char *cipher_ptr, *plaintext_ptr, *tmp_ptr, *salt_ptr; + char keybuf[KEYBUF_SIZE + 1]; + + D(("called with key='%s', salt='%s'.", key, salt)); + + /* reset arrays */ + memset(keybuf, 0, KEYBUF_SIZE + 1); + memset(dec_c2_cryptbuf, 0, CBUF_SIZE); + + /* fill KEYBUF_SIZE with key */ + strncpy(keybuf, key, KEYBUF_SIZE); + + /* deal with case that we are doing a password check for a + conventially encrypted password: the salt will be + SALT_SIZE+ESEGMENT_SIZE long. */ + if (strlen(salt) == (SALT_SIZE + ESEGMENT_SIZE)) + keybuf[SEGMENT_SIZE] = '\0'; /* terminate password early(?) */ + + keylen = strlen(keybuf); + + if (!keylen) { + n_seg = 1; + } else { + /* work out how many segments */ + n_seg = 1 + ((keylen - 1) / SEGMENT_SIZE); + } + + if (n_seg > MAX_PASS_LEN) + n_seg = MAX_PASS_LEN; /* truncate at max length */ + + /* set up some pointers */ + cipher_ptr = dec_c2_cryptbuf; + plaintext_ptr = keybuf; + + /* do the first block with supplied salt */ + tmp_ptr = crypt(plaintext_ptr, salt); /* libc crypt() */ + + /* and place in the static area */ + strncpy(cipher_ptr, tmp_ptr, 13); + cipher_ptr += ESEGMENT_SIZE + SALT_SIZE; + plaintext_ptr += SEGMENT_SIZE; /* first block of SEGMENT_SIZE */ + + /* change the salt (1st 2 chars of previous block) - this was found + by dowsing */ + + salt_ptr = cipher_ptr - ESEGMENT_SIZE; + + /* so far this is identical to "return crypt(key, salt);", if + there is more than one block encrypt them... */ + + if (n_seg > 1) { + for (j = 2; j <= n_seg; j++) { + + tmp_ptr = crypt(plaintext_ptr, salt_ptr); + + /* skip the salt for seg!=0 */ + strncpy(cipher_ptr, (tmp_ptr + SALT_SIZE), ESEGMENT_SIZE); + + cipher_ptr += ESEGMENT_SIZE; + plaintext_ptr += SEGMENT_SIZE; + salt_ptr = cipher_ptr - ESEGMENT_SIZE; + } + } + D(("key=|%s|, salt=|%s|\nbuf=|%s|\n", key, salt, dec_c2_cryptbuf)); + + /* this is the <NUL> terminated encrypted password */ + + return dec_c2_cryptbuf; +} diff --git a/modules/pam_unix/lckpwdf.-c b/modules/pam_unix/lckpwdf.-c new file mode 100644 index 00000000..b5ff4585 --- /dev/null +++ b/modules/pam_unix/lckpwdf.-c @@ -0,0 +1,117 @@ +/* + * This is a hack, but until libc and glibc both include this function + * by default (libc only includes it if nys is not being used, at the + * moment, and glibc doesn't appear to have it at all) we need to have + * it here, too. :-( + * + * This should not become an official part of PAM. + * + * BEGIN_HACK + */ + +/* + * lckpwdf.c -- prevent simultaneous updates of password files + * + * Before modifying any of the password files, call lckpwdf(). It may block + * for up to 15 seconds trying to get the lock. Return value is 0 on success + * or -1 on failure. When you are done, call ulckpwdf() to release the lock. + * The lock is also released automatically when the process exits. Only one + * process at a time may hold the lock. + * + * These functions are supposed to be conformant with AT&T SVID Issue 3. + * + * Written by Marek Michalkiewicz <marekm@i17linuxb.ists.pwr.wroc.pl>, + * public domain. + */ + +#include <fcntl.h> +#include <signal.h> + +#define LOCKFILE "/etc/.pwd.lock" +#define TIMEOUT 15 + +static int lockfd = -1; + +static int set_close_on_exec(int fd) +{ + int flags = fcntl(fd, F_GETFD, 0); + if (flags == -1) + return -1; + flags |= FD_CLOEXEC; + return fcntl(fd, F_SETFD, flags); +} + +static int do_lock(int fd) +{ + struct flock fl; + + memset(&fl, 0, sizeof fl); + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + return fcntl(fd, F_SETLKW, &fl); +} + +static void alarm_catch(int sig) +{ +/* does nothing, but fcntl F_SETLKW will fail with EINTR */ +} + +static int lckpwdf(void) +{ + struct sigaction act, oldact; + sigset_t set, oldset; + + if (lockfd != -1) + return -1; + + lockfd = open(LOCKFILE, O_CREAT | O_WRONLY, 0600); + if (lockfd == -1) + return -1; + if (set_close_on_exec(lockfd) == -1) + goto cleanup_fd; + + memset(&act, 0, sizeof act); + act.sa_handler = alarm_catch; + act.sa_flags = 0; + sigfillset(&act.sa_mask); + if (sigaction(SIGALRM, &act, &oldact) == -1) + goto cleanup_fd; + + sigemptyset(&set); + sigaddset(&set, SIGALRM); + if (sigprocmask(SIG_UNBLOCK, &set, &oldset) == -1) + goto cleanup_sig; + + alarm(TIMEOUT); + if (do_lock(lockfd) == -1) + goto cleanup_alarm; + alarm(0); + sigprocmask(SIG_SETMASK, &oldset, NULL); + sigaction(SIGALRM, &oldact, NULL); + return 0; + + cleanup_alarm: + alarm(0); + sigprocmask(SIG_SETMASK, &oldset, NULL); + cleanup_sig: + sigaction(SIGALRM, &oldact, NULL); + cleanup_fd: + close(lockfd); + lockfd = -1; + return -1; +} + +static int ulckpwdf(void) +{ + unlink(LOCKFILE); + if (lockfd == -1) + return -1; + + if (close(lockfd) == -1) { + lockfd = -1; + return -1; + } + lockfd = -1; + return 0; +} +/* END_HACK */ diff --git a/modules/pam_unix/md5.c b/modules/pam_unix/md5.c new file mode 100644 index 00000000..d88d6810 --- /dev/null +++ b/modules/pam_unix/md5.c @@ -0,0 +1,256 @@ +/* + * $Id$ + * + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + * + */ + +#include <string.h> +#include "md5.h" + +#ifndef HIGHFIRST +#define byteReverse(buf, len) /* Nothing */ +#else +static void byteReverse(unsigned char *buf, unsigned longs); + +#ifndef ASM_MD5 +/* + * Note: this code is harmless on little-endian machines. + */ +static void byteReverse(unsigned char *buf, unsigned longs) +{ + uint32 t; + do { + t = (uint32) ((unsigned) buf[3] << 8 | buf[2]) << 16 | + ((unsigned) buf[1] << 8 | buf[0]); + *(uint32 *) buf = t; + buf += 4; + } while (--longs); +} +#endif +#endif + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void MD5Name(MD5Init)(struct MD5Context *ctx) +{ + ctx->buf[0] = 0x67452301U; + ctx->buf[1] = 0xefcdab89U; + ctx->buf[2] = 0x98badcfeU; + ctx->buf[3] = 0x10325476U; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void MD5Name(MD5Update)(struct MD5Context *ctx, unsigned const char *buf, unsigned len) +{ + uint32 t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((uint32) len << 3)) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if (t) { + unsigned char *p = (unsigned char *) ctx->in + t; + + t = 64 - t; + if (len < t) { + memcpy(p, buf, len); + return; + } + memcpy(p, buf, t); + byteReverse(ctx->in, 16); + MD5Name(MD5Transform)(ctx->buf, (uint32 *) ctx->in); + buf += t; + len -= t; + } + /* Process data in 64-byte chunks */ + + while (len >= 64) { + memcpy(ctx->in, buf, 64); + byteReverse(ctx->in, 16); + MD5Name(MD5Transform)(ctx->buf, (uint32 *) ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + + memcpy(ctx->in, buf, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void MD5Name(MD5Final)(unsigned char digest[16], struct MD5Context *ctx) +{ + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset(p, 0, count); + byteReverse(ctx->in, 16); + MD5Name(MD5Transform)(ctx->buf, (uint32 *) ctx->in); + + /* Now fill the next block with 56 bytes */ + memset(ctx->in, 0, 56); + } else { + /* Pad block to 56 bytes */ + memset(p, 0, count - 8); + } + byteReverse(ctx->in, 14); + + /* Append length in bits and transform */ + ((uint32 *) ctx->in)[14] = ctx->bits[0]; + ((uint32 *) ctx->in)[15] = ctx->bits[1]; + + MD5Name(MD5Transform)(ctx->buf, (uint32 *) ctx->in); + byteReverse((unsigned char *) ctx->buf, 4); + memcpy(digest, ctx->buf, 16); + memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */ +} + +#ifndef ASM_MD5 + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +void MD5Name(MD5Transform)(uint32 buf[4], uint32 const in[16]) +{ + register uint32 a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478U, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756U, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070dbU, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceeeU, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0fafU, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62aU, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613U, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501U, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8U, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7afU, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1U, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7beU, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122U, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193U, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438eU, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821U, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562U, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340U, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51U, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aaU, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105dU, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453U, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681U, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8U, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6U, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6U, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87U, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14edU, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905U, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8U, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9U, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8aU, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942U, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681U, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122U, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380cU, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44U, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9U, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60U, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70U, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6U, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127faU, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085U, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05U, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039U, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5U, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8U, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665U, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244U, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97U, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7U, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039U, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3U, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92U, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47dU, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1U, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4fU, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0U, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314U, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1U, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82U, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235U, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bbU, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391U, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +#endif diff --git a/modules/pam_unix/md5.h b/modules/pam_unix/md5.h new file mode 100644 index 00000000..469e5bd1 --- /dev/null +++ b/modules/pam_unix/md5.h @@ -0,0 +1,35 @@ + +#ifndef MD5_H +#define MD5_H + +#ifdef __alpha +typedef unsigned int uint32; +#else +typedef unsigned long uint32; +#endif + +struct MD5Context { + uint32 buf[4]; + uint32 bits[2]; + unsigned char in[64]; +}; + +void GoodMD5Init(struct MD5Context *); +void GoodMD5Update(struct MD5Context *, unsigned const char *, unsigned); +void GoodMD5Final(unsigned char digest[16], struct MD5Context *); +void GoodMD5Transform(uint32 buf[4], uint32 const in[16]); +void BrokenMD5Init(struct MD5Context *); +void BrokenMD5Update(struct MD5Context *, unsigned const char *, unsigned); +void BrokenMD5Final(unsigned char digest[16], struct MD5Context *); +void BrokenMD5Transform(uint32 buf[4], uint32 const in[16]); + +char *Goodcrypt_md5(const char *pw, const char *salt); +char *Brokencrypt_md5(const char *pw, const char *salt); + +/* + * This is needed to make RSAREF happy on some MS-DOS compilers. + */ + +typedef struct MD5Context MD5_CTX; + +#endif /* MD5_H */ diff --git a/modules/pam_unix/md5_crypt.c b/modules/pam_unix/md5_crypt.c new file mode 100644 index 00000000..a7243a2e --- /dev/null +++ b/modules/pam_unix/md5_crypt.c @@ -0,0 +1,149 @@ +/* + * $Id$ + * + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * <phk@login.dknet.dk> wrote this file. As long as you retain this notice you + * can do whatever you want with this stuff. If we meet some day, and you think + * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp + * ---------------------------------------------------------------------------- + * + * Origin: Id: crypt.c,v 1.3 1995/05/30 05:42:22 rgrimes Exp + * + */ + +#include <string.h> +#include "md5.h" + +static unsigned char itoa64[] = /* 0 ... 63 => ascii - 64 */ +"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + +static void to64(char *s, unsigned long v, int n) +{ + while (--n >= 0) { + *s++ = itoa64[v & 0x3f]; + v >>= 6; + } +} + +/* + * UNIX password + * + * Use MD5 for what it is best at... + */ + +char *MD5Name(crypt_md5)(const char *pw, const char *salt) +{ + const char *magic = "$1$"; + /* This string is magic for this algorithm. Having + * it this way, we can get get better later on */ + static char passwd[120], *p; + static const char *sp, *ep; + unsigned char final[16]; + int sl, pl, i, j; + MD5_CTX ctx, ctx1; + unsigned long l; + + /* Refine the Salt first */ + sp = salt; + + /* If it starts with the magic string, then skip that */ + if (!strncmp(sp, magic, strlen(magic))) + sp += strlen(magic); + + /* It stops at the first '$', max 8 chars */ + for (ep = sp; *ep && *ep != '$' && ep < (sp + 8); ep++) + continue; + + /* get the length of the true salt */ + sl = ep - sp; + + MD5Name(MD5Init)(&ctx); + + /* The password first, since that is what is most unknown */ + MD5Name(MD5Update)(&ctx,(unsigned const char *)pw,strlen(pw)); + + /* Then our magic string */ + MD5Name(MD5Update)(&ctx,(unsigned const char *)magic,strlen(magic)); + + /* Then the raw salt */ + MD5Name(MD5Update)(&ctx,(unsigned const char *)sp,sl); + + /* Then just as many characters of the MD5(pw,salt,pw) */ + MD5Name(MD5Init)(&ctx1); + MD5Name(MD5Update)(&ctx1,(unsigned const char *)pw,strlen(pw)); + MD5Name(MD5Update)(&ctx1,(unsigned const char *)sp,sl); + MD5Name(MD5Update)(&ctx1,(unsigned const char *)pw,strlen(pw)); + MD5Name(MD5Final)(final,&ctx1); + for (pl = strlen(pw); pl > 0; pl -= 16) + MD5Name(MD5Update)(&ctx,(unsigned const char *)final,pl>16 ? 16 : pl); + + /* Don't leave anything around in vm they could use. */ + memset(final, 0, sizeof final); + + /* Then something really weird... */ + for (j = 0, i = strlen(pw); i; i >>= 1) + if (i & 1) + MD5Name(MD5Update)(&ctx, (unsigned const char *)final+j, 1); + else + MD5Name(MD5Update)(&ctx, (unsigned const char *)pw+j, 1); + + /* Now make the output string */ + strcpy(passwd, magic); + strncat(passwd, sp, sl); + strcat(passwd, "$"); + + MD5Name(MD5Final)(final,&ctx); + + /* + * and now, just to make sure things don't run too fast + * On a 60 Mhz Pentium this takes 34 msec, so you would + * need 30 seconds to build a 1000 entry dictionary... + */ + for (i = 0; i < 1000; i++) { + MD5Name(MD5Init)(&ctx1); + if (i & 1) + MD5Name(MD5Update)(&ctx1,(unsigned const char *)pw,strlen(pw)); + else + MD5Name(MD5Update)(&ctx1,(unsigned const char *)final,16); + + if (i % 3) + MD5Name(MD5Update)(&ctx1,(unsigned const char *)sp,sl); + + if (i % 7) + MD5Name(MD5Update)(&ctx1,(unsigned const char *)pw,strlen(pw)); + + if (i & 1) + MD5Name(MD5Update)(&ctx1,(unsigned const char *)final,16); + else + MD5Name(MD5Update)(&ctx1,(unsigned const char *)pw,strlen(pw)); + MD5Name(MD5Final)(final,&ctx1); + } + + p = passwd + strlen(passwd); + + l = (final[0] << 16) | (final[6] << 8) | final[12]; + to64(p, l, 4); + p += 4; + l = (final[1] << 16) | (final[7] << 8) | final[13]; + to64(p, l, 4); + p += 4; + l = (final[2] << 16) | (final[8] << 8) | final[14]; + to64(p, l, 4); + p += 4; + l = (final[3] << 16) | (final[9] << 8) | final[15]; + to64(p, l, 4); + p += 4; + l = (final[4] << 16) | (final[10] << 8) | final[5]; + to64(p, l, 4); + p += 4; + l = final[11]; + to64(p, l, 2); + p += 2; + *p = '\0'; + + /* Don't leave anything around in vm they could use. */ + memset(final, 0, sizeof final); + + return passwd; +} diff --git a/modules/pam_unix/need_nsl.sh b/modules/pam_unix/need_nsl.sh new file mode 100755 index 00000000..23f38ec0 --- /dev/null +++ b/modules/pam_unix/need_nsl.sh @@ -0,0 +1,7 @@ +#!/bin/sh +list=`/bin/ls /lib/libnsl.so.* 2> /dev/null` +if [ -z "$list" ]; then + echo no +else + echo yes +fi diff --git a/modules/pam_unix/pam_unix_acct.c b/modules/pam_unix/pam_unix_acct.c new file mode 100644 index 00000000..f86f56e5 --- /dev/null +++ b/modules/pam_unix/pam_unix_acct.c @@ -0,0 +1,210 @@ +/* + * Copyright Elliot Lee, 1996. All rights reserved. + * Copyright Jan Rêkorajski, 1999. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define _BSD_SOURCE + +#ifdef linux +#define _GNU_SOURCE +#include <features.h> +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <syslog.h> +#include <pwd.h> +#include <shadow.h> +#include <time.h> /* for time() */ + + +#include <security/_pam_macros.h> + +/* indicate that the following groups are defined */ + +#define PAM_SM_ACCOUNT + +#include <security/pam_modules.h> + +#ifndef LINUX_PAM +#include <security/pam_appl.h> +#endif /* LINUX_PAM */ + +#include "support.h" + +/* + * PAM framework looks for this entry-point to pass control to the + * account management module. + */ + +PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t * pamh, int flags, + int argc, const char **argv) +{ + unsigned int ctrl; + const char *uname; + int retval, daysleft; + time_t curdays; + struct spwd *spent; + struct passwd *pwent; + char buf[80]; + + D(("called.")); + + ctrl = _set_ctrl(flags, NULL, argc, argv); + + retval = pam_get_item(pamh, PAM_USER, (const void **) &uname); + D(("user = `%s'", uname)); + if (retval != PAM_SUCCESS || uname == NULL) { + _log_err(LOG_ALERT + ,"could not identify user (from uid=%d)" + ,getuid()); + return PAM_USER_UNKNOWN; + } + + pwent = getpwnam(uname); + if (!pwent) { + _log_err(LOG_ALERT + ,"could not identify user (from getpwnam(%s))" + ,uname); + return PAM_USER_UNKNOWN; + } + + if (!strcmp( pwent->pw_passwd, "*NP*" )) { /* NIS+ */ + uid_t save_euid, save_uid; + + save_euid = geteuid(); + save_uid = getuid(); + if (save_uid == pwent->pw_uid) + setreuid( save_euid, save_uid ); + else { + setreuid( 0, -1 ); + if (setreuid( -1, pwent->pw_uid ) == -1) { + setreuid( -1, 0 ); + setreuid( 0, -1 ); + if(setreuid( -1, pwent->pw_uid ) == -1) + return PAM_CRED_INSUFFICIENT; + } + } + spent = getspnam( uname ); + if (save_uid == pwent->pw_uid) + setreuid( save_uid, save_euid ); + else { + if (setreuid( -1, 0 ) == -1) + setreuid( save_uid, -1 ); + setreuid( -1, save_euid ); + } + + } else if (!strcmp( pwent->pw_passwd, "x" )) { + spent = getspnam(uname); + } else { + return PAM_SUCCESS; + } + + if (!spent) + return PAM_AUTHINFO_UNAVAIL; /* Couldn't get username from shadow */ + + curdays = time(NULL) / (60 * 60 * 24); + D(("today is %d, last change %d", curdays, spent->sp_lstchg)); + if ((curdays > spent->sp_expire) && (spent->sp_expire != -1) + && (spent->sp_lstchg != 0)) { + _log_err(LOG_NOTICE + ,"account %s has expired (account expired)" + ,uname); + _make_remark(pamh, ctrl, PAM_ERROR_MSG, + "Your account has expired; please contact your system administrator"); + D(("account expired")); + return PAM_ACCT_EXPIRED; + } + if ((curdays > (spent->sp_lstchg + spent->sp_max + spent->sp_inact)) + && (spent->sp_max != -1) && (spent->sp_inact != -1) + && (spent->sp_lstchg != 0)) { + _log_err(LOG_NOTICE + ,"account %s has expired (failed to change password)" + ,uname); + _make_remark(pamh, ctrl, PAM_ERROR_MSG, + "Your account has expired; please contact your system administrator"); + D(("account expired 2")); + return PAM_ACCT_EXPIRED; + } + D(("when was the last change")); + if (spent->sp_lstchg == 0) { + _log_err(LOG_NOTICE + ,"expired password for user %s (root enforced)" + ,uname); + _make_remark(pamh, ctrl, PAM_ERROR_MSG, + "You are required to change your password immediately (root enforced)"); + D(("need a new password")); + return PAM_NEW_AUTHTOK_REQD; + } + if (((spent->sp_lstchg + spent->sp_max) < curdays) && (spent->sp_max != -1)) { + _log_err(LOG_DEBUG + ,"expired password for user %s (password aged)" + ,uname); + _make_remark(pamh, ctrl, PAM_ERROR_MSG, + "You are required to change your password immediately (password aged)"); + D(("need a new password 2")); + return PAM_NEW_AUTHTOK_REQD; + } + if ((curdays > (spent->sp_lstchg + spent->sp_max - spent->sp_warn)) + && (spent->sp_max != -1) && (spent->sp_warn != -1)) { + daysleft = (spent->sp_lstchg + spent->sp_max) - curdays; + _log_err(LOG_DEBUG + ,"password for user %s will expire in %d days" + ,uname, daysleft); + snprintf(buf, 80, "Warning: your password will expire in %d day%.2s", + daysleft, daysleft == 1 ? "" : "s"); + _make_remark(pamh, ctrl, PAM_TEXT_INFO, buf); + } + + D(("all done")); + + return PAM_SUCCESS; +} + + +/* static module data */ +#ifdef PAM_STATIC +struct pam_module _pam_unix_acct_modstruct = { + "pam_unix_acct", + NULL, + NULL, + pam_sm_acct_mgmt, + NULL, + NULL, + NULL, +}; +#endif diff --git a/modules/pam_unix/pam_unix_auth.c b/modules/pam_unix/pam_unix_auth.c new file mode 100644 index 00000000..3c301df0 --- /dev/null +++ b/modules/pam_unix/pam_unix_auth.c @@ -0,0 +1,220 @@ +/* + * Copyright Alexander O. Yuriev, 1996. All rights reserved. + * NIS+ support by Thorsten Kukuk <kukuk@weber.uni-paderborn.de> + * Copyright Jan Rêkorajski, 1999. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* #define DEBUG */ + +#ifdef linux +#define _GNU_SOURCE +#include <features.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <ctype.h> +#include <sys/types.h> +#include <sys/stat.h> + +/* indicate the following groups are defined */ + +#define PAM_SM_AUTH + +#define _PAM_EXTERN_FUNCTIONS +#include <security/_pam_macros.h> +#include <security/pam_modules.h> + +#ifndef LINUX_PAM +#include <security/pam_appl.h> +#endif /* LINUX_PAM */ + +#include "support.h" + +/* + * PAM framework looks for these entry-points to pass control to the + * authentication module. + */ + +/* Fun starts here :) + + * pam_sm_authenticate() performs UNIX/shadow authentication + * + * First, if shadow support is available, attempt to perform + * authentication using shadow passwords. If shadow is not + * available, or user does not have a shadow password, fallback + * onto a normal UNIX authentication + */ + +#define _UNIX_AUTHTOK "-UN*X-PASS" + +#define AUTH_RETURN \ +{ \ + if (on(UNIX_LIKE_AUTH, ctrl)) { \ + D(("recording return code for next time [%d]", \ + retval)); \ + pam_set_data(pamh, "unix_setcred_return", \ + (void *) &retval, NULL); \ + } \ + D(("done. [%s]", pam_strerror(pamh, retval))); \ + return retval; \ +} + +PAM_EXTERN int pam_sm_authenticate(pam_handle_t * pamh, int flags + ,int argc, const char **argv) +{ + unsigned int ctrl; + int retval; + const char *name, *p; + + D(("called.")); + + ctrl = _set_ctrl(flags, NULL, argc, argv); + + /* get the user'name' */ + + retval = pam_get_user(pamh, &name, "login: "); + if (retval == PAM_SUCCESS) { + /* + * Various libraries at various times have had bugs related to + * '+' or '-' as the first character of a user name. Don't take + * any chances here. Require that the username starts with an + * alphanumeric character. + */ + if (name == NULL || !isalnum(*name)) { + _log_err(LOG_ERR, "bad username [%s]", name); + retval = PAM_USER_UNKNOWN; + AUTH_RETURN + } + if (retval == PAM_SUCCESS && on(UNIX_DEBUG, ctrl)) + D(("username [%s] obtained", name)); + } else { + D(("trouble reading username")); + if (retval == PAM_CONV_AGAIN) { + D(("pam_get_user/conv() function is not ready yet")); + /* it is safe to resume this function so we translate this + * retval to the value that indicates we're happy to resume. + */ + retval = PAM_INCOMPLETE; + } + AUTH_RETURN + } + + /* if this user does not have a password... */ + + if (_unix_blankpasswd(ctrl, name)) { + D(("user '%s' has blank passwd", name)); + name = NULL; + retval = PAM_SUCCESS; + AUTH_RETURN + } + /* get this user's authentication token */ + + retval = _unix_read_password(pamh, ctrl, NULL, "Password: ", NULL + ,_UNIX_AUTHTOK, &p); + if (retval != PAM_SUCCESS) { + if (retval != PAM_CONV_AGAIN) { + _log_err(LOG_CRIT, "auth could not identify password for [%s]" + ,name); + } else { + D(("conversation function is not ready yet")); + /* + * it is safe to resume this function so we translate this + * retval to the value that indicates we're happy to resume. + */ + retval = PAM_INCOMPLETE; + } + name = NULL; + AUTH_RETURN + } + D(("user=%s, password=[%s]", name, p)); + + /* verify the password of this user */ + retval = _unix_verify_password(pamh, name, p, ctrl); + name = p = NULL; + + AUTH_RETURN +} + + +/* + * The only thing _pam_set_credentials_unix() does is initialization of + * UNIX group IDs. + * + * Well, everybody but me on linux-pam is convinced that it should not + * initialize group IDs, so I am not doing it but don't say that I haven't + * warned you. -- AOY + */ + +PAM_EXTERN int pam_sm_setcred(pam_handle_t * pamh, int flags + ,int argc, const char **argv) +{ + unsigned int ctrl; + int retval; + + D(("called.")); + + /* FIXME: it shouldn't be necessary to parse the arguments again. The + only argument we need is UNIX_LIKE_AUTH: if it was set, + pam_get_data will succeed. If it wasn't, it will fail, and we + return PAM_SUCCESS. -SRL */ + ctrl = _set_ctrl(flags, NULL, argc, argv); + retval = PAM_SUCCESS; + + if (on(UNIX_LIKE_AUTH, ctrl)) { + int *pretval = &retval; + + D(("recovering return code from auth call")); + pam_get_data(pamh, "unix_setcred_return", (const void **) &pretval); + pam_set_data(pamh, "unix_setcred_return", NULL, NULL); + D(("recovered data indicates that old retval was %d", retval)); + } + return retval; +} + +#ifdef PAM_STATIC +struct pam_module _pam_unix_auth_modstruct = { + "pam_unix_auth", + pam_sm_authenticate, + pam_sm_setcred, + NULL, + NULL, + NULL, + NULL, +}; +#endif diff --git a/modules/pam_unix/pam_unix_passwd.c b/modules/pam_unix/pam_unix_passwd.c new file mode 100644 index 00000000..cfa294f8 --- /dev/null +++ b/modules/pam_unix/pam_unix_passwd.c @@ -0,0 +1,985 @@ +/* + * Main coding by Elliot Lee <sopwith@redhat.com>, Red Hat Software. + * Copyright (C) 1996. + * Copyright (c) Jan Rêkorajski, 1999. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define _BSD_SOURCE +#define __USE_SVID + +#ifdef linux +#define _GNU_SOURCE +#include <features.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <malloc.h> +#include <unistd.h> +#include <errno.h> +#include <pwd.h> +#include <syslog.h> +#include <shadow.h> +#include <time.h> /* for time() */ +#include <fcntl.h> +#include <ctype.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <rpc/rpc.h> +#include <rpcsvc/yp_prot.h> +#include <rpcsvc/ypclnt.h> + +#ifdef USE_CRACKLIB +#include <crack.h> +#endif + +#include <security/_pam_macros.h> + +/* indicate the following groups are defined */ + +#define PAM_SM_PASSWORD + +#include <security/pam_modules.h> + +#ifndef LINUX_PAM +#include <security/pam_appl.h> +#endif /* LINUX_PAM */ + +#include "yppasswd.h" +#include "md5.h" +#include "support.h" + +#if !((__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 1)) +extern int getrpcport(const char *host, unsigned long prognum, + unsigned long versnum, unsigned int proto); +#endif /* GNU libc 2.1 */ + +/* + * PAM framework looks for these entry-points to pass control to the + * password changing module. + */ + +#ifdef NEED_LCKPWDF +#include "./lckpwdf.-c" +#endif + +extern char *bigcrypt(const char *key, const char *salt); + +/* + How it works: + Gets in username (has to be done) from the calling program + Does authentication of user (only if we are not running as root) + Gets new password/checks for sanity + Sets it. + */ + +/* passwd/salt conversion macros */ + +#define ascii_to_bin(c) ((c)>='a'?(c-59):(c)>='A'?((c)-53):(c)-'.') +#define bin_to_ascii(c) ((c)>=38?((c)-38+'a'):(c)>=12?((c)-12+'A'):(c)+'.') + +/* data tokens */ + +#define _UNIX_OLD_AUTHTOK "-UN*X-OLD-PASS" +#define _UNIX_NEW_AUTHTOK "-UN*X-NEW-PASS" + +#define MAX_PASSWD_TRIES 3 +#define PW_TMPFILE "/etc/npasswd" +#define SH_TMPFILE "/etc/nshadow" +#define CRACKLIB_DICTS "/usr/share/dict/cracklib_dict" +#define OPW_TMPFILE "/etc/security/nopasswd" +#define OLD_PASSWORDS_FILE "/etc/security/opasswd" + +/* + * i64c - convert an integer to a radix 64 character + */ +static int i64c(int i) +{ + if (i < 0) + return ('.'); + else if (i > 63) + return ('z'); + if (i == 0) + return ('.'); + if (i == 1) + return ('/'); + if (i >= 2 && i <= 11) + return ('0' - 2 + i); + if (i >= 12 && i <= 37) + return ('A' - 12 + i); + if (i >= 38 && i <= 63) + return ('a' - 38 + i); + return ('\0'); +} + +static char *crypt_md5_wrapper(const char *pass_new) +{ + /* + * Code lifted from Marek Michalkiewicz's shadow suite. (CG) + * removed use of static variables (AGM) + */ + + struct timeval tv; + MD5_CTX ctx; + unsigned char result[16]; + char *cp = (char *) result; + unsigned char tmp[16]; + int i; + char *x, *e = NULL; + + GoodMD5Init(&ctx); + gettimeofday(&tv, (struct timezone *) 0); + GoodMD5Update(&ctx, (void *) &tv, sizeof tv); + i = getpid(); + GoodMD5Update(&ctx, (void *) &i, sizeof i); + i = clock(); + GoodMD5Update(&ctx, (void *) &i, sizeof i); + GoodMD5Update(&ctx, result, sizeof result); + GoodMD5Final(tmp, &ctx); + strcpy(cp, "$1$"); /* magic for the MD5 */ + cp += strlen(cp); + for (i = 0; i < 8; i++) + *cp++ = i64c(tmp[i] & 077); + *cp = '\0'; + + /* no longer need cleartext */ + e = Goodcrypt_md5(pass_new, (const char *) result); + x = x_strdup(e); /* put e in malloc()ed memory */ + _pam_overwrite(e); /* clean up */ + + return x; +} + +static char *getNISserver(void) +{ + char *master; + char *domainname; + int port, err; + + if ((err = yp_get_default_domain(&domainname)) != 0) { + _log_err(LOG_WARNING, "can't get local yp domain: %s\n", + yperr_string(err)); + return NULL; + } + if ((err = yp_master(domainname, "passwd.byname", &master)) != 0) { + _log_err(LOG_WARNING, "can't find the master ypserver: %s\n", + yperr_string(err)); + return NULL; + } + port = getrpcport(master, YPPASSWDPROG, YPPASSWDPROC_UPDATE, IPPROTO_UDP); + if (port == 0) { + _log_err(LOG_WARNING, "yppasswdd not running on NIS master host\n"); + return NULL; + } + if (port >= IPPORT_RESERVED) { + _log_err(LOG_WARNING, "yppasswd daemon running on illegal port.\n"); + return NULL; + } + return master; +} + +static int check_old_password(const char *forwho, const char *newpass) +{ + static char buf[16384]; + char *s_luser, *s_uid, *s_npas, *s_pas; + int retval = PAM_SUCCESS; + FILE *opwfile; + + opwfile = fopen(OLD_PASSWORDS_FILE, "r"); + if (opwfile == NULL) + return PAM_AUTHTOK_ERR; + + while (fgets(buf, 16380, opwfile)) { + if (!strncmp(buf, forwho, strlen(forwho))) { + buf[strlen(buf) - 1] = '\0'; + s_luser = strtok(buf, ":,"); + s_uid = strtok(NULL, ":,"); + s_npas = strtok(NULL, ":,"); + s_pas = strtok(NULL, ":,"); + while (s_pas != NULL) { + if (!strcmp(Goodcrypt_md5(newpass, s_pas), s_pas)) { + retval = PAM_AUTHTOK_ERR; + break; + } + s_pas = strtok(NULL, ":,"); + } + break; + } + } + fclose(opwfile); + + return retval; +} + +static int save_old_password(const char *forwho, const char *oldpass, int howmany) +{ + static char buf[16384]; + static char nbuf[16384]; + char *s_luser, *s_uid, *s_npas, *s_pas, *pass; + int retval = 0, npas; + FILE *pwfile, *opwfile; + int err = 0; + int oldmask; + int found = 0; + struct passwd *pwd = NULL; + + if (howmany < 0) + return retval; + + if (oldpass == NULL) + return retval; + + oldmask = umask(077); + pwfile = fopen(OPW_TMPFILE, "w"); + umask(oldmask); + opwfile = fopen(OLD_PASSWORDS_FILE, "r"); + if (pwfile == NULL || opwfile == NULL) + return PAM_AUTHTOK_ERR; + chown(OPW_TMPFILE, 0, 0); + chmod(OPW_TMPFILE, 0600); + + while (fgets(buf, 16380, opwfile)) { + if (!strncmp(buf, forwho, strlen(forwho))) { + buf[strlen(buf) - 1] = '\0'; + s_luser = strtok(buf, ":"); + s_uid = strtok(NULL, ":"); + s_npas = strtok(NULL, ":"); + s_pas = strtok(NULL, ":"); + npas = strtol(s_npas, NULL, 10) + 1; + while (npas > howmany) { + s_pas = strpbrk(s_pas, ","); + if (s_pas != NULL) + s_pas++; + npas--; + } + pass = crypt_md5_wrapper(oldpass); + if (s_pas == NULL) + sprintf(nbuf, "%s:%s:%d:%s\n", s_luser, s_uid, npas, pass); + else + sprintf(nbuf, "%s:%s:%d:%s,%s\n", s_luser, s_uid, npas, s_pas, pass); + if (fputs(nbuf, pwfile) < 0) { + retval = PAM_AUTHTOK_ERR; + err = 1; + break; + } + found = 1; + } else if (fputs(buf, pwfile) < 0) { + retval = PAM_AUTHTOK_ERR; + err = 1; + break; + } + } + fclose(opwfile); + if (!found) { + pwd = getpwnam(forwho); + if (pwd == NULL) { + retval = PAM_AUTHTOK_ERR; + err = 1; + } else { + pass = crypt_md5_wrapper(oldpass); + sprintf(nbuf, "%s:%d:1:%s\n", forwho, pwd->pw_uid, pass); + if (fputs(nbuf, pwfile) < 0) { + retval = PAM_AUTHTOK_ERR; + err = 1; + } + } + } + if (fclose(pwfile)) { + fprintf(stderr, "error writing entries to old passwords file: %s\n", + strerror(errno)); + retval = PAM_AUTHTOK_ERR; + err = 1; + } + if (!err) + rename(OPW_TMPFILE, OLD_PASSWORDS_FILE); + else + unlink(OPW_TMPFILE); + + return retval; +} + +static int _update_passwd(const char *forwho, char *towhat) +{ + struct passwd *tmpent = NULL; + FILE *pwfile, *opwfile; + int retval = 0; + int err = 0; + int oldmask; + + oldmask = umask(077); + pwfile = fopen(PW_TMPFILE, "w"); + umask(oldmask); + opwfile = fopen("/etc/passwd", "r"); + if (pwfile == NULL || opwfile == NULL) + return PAM_AUTHTOK_ERR; + chown(PW_TMPFILE, 0, 0); + chmod(PW_TMPFILE, 0644); + tmpent = fgetpwent(opwfile); + while (tmpent) { + if (!strcmp(tmpent->pw_name, forwho)) { + tmpent->pw_passwd = towhat; + } + if (putpwent(tmpent, pwfile)) { + fprintf(stderr, "error writing entry to password file: %s\n", + strerror(errno)); + err = 1; + retval = PAM_AUTHTOK_ERR; + break; + } + tmpent = fgetpwent(opwfile); + } + fclose(opwfile); + + if (fclose(pwfile)) { + fprintf(stderr, "error writing entries to password file: %s\n", + strerror(errno)); + retval = PAM_AUTHTOK_ERR; + err = 1; + } + if (!err) + rename(PW_TMPFILE, "/etc/passwd"); + else + unlink(PW_TMPFILE); + + return retval; +} + +static int _update_shadow(const char *forwho, char *towhat) +{ + struct spwd *spwdent = NULL, *stmpent = NULL; + FILE *pwfile, *opwfile; + int retval = 0; + int err = 0; + int oldmask; + + spwdent = getspnam(forwho); + if (spwdent == NULL) + return PAM_USER_UNKNOWN; + oldmask = umask(077); + pwfile = fopen(SH_TMPFILE, "w"); + umask(oldmask); + opwfile = fopen("/etc/shadow", "r"); + if (pwfile == NULL || opwfile == NULL) + return PAM_AUTHTOK_ERR; + chown(SH_TMPFILE, 0, 0); + chmod(SH_TMPFILE, 0600); + stmpent = fgetspent(opwfile); + while (stmpent) { + if (!strcmp(stmpent->sp_namp, forwho)) { + stmpent->sp_pwdp = towhat; + stmpent->sp_lstchg = time(NULL) / (60 * 60 * 24); + + D(("Set password %s for %s", stmpent->sp_pwdp, forwho)); + } + if (putspent(stmpent, pwfile)) { + fprintf(stderr, "error writing entry to shadow file: %s\n", + strerror(errno)); + err = 1; + retval = PAM_AUTHTOK_ERR; + break; + } + stmpent = fgetspent(opwfile); + } + fclose(opwfile); + + if (fclose(pwfile)) { + fprintf(stderr, "error writing entries to shadow file: %s\n", + strerror(errno)); + retval = PAM_AUTHTOK_ERR; + err = 1; + } + if (!err) + rename(SH_TMPFILE, "/etc/shadow"); + else + unlink(SH_TMPFILE); + + return retval; +} + +static int _do_setpass(const char *forwho, char *fromwhat, char *towhat, + unsigned int ctrl, int remember) +{ + struct passwd *pwd = NULL; + int retval = 0; + + D(("called")); + + setpwent(); + pwd = getpwnam(forwho); + endpwent(); + + if (pwd == NULL) + return PAM_AUTHTOK_ERR; + + if (on(UNIX_NIS, ctrl)) { + struct timeval timeout; + struct yppasswd yppwd; + CLIENT *clnt; + char *master; + int status; + int err = 0; + + /* Make RPC call to NIS server */ + if ((master = getNISserver()) == NULL) + return PAM_TRY_AGAIN; + + /* Initialize password information */ + yppwd.newpw.pw_passwd = pwd->pw_passwd; + yppwd.newpw.pw_name = pwd->pw_name; + yppwd.newpw.pw_uid = pwd->pw_uid; + yppwd.newpw.pw_gid = pwd->pw_gid; + yppwd.newpw.pw_gecos = pwd->pw_gecos; + yppwd.newpw.pw_dir = pwd->pw_dir; + yppwd.newpw.pw_shell = pwd->pw_shell; + yppwd.oldpass = fromwhat; + yppwd.newpw.pw_passwd = towhat; + + D(("Set password %s for %s", yppwd.newpw.pw_passwd, forwho)); + + /* The yppasswd.x file said `unix authentication required', + * so I added it. This is the only reason it is in here. + * My yppasswdd doesn't use it, but maybe some others out there + * do. --okir + */ + clnt = clnt_create(master, YPPASSWDPROG, YPPASSWDVERS, "udp"); + clnt->cl_auth = authunix_create_default(); + memset((char *) &status, '\0', sizeof(status)); + timeout.tv_sec = 25; + timeout.tv_usec = 0; + err = clnt_call(clnt, YPPASSWDPROC_UPDATE, + (xdrproc_t) xdr_yppasswd, (char *) &yppwd, + (xdrproc_t) xdr_int, (char *) &status, + timeout); + + if (err) { + clnt_perrno(err); + retval = PAM_TRY_AGAIN; + } else if (status) { + fprintf(stderr, "Error while changing NIS password.\n"); + retval = PAM_TRY_AGAIN; + } + printf("\nThe password has%s been changed on %s.\n", + (err || status) ? " not" : "", master); + + auth_destroy(clnt->cl_auth); + clnt_destroy(clnt); + if ((err || status) != 0) { + retval = PAM_TRY_AGAIN; + } +#ifdef DEBUG + sleep(5); +#endif + return retval; + } + /* first, save old password */ + if (save_old_password(forwho, fromwhat, remember)) { + return PAM_AUTHTOK_ERR; + } + if (on(UNIX_SHADOW, ctrl) || (strcmp(pwd->pw_passwd, "x") == 0)) { + retval = _update_shadow(forwho, towhat); + if (retval == PAM_SUCCESS) + retval = _update_passwd(forwho, "x"); + } else { + retval = _update_passwd(forwho, towhat); + } + + return retval; +} + +static int _unix_verify_shadow(const char *user, unsigned int ctrl) +{ + struct passwd *pwd = NULL; /* Password and shadow password */ + struct spwd *spwdent = NULL; /* file entries for the user */ + time_t curdays; + int retval = PAM_SUCCESS; + + /* UNIX passwords area */ + setpwent(); + pwd = getpwnam(user); /* Get password file entry... */ + endpwent(); + if (pwd == NULL) + return PAM_AUTHINFO_UNAVAIL; /* We don't need to do the rest... */ + + if (strcmp(pwd->pw_passwd, "x") == 0) { + /* ...and shadow password file entry for this user, if shadowing + is enabled */ + setspent(); + spwdent = getspnam(user); + endspent(); + + if (spwdent == NULL) + return PAM_AUTHINFO_UNAVAIL; + } else { + if (strcmp(pwd->pw_passwd,"*NP*") == 0) { /* NIS+ */ + uid_t save_uid; + + save_uid = geteuid(); + seteuid (pwd->pw_uid); + spwdent = getspnam( user ); + seteuid (save_uid); + + if (spwdent == NULL) + return PAM_AUTHINFO_UNAVAIL; + } else + spwdent = NULL; + } + + if (spwdent != NULL) { + /* We have the user's information, now let's check if their account + has expired (60 * 60 * 24 = number of seconds in a day) */ + + if (off(UNIX__IAMROOT, ctrl)) { + /* Get the current number of days since 1970 */ + curdays = time(NULL) / (60 * 60 * 24); + if ((curdays < (spwdent->sp_lstchg + spwdent->sp_min)) + && (spwdent->sp_min != -1)) + retval = PAM_AUTHTOK_ERR; + else if ((curdays > (spwdent->sp_lstchg + spwdent->sp_max + spwdent->sp_inact)) + && (spwdent->sp_max != -1) && (spwdent->sp_inact != -1) + && (spwdent->sp_lstchg != 0)) + /* + * Their password change has been put off too long, + */ + retval = PAM_ACCT_EXPIRED; + else if ((curdays > spwdent->sp_expire) && (spwdent->sp_expire != -1) + && (spwdent->sp_lstchg != 0)) + /* + * OR their account has just plain expired + */ + retval = PAM_ACCT_EXPIRED; + } + } + return retval; +} + +static int _pam_unix_approve_pass(pam_handle_t * pamh + ,unsigned int ctrl + ,const char *pass_old + ,const char *pass_new) +{ + const char *user; + char *remark = NULL; + int retval = PAM_SUCCESS; + + D(("&new=%p, &old=%p", pass_old, pass_new)); + D(("new=[%s]", pass_new)); + D(("old=[%s]", pass_old)); + + if (pass_new == NULL || (pass_old && !strcmp(pass_old, pass_new))) { + if (on(UNIX_DEBUG, ctrl)) { + _log_err(LOG_DEBUG, "bad authentication token"); + } + _make_remark(pamh, ctrl, PAM_ERROR_MSG, pass_new == NULL ? + "No password supplied" : "Password unchanged"); + return PAM_AUTHTOK_ERR; + } + /* + * if one wanted to hardwire authentication token strength + * checking this would be the place - AGM + */ + + retval = pam_get_item(pamh, PAM_USER, (const void **) &user); + if (retval != PAM_SUCCESS) { + if (on(UNIX_DEBUG, ctrl)) { + _log_err(LOG_ERR, "Can not get username"); + return PAM_AUTHTOK_ERR; + } + } + if (off(UNIX__IAMROOT, ctrl)) { +#ifdef USE_CRACKLIB + remark = FascistCheck(pass_new, CRACKLIB_DICTS); + D(("called cracklib [%s]", remark)); +#else + if (strlen(pass_new) < 6) + remark = "You must choose a longer password"; + D(("lenth check [%s]", remark)); +#endif + if (on(UNIX_REMEMBER_PASSWD, ctrl)) + if ((retval = check_old_password(user, pass_new)) != PAM_SUCCESS) + remark = "Password has been already used. Choose another."; + } + if (remark) { + _make_remark(pamh, ctrl, PAM_ERROR_MSG, remark); + retval = PAM_AUTHTOK_ERR; + } + return retval; +} + + +PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags, + int argc, const char **argv) +{ + unsigned int ctrl, lctrl; + int retval; + int remember = -1; + + /* <DO NOT free() THESE> */ + const char *user; + char *pass_old, *pass_new; + /* </DO NOT free() THESE> */ + + D(("called.")); + +#ifdef USE_LCKPWDF + /* our current locking system requires that we lock the + entire password database. This avoids both livelock + and deadlock. */ + lckpwdf(); +#endif + ctrl = _set_ctrl(flags, &remember, argc, argv); + + /* + * First get the name of a user + */ + retval = pam_get_user(pamh, &user, "Username: "); + if (retval == PAM_SUCCESS) { + /* + * Various libraries at various times have had bugs related to + * '+' or '-' as the first character of a user name. Don't take + * any chances here. Require that the username starts with an + * alphanumeric character. + */ + if (user == NULL || !isalnum(*user)) { + _log_err(LOG_ERR, "bad username [%s]", user); +#ifdef USE_LCKPWDF + ulckpwdf(); +#endif + return PAM_USER_UNKNOWN; + } + if (retval == PAM_SUCCESS && on(UNIX_DEBUG, ctrl)) + _log_err(LOG_DEBUG, "username [%s] obtained", user); + } else { + if (on(UNIX_DEBUG, ctrl)) + _log_err(LOG_DEBUG, "password - could not identify user"); +#ifdef USE_LCKPWDF + ulckpwdf(); +#endif + return retval; + } + + D(("Got username of %s", user)); + + /* + * This is not an AUTH module! + */ + if (on(UNIX__NONULL, ctrl)) + set(UNIX__NULLOK, ctrl); + + if (on(UNIX__PRELIM, ctrl)) { + /* + * obtain and verify the current password (OLDAUTHTOK) for + * the user. + */ + char *Announce; + + D(("prelim check")); + + if (_unix_blankpasswd(ctrl, user)) { +#ifdef USE_LCKPWDF + ulckpwdf(); +#endif + return PAM_SUCCESS; + } else if (off(UNIX__IAMROOT, ctrl)) { + + /* instruct user what is happening */ +#define greeting "Changing password for " + Announce = (char *) malloc(sizeof(greeting) + strlen(user)); + if (Announce == NULL) { + _log_err(LOG_CRIT, "password - out of memory"); +#ifdef USE_LCKPWDF + ulckpwdf(); +#endif + return PAM_BUF_ERR; + } + (void) strcpy(Announce, greeting); + (void) strcpy(Announce + sizeof(greeting) - 1, user); +#undef greeting + + lctrl = ctrl; + set(UNIX__OLD_PASSWD, lctrl); + retval = _unix_read_password(pamh, lctrl + ,Announce + ,"(current) UNIX password: " + ,NULL + ,_UNIX_OLD_AUTHTOK + ,(const char **) &pass_old); + free(Announce); + + if (retval != PAM_SUCCESS) { + _log_err(LOG_NOTICE + ,"password - (old) token not obtained"); +#ifdef USE_LCKPWDF + ulckpwdf(); +#endif + return retval; + } + /* verify that this is the password for this user */ + + retval = _unix_verify_password(pamh, user, pass_old, ctrl); + } else { + D(("process run by root so do nothing this time around")); + pass_old = NULL; + retval = PAM_SUCCESS; /* root doesn't have too */ + } + + if (retval != PAM_SUCCESS) { + D(("Authentication failed")); + pass_old = NULL; +#ifdef USE_LCKPWDF + ulckpwdf(); +#endif + return retval; + } + retval = pam_set_item(pamh, PAM_OLDAUTHTOK, (const void *) pass_old); + pass_old = NULL; + if (retval != PAM_SUCCESS) { + _log_err(LOG_CRIT, "failed to set PAM_OLDAUTHTOK"); + } + retval = _unix_verify_shadow(user, ctrl); + if (retval == PAM_AUTHTOK_ERR) { + if (off(UNIX__IAMROOT, ctrl)) + _make_remark(pamh, ctrl, PAM_ERROR_MSG, + "You must wait longer to change your password"); + else + retval = PAM_SUCCESS; + } + } else if (on(UNIX__UPDATE, ctrl)) { + /* + * tpass is used below to store the _pam_md() return; it + * should be _pam_delete()'d. + */ + + char *tpass = NULL; + int retry = 0; + + /* + * obtain the proposed password + */ + + D(("do update")); + + /* + * get the old token back. NULL was ok only if root [at this + * point we assume that this has already been enforced on a + * previous call to this function]. + */ + + if (off(UNIX_NOT_SET_PASS, ctrl)) { + retval = pam_get_item(pamh, PAM_OLDAUTHTOK + ,(const void **) &pass_old); + } else { + retval = pam_get_data(pamh, _UNIX_OLD_AUTHTOK + ,(const void **) &pass_old); + if (retval == PAM_NO_MODULE_DATA) { + retval = PAM_SUCCESS; + pass_old = NULL; + } + } + D(("pass_old [%s]", pass_old)); + + if (retval != PAM_SUCCESS) { + _log_err(LOG_NOTICE, "user not authenticated"); +#ifdef USE_LCKPWDF + ulckpwdf(); +#endif + return retval; + } + retval = _unix_verify_shadow(user, ctrl); + if (retval != PAM_SUCCESS) { + _log_err(LOG_NOTICE, "user not authenticated 2"); +#ifdef USE_LCKPWDF + ulckpwdf(); +#endif + return retval; + } + D(("get new password now")); + + lctrl = ctrl; + + if (on(UNIX_USE_AUTHTOK, lctrl)) { + set(UNIX_USE_FIRST_PASS, lctrl); + } + retry = 0; + retval = PAM_AUTHTOK_ERR; + while ((retval != PAM_SUCCESS) && (retry++ < MAX_PASSWD_TRIES)) { + /* + * use_authtok is to force the use of a previously entered + * password -- needed for pluggable password strength checking + */ + + retval = _unix_read_password(pamh, lctrl + ,NULL + ,"Enter new UNIX password: " + ,"Retype new UNIX password: " + ,_UNIX_NEW_AUTHTOK + ,(const char **) &pass_new); + + if (retval != PAM_SUCCESS) { + if (on(UNIX_DEBUG, ctrl)) { + _log_err(LOG_ALERT + ,"password - new password not obtained"); + } + pass_old = NULL; /* tidy up */ +#ifdef USE_LCKPWDF + ulckpwdf(); +#endif + return retval; + } + D(("returned to _unix_chauthtok")); + + /* + * At this point we know who the user is and what they + * propose as their new password. Verify that the new + * password is acceptable. + */ + + if (pass_new[0] == '\0') { /* "\0" password = NULL */ + pass_new = NULL; + } + retval = _pam_unix_approve_pass(pamh, ctrl, pass_old, pass_new); + } + + if (retval != PAM_SUCCESS) { + _log_err(LOG_NOTICE, "new password not acceptable"); + _pam_overwrite(pass_new); + _pam_overwrite(pass_old); + pass_new = pass_old = NULL; /* tidy up */ +#ifdef USE_LCKPWDF + ulckpwdf(); +#endif + return retval; + } + /* + * By reaching here we have approved the passwords and must now + * rebuild the password database file. + */ + + /* + * First we encrypt the new password. + */ + + if (on(UNIX_MD5_PASS, ctrl)) { + tpass = crypt_md5_wrapper(pass_new); + } else { + /* + * Salt manipulation is stolen from Rick Faith's passwd + * program. Sorry Rick :) -- alex + */ + + time_t tm; + char salt[3]; + + time(&tm); + salt[0] = bin_to_ascii(tm & 0x3f); + salt[1] = bin_to_ascii((tm >> 6) & 0x3f); + salt[2] = '\0'; + + if (off(UNIX_BIGCRYPT, ctrl) && strlen(pass_new) > 8) { + /* + * to avoid using the _extensions_ of the bigcrypt() + * function we truncate the newly entered password + */ + char *temp = malloc(9); + char *e; + + if (temp == NULL) { + _log_err(LOG_CRIT, "out of memory for password"); + _pam_overwrite(pass_new); + _pam_overwrite(pass_old); + pass_new = pass_old = NULL; /* tidy up */ +#ifdef USE_LCKPWDF + ulckpwdf(); +#endif + return PAM_BUF_ERR; + } + /* copy first 8 bytes of password */ + strncpy(temp, pass_new, 8); + temp[8] = '\0'; + + /* no longer need cleartext */ + e = bigcrypt(temp, salt); + tpass = x_strdup(e); + + _pam_overwrite(e); + _pam_delete(temp); /* tidy up */ + } else { + char *e; + + /* no longer need cleartext */ + e = bigcrypt(pass_new, salt); + tpass = x_strdup(e); + + _pam_overwrite(e); + } + } + + D(("password processed")); + + /* update the password database(s) -- race conditions..? */ + + retval = _do_setpass(user, pass_old, tpass, ctrl, remember); + _pam_overwrite(pass_new); + _pam_overwrite(pass_old); + _pam_delete(tpass); + pass_old = pass_new = NULL; + } else { /* something has broken with the module */ + _log_err(LOG_ALERT, "password received unknown request"); + retval = PAM_ABORT; + } + + D(("retval was %d", retval)); + +#ifdef USE_LCKPWDF + ulckpwdf(); +#endif + return retval; +} + + +/* static module data */ +#ifdef PAM_STATIC +struct pam_module _pam_unix_passwd_modstruct = { + "pam_unix_passwd", + NULL, + NULL, + NULL, + NULL, + NULL, + pam_sm_chauthtok, +}; +#endif + diff --git a/modules/pam_unix/pam_unix_sess.c b/modules/pam_unix/pam_unix_sess.c new file mode 100644 index 00000000..ec658453 --- /dev/null +++ b/modules/pam_unix/pam_unix_sess.c @@ -0,0 +1,138 @@ +/* + * Copyright Alexander O. Yuriev, 1996. All rights reserved. + * Copyright Jan Rêkorajski, 1999. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * 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. + */ + +#ifdef linux +#define _GNU_SOURCE +#include <features.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <unistd.h> +#include <syslog.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> + +/* indicate the following groups are defined */ + +#define PAM_SM_SESSION + +#include <security/_pam_macros.h> +#include <security/pam_modules.h> + +#ifndef LINUX_PAM +#include <security/pam_appl.h> +#endif /* LINUX_PAM */ + +#include "support.h" + +/* + * PAM framework looks for these entry-points to pass control to the + * session module. + */ + +PAM_EXTERN int pam_sm_open_session(pam_handle_t * pamh, int flags, + int argc, const char **argv) +{ + char *user_name, *service; + unsigned int ctrl; + int retval; + + D(("called.")); + + ctrl = _set_ctrl(flags, NULL, argc, argv); + + retval = pam_get_item(pamh, PAM_USER, (void *) &user_name); + if (user_name == NULL || retval != PAM_SUCCESS) { + _log_err(LOG_CRIT, "open_session - error recovering username"); + return PAM_SESSION_ERR; /* How did we get authenticated with + no username?! */ + } + retval = pam_get_item(pamh, PAM_SERVICE, (void *) &service); + if (service == NULL || retval != PAM_SUCCESS) { + _log_err(LOG_CRIT, "open_session - error recovering service"); + return PAM_SESSION_ERR; + } + _log_err(LOG_INFO, "(%s) session opened for user %s by %s(uid=%d)" + ,service, user_name + ,PAM_getlogin() == NULL ? "" : PAM_getlogin(), getuid()); + + return PAM_SUCCESS; +} + +PAM_EXTERN int pam_sm_close_session(pam_handle_t * pamh, int flags, + int argc, const char **argv) +{ + char *user_name, *service; + unsigned int ctrl; + int retval; + + D(("called.")); + + ctrl = _set_ctrl(flags, NULL, argc, argv); + + retval = pam_get_item(pamh, PAM_USER, (void *) &user_name); + if (user_name == NULL || retval != PAM_SUCCESS) { + _log_err(LOG_CRIT, "close_session - error recovering username"); + return PAM_SESSION_ERR; /* How did we get authenticated with + no username?! */ + } + retval = pam_get_item(pamh, PAM_SERVICE, (void *) &service); + if (service == NULL || retval != PAM_SUCCESS) { + _log_err(LOG_CRIT, "close_session - error recovering service"); + return PAM_SESSION_ERR; + } + _log_err(LOG_INFO, "(%s) session closed for user %s" + ,service, user_name); + + return PAM_SUCCESS; +} + +/* static module data */ +#ifdef PAM_STATIC +struct pam_module _pam_unix_session_modstruct = { + "pam_unix_session", + NULL, + NULL, + NULL, + pam_sm_open_session, + pam_sm_close_session, + NULL, +}; +#endif + diff --git a/modules/pam_unix/support.c b/modules/pam_unix/support.c new file mode 100644 index 00000000..610b29a7 --- /dev/null +++ b/modules/pam_unix/support.c @@ -0,0 +1,821 @@ +/* + * $Id$ + * + * Copyright information at end of file. + */ + +#define _BSD_SOURCE + +#include <stdlib.h> +#include <unistd.h> +#include <stdarg.h> +#include <string.h> +#include <malloc.h> +#include <pwd.h> +#include <shadow.h> +#include <limits.h> +#include <utmp.h> + +#include <security/_pam_macros.h> +#include <security/pam_modules.h> + +#include "md5.h" +#include "support.h" + +extern char *crypt(const char *key, const char *salt); +extern char *bigcrypt(const char *key, const char *salt); + +/* syslogging function for errors and other information */ + +void _log_err(int err, const char *format,...) +{ + va_list args; + + va_start(args, format); + openlog("PAM_unix", LOG_CONS | LOG_PID, LOG_AUTH); + vsyslog(err, format, args); + va_end(args); + closelog(); +} + +/* this is a front-end for module-application conversations */ + +static int converse(pam_handle_t * pamh, int ctrl, int nargs + ,struct pam_message **message + ,struct pam_response **response) +{ + int retval; + struct pam_conv *conv; + + D(("begin to converse")); + + retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv); + if (retval == PAM_SUCCESS) { + + retval = conv->conv(nargs, (const struct pam_message **) message + ,response, conv->appdata_ptr); + + D(("returned from application's conversation function")); + + if (retval != PAM_SUCCESS && on(UNIX_DEBUG, ctrl)) { + _log_err(LOG_DEBUG, "conversation failure [%s]" + ,pam_strerror(pamh, retval)); + } + } else if (retval != PAM_CONV_AGAIN) { + _log_err(LOG_ERR, "couldn't obtain coversation function [%s]" + ,pam_strerror(pamh, retval)); + } + D(("ready to return from module conversation")); + + return retval; /* propagate error status */ +} + +int _make_remark(pam_handle_t * pamh, unsigned int ctrl + ,int type, const char *text) +{ + int retval = PAM_SUCCESS; + + if (off(UNIX__QUIET, ctrl)) { + struct pam_message *pmsg[1], msg[1]; + struct pam_response *resp; + + pmsg[0] = &msg[0]; + msg[0].msg = text; + msg[0].msg_style = type; + + resp = NULL; + retval = converse(pamh, ctrl, 1, pmsg, &resp); + + if (resp) { + _pam_drop_reply(resp, 1); + } + } + return retval; +} + + /* + * Beacause getlogin() is fucked in a weird way, and + * sometimes it just don't work, we reimplement it here. + */ +char *PAM_getlogin(void) +{ + struct utmp *ut, line; + char *curr_tty, *retval; + static char curr_user[UT_NAMESIZE + 4]; + + retval = NULL; + + curr_tty = ttyname(0); + if (curr_tty != NULL) { + D(("PAM_getlogin ttyname: %s", curr_tty)); + curr_tty += 5; + setutent(); + strncpy(line.ut_line, curr_tty, sizeof line.ut_line); + if ((ut = getutline(&line)) != NULL) { + strncpy(curr_user, ut->ut_user, UT_NAMESIZE); + retval = curr_user; + } + endutent(); + } + D(("PAM_getlogin retval: %s", retval)); + + return retval; +} + +/* + * set the control flags for the UNIX module. + */ + +int _set_ctrl(int flags, int *remember, int argc, const char **argv) +{ + unsigned int ctrl; + + D(("called.")); + + ctrl = UNIX_DEFAULTS; /* the default selection of options */ + + /* set some flags manually */ + + if (getuid() == 0 && !(flags & PAM_CHANGE_EXPIRED_AUTHTOK)) { + D(("IAMROOT")); + set(UNIX__IAMROOT, ctrl); + } + if (flags & PAM_UPDATE_AUTHTOK) { + D(("UPDATE_AUTHTOK")); + set(UNIX__UPDATE, ctrl); + } + if (flags & PAM_PRELIM_CHECK) { + D(("PRELIM_CHECK")); + set(UNIX__PRELIM, ctrl); + } + if (flags & PAM_DISALLOW_NULL_AUTHTOK) { + D(("DISALLOW_NULL_AUTHTOK")); + set(UNIX__NONULL, ctrl); + } + if (flags & PAM_SILENT) { + D(("SILENT")); + set(UNIX__QUIET, ctrl); + } + /* now parse the arguments to this module */ + + while (argc-- > 0) { + int j; + + D(("pam_unix arg: %s", *argv)); + + for (j = 0; j < UNIX_CTRLS_; ++j) { + if (unix_args[j].token + && !strncmp(*argv, unix_args[j].token, strlen(unix_args[j].token))) { + break; + } + } + + if (j >= UNIX_CTRLS_) { + _log_err(LOG_ERR, "unrecognized option [%s]", *argv); + } else { + ctrl &= unix_args[j].mask; /* for turning things off */ + ctrl |= unix_args[j].flag; /* for turning things on */ + + if (remember != NULL) { + if (j == UNIX_REMEMBER_PASSWD) { + *remember = strtol(*argv + 9, NULL, 10); + if ((*remember == LONG_MIN) || (*remember == LONG_MAX)) + *remember = -1; + if (*remember > 400) + *remember = 400; + } + } + } + + ++argv; /* step to next argument */ + } + + /* auditing is a more sensitive version of debug */ + + if (on(UNIX_AUDIT, ctrl)) { + set(UNIX_DEBUG, ctrl); + } + /* return the set of flags */ + + D(("done.")); + return ctrl; +} + +static void _cleanup(pam_handle_t * pamh, void *x, int error_status) +{ + _pam_delete(x); +} + +/* ************************************************************** * + * Useful non-trivial functions * + * ************************************************************** */ + + /* + * the following is used to keep track of the number of times a user fails + * to authenticate themself. + */ + +#define FAIL_PREFIX "-UN*X-FAIL-" +#define UNIX_MAX_RETRIES 3 + +struct _pam_failed_auth { + char *user; /* user that's failed to be authenticated */ + char *name; /* attempt from user with name */ + int id; /* uid of name'd user */ + int count; /* number of failures so far */ +}; + +#ifndef PAM_DATA_REPLACE +#error "Need to get an updated libpam 0.52 or better" +#endif + +static void _cleanup_failures(pam_handle_t * pamh, void *fl, int err) +{ + int quiet; + const char *service = NULL; + struct _pam_failed_auth *failure; + + D(("called")); + + quiet = err & PAM_DATA_SILENT; /* should we log something? */ + err &= PAM_DATA_REPLACE; /* are we just replacing data? */ + failure = (struct _pam_failed_auth *) fl; + + if (failure != NULL) { + + if (!quiet && !err) { /* under advisement from Sun,may go away */ + + /* log the number of authentication failures */ + if (failure->count > 1) { + (void) pam_get_item(pamh, PAM_SERVICE + ,(const void **) &service); + _log_err(LOG_NOTICE + ,"%d more authentication failure%s; %s(uid=%d) -> " + "%s for %s service" + ,failure->count - 1, failure->count == 2 ? "" : "s" + ,failure->name + ,failure->id + ,failure->user + ,service == NULL ? "**unknown**" : service + ); + if (failure->count > UNIX_MAX_RETRIES) { + _log_err(LOG_ALERT + ,"service(%s) ignoring max retries; %d > %d" + ,service == NULL ? "**unknown**" : service + ,failure->count + ,UNIX_MAX_RETRIES); + } + } + } + _pam_delete(failure->user); /* tidy up */ + _pam_delete(failure->name); /* tidy up */ + free(failure); + } +} + +/* + * _unix_blankpasswd() is a quick check for a blank password + * + * returns TRUE if user does not have a password + * - to avoid prompting for one in such cases (CG) + */ + +int _unix_blankpasswd(unsigned int ctrl, const char *name) +{ + struct passwd *pwd = NULL; + struct spwd *spwdent = NULL; + char *salt = NULL; + int retval; + + D(("called")); + + /* + * This function does not have to be too smart if something goes + * wrong, return FALSE and let this case to be treated somewhere + * else (CG) + */ + + if (on(UNIX__NONULL, ctrl)) + return 0; /* will fail but don't let on yet */ + + /* UNIX passwords area */ + pwd = getpwnam(name); /* Get password file entry... */ + + if (pwd != NULL) { + if (strcmp( pwd->pw_passwd, "*NP*" ) == 0) + { /* NIS+ */ + uid_t save_euid, save_uid; + + save_euid = geteuid(); + save_uid = getuid(); + if (save_uid == pwd->pw_uid) + setreuid( save_euid, save_uid ); + else { + setreuid( 0, -1 ); + if (setreuid( -1, pwd->pw_uid ) == -1) { + setreuid( -1, 0 ); + setreuid( 0, -1 ); + if(setreuid( -1, pwd->pw_uid ) == -1) + /* Will fail elsewhere. */ + return 0; + } + } + + spwdent = getspnam( name ); + if (save_uid == pwd->pw_uid) + setreuid( save_uid, save_euid ); + else { + if (setreuid( -1, 0 ) == -1) + setreuid( save_uid, -1 ); + setreuid( -1, save_euid ); + } + } else if (strcmp(pwd->pw_passwd, "x") == 0) { + /* + * ...and shadow password file entry for this user, + * if shadowing is enabled + */ + spwdent = getspnam(name); + } + if (spwdent) + salt = x_strdup(spwdent->sp_pwdp); + else + salt = x_strdup(pwd->pw_passwd); + } + /* Does this user have a password? */ + if (salt == NULL) { + retval = 0; + } else { + if (strlen(salt) == 0) + retval = 1; + else + retval = 0; + } + + /* tidy up */ + + if (salt) + _pam_delete(salt); + + return retval; +} + +/* + * verify the password of a user + */ + +#include <sys/types.h> +#include <sys/wait.h> + +static int _unix_run_helper_binary(pam_handle_t *pamh, const char *passwd, unsigned int ctrl) +{ + int retval, child, fds[2]; + + D(("called.")); + /* create a pipe for the password */ + if (pipe(fds) != 0) { + D(("could not make pipe")); + return PAM_AUTH_ERR; + } + + /* fork */ + child = fork(); + if (child == 0) { + static char *args[] = { NULL, NULL }; + static char *envp[] = { NULL }; + + /* XXX - should really tidy up PAM here too */ + + /* reopen stdin as pipe */ + close(fds[1]); + dup2(fds[0], STDIN_FILENO); + + /* exec binary helper */ + args[0] = x_strdup(CHKPWD_HELPER); + execve(CHKPWD_HELPER, args, envp); + + /* should not get here: exit with error */ + D(("helper binary is not available")); + exit(PAM_AUTHINFO_UNAVAIL); + } else if (child > 0) { + /* wait for child */ + close(fds[0]); + /* if the stored password is NULL */ + if (off(UNIX__NONULL, ctrl)) { /* this means we've succeeded */ + write(fds[1], "nullok\0\0", 8); + } else { + write(fds[1], "nonull\0\0", 8); + } + if (passwd != NULL) { /* send the password to the child */ + write(fds[1], passwd, strlen(passwd)+1); + passwd = NULL; + } else { + write(fds[1], "", 1); /* blank password */ + } + close(fds[1]); + (void) waitpid(child, &retval, 0); /* wait for helper to complete */ + retval = (retval == 0) ? PAM_SUCCESS:PAM_AUTH_ERR; + } else { + D(("fork failed")); + retval = PAM_AUTH_ERR; + } + + D(("returning %d", retval)); + return retval; +} + +int _unix_verify_password(pam_handle_t * pamh, const char *name + ,const char *p, unsigned int ctrl) +{ + struct passwd *pwd = NULL; + struct spwd *spwdent = NULL; + char *salt = NULL; + char *pp = NULL; + char *data_name; + int retval; + + D(("called")); + +#ifdef HAVE_PAM_FAIL_DELAY + if (off(UNIX_NODELAY, ctrl)) { + D(("setting delay")); + (void) pam_fail_delay(pamh, 2000000); /* 2 sec delay for on failure */ + } +#endif + + /* locate the entry for this user */ + + D(("locating user's record")); + + /* UNIX passwords area */ + pwd = getpwnam(name); /* Get password file entry... */ + + if (pwd != NULL) { + if (strcmp( pwd->pw_passwd, "*NP*" ) == 0) + { /* NIS+ */ + uid_t save_euid, save_uid; + + save_euid = geteuid(); + save_uid = getuid(); + if (save_uid == pwd->pw_uid) + setreuid( save_euid, save_uid ); + else { + setreuid( 0, -1 ); + if (setreuid( -1, pwd->pw_uid ) == -1) { + setreuid( -1, 0 ); + setreuid( 0, -1 ); + if(setreuid( -1, pwd->pw_uid ) == -1) + return PAM_CRED_INSUFFICIENT; + } + } + + spwdent = getspnam( name ); + if (save_uid == pwd->pw_uid) + setreuid( save_uid, save_euid ); + else { + if (setreuid( -1, 0 ) == -1) + setreuid( save_uid, -1 ); + setreuid( -1, save_euid ); + } + } else if (strcmp(pwd->pw_passwd, "x") == 0) { + /* + * ...and shadow password file entry for this user, + * if shadowing is enabled + */ + spwdent = getspnam(name); + } + if (spwdent) + salt = x_strdup(spwdent->sp_pwdp); + else + salt = x_strdup(pwd->pw_passwd); + } + + data_name = (char *) malloc(sizeof(FAIL_PREFIX) + strlen(name)); + if (data_name == NULL) { + _log_err(LOG_CRIT, "no memory for data-name"); + } else { + strcpy(data_name, FAIL_PREFIX); + strcpy(data_name + sizeof(FAIL_PREFIX) - 1, name); + } + + retval = PAM_SUCCESS; + if (pwd == NULL || salt == NULL || !strcmp(salt, "x")) { + if (geteuid()) { + /* we are not root perhaps this is the reason? Run helper */ + D(("running helper binary")); + retval = _unix_run_helper_binary(pamh, p, ctrl); + } else { + D(("user's record unavailable")); + if (on(UNIX_AUDIT, ctrl)) { + /* this might be a typo and the user has given a password + instead of a username. Careful with this. */ + _log_err(LOG_ALERT, "check pass; user (%s) unknown", name); + } else { + _log_err(LOG_ALERT, "check pass; user unknown"); + } + p = NULL; + retval = PAM_AUTHINFO_UNAVAIL; + } + } else { + if (!strlen(salt)) { + /* the stored password is NULL */ + if (off(UNIX__NONULL, ctrl)) { /* this means we've succeeded */ + D(("user has empty password - access granted")); + retval = PAM_SUCCESS; + } else { + D(("user has empty password - access denied")); + retval = PAM_AUTH_ERR; + } + } else { + if (!strncmp(salt, "$1$", 3)) { + pp = Goodcrypt_md5(p, salt); + if (strcmp(pp, salt) != 0) { + pp = Brokencrypt_md5(p, salt); + } + } else { + pp = bigcrypt(p, salt); + } + p = NULL; /* no longer needed here */ + + /* the moment of truth -- do we agree with the password? */ + D(("comparing state of pp[%s] and salt[%s]", pp, salt)); + + if (strcmp(pp, salt) == 0) { + retval = PAM_SUCCESS; + } else { + retval = PAM_AUTH_ERR; + } + } + } + + if (retval == PAM_SUCCESS) { + if (data_name) /* reset failures */ + pam_set_data(pamh, data_name, NULL, _cleanup_failures); + } else { + if (data_name != NULL) { + struct _pam_failed_auth *new = NULL; + const struct _pam_failed_auth *old = NULL; + + /* get a failure recorder */ + + new = (struct _pam_failed_auth *) + malloc(sizeof(struct _pam_failed_auth)); + + if (new != NULL) { + + new->user = x_strdup(name); + new->id = getuid(); + new->name = x_strdup(PAM_getlogin()? PAM_getlogin() : ""); + + /* any previous failures for this user ? */ + pam_get_data(pamh, data_name, (const void **) &old); + + if (old != NULL) { + new->count = old->count + 1; + if (new->count >= UNIX_MAX_RETRIES) { + retval = PAM_MAXTRIES; + } + } else { + const char *service=NULL; + (void) pam_get_item(pamh, PAM_SERVICE, + (const void **)&service); + _log_err(LOG_NOTICE + ,"authentication failure; %s(uid=%d) -> " + "%s for %s service" + ,new->name + ,new->id + ,new->user + ,service == NULL ? "**unknown**":service + ); + new->count = 1; + } + + pam_set_data(pamh, data_name, new, _cleanup_failures); + + } else { + _log_err(LOG_CRIT, "no memory for failure recorder"); + } + } + } + + if (data_name) + _pam_delete(data_name); + if (salt) + _pam_delete(salt); + if (pp) + _pam_overwrite(pp); + + D(("done [%d].", retval)); + + return retval; +} + +/* + * obtain a password from the user + */ + +int _unix_read_password(pam_handle_t * pamh + ,unsigned int ctrl + ,const char *comment + ,const char *prompt1 + ,const char *prompt2 + ,const char *data_name + ,const char **pass) +{ + int authtok_flag; + int retval; + const char *item; + char *token; + + D(("called")); + + /* + * make sure nothing inappropriate gets returned + */ + + *pass = token = NULL; + + /* + * which authentication token are we getting? + */ + + authtok_flag = on(UNIX__OLD_PASSWD, ctrl) ? PAM_OLDAUTHTOK : PAM_AUTHTOK; + + /* + * should we obtain the password from a PAM item ? + */ + + if (on(UNIX_TRY_FIRST_PASS, ctrl) || on(UNIX_USE_FIRST_PASS, ctrl)) { + retval = pam_get_item(pamh, authtok_flag, (const void **) &item); + if (retval != PAM_SUCCESS) { + /* very strange. */ + _log_err(LOG_ALERT + ,"pam_get_item returned error to unix-read-password" + ); + return retval; + } else if (item != NULL) { /* we have a password! */ + *pass = item; + item = NULL; + return PAM_SUCCESS; + } else if (on(UNIX_USE_FIRST_PASS, ctrl)) { + return PAM_AUTHTOK_RECOVER_ERR; /* didn't work */ + } else if (on(UNIX_USE_AUTHTOK, ctrl) + && off(UNIX__OLD_PASSWD, ctrl)) { + return PAM_AUTHTOK_RECOVER_ERR; + } + } + /* + * getting here implies we will have to get the password from the + * user directly. + */ + + { + struct pam_message msg[3], *pmsg[3]; + struct pam_response *resp; + int i, replies; + + /* prepare to converse */ + + if (comment != NULL && off(UNIX__QUIET, ctrl)) { + pmsg[0] = &msg[0]; + msg[0].msg_style = PAM_TEXT_INFO; + msg[0].msg = comment; + i = 1; + } else { + i = 0; + } + + pmsg[i] = &msg[i]; + msg[i].msg_style = PAM_PROMPT_ECHO_OFF; + msg[i++].msg = prompt1; + replies = 1; + + if (prompt2 != NULL) { + pmsg[i] = &msg[i]; + msg[i].msg_style = PAM_PROMPT_ECHO_OFF; + msg[i++].msg = prompt2; + ++replies; + } + /* so call the conversation expecting i responses */ + resp = NULL; + retval = converse(pamh, ctrl, i, pmsg, &resp); + + if (resp != NULL) { + + /* interpret the response */ + + if (retval == PAM_SUCCESS) { /* a good conversation */ + + token = x_strdup(resp[i - replies].resp); + if (token != NULL) { + if (replies == 2) { + + /* verify that password entered correctly */ + if (!resp[i - 1].resp + || strcmp(token, resp[i - 1].resp)) { + _pam_delete(token); /* mistyped */ + retval = PAM_AUTHTOK_RECOVER_ERR; + _make_remark(pamh, ctrl + ,PAM_ERROR_MSG, MISTYPED_PASS); + } + } + } else { + _log_err(LOG_NOTICE + ,"could not recover authentication token"); + } + + } + /* + * tidy up the conversation (resp_retcode) is ignored + * -- what is it for anyway? AGM + */ + + _pam_drop_reply(resp, i); + + } else { + retval = (retval == PAM_SUCCESS) + ? PAM_AUTHTOK_RECOVER_ERR : retval; + } + } + + if (retval != PAM_SUCCESS) { + if (on(UNIX_DEBUG, ctrl)) + _log_err(LOG_DEBUG, "unable to obtain a password"); + return retval; + } + /* 'token' is the entered password */ + + if (off(UNIX_NOT_SET_PASS, ctrl)) { + + /* we store this password as an item */ + + retval = pam_set_item(pamh, authtok_flag, token); + _pam_delete(token); /* clean it up */ + if (retval != PAM_SUCCESS + || (retval = pam_get_item(pamh, authtok_flag + ,(const void **) &item)) + != PAM_SUCCESS) { + + _log_err(LOG_CRIT, "error manipulating password"); + return retval; + + } + } else { + /* + * then store it as data specific to this module. pam_end() + * will arrange to clean it up. + */ + + retval = pam_set_data(pamh, data_name, (void *) token, _cleanup); + if (retval != PAM_SUCCESS) { + _log_err(LOG_CRIT, "error manipulating password data [%s]" + ,pam_strerror(pamh, retval)); + _pam_delete(token); + return retval; + } + item = token; + token = NULL; /* break link to password */ + } + + *pass = item; + item = NULL; /* break link to password */ + + return PAM_SUCCESS; +} + +/* ****************************************************************** * + * Copyright (c) Jan Rêkorajski 1999. + * Copyright (c) Andrew G. Morgan 1996-8. + * Copyright (c) Alex O. Yuriev, 1996. + * Copyright (c) Cristian Gafton 1996. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * 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. + */ diff --git a/modules/pam_unix/support.h b/modules/pam_unix/support.h new file mode 100644 index 00000000..68686738 --- /dev/null +++ b/modules/pam_unix/support.h @@ -0,0 +1,139 @@ +#ifndef _PAM_UNIX_SUPPORT_H +#define _PAM_UNIX_SUPPORT_H + + +/* + * here is the string to inform the user that the new passwords they + * typed were not the same. + */ + +#define MISTYPED_PASS "Sorry, passwords do not match" + +/* type definition for the control options */ + +typedef struct { + const char *token; + unsigned int mask; /* shall assume 32 bits of flags */ + unsigned int flag; +} UNIX_Ctrls; + +/* + * macro to determine if a given flag is on + */ + +#define on(x,ctrl) (unix_args[x].flag & ctrl) + +/* + * macro to determine that a given flag is NOT on + */ + +#define off(x,ctrl) (!on(x,ctrl)) + +/* + * macro to turn on/off a ctrl flag manually + */ + +#define set(x,ctrl) (ctrl = ((ctrl)&unix_args[x].mask)|unix_args[x].flag) +#define unset(x,ctrl) (ctrl &= ~(unix_args[x].flag)) + +/* the generic mask */ + +#define _ALL_ON_ (~0U) + +/* end of macro definitions definitions for the control flags */ + +/* ****************************************************************** * + * ctrl flags proper.. + */ + +/* + * here are the various options recognized by the unix module. They + * are enumerated here and then defined below. Internal arguments are + * given NULL tokens. + */ + +#define UNIX__OLD_PASSWD 0 /* internal */ +#define UNIX__VERIFY_PASSWD 1 /* internal */ +#define UNIX__IAMROOT 2 /* internal */ + +#define UNIX_AUDIT 3 /* print more things than debug.. + some information may be sensitive */ +#define UNIX_USE_FIRST_PASS 4 +#define UNIX_TRY_FIRST_PASS 5 +#define UNIX_NOT_SET_PASS 6 /* don't set the AUTHTOK items */ + +#define UNIX__PRELIM 7 /* internal */ +#define UNIX__UPDATE 8 /* internal */ +#define UNIX__NONULL 9 /* internal */ +#define UNIX__QUIET 10 /* internal */ +#define UNIX_USE_AUTHTOK 11 /* insist on reading PAM_AUTHTOK */ +#define UNIX_SHADOW 12 /* signal shadow on */ +#define UNIX_MD5_PASS 13 /* force the use of MD5 passwords */ +#define UNIX__NULLOK 14 /* Null token ok */ +#define UNIX_DEBUG 15 /* send more info to syslog(3) */ +#define UNIX_NODELAY 16 /* admin does not want a fail-delay */ +#define UNIX_NIS 17 /* wish to use NIS for pwd */ +#define UNIX_BIGCRYPT 18 /* use DEC-C2 crypt()^x function */ +#define UNIX_LIKE_AUTH 19 /* need to auth for setcred to work */ +#define UNIX_REMEMBER_PASSWD 20 /* Remember N previous passwords */ +/* -------------- */ +#define UNIX_CTRLS_ 21 /* number of ctrl arguments defined */ + + +static const UNIX_Ctrls unix_args[UNIX_CTRLS_] = +{ +/* symbol token name ctrl mask ctrl * + * ----------------------- ------------------- --------------------- -------- */ + +/* UNIX__OLD_PASSWD */ {NULL, _ALL_ON_, 01}, +/* UNIX__VERIFY_PASSWD */ {NULL, _ALL_ON_, 02}, +/* UNIX__IAMROOT */ {NULL, _ALL_ON_, 04}, +/* UNIX_AUDIT */ {"audit", _ALL_ON_, 010}, +/* UNIX_USE_FIRST_PASS */ {"use_first_pass", _ALL_ON_^(060), 020}, +/* UNIX_TRY_FIRST_PASS */ {"try_first_pass", _ALL_ON_^(060), 040}, +/* UNIX_NOT_SET_PASS */ {"not_set_pass", _ALL_ON_, 0100}, +/* UNIX__PRELIM */ {NULL, _ALL_ON_^(0600), 0200}, +/* UNIX__UPDATE */ {NULL, _ALL_ON_^(0600), 0400}, +/* UNIX__NONULL */ {NULL, _ALL_ON_, 01000}, +/* UNIX__QUIET */ {NULL, _ALL_ON_, 02000}, +/* UNIX_USE_AUTHTOK */ {"use_authtok", _ALL_ON_, 04000}, +/* UNIX_SHADOW */ {"shadow", _ALL_ON_, 010000}, +/* UNIX_MD5_PASS */ {"md5", _ALL_ON_^(0400000), 020000}, +/* UNIX__NULLOK */ {"nullok", _ALL_ON_^(01000), 0}, +/* UNIX_DEBUG */ {"debug", _ALL_ON_, 040000}, +/* UNIX_NODELAY */ {"nodelay", _ALL_ON_, 0100000}, +/* UNIX_NIS */ {"nis", _ALL_ON_^(010000), 0200000}, +/* UNIX_BIGCRYPT */ {"bigcrypt", _ALL_ON_^(020000), 0400000}, +/* UNIX_LIKE_AUTH */ {"likeauth", _ALL_ON_, 01000000}, +/* UNIX_REMEMBER_PASSWD */ {"remember=", _ALL_ON_, 02000000}, +}; + +#define UNIX_DEFAULTS (unix_args[UNIX__NONULL].flag) + + +/* use this to free strings. ESPECIALLY password strings */ + +#define _pam_delete(xx) \ +{ \ + _pam_overwrite(xx); \ + _pam_drop(xx); \ +} + +extern char *PAM_getlogin(void); +extern void _log_err(int err, const char *format,...); +extern int _make_remark(pam_handle_t * pamh, unsigned int ctrl + ,int type, const char *text); +extern int _set_ctrl(int flags, int *remember, int argc, const char **argv); +extern int _unix_blankpasswd(unsigned int ctrl, const char *name); +extern int _unix_verify_password(pam_handle_t * pamh, const char *name + ,const char *p, unsigned int ctrl); +extern int _unix_read_password(pam_handle_t * pamh + ,unsigned int ctrl + ,const char *comment + ,const char *prompt1 + ,const char *prompt2 + ,const char *data_name + ,const char **pass); + +#endif /* _PAM_UNIX_SUPPORT_H */ + diff --git a/modules/pam_unix/unix_chkpwd.c b/modules/pam_unix/unix_chkpwd.c new file mode 100644 index 00000000..66c0ad7f --- /dev/null +++ b/modules/pam_unix/unix_chkpwd.c @@ -0,0 +1,324 @@ +/* + * $Id$ + * + * This program is designed to run setuid(root) or with sufficient + * privilege to read all of the unix password databases. It is designed + * to provide a mechanism for the current user (defined by this + * process' uid) to verify their own password. + * + * The password is read from the standard input. The exit status of + * this program indicates whether the user is authenticated or not. + * + * Copyright information is located at the end of the file. + * + */ + +#define _BSD_SOURCE +#ifdef linux +# define _GNU_SOURCE +# include <features.h> +#endif + +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <sys/types.h> +#include <pwd.h> +#include <shadow.h> +#include <signal.h> + +#define MAXPASS 200 /* the maximum length of a password */ + +#include <security/_pam_macros.h> + +#include "md5.h" + +extern char *crypt(const char *key, const char *salt); +extern char *bigcrypt(const char *key, const char *salt); + +#define UNIX_PASSED 0 +#define UNIX_FAILED 1 + +/* syslogging function for errors and other information */ + +static void _log_err(int err, const char *format,...) +{ + va_list args; + + va_start(args, format); + openlog("unix_chkpwd", LOG_CONS | LOG_PID, LOG_AUTH); + vsyslog(err, format, args); + va_end(args); + closelog(); +} + +static void su_sighandler(int sig) +{ + if (sig > 0) { + _log_err(LOG_NOTICE, "caught signal %d.", sig); + exit(sig); + } +} + +static void setup_signals(void) +{ + struct sigaction action; /* posix signal structure */ + + /* + * Setup signal handlers + */ + (void) memset((void *) &action, 0, sizeof(action)); + action.sa_handler = su_sighandler; + action.sa_flags = SA_RESETHAND; + (void) sigaction(SIGILL, &action, NULL); + (void) sigaction(SIGTRAP, &action, NULL); + (void) sigaction(SIGBUS, &action, NULL); + (void) sigaction(SIGSEGV, &action, NULL); + action.sa_handler = SIG_IGN; + action.sa_flags = 0; + (void) sigaction(SIGTERM, &action, NULL); + (void) sigaction(SIGHUP, &action, NULL); + (void) sigaction(SIGINT, &action, NULL); + (void) sigaction(SIGQUIT, &action, NULL); +} + +static int _unix_verify_password(const char *name, const char *p, int opt) +{ + struct passwd *pwd = NULL; + struct spwd *spwdent = NULL; + char *salt = NULL; + char *pp = NULL; + int retval = UNIX_FAILED; + + /* UNIX passwords area */ + setpwent(); + pwd = getpwnam(name); /* Get password file entry... */ + endpwent(); + if (pwd != NULL) { + if (strcmp(pwd->pw_passwd, "x") == 0) { + /* + * ...and shadow password file entry for this user, + * if shadowing is enabled + */ + setspent(); + spwdent = getspnam(name); + endspent(); + if (spwdent != NULL) + salt = x_strdup(spwdent->sp_pwdp); + else + pwd = NULL; + } else { + if (strcmp(pwd->pw_passwd, "*NP*") == 0) { /* NIS+ */ + uid_t save_uid; + + save_uid = geteuid(); + seteuid(pwd->pw_uid); + spwdent = getspnam(name); + seteuid(save_uid); + + salt = x_strdup(spwdent->sp_pwdp); + } else { + salt = x_strdup(pwd->pw_passwd); + } + } + } + if (pwd == NULL || salt == NULL) { + _log_err(LOG_ALERT, "check pass; user unknown"); + p = NULL; + return retval; + } + + if (strlen(salt) == 0) + return (opt == 0) ? UNIX_FAILED : UNIX_PASSED; + + /* the moment of truth -- do we agree with the password? */ + retval = UNIX_FAILED; + if (!strncmp(salt, "$1$", 3)) { + pp = Goodcrypt_md5(p, salt); + if (strcmp(pp, salt) == 0) { + retval = UNIX_PASSED; + } else { + pp = Brokencrypt_md5(p, salt); + if (strcmp(pp, salt) == 0) + retval = UNIX_PASSED; + } + } else { + pp = bigcrypt(p, salt); + if (strcmp(pp, salt) == 0) { + retval = UNIX_PASSED; + } + } + p = NULL; /* no longer needed here */ + + /* clean up */ + { + char *tp = pp; + if (pp != NULL) { + while (tp && *tp) + *tp++ = '\0'; + } + pp = tp = NULL; + } + + return retval; +} + +static char *getuidname(uid_t uid) +{ + struct passwd *pw; +#if 0 + char *envname; + + envname = getenv("LOGNAME"); + if (envname == NULL) + return NULL; + + pw = getpwuid(uid); + if (pw == NULL) + return NULL; + + if (strcmp(envname, pw->pw_name)) + return NULL; + + return envname; +#else + static char username[32]; + + pw = getpwuid(uid); + if (pw == NULL) + return NULL; + + memset(username, 0, 32); + strncpy(username, pw->pw_name, 32); + username[31] = '\0'; + + return username; +#endif +} + +int main(int argc, char *argv[]) +{ + char pass[MAXPASS + 1]; + char option[8]; + int npass, opt; + int retval = UNIX_FAILED; + char *user; + + /* + * Catch or ignore as many signal as possible. + */ + setup_signals(); + + /* + * we establish that this program is running with non-tty stdin. + * this is to discourage casual use. It does *NOT* prevent an + * intruder from repeatadly running this program to determine the + * password of the current user (brute force attack, but one for + * which the attacker must already have gained access to the user's + * account). + */ + + if (isatty(STDIN_FILENO)) { + + _log_err(LOG_NOTICE + ,"inappropriate use of Unix helper binary [UID=%d]" + ,getuid()); + fprintf(stderr + ,"This binary is not designed for running in this way\n" + "-- the system administrator has been informed\n"); + sleep(10); /* this should discourage/annoy the user */ + return UNIX_FAILED; + } + /* + * determine the current user's name is + * 1. supplied as a environment variable as LOGNAME + * 2. the uid has to match the one associated with the LOGNAME. + */ + user = getuidname(getuid()); + + /* read the nollok/nonull option */ + + npass = read(STDIN_FILENO, option, 8); + + if (npass < 0) { + _log_err(LOG_DEBUG, "no option supplied"); + return UNIX_FAILED; + } else { + option[7] = '\0'; + if (strncmp(option, "nullok", 8) == 0) + opt = 1; + else + opt = 0; + } + + /* read the password from stdin (a pipe from the pam_unix module) */ + + npass = read(STDIN_FILENO, pass, MAXPASS); + + if (npass < 0) { /* is it a valid password? */ + + _log_err(LOG_DEBUG, "no password supplied"); + + } else if (npass >= MAXPASS) { + + _log_err(LOG_DEBUG, "password too long"); + + } else { + if (npass == 0) { + /* the password is NULL */ + + retval = _unix_verify_password(user, NULL, opt); + + } else { + /* does pass agree with the official one? */ + + pass[npass] = '\0'; /* NUL terminate */ + retval = _unix_verify_password(user, pass, opt); + + } + } + + memset(pass, '\0', MAXPASS); /* clear memory of the password */ + + /* return pass or fail */ + + return retval; +} + +/* + * Copyright (c) Andrew G. Morgan, 1996. All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * 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. + */ diff --git a/modules/pam_unix/yppasswd.h b/modules/pam_unix/yppasswd.h new file mode 100644 index 00000000..6b414be0 --- /dev/null +++ b/modules/pam_unix/yppasswd.h @@ -0,0 +1,51 @@ +/* + * yppasswdd + * Copyright 1994, 1995, 1996 Olaf Kirch, <okir@monad.swb.de> + * + * This program is covered by the GNU General Public License, version 2. + * It is provided in the hope that it is useful. However, the author + * disclaims ALL WARRANTIES, expressed or implied. See the GPL for details. + * + * This file was generated automatically by rpcgen from yppasswd.x, and + * editied manually. + */ + +#ifndef _YPPASSWD_H_ +#define _YPPASSWD_H_ + +#define YPPASSWDPROG ((u_long)100009) +#define YPPASSWDVERS ((u_long)1) +#define YPPASSWDPROC_UPDATE ((u_long)1) + +/* + * The password struct passed by the update call. I renamed it to + * xpasswd to avoid a type clash with the one defined in <pwd.h>. + */ +#ifndef __sgi +typedef struct xpasswd { + char *pw_name; + char *pw_passwd; + int pw_uid; + int pw_gid; + char *pw_gecos; + char *pw_dir; + char *pw_shell; +} xpasswd; + +#else +#include <pwd.h> +typedef struct xpasswd xpasswd; +#endif + +/* The updated password information, plus the old password. + */ +typedef struct yppasswd { + char *oldpass; + xpasswd newpw; +} yppasswd; + +/* XDR encoding/decoding routines */ +bool_t xdr_xpasswd(XDR * xdrs, xpasswd * objp); +bool_t xdr_yppasswd(XDR * xdrs, yppasswd * objp); + +#endif /* _YPPASSWD_H_ */ diff --git a/modules/pam_unix/yppasswd_xdr.c b/modules/pam_unix/yppasswd_xdr.c new file mode 100644 index 00000000..eeb36423 --- /dev/null +++ b/modules/pam_unix/yppasswd_xdr.c @@ -0,0 +1,41 @@ +/* + * yppasswdd + * Copyright 1994, 1995, 1996 Olaf Kirch, <okir@monad.swb.de> + * + * This program is covered by the GNU General Public License, version 2. + * It is provided in the hope that it is useful. However, the author + * disclaims ALL WARRANTIES, expressed or implied. See the GPL for details. + * + * This file was generated automatically by rpcgen from yppasswd.x, and + * editied manually. + */ + +#ifdef linux +# define _GNU_SOURCE +# include <features.h> +#endif + +#include <rpc/rpc.h> +#include <rpcsvc/yp_prot.h> +#include <rpcsvc/ypclnt.h> +#include "yppasswd.h" + +bool_t +xdr_xpasswd(XDR * xdrs, xpasswd * objp) +{ + return xdr_string(xdrs, &objp->pw_name, ~0) + && xdr_string(xdrs, &objp->pw_passwd, ~0) + && xdr_int(xdrs, &objp->pw_uid) + && xdr_int(xdrs, &objp->pw_gid) + && xdr_string(xdrs, &objp->pw_gecos, ~0) + && xdr_string(xdrs, &objp->pw_dir, ~0) + && xdr_string(xdrs, &objp->pw_shell, ~0); +} + + +bool_t +xdr_yppasswd(XDR * xdrs, yppasswd * objp) +{ + return xdr_string(xdrs, &objp->oldpass, ~0) + && xdr_xpasswd(xdrs, &objp->newpw); +} |