From 9fb56863cfad194547d8ca990f8b94ada92a6aa1 Mon Sep 17 00:00:00 2001 From: Charl Botha Date: Sat, 11 May 2002 14:42:35 +0000 Subject: Fixed md5 crypting + ssl bug. --- Makefile | 100 ++++++++++- README | 4 +- bigcrypt.c | 119 +++++++++++++ changelog | 10 +- md5.c | 256 ++++++++++++++++++++++++++++ md5.h | 31 ++++ md5_crypt.c | 149 ++++++++++++++++ pam_pwdfile.c | 533 +++++++++++++++++++++++++++++++--------------------------- 8 files changed, 944 insertions(+), 258 deletions(-) create mode 100644 bigcrypt.c create mode 100644 md5.c create mode 100644 md5.h create mode 100644 md5_crypt.c diff --git a/Makefile b/Makefile index 785998b..2e8b433 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -# $Id: Makefile,v 1.3 2001-07-14 20:50:21 cpbotha Exp $ +# $Id: Makefile,v 1.4 2002-05-11 14:42:35 cpbotha Exp $ # # This Makefile controls a build process of $(TITLE) module for # Linux-PAM. You should not modify this Makefile (unless you know @@ -10,6 +10,100 @@ include ../../Make.Rules TITLE=pam_pwdfile CFLAGS += -D_BSD_SOURCE -MODULE_SIMPLE_EXTRALIBS = -lcrypt +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 $@ + +##### The following mostly from Simple.Rules +##### * modifications to first 5 definitions + +LIBFILES = $(TITLE) bigcrypt +LIBSRC = $(addsuffix .c,$(LIBFILES)) md5.c md5_crypt.c +LIBOBJ = $(addsuffix .o,$(LIBFILES)) +LIBOBJD = $(addprefix dynamic/,$(LIBOBJ)) md5_good.o md5_broken.o md5_crypt_good.o md5_crypt_broken.o +LIBOBJS = $(addprefix static/,$(LIBOBJ)) md5_good.o md5_broken.o md5_crypt_good.o md5_crypt_broken.o + +ifdef DYNAMIC +LIBSHARED = $(TITLE).so +endif + +ifdef STATIC +LIBSTATIC = lib$(TITLE).o +endif + +####################### don't edit below ####################### + +all: dirs $(LIBSHARED) $(LIBSTATIC) register + +dynamic/%.o : %.c + $(CC) $(CFLAGS) $(DYNAMIC) $(TARGET_ARCH) -c $< -o $@ + +static/%.o : %.c + $(CC) $(CFLAGS) $(STATIC) $(TARGET_ARCH) -c $< -o $@ + +dirs: +ifdef DYNAMIC + $(MKDIR) ./dynamic +endif +ifdef STATIC + $(MKDIR) ./static +endif + +register: +ifdef STATIC + ( cd .. ; ./register_static $(TITLE) $(TITLE)/$(LIBSTATIC) ) +endif + +ifdef DYNAMIC +$(LIBOBJD): $(LIBSRC) +endif + +ifdef DYNAMIC +$(LIBSHARED): $(LIBOBJD) + $(LD_D) -o $@ $(LIBOBJD) $(MODULE_SIMPLE_EXTRALIBS) $(NEED_LINK_LIB_C) + +endif + +ifdef STATIC +$(LIBOBJS): $(LIBSRC) +endif + +ifdef STATIC +$(LIBSTATIC): $(LIBOBJS) + $(LD) -r -o $@ $(LIBOBJS) $(MODULE_SIMPLE_EXTRALIBS) +endif + +install: all + $(MKDIR) $(FAKEROOT)$(SECUREDIR) +ifdef DYNAMIC + $(INSTALL) -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR) +endif + $(MODULE_SIMPLE_INSTALL) + +remove: + rm -f $(FAKEROOT)$(SECUREDIR)/$(TITLE).so + $(MODULE_SIMPLE_REMOVE) + +clean: + rm -f $(LIBOBJD) $(LIBOBJS) core *~ + $(MODULE_SIMPLE_CLEAN) + rm -f *.a *.o *.so *.bak + rm -rf dynamic static + $(MODULE_SIMPLE_EXTRACLEAN) + +.c.o: + $(CC) $(CFLAGS) -c $< + -include ../Simple.Rules diff --git a/README b/README index 0bce847..fd7feea 100644 --- a/README +++ b/README @@ -1,8 +1,8 @@ README for pam_pwdfile PAM module - Charl P. Botha -$Id: README,v 1.8 2001-07-14 20:50:21 cpbotha Exp $ +$Id: README,v 1.9 2002-05-11 14:42:35 cpbotha Exp $ --------------------------------------------------------------------------- -This is version 0.95 of pam_pwdfile. +This is version 0.97 of pam_pwdfile. This pam module can be used for the authentication service only, in cases where one wants to use a different set of passwords than those in the main diff --git a/bigcrypt.c b/bigcrypt.c new file mode 100644 index 0000000..b1568d6 --- /dev/null +++ b/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 + */ + +#include +#include + +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 terminated encrypted password */ + + return dec_c2_cryptbuf; +} diff --git a/changelog b/changelog index 1f01ecd..7947874 100644 --- a/changelog +++ b/changelog @@ -1,7 +1,15 @@ changelog for pam_pwdfile PAM module - Charl P. Botha -$Id: changelog,v 1.11 2001-07-14 20:50:21 cpbotha Exp $ +$Id: changelog,v 1.12 2002-05-11 14:42:35 cpbotha Exp $ --------------------------------------------------------------------------- +0.97 : Sat May 11 16:40:19 CEST 2002 + +* added md5.h, md5.c, md5_crypt.c and bigcrypt.c from the pam_unix.c module. + This fixes the bug where crypt() would not be able to do md5 crypting if + an SSL library was linked into the calling application. Several users + reported this, but the report (and possible patch, which I have NOT used) + of Yu Guanghui was most useful. + 0.95 : Sat Jul 14 22:38:16 CEST 2001 * added features.h, _BSD_SOURCE now defined by Makefile (for vsyslog a.o.) diff --git a/md5.c b/md5.c new file mode 100644 index 0000000..0fb1a78 --- /dev/null +++ b/md5.c @@ -0,0 +1,256 @@ +/* + * $Id: md5.c,v 1.1 2002-05-11 14:42:35 cpbotha Exp $ + * + * 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 +#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<>(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/md5.h b/md5.h new file mode 100644 index 0000000..103f168 --- /dev/null +++ b/md5.h @@ -0,0 +1,31 @@ + +#ifndef MD5_H +#define MD5_H + +typedef unsigned int uint32; + +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/md5_crypt.c b/md5_crypt.c new file mode 100644 index 0000000..7871b45 --- /dev/null +++ b/md5_crypt.c @@ -0,0 +1,149 @@ +/* + * $Id: md5_crypt.c,v 1.1 2002-05-11 14:42:35 cpbotha Exp $ + * + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * 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 +#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/pam_pwdfile.c b/pam_pwdfile.c index 5a7f580..09b3fed 100644 --- a/pam_pwdfile.c +++ b/pam_pwdfile.c @@ -1,14 +1,14 @@ /* pam_pwdfile.c copyright 1999-2001 by Charl P. Botha * - * $Id: pam_pwdfile.c,v 1.15 2001-07-14 20:50:21 cpbotha Exp $ + * $Id: pam_pwdfile.c,v 1.16 2002-05-11 14:42:35 cpbotha Exp $ * * pam authentication module that can be pointed at any username/crypted * text file so that pam using application can use an alternate set of * passwords than specified in system password database * - * version 0.95 + * version 0.97 * - * Copyright (c) Charl P. Botha, 1999-2001. All rights reserved + * Copyright (c) Charl P. Botha, 1999-2002. All rights reserved * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -60,8 +60,9 @@ #define PAM_SM_AUTH #include -/* unistd.h does not declare this as it should */ -extern char *crypt(const char *key, const char *salt); +#include "md5.h" +/*extern char *crypt(const char *key, const char *salt);*/ +extern char *bigcrypt(const char *key, const char *salt); #define PWDF_PARAM "pwdfile" #define FLOCK_PARAM "flock" @@ -82,30 +83,30 @@ int _set_auth_tok(pam_handle_t *, int, int, const char **); /* logging function ripped from pam_listfile.c */ static void _pam_log(int err, const char *format, ...) { - va_list args; - - va_start(args, format); - openlog("pam_pwdfile", LOG_CONS|LOG_PID, LOG_AUTH); - vsyslog(err, format, args); - va_end(args); - closelog(); + va_list args; + + va_start(args, format); + openlog("pam_pwdfile", LOG_CONS|LOG_PID, LOG_AUTH); + vsyslog(err, format, args); + va_end(args); + closelog(); } static int lock_fd(int fd) { - int delay; - - for (delay = 5; delay <= 40; delay *= 2) { - if (flock(fd, LOCK_SH | LOCK_NB) == -1) { - /* failed */ - if (errno != EWOULDBLOCK) goto failed; - sleep(delay); - }else{ - return 0; - } - } - if (flock(fd, LOCK_SH | LOCK_NB) != -1) return 0; -failed: - return -1; + int delay; + + for (delay = 5; delay <= 40; delay *= 2) { + if (flock(fd, LOCK_SH | LOCK_NB) == -1) { + /* failed */ + if (errno != EWOULDBLOCK) goto failed; + sleep(delay); + }else{ + return 0; + } + } + if (flock(fd, LOCK_SH | LOCK_NB) != -1) return 0; + failed: + return -1; } /* this function ripped from pam_unix/support.c */ @@ -113,47 +114,47 @@ int converse( pam_handle_t *pamh, int nargs, struct pam_message **message, struct pam_response **response ) { - int retval; - struct pam_conv *conv; - - retval = pam_get_item( pamh, PAM_CONV, (const void **) &conv ) ; - if ( retval == PAM_SUCCESS ) { + int retval; + struct pam_conv *conv; + + 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 ); - } - return retval; + ( const struct pam_message ** ) message, + response, + conv->appdata_ptr ); + } + return retval; } /* this function ripped from pam_unix/support.c */ int _set_auth_tok( pam_handle_t *pamh, int flags, int argc, const char **argv ) { - int retval; - char *p; - - struct pam_message msg[1],*pmsg[1]; - struct pam_response *resp; - - /* set up conversation call */ - - pmsg[0] = &msg[0]; - msg[0].msg_style = PAM_PROMPT_ECHO_OFF; - msg[0].msg = "Password: "; - resp = NULL; - - if ( ( retval = converse( pamh, 1 , pmsg, &resp ) ) != PAM_SUCCESS ) - return retval; - - if ( resp ) - { + int retval; + char *p; + + struct pam_message msg[1],*pmsg[1]; + struct pam_response *resp; + + /* set up conversation call */ + + pmsg[0] = &msg[0]; + msg[0].msg_style = PAM_PROMPT_ECHO_OFF; + msg[0].msg = "Password: "; + resp = NULL; + + if ( ( retval = converse( pamh, 1 , pmsg, &resp ) ) != PAM_SUCCESS ) + return retval; + + if ( resp ) + { if ( ( flags & PAM_DISALLOW_NULL_AUTHTOK ) && - resp[0].resp == NULL ) - { - free( resp ); - return PAM_AUTH_ERR; - } + resp[0].resp == NULL ) + { + free( resp ); + return PAM_AUTH_ERR; + } p = resp[ 0 ].resp; @@ -164,13 +165,13 @@ int _set_auth_tok( pam_handle_t *pamh, resp[ 0 ].resp = NULL; - } - else - return PAM_CONV_ERR; - - free( resp ); - pam_set_item( pamh, PAM_AUTHTOK, p ); - return PAM_SUCCESS; + } + else + return PAM_CONV_ERR; + + free( resp ); + pam_set_item( pamh, PAM_AUTHTOK, p ); + return PAM_SUCCESS; } @@ -179,211 +180,239 @@ int _set_auth_tok( pam_handle_t *pamh, * if unsucessful, returns 0 */ static int fgetpwnam(FILE *stream, const char *name, char *password) { - char tempLine[256], *tpointer, *curname, *curpass, *fgr; - int loopdone, pwdfound; - int len; - - /* go to beginning of file */ - rewind(stream); - /* some control variables */ - loopdone = pwdfound = 0; - /* fgets should do this, but we make sure */ - tempLine[255] = '\0'; - /* iterate through lines in file, until end of file */ - do { - /* get the current line */ - fgr = fgets(tempLine,255,stream); - /* if it's valid, go on */ - if ( fgr != NULL) { - /* first get the username out */ - tpointer = tempLine; - curname = strsep(&tpointer,":"); - /* check to see if it's the right one */ - if (strcmp(curname,name)==0) { - /* at least we know our loop is done */ - loopdone = 1; - /* remove possible trailing newline */ - len = strlen(tpointer); - if (tpointer[len - 1] == '\n') - tpointer[len - 1] = '\0'; - /* get the password and put it in its place */ - curpass = strsep(&tpointer,":"); - if (curpass != NULL) { - /* we use md5 pwd len, as this is just a safe maximum */ - strncpy(password,curpass,CRYPTED_MD5PWD_LEN+1); - pwdfound = 1; - } /* if (curpass... */ - } /* if (strcmp(curname... */ - } /* if (tempLine... */ - } while (fgr != NULL); - return pwdfound; + char tempLine[256], *tpointer, *curname, *curpass, *fgr; + int loopdone, pwdfound; + int len; + + /* go to beginning of file */ + rewind(stream); + /* some control variables */ + loopdone = pwdfound = 0; + /* fgets should do this, but we make sure */ + tempLine[255] = '\0'; + /* iterate through lines in file, until end of file */ + do { + /* get the current line */ + fgr = fgets(tempLine,255,stream); + /* if it's valid, go on */ + if ( fgr != NULL) { + /* first get the username out */ + tpointer = tempLine; + curname = strsep(&tpointer,":"); + /* check to see if it's the right one */ + if (strcmp(curname,name)==0) { + /* at least we know our loop is done */ + loopdone = 1; + /* remove possible trailing newline */ + len = strlen(tpointer); + if (tpointer[len - 1] == '\n') + tpointer[len - 1] = '\0'; + /* get the password and put it in its place */ + curpass = strsep(&tpointer,":"); + if (curpass != NULL) { + /* we use md5 pwd len, as this is just a safe maximum */ + strncpy(password,curpass,CRYPTED_MD5PWD_LEN+1); + pwdfound = 1; + } /* if (curpass... */ + } /* if (strcmp(curname... */ + } /* if (tempLine... */ + } while (fgr != NULL); + return pwdfound; } /* expected hook for auth service */ PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) { - int retval, pcnt, pwdfilename_found; - const char *name; - char *password; - char pwdfilename[PWDFN_LEN]; - char salt[12], crypted_password[CRYPTED_MD5PWD_LEN+1]; - FILE *pwdfile; - int use_flock = 0; - int use_delay = 1; - - /* we require the pwdfile switch and argument to be present, else we don't work */ - /* pcnt is the parameter counter variable for iterating through argv */ - pcnt = pwdfilename_found = 0; - do { - /* see if the current parameter looks like "pwdfile" */ - if (strcmp(argv[pcnt],PWDF_PARAM)==0) { - /* if argv is long enough, grab the subsequent parameter */ - if (pcnt+1 < argc) { + int retval, pcnt, pwdfilename_found; + const char *name; + char *password; + char pwdfilename[PWDFN_LEN]; + char salt[12], stored_crypted_password[CRYPTED_MD5PWD_LEN+1]; + char *crypted_password; + FILE *pwdfile; + int use_flock = 0; + int use_delay = 1; + int temp_result = 0; + + /* we require the pwdfile switch and argument to be present, else we don't work */ + /* pcnt is the parameter counter variable for iterating through argv */ + pcnt = pwdfilename_found = 0; + do { + /* see if the current parameter looks like "pwdfile" */ + if (strcmp(argv[pcnt],PWDF_PARAM)==0) { + /* if argv is long enough, grab the subsequent parameter */ + if (pcnt+1 < argc) { + /* make sure we can't overflow */ + strncpy(pwdfilename,argv[++pcnt],PWDFN_LEN); + /* indicate that we've found it */ + pwdfilename_found = 1; + } + /* also check for "pwdfile=blah" */ + } else if (strncmp(argv[pcnt],PWDF_PARAM "=",sizeof(PWDF_PARAM "=")-1)==0) { /* make sure we can't overflow */ - strncpy(pwdfilename,argv[++pcnt],PWDFN_LEN); + strncpy(pwdfilename,argv[pcnt]+sizeof(PWDF_PARAM),PWDFN_LEN); /* indicate that we've found it */ pwdfilename_found = 1; - } - /* also check for "pwdfile=blah" */ - } else if (strncmp(argv[pcnt],PWDF_PARAM "=",sizeof(PWDF_PARAM "=")-1)==0) { - /* make sure we can't overflow */ - strncpy(pwdfilename,argv[pcnt]+sizeof(PWDF_PARAM),PWDFN_LEN); - /* indicate that we've found it */ - pwdfilename_found = 1; - } else if (strcmp(argv[pcnt],FLOCK_PARAM)==0) { - /* we have a "flock" parameter */ - use_flock = 1; - } else if (strcmp(argv[pcnt],"no" FLOCK_PARAM)==0) { - /* or a "noflock" parameter */ - use_flock = 0; - } else if (strcmp(argv[pcnt],NODELAY_PARAM)==0) { - /* no delay on authentication failure */ - use_delay = 0; - } - - } while (++pcnt < argc); - + } else if (strcmp(argv[pcnt],FLOCK_PARAM)==0) { + /* we have a "flock" parameter */ + use_flock = 1; + } else if (strcmp(argv[pcnt],"no" FLOCK_PARAM)==0) { + /* or a "noflock" parameter */ + use_flock = 0; + } else if (strcmp(argv[pcnt],NODELAY_PARAM)==0) { + /* no delay on authentication failure */ + use_delay = 0; + } + + } while (++pcnt < argc); + #ifdef HAVE_PAM_FAIL_DELAY - if (use_delay) { - D(("setting delay")); - (void) pam_fail_delay(pamh, 2000000); /* 2 sec delay for on failure */ - } + if (use_delay) { + D(("setting delay")); + (void) pam_fail_delay(pamh, 2000000); /* 2 sec delay for on failure */ + } #endif - - /* for some or other reason, the password file wasn't specified */ - if (!pwdfilename_found) { - _pam_log(LOG_ERR,"password file name not specified"); - return PAM_AUTHINFO_UNAVAIL; - } - - /* DEBUG */ - D(_pam_log(LOG_ERR, "password filename extracted")); - - /* now try to open the password file */ - if ((pwdfile=fopen(pwdfilename,"r"))==NULL) { - _pam_log(LOG_ERR,"couldn't open password file %s",pwdfilename); - return PAM_AUTHINFO_UNAVAIL; - } - - /* set a lock on the password file */ - if (use_flock && lock_fd(fileno(pwdfile)) == -1) { - _pam_log(LOG_ERR,"couldn't lock password file %s",pwdfilename); - return PAM_AUTHINFO_UNAVAIL; - } - - /* get user name */ - if ((retval = pam_get_user(pamh,&name,"login: ")) != PAM_SUCCESS) { - _pam_log(LOG_ERR, "username not found"); - fclose(pwdfile); - return retval; - } - - - /* DEBUG */ - D(_pam_log(LOG_ERR,"username is %s", name)); - - /* get password - code from pam_unix_auth.c */ - pam_get_item(pamh, PAM_AUTHTOK, (void *)&password); - if (!password) { - retval = _set_auth_tok(pamh, flags, argc, argv); - if (retval!=PAM_SUCCESS) { - fclose(pwdfile); - return retval; - } - } - pam_get_item(pamh, PAM_AUTHTOK, (void *)&password); - - if ((retval = pam_get_item(pamh, PAM_AUTHTOK, (void *)&password)) != PAM_SUCCESS) { - _pam_log(LOG_ERR, "auth token not found"); - fclose(pwdfile); - return retval; - } - - /* DEBUG */ - D(_pam_log(LOG_ERR,"got password from user", password)); - - /* now crypt password and compare to the user entry in the password file */ - /* first make sure password is long enough -- may I do this? */ - if (strlen(password)<2 || password==NULL) { - _pam_log(LOG_ERR,"password too short or NULL"); - fclose(pwdfile); - return PAM_AUTH_ERR; - } - - /* get the crypted password corresponding to this user */ - if (!fgetpwnam(pwdfile, name, crypted_password)) { - _pam_log(LOG_ERR,"user not found in password database"); - fclose(pwdfile); - return PAM_AUTHINFO_UNAVAIL; - } - - /* DEBUG */ - D(_pam_log(LOG_ERR,"got crypted password == '%s'", crypted_password)); - - /* Extract the salt and set the passwd length, depending on MD5 or DES */ - if (strncmp(crypted_password, "$1$", 3) == 0) { - strncpy(salt, crypted_password, 11); - salt[11] = '\0'; - crypted_password[CRYPTED_MD5PWD_LEN] = '\0'; - } else { - strncpy(salt, crypted_password, 2); - salt[2] = '\0'; - crypted_password[CRYPTED_DESPWD_LEN] = '\0'; - } - - /* DEBUG */ - D(_pam_log(LOG_ERR,"user password crypted is '%s'", crypt(password,salt))); - - /* if things don't match up, complain */ - if (strcmp(crypt(password,salt),crypted_password)!=0) { - _pam_log(LOG_ERR,"wrong password for user %s",name); - fclose(pwdfile); - return PAM_AUTH_ERR; - } - - /* DEBUG */ - D(_pam_log(LOG_ERR,"passwords match")); - - /* we've gotten here, i.e. authentication was sucessful! */ - fclose(pwdfile); - return PAM_SUCCESS; + + /* for some or other reason, the password file wasn't specified */ + if (!pwdfilename_found) { + _pam_log(LOG_ERR,"password file name not specified"); + return PAM_AUTHINFO_UNAVAIL; + } + + /* DEBUG */ + D(_pam_log(LOG_ERR, "password filename extracted")); + + /* now try to open the password file */ + if ((pwdfile=fopen(pwdfilename,"r"))==NULL) { + _pam_log(LOG_ERR,"couldn't open password file %s",pwdfilename); + return PAM_AUTHINFO_UNAVAIL; + } + + /* set a lock on the password file */ + if (use_flock && lock_fd(fileno(pwdfile)) == -1) { + _pam_log(LOG_ERR,"couldn't lock password file %s",pwdfilename); + return PAM_AUTHINFO_UNAVAIL; + } + + /* get user name */ + if ((retval = pam_get_user(pamh,&name,"login: ")) != PAM_SUCCESS) { + _pam_log(LOG_ERR, "username not found"); + fclose(pwdfile); + return retval; + } + + + /* DEBUG */ + D(_pam_log(LOG_ERR,"username is %s", name)); + + /* get password - code from pam_unix_auth.c */ + pam_get_item(pamh, PAM_AUTHTOK, (void *)&password); + if (!password) { + retval = _set_auth_tok(pamh, flags, argc, argv); + if (retval!=PAM_SUCCESS) { + fclose(pwdfile); + return retval; + } + } + pam_get_item(pamh, PAM_AUTHTOK, (void *)&password); + + if ((retval = pam_get_item(pamh, PAM_AUTHTOK, (void *)&password)) != PAM_SUCCESS) { + _pam_log(LOG_ERR, "auth token not found"); + fclose(pwdfile); + return retval; + } + + /* DEBUG */ + D(_pam_log(LOG_ERR,"got password from user", password)); + + /* now crypt password and compare to the user entry in the password file */ + /* first make sure password is long enough -- may I do this? */ + if (strlen(password)<2 || password==NULL) { + _pam_log(LOG_ERR,"password too short or NULL"); + fclose(pwdfile); + return PAM_AUTH_ERR; + } + + /* get the crypted password corresponding to this user */ + if (!fgetpwnam(pwdfile, name, stored_crypted_password)) { + _pam_log(LOG_ERR,"user not found in password database"); + fclose(pwdfile); + return PAM_AUTHINFO_UNAVAIL; + } + + /* DEBUG */ + D(_pam_log(LOG_ERR,"got crypted password == '%s'", stored_crypted_password)); + + + temp_result = 0; + + /* Extract the salt and set the passwd length, depending on MD5 or DES */ + if (strncmp(stored_crypted_password, "$1$", 3) == 0) { + /* get out the salt into "salt" */ + strncpy(salt, stored_crypted_password, 11); + salt[11] = '\0'; + stored_crypted_password[CRYPTED_MD5PWD_LEN] = '\0'; + /* try both md5 crypts */ + crypted_password = Goodcrypt_md5(password, salt); + if (strcmp(crypted_password, stored_crypted_password) == 0) + { + temp_result = 1; + } + else + { + crypted_password = Brokencrypt_md5(password, salt); + if (strcmp(crypted_password, stored_crypted_password) == 0) + { + temp_result = 1; + } + } + } else { + /* get the salt out into "salt" */ + strncpy(salt, stored_crypted_password, 2); + salt[2] = '\0'; + stored_crypted_password[CRYPTED_DESPWD_LEN] = '\0'; + + crypted_password = bigcrypt(password, salt); + if (strcmp(crypted_password, stored_crypted_password) == 0) + { + temp_result = 1; + } + } + + /* DEBUG */ + D(_pam_log(LOG_ERR,"user password crypted is '%s'", crypted_password)); + + /* if things don't match up, complain */ + if (!temp_result) + { + _pam_log(LOG_ERR,"wrong password for user %s",name); + fclose(pwdfile); + return PAM_AUTH_ERR; + } + + /* DEBUG */ + D(_pam_log(LOG_ERR,"passwords match")); + + /* we've gotten here, i.e. authentication was sucessful! */ + fclose(pwdfile); + return PAM_SUCCESS; } /* another expected hook */ PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) { - return PAM_SUCCESS; + return PAM_SUCCESS; } #ifdef PAM_STATIC struct pam_module _pam_listfile_modstruct = { - "pam_pwdfile", - pam_sm_authenticate, - pam_sm_setcred, - NULL, - NULL, - NULL, - NULL, + "pam_pwdfile", + pam_sm_authenticate, + pam_sm_setcred, + NULL, + NULL, + NULL, + NULL, }; #endif -- cgit v1.2.3