diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/config.h.in | 236 | ||||
| -rw-r--r-- | src/md5.c | 278 | ||||
| -rw-r--r-- | src/md5.h | 60 | ||||
| -rw-r--r-- | src/pam_radius_auth.c | 1596 | ||||
| -rw-r--r-- | src/pam_radius_auth.h | 126 | ||||
| -rw-r--r-- | src/radius.h | 230 | 
6 files changed, 2526 insertions, 0 deletions
| diff --git a/src/config.h.in b/src/config.h.in new file mode 100644 index 0000000..4780376 --- /dev/null +++ b/src/config.h.in @@ -0,0 +1,236 @@ +/* src/config.h.in.  Generated from configure.ac by autoheader.  */ + +/* Define if building universal (internal helper macro) */ +#undef AC_APPLE_UNIVERSAL_BUILD + +/* Define if your processor stores words with the most significant byte first +   */ +#undef AC_BIG_ENDIAN + +/* Define if your processor stores words with the least significant byte first +   */ +#undef AC_LITTLE_ENDIAN + +/* Define to 1 if you have the <ctype.h> header file. */ +#undef HAVE_CTYPE_H + +/* Define to 1 if you have the <errno.h> header file. */ +#undef HAVE_ERRNO_H + +/* Define to 1 if you have the <fcntl.h> header file. */ +#undef HAVE_FCNTL_H + +/* Define to 1 if you have the `inet_aton' function. */ +#undef HAVE_INET_ATON + +/* Define to 1 if you have the `inet_ntop' function. */ +#undef HAVE_INET_NTOP + +/* Define to 1 if you have the `inet_pton' function. */ +#undef HAVE_INET_PTON + +/* Define to 1 if you have the <inttypes.h> header file. */ +#undef HAVE_INTTYPES_H + +/* Define to 1 if you have the `nsl' library (-lnsl). */ +#undef HAVE_LIBNSL + +/* Define to 1 if you have the `resolv' library (-lresolv). */ +#undef HAVE_LIBRESOLV + +/* Define to 1 if you have the <limits.h> header file. */ +#undef HAVE_LIMITS_H + +/* Define to 1 if you have the <malloc.h> header file. */ +#undef HAVE_MALLOC_H + +/* Define to 1 if you have the <memory.h> header file. */ +#undef HAVE_MEMORY_H + +/* Define to 1 if you have the <netdb.h> header file. */ +#undef HAVE_NETDB_H + +/* Define to 1 if you have the <netinet/in.h> header file. */ +#undef HAVE_NETINET_IN_H + +/* Define to 1 if you have the <net/if.h> header file. */ +#undef HAVE_NET_IF_H + +/* Define to 1 if you have the <pam/pam_appl.h> header file. */ +#undef HAVE_PAM_PAM_APPL_H + +/* Define to 1 if you have the <pam/pam_modules.h> header file. */ +#undef HAVE_PAM_PAM_MODULES_H + +/* Define to 1 if you have the <security/pam_appl.h> header file. */ +#undef HAVE_SECURITY_PAM_APPL_H + +/* Define to 1 if you have the <security/pam_modules.h> header file. */ +#undef HAVE_SECURITY_PAM_MODULES_H + +/* Define to 1 if you have the `snprintf' function. */ +#undef HAVE_SNPRINTF + +/* Define to 1 if you have the <stdarg.h> header file. */ +#undef HAVE_STDARG_H + +/* Define to 1 if you have the <stdint.h> header file. */ +#undef HAVE_STDINT_H + +/* Define to 1 if you have the <stdio.h> header file. */ +#undef HAVE_STDIO_H + +/* Define to 1 if you have the <stdlib.h> header file. */ +#undef HAVE_STDLIB_H + +/* Define to 1 if you have the <strings.h> header file. */ +#undef HAVE_STRINGS_H + +/* Define to 1 if you have the <string.h> header file. */ +#undef HAVE_STRING_H + +/* Define to 1 if you have the `strlcat' function. */ +#undef HAVE_STRLCAT + +/* Define to 1 if you have the `strlcpy' function. */ +#undef HAVE_STRLCPY + +/* IPv6 address structure */ +#undef HAVE_STRUCT_IN6_ADDR + +/* Define to 1 if you have the <syslog.h> header file. */ +#undef HAVE_SYSLOG_H + +/* Define to 1 if you have the <sys/param.h> header file. */ +#undef HAVE_SYS_PARAM_H + +/* Define to 1 if you have the <sys/resource.h> header file. */ +#undef HAVE_SYS_RESOURCE_H + +/* Define to 1 if you have the <sys/socket.h> header file. */ +#undef HAVE_SYS_SOCKET_H + +/* Define to 1 if you have the <sys/stat.h> header file. */ +#undef HAVE_SYS_STAT_H + +/* Define to 1 if you have the <sys/time.h> header file. */ +#undef HAVE_SYS_TIME_H + +/* Define to 1 if you have the <sys/types.h> header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define to 1 if you have the <time.h> header file. */ +#undef HAVE_TIME_H + +/* Define to 1 if you have the <unistd.h> header file. */ +#undef HAVE_UNISTD_H + +/* Define to 1 if you have the <utmp.h> header file. */ +#undef HAVE_UTMP_H + +/* Define to the address where bug reports for this package should be sent. */ +#undef PACKAGE_BUGREPORT + +/* Define to the full name of this package. */ +#undef PACKAGE_NAME + +/* Define to the full name and version of this package. */ +#undef PACKAGE_STRING + +/* Define to the one symbol short name of this package. */ +#undef PACKAGE_TARNAME + +/* Define to the home page for this package. */ +#undef PACKAGE_URL + +/* Define to the version of this package. */ +#undef PACKAGE_VERSION + +/* Version integer in format <ma><ma><mi><mi><in><in> */ +#undef PAM_RADIUS_VERSION + +/* Raw version string from VERSION file */ +#undef PAM_RADIUS_VERSION_STRING + +/* Define to 1 if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Enable extensions on AIX 3, Interix.  */ +#ifndef _ALL_SOURCE +# undef _ALL_SOURCE +#endif +/* Enable GNU extensions on systems that have them.  */ +#ifndef _GNU_SOURCE +# undef _GNU_SOURCE +#endif +/* Enable threading extensions on Solaris.  */ +#ifndef _POSIX_PTHREAD_SEMANTICS +# undef _POSIX_PTHREAD_SEMANTICS +#endif +/* Enable extensions on HP NonStop.  */ +#ifndef _TANDEM_SOURCE +# undef _TANDEM_SOURCE +#endif +/* Enable general extensions on Solaris.  */ +#ifndef __EXTENSIONS__ +# undef __EXTENSIONS__ +#endif + + +/* define if pam_radius was built with -DNDEBUG */ +#undef WITH_NDEBUG + +/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most +   significant byte first (like Motorola and SPARC, unlike Intel). */ +#if defined AC_APPLE_UNIVERSAL_BUILD +# if defined __BIG_ENDIAN__ +#  define WORDS_BIGENDIAN 1 +# endif +#else +# ifndef WORDS_BIGENDIAN +#  undef WORDS_BIGENDIAN +# endif +#endif + +/* Define to 1 if on MINIX. */ +#undef _MINIX + +/* Define to 2 if the system does not provide POSIX.1 features except with +   this defined. */ +#undef _POSIX_1_SOURCE + +/* Define to 1 if you need to in order for `stat' and other things to work. */ +#undef _POSIX_SOURCE + +/* Define to empty if `const' does not conform to ANSI C. */ +#undef const + +/* Define to `int' if <sys/types.h> doesn't define. */ +#undef gid_t + +/* Define to `long int' if <sys/types.h> does not define. */ +#undef off_t + +/* Define to `int' if <sys/types.h> does not define. */ +#undef pid_t + +/* Define to `unsigned int' if <sys/types.h> does not define. */ +#undef size_t + +/* socklen_t is generally 'int' on systems which don't use it */ +#undef socklen_t + +/* Define to `int' if <sys/types.h> doesn't define. */ +#undef uid_t + +/* uint16_t should be the canonical '2 octets' for network traffic */ +#undef uint16_t + +/* uint32_t should be the canonical 'network integer' */ +#undef uint32_t + +/* uint64_t is required for larger counters */ +#undef uint64_t + +/* uint8_t should be the canonical 'octet' for network traffic */ +#undef uint8_t diff --git a/src/md5.c b/src/md5.c new file mode 100644 index 0000000..bb4546e --- /dev/null +++ b/src/md5.c @@ -0,0 +1,278 @@ +/* $Id: md5.c,v 1.3 2007/03/26 04:21:07 fcusack 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. + * + * $Log: md5.c,v $ + * Revision 1.3  2007/03/26 04:21:07  fcusack + * 	use uint32_t (C99) not u_int32_t + * + * Revision 1.2  2002/06/28 06:29:21  fcusack + * 	change HIGHFIRST #ifdef from 'sun' to __sparc, and add __mips + * + * Revision 1.1.1.1  1999/08/19 13:13:26  aland + * 	Start of the pam_radius module + * + * Revision 1.2  1998/04/03 20:19:21  aland + * now builds cleanly on Solaris 2.6 + * + * Revision 1.1  1998/04/03 19:36:59  aland + * oh yeah, do MD5 stuff, too + * + * Revision 1.1  1996/12/01 03:06:54  morgan + * Initial revision + * + * Revision 1.1  1996/09/05 06:43:31  morgan + * Initial revision + * + */ + +#include <string.h> +#include "md5.h" + +#ifdef LITTLE_ENDIAN +#  define byteReverse(buf, len)	/* Nothing */ +#else +void byteReverse(unsigned char *buf, unsigned longs); + +#ifndef ASM_MD5 +/* + * Note: this code is harmless on little-endian machines. + */ +void byteReverse(unsigned char *buf, unsigned longs) +{ +	uint32_t t; +	do { +		t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 | +				((unsigned) buf[1] << 8 | buf[0]); +		*(uint32_t *) buf = t; +		buf += 4; +	} while (--longs); +} +#endif +#endif + +/* + * Start MD5 accumulation.	Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void 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 MD5Update(struct MD5Context *ctx, unsigned const char *buf, unsigned len) +{ +	uint32_t t; + +	/* Update bitcount */ + +	t = ctx->bits[0]; +	if ((ctx->bits[0] = t + ((uint32_t) 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); +		MD5Transform(ctx->buf, (uint32_t *) ctx->in); +		buf += t; +		len -= t; +	} +	/* Process data in 64-byte chunks */ + +	while (len >= 64) { +		memcpy(ctx->in, buf, 64); +		byteReverse(ctx->in, 16); +		MD5Transform(ctx->buf, (uint32_t *) 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 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); +		MD5Transform(ctx->buf, (uint32_t *) 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_t *) ctx->in)[14] = ctx->bits[0]; +	((uint32_t *) ctx->in)[15] = ctx->bits[1]; + +	MD5Transform(ctx->buf, (uint32_t *) 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 MD5Transform(uint32_t buf[4], uint32_t const in[16]) +{ +	register uint32_t 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/src/md5.h b/src/md5.h new file mode 100644 index 0000000..ce0e350 --- /dev/null +++ b/src/md5.h @@ -0,0 +1,60 @@ +#ifndef MD5_H +#define MD5_H +#include "config.h" + +/* + *	Try and determine endianness of the target system. + * + *	Other projects seem to use endian.h and variants, but these are + *	in non standard locations, and may mess up cross compiling. + * + *	Here at least the endianess can be set explicitly with + *	-DLITTLE_ENDIAN or -DBIG_ENDIAN. + */ +#if !defined(LITTLE_ENDIAN) && !defined(BIG_ENDIAN) +#  if defined(__LITTLE_ENDIAN__) || \ +      (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) || \ +      defined(AC_LITTLE_ENDIAN) +#    define LITTLE_ENDIAN 1 +#  elif defined(__BIG_ENDIAN__) || \ +      (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) || \ +      defined(AC_BIG_ENDIAN) +#    define BIG_ENDIAN 1 +#  else +#    error Failed determining endianness of system +#  endif +#endif + +/* + *  Some operating systems MAY resolve the MD5* functions to + *  secret functions in one of their libraries.  These OS supplied + *  MD5 functions almost always blow up, and cause problems. + *  To get around the issue, we re-define the MD5 function names + *  so that we're sure that our module uses our tested and working + *  MD5 functions. + */ +#define MD5Init       pra_MD5Init +#define MD5Update     pra_MD5Update +#define MD5Final      pra_MD5Final +#define MD5Transform  pra_MD5Transform + +#include <inttypes.h> + +struct MD5Context { +	uint32_t buf[4]; +	uint32_t bits[2]; +	unsigned char in[64]; +}; + +void MD5Init(struct MD5Context *); +void MD5Update(struct MD5Context *, unsigned const char *, unsigned); +void MD5Final(unsigned char digest[16], struct MD5Context *); +void MD5Transform(uint32_t buf[4], uint32_t const in[16]); + +/* + * This is needed to make RSAREF happy on some MS-DOS compilers. + */ + +typedef struct MD5Context MD5_CTX; + +#endif /* MD5_H */ diff --git a/src/pam_radius_auth.c b/src/pam_radius_auth.c new file mode 100644 index 0000000..887ee1e --- /dev/null +++ b/src/pam_radius_auth.c @@ -0,0 +1,1596 @@ +/* + * $Id: pam_radius_auth.c,v 1.39 2007/03/26 05:35:31 fcusack Exp $ + * pam_radius_auth + *      Authenticate a user via a RADIUS session + * + * 0.9.0 - Didn't compile quite right. + * 0.9.1 - Hands off passwords properly.  Solaris still isn't completely happy + * 0.9.2 - Solaris now does challenge-response.  Added configuration file + *         handling, and skip_passwd field + * 1.0.0 - Added handling of port name/number, and continue on select + * 1.1.0 - more options, password change requests work now, too. + * 1.1.1 - Added client_id=foo (NAS-Identifier), defaulting to PAM_SERVICE + * 1.1.2 - multi-server capability. + * 1.2.0 - ugly merger of pam_radius.c to get full RADIUS capability + * 1.3.0 - added my own accounting code.  Simple, clean, and neat. + * 1.3.1 - Supports accounting port (oops!), and do accounting authentication + * 1.3.2 - added support again for 'skip_passwd' control flag. + * 1.3.10 - ALWAYS add Password attribute, to make packets RFC compliant. + * 1.3.11 - Bug fixes by Jon Nelson <jnelson@securepipe.com> + * 1.3.12 - miscellanous bug fixes.  Don't add password to accounting + *          requests; log more errors; add NAS-Port and NAS-Port-Type + *          attributes to ALL packets.  Some patches based on input from + *          Grzegorz Paszka <Grzegorz.Paszka@pik-net.pl> + * 1.3.13 - Always update the configuration file, even if we're given + *          no options.  Patch from Jon Nelson <jnelson@securepipe.com> + * 1.3.14 - Don't use PATH_MAX, so it builds on GNU Hurd. + * 1.3.15 - Implement retry option, miscellanous bug fixes. + * 1.3.16 - Miscellaneous fixes (see CVS for history) + * 1.3.17 - Security fixes + * + * + *   This program is free software; you can redistribute it and/or modify + *   it under the terms of the GNU General Public License as published by + *   the Free Software Foundation; either version 2 of the License, or + *   (at your option) any later version. + * + *   This program is distributed in the hope that it will be useful, + *   but WITHOUT ANY WARRANTY; without even the implied warranty of + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *   GNU General Public License for more details. + * + *   You should have received a copy of the GNU General Public License + *   along with this program; if not, write to the Free Software + *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA + * + * The original pam_radius.c code is copyright (c) Cristian Gafton, 1996, + *                                             <gafton@redhat.com> + * + * Some challenge-response code is copyright (c) CRYPTOCard Inc, 1998. + *                                              All rights reserved. + */ + +#define PAM_SM_AUTH +#define PAM_SM_PASSWORD +#define PAM_SM_SESSION + +#include <limits.h> +#include <errno.h> +#include <sys/time.h> + +#include "pam_radius_auth.h" + +#define DPRINT if (ctrl & PAM_DEBUG_ARG) _pam_log + +/* internal data */ +static CONST char *pam_module_name = "pam_radius_auth"; +static char conf_file[BUFFER_SIZE]; /* configuration file */ + +/* we need to save these from open_session to close_session, since + * when close_session will be called we won't be root anymore and + * won't be able to access again the radius server configuration file + * -- cristiang */ +static radius_server_t *live_server = NULL; +static time_t session_time; + +/* logging */ +static void _pam_log(int err, CONST char *format, ...) +{ +	va_list args; +	char buffer[BUFFER_SIZE]; + +	va_start(args, format); +	vsprintf(buffer, format, args); +	/* don't do openlog or closelog, but put our name in to be friendly */ +	syslog(err, "%s: %s", pam_module_name, buffer); +	va_end(args); +} + +/* argument parsing */ +static int _pam_parse(int argc, CONST char **argv, radius_conf_t *conf) +{ +	int ctrl=0; + +	memset(conf, 0, sizeof(radius_conf_t)); /* ensure it's initialized */ + +	strcpy(conf_file, CONF_FILE); + +	/* +	 *	If either is not there, then we can't parse anything. +	 */ +	if ((argc == 0) || (argv == NULL)) { +		return ctrl; +	} + +	/* step through arguments */ +	for (ctrl=0; argc-- > 0; ++argv) { + +		/* generic options */ +		if (!strncmp(*argv,"conf=",5)) { +			strcpy(conf_file,*argv+5); + +		} else if (!strcmp(*argv, "use_first_pass")) { +			ctrl |= PAM_USE_FIRST_PASS; + +		} else if (!strcmp(*argv, "try_first_pass")) { +			ctrl |= PAM_TRY_FIRST_PASS; + +		} else if (!strcmp(*argv, "skip_passwd")) { +			ctrl |= PAM_SKIP_PASSWD; + +		} else if (!strncmp(*argv, "retry=", 6)) { +			conf->retries = atoi(*argv+6); + +		} else if (!strcmp(*argv, "localifdown")) { +			conf->localifdown = 1; + +		} else if (!strncmp(*argv, "client_id=", 10)) { +			if (conf->client_id) { +				_pam_log(LOG_WARNING, "ignoring duplicate '%s'", *argv); +			} else { +				conf->client_id = (char *) *argv+10; /* point to the client-id */ +			} +		} else if (!strcmp(*argv, "accounting_bug")) { +			conf->accounting_bug = TRUE; + +		} else if (!strcmp(*argv, "ruser")) { +			ctrl |= PAM_RUSER_ARG; + +		} else if (!strcmp(*argv, "debug")) { +			ctrl |= PAM_DEBUG_ARG; +			conf->debug = 1; + +		} else { +			_pam_log(LOG_WARNING, "unrecognized option '%s'", *argv); +		} +	} + +	return ctrl; +} + +/* Callback function used to free the saved return value for pam_setcred. */ +void _int_free(pam_handle_t * pamh, void *x, int error_status) +{ +		free(x); +} + +/************************************************************************* + * SMALL HELPER FUNCTIONS + *************************************************************************/ + +/* + * Return an IP address in host long notation from + * one supplied in standard dot notation. + */ +static uint32_t ipstr2long(char *ip_str) { +	char	buf[6]; +	char	*ptr; +	int	i; +	int	count; +	uint32_t	ipaddr; +	int	cur_byte; + +	ipaddr = (uint32_t)0; + +	for(i = 0;i < 4;i++) { +		ptr = buf; +		count = 0; +		*ptr = '\0'; + +		while(*ip_str != '.' && *ip_str != '\0' && count < 4) { +			if(!isdigit(*ip_str)) { +				return((uint32_t)0); +			} +			*ptr++ = *ip_str++; +			count++; +		} + +		if(count >= 4 || count == 0) { +			return((uint32_t)0); +		} + +		*ptr = '\0'; +		cur_byte = atoi(buf); +		if(cur_byte < 0 || cur_byte > 255) { +			return ((uint32_t)0); +		} + +		ip_str++; +		ipaddr = ipaddr << 8 | (uint32_t)cur_byte; +	} +	return(ipaddr); +} + +/* + * Check for valid IP address in standard dot notation. + */ +static int good_ipaddr(char *addr) { +	int dot_count; +	int digit_count; + +	dot_count = 0; +	digit_count = 0; +	while(*addr != '\0' && *addr != ' ') { +		if(*addr == '.') { +			dot_count++; +			digit_count = 0; +		} else if(!isdigit(*addr)) { +			dot_count = 5; +		} else { +			digit_count++; +			if(digit_count > 3) { +				dot_count = 5; +			} +		} +		addr++; +	} +	if(dot_count != 3) { +		return(-1); +	} else { +		return(0); +	} +} + +/* + * Return an IP address in host long notation from a host + * name or address in dot notation. + */ +static uint32_t get_ipaddr(char *host) { +	struct hostent *hp; + +	if(good_ipaddr(host) == 0) { +		return(ipstr2long(host)); +	} else if((hp = gethostbyname(host)) == (struct hostent *)NULL) { +		return((uint32_t)0); +	} + +	return(ntohl(*(uint32_t *)hp->h_addr)); +} + +/* + * take server->hostname, and convert it to server->ip and server->port + */ +static int host2server(radius_server_t *server) +{ +	char *p; +	int ctrl = 1; /* for DPRINT */ + +	if ((p = strchr(server->hostname, ':')) != NULL) { +		*(p++) = '\0';		/* split the port off from the host name */ +	} + +	if ((server->ip.s_addr = get_ipaddr(server->hostname)) == ((uint32_t)0)) { +		DPRINT(LOG_DEBUG, "DEBUG: get_ipaddr(%s) returned 0.\n", server->hostname); +		return PAM_AUTHINFO_UNAVAIL; +	} + +	/* +	 *	If the server port hasn't already been defined, go get it. +	 */ +	if (!server->port) { +		if (p && isdigit(*p)) {	/* the port looks like it's a number */ +			unsigned int i = atoi(p) & 0xffff; + +			if (!server->accounting) { +				server->port = htons((uint16_t) i); +			} else { +				server->port = htons((uint16_t) (i + 1)); +			} +		} else {			/* the port looks like it's a name */ +			struct servent *svp; + +			if (p) {			/* maybe it's not "radius" */ +				svp = getservbyname (p, "udp"); +				/* quotes allow distinction from above, lest p be radius or radacct */ +				DPRINT(LOG_DEBUG, "DEBUG: getservbyname('%s', udp) returned %d.\n", p, svp); +				*(--p) = ':';		/* be sure to put the delimiter back */ +			} else { +				if (!server->accounting) { +					svp = getservbyname ("radius", "udp"); +					DPRINT(LOG_DEBUG, "DEBUG: getservbyname(radius, udp) returned %d.\n", svp); +				} else { +					svp = getservbyname ("radacct", "udp"); +					DPRINT(LOG_DEBUG, "DEBUG: getservbyname(radacct, udp) returned %d.\n", svp); +				} +			} + +			if (svp == (struct servent *) 0) { +				/* debugging above... */ +				return PAM_AUTHINFO_UNAVAIL; +			} + +			server->port = svp->s_port; +		} +	} + +	return PAM_SUCCESS; +} + +/* + * Do XOR of two buffers. + */ +static unsigned char * xor(unsigned char *p, unsigned char *q, int length) +{ +	int i; +	unsigned char *retval = p; + +	for (i = 0; i < length; i++) { +		*(p++) ^= *(q++); +	} +	return retval; +} + +/************************************************************************** + * MID-LEVEL RADIUS CODE + **************************************************************************/ + +/* + * get a pseudo-random vector. + */ +static void get_random_vector(unsigned char *vector) +{ +#ifdef linux +	int fd = open("/dev/urandom",O_RDONLY); /* Linux: get *real* random numbers */ +	int total = 0; +	if (fd >= 0) { +		while (total < AUTH_VECTOR_LEN) { +			int bytes = read(fd, vector + total, AUTH_VECTOR_LEN - total); +			if (bytes <= 0) +				break;			/* oops! Error */ +			total += bytes; +		} +		close(fd); +	} + +	if (total != AUTH_VECTOR_LEN) +#endif +	{				/* do this *always* on other platforms */ +		MD5_CTX my_md5; +		struct timeval tv; +		struct timezone tz; +		static unsigned int session = 0; /* make the number harder to guess */ + +		/* Use the time of day with the best resolution the system can +	 	   give us -- often close to microsecond accuracy. */ +		gettimeofday(&tv,&tz); + +		if (session == 0) { +			session = getppid();	/* (possibly) hard to guess information */ +		} + +		tv.tv_sec ^= getpid() * session++; + +		/* Hash things to get maybe cryptographically strong pseudo-random numbers */ +		MD5Init(&my_md5); +		MD5Update(&my_md5, (unsigned char *) &tv, sizeof(tv)); +		MD5Update(&my_md5, (unsigned char *) &tz, sizeof(tz)); +		MD5Final(vector, &my_md5);				/* set the final vector */ +	} +} + +/* + * RFC 2139 says to do generate the accounting request vector this way. + * However, the Livingston 1.16 server doesn't check it.	The Cistron + * server (http://home.cistron.nl/~miquels/radius/) does, and this code + * seems to work with it.	It also works with Funk's Steel-Belted RADIUS. + */ +static void get_accounting_vector(AUTH_HDR *request, radius_server_t *server) +{ +	MD5_CTX my_md5; +	int secretlen = strlen(server->secret); +	int len = ntohs(request->length); + +	memset(request->vector, 0, AUTH_VECTOR_LEN); +	MD5Init(&my_md5); +	memcpy(((char *)request) + len, server->secret, secretlen); + +	MD5Update(&my_md5, (unsigned char *)request, len + secretlen); +	MD5Final(request->vector, &my_md5);			/* set the final vector */ +} + +/* + * Verify the response from the server + */ +static int verify_packet(char *secret, AUTH_HDR *response, AUTH_HDR *request) +{ +	MD5_CTX my_md5; +	unsigned char	calculated[AUTH_VECTOR_LEN]; +	unsigned char	reply[AUTH_VECTOR_LEN]; + +	/* +	 * We could dispense with the memcpy, and do MD5's of the packet +	 * + vector piece by piece.	This is easier understand, and maybe faster. +	 */ +	memcpy(reply, response->vector, AUTH_VECTOR_LEN); /* save the reply */ +	memcpy(response->vector, request->vector, AUTH_VECTOR_LEN); /* sent vector */ + +	/* MD5(response packet header + vector + response packet data + secret) */ +	MD5Init(&my_md5); +	MD5Update(&my_md5, (unsigned char *) response, ntohs(response->length)); + +	/* +	 * This next bit is necessary because of a bug in the original Livingston +	 * RADIUS server.	The authentication vector is *supposed* to be MD5'd +	 * with the old password (as the secret) for password changes. +	 * However, the old password isn't used.	The "authentication" vector +	 * for the server reply packet is simply the MD5 of the reply packet. +	 * Odd, the code is 99% there, but the old password is never copied +	 * to the secret! +	 */ +	if (*secret) { +		MD5Update(&my_md5, (unsigned char *) secret, strlen(secret)); +	} + +	MD5Final(calculated, &my_md5);			/* set the final vector */ + +	/* Did he use the same random vector + shared secret? */ +	if (memcmp(calculated, reply, AUTH_VECTOR_LEN) != 0) { +		return FALSE; +	} +	return TRUE; +} + +/* + * Find an attribute in a RADIUS packet.	Note that the packet length + * is *always* kept in network byte order. + */ +static attribute_t *find_attribute(AUTH_HDR *response, unsigned char type) +{ +	attribute_t *attr = (attribute_t *) &response->data; + +	int len = ntohs(response->length) - AUTH_HDR_LEN; + +	while (attr->attribute != type) { +		if ((len -= attr->length) <= 0) { +			return NULL;		/* not found */ +		} +		attr = (attribute_t *) ((char *) attr + attr->length); +	} + +	return attr; +} + +/* + * Add an attribute to a RADIUS packet. + */ +static void add_attribute(AUTH_HDR *request, unsigned char type, CONST unsigned char *data, int length) +{ +	attribute_t *p; + +	p = (attribute_t *) ((unsigned char *)request + ntohs(request->length)); +	p->attribute = type; +	p->length = length + 2;		/* the total size of the attribute */ +	request->length = htons(ntohs(request->length) + p->length); +	memcpy(p->data, data, length); +} + +/* + * Add an integer attribute to a RADIUS packet. + */ +static void add_int_attribute(AUTH_HDR *request, unsigned char type, int data) +{ +	int value = htonl(data); + +	add_attribute(request, type, (unsigned char *) &value, sizeof(int)); +} + +/* + * Add a RADIUS password attribute to the packet.	Some magic is done here. + * + * If it's an PW_OLD_PASSWORD attribute, it's encrypted using the encrypted + * PW_PASSWORD attribute as the initialization vector. + * + * If the password attribute already exists, it's over-written.	This allows + * us to simply call add_password to update the password for different + * servers. + */ +static void add_password(AUTH_HDR *request, unsigned char type, CONST char *password, char *secret) +{ +	MD5_CTX md5_secret, my_md5; +	unsigned char misc[AUTH_VECTOR_LEN]; +	int i; +	int length = strlen(password); +	unsigned char hashed[256 + AUTH_PASS_LEN];	/* can't be longer than this */ +	unsigned char *vector; +	attribute_t *attr; + +	if (length > MAXPASS) {				/* shorten the password for now */ +		length = MAXPASS; +	} + +	if (length == 0) { +		length = AUTH_PASS_LEN;			/* 0 maps to 16 */ +	} if ((length & (AUTH_PASS_LEN - 1)) != 0) { +		length += (AUTH_PASS_LEN - 1);		/* round it up */ +		length &= ~(AUTH_PASS_LEN - 1);		/* chop it off */ +	}						/* 16*N maps to itself */ + +	memset(hashed, 0, length); +	memcpy(hashed, password, strlen(password)); + +	attr = find_attribute(request, PW_PASSWORD); + +	if (type == PW_PASSWORD) { +		vector = request->vector; +	} else { +		vector = attr->data;			/* attr CANNOT be NULL here. */ +	} + +	/* ************************************************************ */ +	/* encrypt the password */ +	/* password : e[0] = p[0] ^ MD5(secret + vector) */ +	MD5Init(&md5_secret); +	MD5Update(&md5_secret, (unsigned char *) secret, strlen(secret)); +	my_md5 = md5_secret;				/* so we won't re-do the hash later */ +	MD5Update(&my_md5, vector, AUTH_VECTOR_LEN); +	MD5Final(misc, &my_md5);			/* set the final vector */ +	xor(hashed, misc, AUTH_PASS_LEN); + +	/* For each step through, e[i] = p[i] ^ MD5(secret + e[i-1]) */ +	for (i = 1; i < (length >> 4); i++) { +		my_md5 = md5_secret;			/* grab old value of the hash */ +		MD5Update(&my_md5, &hashed[(i-1) * AUTH_PASS_LEN], AUTH_PASS_LEN); +		MD5Final(misc, &my_md5);			/* set the final vector */ +		xor(&hashed[i * AUTH_PASS_LEN], misc, AUTH_PASS_LEN); +	} + +	if (type == PW_OLD_PASSWORD) { +		attr = find_attribute(request, PW_OLD_PASSWORD); +	} + +	if (!attr) { +		add_attribute(request, type, hashed, length); +	} else { +		memcpy(attr->data, hashed, length); /* overwrite the packet */ +	} +} + +static void cleanup(radius_server_t *server) +{ +	radius_server_t *next; + +	while (server) { +		next = server->next; +		_pam_drop(server->hostname); +		_pam_forget(server->secret); +		_pam_drop(server); +		server = next; +	} +} + +/* + * allocate and open a local port for communication with the RADIUS + * server + */ +static int initialize(radius_conf_t *conf, int accounting) +{ +	struct sockaddr salocal; +	uint16_t local_port; +	char hostname[BUFFER_SIZE]; +	char secret[BUFFER_SIZE]; + +	char buffer[BUFFER_SIZE]; +	char *p; +	FILE *fserver; +	radius_server_t *server = NULL; +	struct sockaddr_in * s_in; +	int timeout; +	int line = 0; + +	/* the first time around, read the configuration file */ +	if ((fserver = fopen (conf_file, "r")) == (FILE*)NULL) { +		_pam_log(LOG_ERR, "Could not open configuration file %s: %s\n", +			conf_file, strerror(errno)); +		return PAM_ABORT; +	} + +	while (!feof(fserver) && (fgets (buffer, sizeof(buffer), fserver) != (char*) NULL) && (!ferror(fserver))) { +		line++; +		p = buffer; + +		/* +		 *	Skip blank lines and whitespace +		 */ +		while (*p && ((*p == ' ') || (*p == '\t') || (*p == '\r') || (*p == '\n'))) { +			p++; +		} + +		/* +		 *	Nothing, or just a comment. Ignore the line. +		 */ +		if ((!*p) || (*p == '#')) { +			continue; +		} + +		timeout = 3; +		if (sscanf(p, "%s %s %d", hostname, secret, &timeout) < 2) { +			_pam_log(LOG_ERR, "ERROR reading %s, line %d: Could not read hostname or secret\n", +				 conf_file, line); +			continue;			/* invalid line */ +		} else {				/* read it in and save the data */ +			radius_server_t *tmp; + +			tmp = malloc(sizeof(radius_server_t)); +			if (server) { +				server->next = tmp; +				server = server->next; +			} else { +				conf->server = tmp; +				server= tmp;		/* first time */ +			} + +			/* sometime later do memory checks here */ +			server->hostname = strdup(hostname); +			server->secret = strdup(secret); +			server->accounting = accounting; +			server->port = 0; + +			if ((timeout < 1) || (timeout > 60)) { +				server->timeout = 3; +			} else { +				server->timeout = timeout; +			} +			server->next = NULL; +		} +	} +	fclose(fserver); + +	if (!server) {		/* no server found, die a horrible death */ +		_pam_log(LOG_ERR, "No RADIUS server found in configuration file %s\n", +			 conf_file); +		return PAM_AUTHINFO_UNAVAIL; +	} + +	/* open a socket.	Dies if it fails */ +	conf->sockfd = socket(AF_INET, SOCK_DGRAM, 0); +	if (conf->sockfd < 0) { +		_pam_log(LOG_ERR, "Failed to open RADIUS socket: %s\n", strerror(errno)); +		return PAM_AUTHINFO_UNAVAIL; +	} + +	/* set up the local end of the socket communications */ +	s_in = (struct sockaddr_in *) &salocal; +	memset ((char *) s_in, '\0', sizeof(struct sockaddr)); +	s_in->sin_family = AF_INET; +	s_in->sin_addr.s_addr = INADDR_ANY; + +	/* +	 *	Use our process ID as a local port for RADIUS. +	 */ +	local_port = (getpid() & 0x7fff) + 1024; +	do { +		local_port++; +		s_in->sin_port = htons(local_port); +	} while ((bind(conf->sockfd, &salocal, sizeof (struct sockaddr_in)) < 0) && (local_port < 64000)); + +	if (local_port >= 64000) { +		close(conf->sockfd); +		_pam_log(LOG_ERR, "No open port we could bind to."); +		return PAM_AUTHINFO_UNAVAIL; +	} + +	return PAM_SUCCESS; +} + +/* + * Helper function for building a radius packet. + * It initializes *some* of the header, and adds common attributes. + */ +static void build_radius_packet(AUTH_HDR *request, CONST char *user, CONST char *password, radius_conf_t *conf) +{ +	char hostname[256]; +	uint32_t ipaddr; + +	hostname[0] = '\0'; +	gethostname(hostname, sizeof(hostname) - 1); + +	request->length = htons(AUTH_HDR_LEN); + +	if (password) {		/* make a random authentication req vector */ +		get_random_vector(request->vector); +	} + +	add_attribute(request, PW_USER_NAME, (unsigned char *) user, strlen(user)); + +	/* +	 *	Add a password, if given. +	 */ +	if (password) { +		add_password(request, PW_PASSWORD, password, conf->server->secret); + +		/* +		 *	Add a NULL password to non-accounting requests. +		 */ +	} else if (request->code != PW_ACCOUNTING_REQUEST) { +		add_password(request, PW_PASSWORD, "", conf->server->secret); +	} + +	/* the packet is from localhost if on localhost, to make configs easier */ +	if ((conf->server->ip.s_addr == ntohl(0x7f000001)) || (!hostname[0])) { +		ipaddr = 0x7f000001; +	} else { +		struct hostent *hp; + +		if ((hp = gethostbyname(hostname)) == (struct hostent *) NULL) { +			ipaddr = 0x00000000;	/* no client IP address */ +		} else { +			ipaddr = ntohl(*(uint32_t *) hp->h_addr); /* use the first one available */ +		} +	} + +	/* If we can't find an IP address, then don't add one */ +	if (ipaddr) { +		add_int_attribute(request, PW_NAS_IP_ADDRESS, ipaddr); +	} + +	/* There's always a NAS identifier */ +	if (conf->client_id && *conf->client_id) { +		add_attribute(request, PW_NAS_IDENTIFIER, (unsigned char *) conf->client_id, strlen(conf->client_id)); +	} + +	/* +	 *	Add in the port (pid) and port type (virtual). +	 * +	 *	We might want to give the TTY name here, too. +	 */ +	add_int_attribute(request, PW_NAS_PORT_ID, getpid()); +	add_int_attribute(request, PW_NAS_PORT_TYPE, PW_NAS_PORT_TYPE_VIRTUAL); +} + +/* + * Talk RADIUS to a server. + * Send a packet and get the response + */ +static int talk_radius(radius_conf_t *conf, AUTH_HDR *request, AUTH_HDR *response, +		       char *password, char *old_password, int tries) +{ +	socklen_t salen; +	int total_length; +	fd_set set; +	struct timeval tv; +	time_t now, end; +	int rcode; +	struct sockaddr saremote; +	struct sockaddr_in *s_in = (struct sockaddr_in *) &saremote; +	radius_server_t *server = conf->server; +	int ok; +	int server_tries; +	int retval; + +	/* ************************************************************ */ +	/* Now that we're done building the request, we can send it */ + +	/* +	 Hmm... on password change requests, all of the found server information +	 could be saved with a pam_set_data(), which means even the radius_conf_t +	 information will have to be malloc'd at some point + +	 On the other hand, we could just try all of the servers again in +	 sequence, on the off chance that one may have ended up fixing itself. + +	 */ + +	/* loop over all available servers */ +	while (server != NULL) { + +		/* only look up IP information as necessary */ +		if ((retval = host2server(server)) != PAM_SUCCESS) { +			_pam_log(LOG_ERR, +				 "Failed looking up IP address for RADIUS server %s (errcode=%d)", +				 server->hostname, retval); +			ok = FALSE; +			goto next;		/* skip to the next server */ +		} + +		/* set up per-server IP && port configuration */ +		memset ((char *) s_in, '\0', sizeof(struct sockaddr)); +		s_in->sin_family = AF_INET; +		s_in->sin_addr.s_addr = htonl(server->ip.s_addr); +		s_in->sin_port = server->port; +		total_length = ntohs(request->length); + +		if (!password) { 		/* make an RFC 2139 p6 request authenticator */ +			get_accounting_vector(request, server); +		} + +		server_tries = tries; +	send: +		/* send the packet */ +		if (sendto(conf->sockfd, (char *) request, total_length, 0, +			   &saremote, sizeof(struct sockaddr_in)) < 0) { +			_pam_log(LOG_ERR, "Error sending RADIUS packet to server %s: %s", +				 server->hostname, strerror(errno)); +			ok = FALSE; +			goto next;		/* skip to the next server */ +		} + +		/* ************************************************************ */ +		/* Wait for the response, and verify it. */ +		salen = sizeof(struct sockaddr); +		tv.tv_sec = server->timeout;	/* wait for the specified time */ +		tv.tv_usec = 0; +		FD_ZERO(&set);			/* clear out the set */ +		FD_SET(conf->sockfd, &set);	/* wait only for the RADIUS UDP socket */ + +		time(&now); +		end = now + tv.tv_sec; + +		/* loop, waiting for the select to return data */ +		ok = TRUE; +		while (ok) { +			rcode = select(conf->sockfd + 1, &set, NULL, NULL, &tv); + +			/* select timed out */ +			if (rcode == 0) { +				_pam_log(LOG_ERR, "RADIUS server %s failed to respond", server->hostname); +				if (--server_tries) { +					goto send; +				} +				ok = FALSE; +				break;			/* exit from the select loop */ +			} else if (rcode < 0) { + +				/* select had an error */ +				if (errno == EINTR) {	/* we were interrupted */ +					time(&now); + +					if (now > end) { +						_pam_log(LOG_ERR, "RADIUS server %s failed to respond", +							 server->hostname); +						if (--server_tries) goto send; +						ok = FALSE; +						break;		/* exit from the select loop */ +					} + +					tv.tv_sec = end - now; +					if (tv.tv_sec == 0) {	/* keep waiting */ +						tv.tv_sec = 1; +					} + +				} else {			/* not an interrupt, it was a real error */ +					_pam_log(LOG_ERR, "Error waiting for response from RADIUS server %s: %s", +						 server->hostname, strerror(errno)); +					ok = FALSE; +					break; +				} + +			/* the select returned OK */ +			} else if (FD_ISSET(conf->sockfd, &set)) { + +				/* try to receive some data */ +				if ((total_length = recvfrom(conf->sockfd, (void *) response, BUFFER_SIZE, +						     	     0, &saremote, &salen)) < 0) { +					_pam_log(LOG_ERR, "error reading RADIUS packet from server %s: %s", +					 	 server->hostname, strerror(errno)); +					ok = FALSE; +					break; + +				/* there's data, see if it's valid */ +				} else { +					char *p = server->secret; + +					if ((ntohs(response->length) != total_length) || +					    (ntohs(response->length) > BUFFER_SIZE)) { +						_pam_log(LOG_ERR, "RADIUS packet from server %s is corrupted", +						 	 server->hostname); +						ok = FALSE; +						break; +					} + +					/* Check if we have the data OK. We should also check request->id */ +					if (password) { +						if (old_password) { +#ifdef LIVINGSTON_PASSWORD_VERIFY_BUG_FIXED +							p = old_password;	/* what it should be */ +#else +							p = "";			/* what it really is */ +#endif +						} +					/* +					 * RFC 2139 p.6 says not do do this, but the Livingston 1.16 +					 * server disagrees.	If the user says he wants the bug, give in. +					 */ +					} else {		/* authentication request */ +						if (conf->accounting_bug) { +							p = ""; +						} +					} + +					if (!verify_packet(p, response, request)) { +						_pam_log(LOG_ERR, "packet from RADIUS server %s failed verification: " +							 "The shared secret is probably incorrect.", server->hostname); +						ok = FALSE; +						break; +					} + +					/* +					 * Check that the response ID matches the request ID. +					 */ +					if (response->id != request->id) { +						_pam_log(LOG_WARNING, "Response packet ID %d does not match the " +							 "request packet ID %d: verification of packet fails", +							 response->id, request->id); +						ok = FALSE; +						break; +					} +				} + +				/* +				 * Whew! The select is done. It hasn't timed out, or errored out. +				 * It's our descriptor.	We've got some data. It's the right size. +				 * The packet is valid. +				 * NOW, we can skip out of the select loop, and process the packet +				 */ +				break; +			} +			/* otherwise, we've got data on another descriptor, keep select'ing */ +		} + +			/* go to the next server if this one didn't respond */ +		next: +		if (!ok) { +			radius_server_t *old;	/* forget about this server */ + +			old = server; +			server = server->next; +			conf->server = server; + +			_pam_forget(old->secret); +			free(old->hostname); +			free(old); + +			if (server) {		/* if there's more servers to check */ +				/* get a new authentication vector, and update the passwords */ +				get_random_vector(request->vector); +				request->id = request->vector[0]; + +				/* update passwords, as appropriate */ +				if (password) { +					get_random_vector(request->vector); +					if (old_password) {	/* password change request */ +						add_password(request, PW_PASSWORD, password, old_password); +						add_password(request, PW_OLD_PASSWORD, old_password, old_password); +					} else {		/* authentication request */ +						add_password(request, PW_PASSWORD, password, server->secret); +					} +				} +			} +			continue; + +		} else { +			/* we've found one that does respond, forget about the other servers */ +			cleanup(server->next); +			server->next = NULL; +			live_server = server;	/* we've got a live one! */ +			break; +		} +	} + +	if (!server) { +		_pam_log(LOG_ERR, "All RADIUS servers failed to respond."); +		if (conf->localifdown) +			retval = PAM_IGNORE; +		else +			retval = PAM_AUTHINFO_UNAVAIL; +	} else { +		retval = PAM_SUCCESS; +	} + +	return retval; +} + +/************************************************************************** + * MIDLEVEL PAM CODE + **************************************************************************/ + +/* this is our front-end for module-application conversations */ + +#undef PAM_FAIL_CHECK +#define PAM_FAIL_CHECK if (retval != PAM_SUCCESS) { return retval; } + +static int rad_converse(pam_handle_t *pamh, int msg_style, char *message, char **password) +{ +	CONST struct pam_conv *conv; +	struct pam_message resp_msg; +	CONST struct pam_message *msg[1]; +	struct pam_response *resp = NULL; +	int retval; + +	resp_msg.msg_style = msg_style; +	resp_msg.msg = message; +	msg[0] = &resp_msg; + +	/* grab the password */ +	retval = pam_get_item(pamh, PAM_CONV, (CONST void **) &conv); +	PAM_FAIL_CHECK; + +	retval = conv->conv(1, msg, &resp,conv->appdata_ptr); +	PAM_FAIL_CHECK; + +	if (password) {		/* assume msg.type needs a response */ +		/* I'm not sure if this next bit is necessary on Linux */ +#ifdef sun +		/* NULL response, fail authentication */ +		if ((resp == NULL) || (resp->resp == NULL)) { +			return PAM_SYSTEM_ERR; +		} +#endif + +		*password = resp->resp; +		free(resp); +	} + +	return PAM_SUCCESS; +} + +/************************************************************************** + * GENERAL CODE + **************************************************************************/ + +#undef PAM_FAIL_CHECK +#define PAM_FAIL_CHECK if (retval != PAM_SUCCESS) { \ +	int *pret = malloc(sizeof(int)); \ +	*pret = retval;	\ +	pam_set_data(pamh, "rad_setcred_return", (void *) pret, _int_free);	\ +	return retval; } + +PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh,int flags,int argc,CONST char **argv) +{ +	CONST char *user; +	CONST char *userinfo; +	char *password = NULL; +	CONST char *rhost; +	char *resp2challenge = NULL; +	int ctrl; +	int retval = PAM_AUTH_ERR; + +	char recv_buffer[4096]; +	char send_buffer[4096]; +	AUTH_HDR *request = (AUTH_HDR *) send_buffer; +	AUTH_HDR *response = (AUTH_HDR *) recv_buffer; +	radius_conf_t config; + +	ctrl = _pam_parse(argc, argv, &config); + +	/* grab the user name */ +	retval = pam_get_user(pamh, &user, NULL); +	PAM_FAIL_CHECK; + +	/* check that they've entered something, and not too long, either */ +	if ((user == NULL) || (strlen(user) > MAXPWNAM)) { +		int *pret = malloc(sizeof(int)); +		*pret = PAM_USER_UNKNOWN; +		pam_set_data(pamh, "rad_setcred_return", (void *) pret, _int_free); + +		DPRINT(LOG_DEBUG, "User name was NULL, or too long"); +		return PAM_USER_UNKNOWN; +	} +	DPRINT(LOG_DEBUG, "Got user name %s", user); + +	if (ctrl & PAM_RUSER_ARG) { +		retval = pam_get_item(pamh, PAM_RUSER, (CONST void **) &userinfo); +		PAM_FAIL_CHECK; +		DPRINT(LOG_DEBUG, "Got PAM_RUSER name %s", userinfo); + +		if (!strcmp("root", user)) { +			user = userinfo; +			DPRINT(LOG_DEBUG, "Username now %s from ruser", user); +		} else { +			DPRINT(LOG_DEBUG, "Skipping ruser for non-root auth"); +		}; +	}; + +	/* +	 * Get the IP address of the authentication server +	 * Then, open a socket, and bind it to a port +	 */ +	retval = initialize(&config, FALSE); +	PAM_FAIL_CHECK; + +	/* +	 * If there's no client id specified, use the service type, to help +	 * keep track of which service is doing the authentication. +	 */ +	if (!config.client_id) { +		retval = pam_get_item(pamh, PAM_SERVICE, (CONST void **) &config.client_id); +		PAM_FAIL_CHECK; +	} + +	/* now we've got a socket open, so we've got to clean it up on error */ +#undef PAM_FAIL_CHECK +#define PAM_FAIL_CHECK if (retval != PAM_SUCCESS) {goto error; } + +	/* build and initialize the RADIUS packet */ +	request->code = PW_AUTHENTICATION_REQUEST; +	get_random_vector(request->vector); +	request->id = request->vector[0]; /* this should be evenly distributed */ + +	/* grab the password (if any) from the previous authentication layer */ +	retval = pam_get_item(pamh, PAM_AUTHTOK, (CONST void **) &password); +	PAM_FAIL_CHECK; + +	if(password) { +		password = strdup(password); +		DPRINT(LOG_DEBUG, "Got password %s", password); +	} + +	/* no previous password: maybe get one from the user */ +	if (!password) { +		if (ctrl & PAM_USE_FIRST_PASS) { +			retval = PAM_AUTH_ERR;	/* use one pass only, stopping if it fails */ +			goto error; +		} + +		/* check to see if we send a NULL password the first time around */ +		if (!(ctrl & PAM_SKIP_PASSWD)) { +			retval = rad_converse(pamh, PAM_PROMPT_ECHO_OFF, "Password: ", &password); +			PAM_FAIL_CHECK; + +		} +	} /* end of password == NULL */ + +	build_radius_packet(request, user, password, &config); +	/* not all servers understand this service type, but some do */ +	add_int_attribute(request, PW_USER_SERVICE_TYPE, PW_AUTHENTICATE_ONLY); + +	/* +	 *	Tell the server which host the user is coming from. +	 * +	 *	Note that this is NOT the IP address of the machine running PAM! +	 *	It's the IP address of the client. +	 */ +	retval = pam_get_item(pamh, PAM_RHOST, (CONST void **) &rhost); +	PAM_FAIL_CHECK; +	if (rhost) { +		add_attribute(request, PW_CALLING_STATION_ID, (unsigned char *) rhost, +			strlen(rhost)); +	} + +	DPRINT(LOG_DEBUG, "Sending RADIUS request code %d", request->code); + +	retval = talk_radius(&config, request, response, password, NULL, config.retries + 1); +	PAM_FAIL_CHECK; + +	DPRINT(LOG_DEBUG, "Got RADIUS response code %d", response->code); + +	/* +	 *	If we get an authentication failure, and we sent a NULL password, +	 *	ask the user for one and continue. +	 * +	 *	If we get an access challenge, then do a response, for as many +	 *	challenges as we receive. +	 */ +	while (response->code == PW_ACCESS_CHALLENGE) { +		attribute_t *a_state, *a_reply; +		char challenge[BUFFER_SIZE]; + +		/* Now we do a bit more work: challenge the user, and get a response */ +		if (((a_state = find_attribute(response, PW_STATE)) == NULL) || +		    ((a_reply = find_attribute(response, PW_REPLY_MESSAGE)) == NULL)) { +			/* Actually, State isn't required. */ +			_pam_log(LOG_ERR, "RADIUS Access-Challenge received with State or Reply-Message missing"); +			retval = PAM_AUTHINFO_UNAVAIL; +			goto error; +		} + +		/* +		 *	Security fixes. +		 */ +		if ((a_state->length <= 2) || (a_reply->length <= 2)) { +			_pam_log(LOG_ERR, "RADIUS Access-Challenge received with invalid State or Reply-Message"); +			retval = PAM_AUTHINFO_UNAVAIL; +			goto error; +		} + +		memcpy(challenge, a_reply->data, a_reply->length - 2); +		challenge[a_reply->length - 2] = 0; + +		/* It's full challenge-response, we should have echo on */ +		retval = rad_converse(pamh, PAM_PROMPT_ECHO_ON, challenge, &resp2challenge); + +		/* now that we've got a response, build a new radius packet */ +		build_radius_packet(request, user, resp2challenge, &config); +		/* request->code is already PW_AUTHENTICATION_REQUEST */ +		request->id++;		/* one up from the request */ + +		/* copy the state over from the servers response */ +		add_attribute(request, PW_STATE, a_state->data, a_state->length - 2); + +		retval = talk_radius(&config, request, response, resp2challenge, NULL, 1); +		PAM_FAIL_CHECK; + +		DPRINT(LOG_DEBUG, "Got response to challenge code %d", response->code); +	} + +	/* Whew! Done the pasword checks, look for an authentication acknowledge */ +	if (response->code == PW_AUTHENTICATION_ACK) { +		retval = PAM_SUCCESS; +	} else { +		retval = PAM_AUTH_ERR;	/* authentication failure */ + +error: +		/* If there was a password pass it to the next layer */ +		if (password && *password) { +			pam_set_item(pamh, PAM_AUTHTOK, password); +		} +	} + +	if (ctrl & PAM_DEBUG_ARG) { +		_pam_log(LOG_DEBUG, "authentication %s", retval==PAM_SUCCESS ? "succeeded":"failed"); +	} + +	close(config.sockfd); +	cleanup(config.server); +	_pam_forget(password); +	_pam_forget(resp2challenge); +	{ +		int *pret = malloc(sizeof(int)); +		*pret = retval; +		pam_set_data(pamh, "rad_setcred_return", (void *) pret, _int_free); +	} +	return retval; +} + +/* + * Return a value matching the return value of pam_sm_authenticate, for + * greatest compatibility. + * (Always returning PAM_SUCCESS breaks other authentication modules; + * always returning PAM_IGNORE breaks PAM when we're the only module.) + */ +PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh,int flags,int argc,CONST char **argv) +{ +	int retval, *pret; + +	retval = PAM_SUCCESS; +	pret = &retval; +	pam_get_data(pamh, "rad_setcred_return", (CONST void **) &pret); +	return *pret; +} + +#undef PAM_FAIL_CHECK +#define PAM_FAIL_CHECK if (retval != PAM_SUCCESS) { return PAM_SESSION_ERR; } + +static int pam_private_session(pam_handle_t *pamh, int flags, int argc, CONST char **argv, int status) +{ +	CONST char *user; +	int ctrl; +	int retval = PAM_AUTH_ERR; + +	char recv_buffer[4096]; +	char send_buffer[4096]; +	AUTH_HDR *request = (AUTH_HDR *) send_buffer; +	AUTH_HDR *response = (AUTH_HDR *) recv_buffer; +	radius_conf_t config; + +	ctrl = _pam_parse(argc, argv, &config); + +	/* grab the user name */ +	retval = pam_get_user(pamh, &user, NULL); +	PAM_FAIL_CHECK; + +	/* check that they've entered something, and not too long, either */ +	if ((user == NULL) || (strlen(user) > MAXPWNAM)) { +		return PAM_USER_UNKNOWN; +	} + +	/* +	 * Get the IP address of the authentication server +	 * Then, open a socket, and bind it to a port +	 */ +	retval = initialize(&config, TRUE); +	PAM_FAIL_CHECK; + +	/* +	 * If there's no client id specified, use the service type, to help +	 * keep track of which service is doing the authentication. +	 */ +	if (!config.client_id) { +		retval = pam_get_item(pamh, PAM_SERVICE, (CONST void **) &config.client_id); +		PAM_FAIL_CHECK; +	} + +	/* now we've got a socket open, so we've got to clean it up on error */ +#undef PAM_FAIL_CHECK +#define PAM_FAIL_CHECK if (retval != PAM_SUCCESS) {goto error; } + +	/* build and initialize the RADIUS packet */ +	request->code = PW_ACCOUNTING_REQUEST; +	get_random_vector(request->vector); +	request->id = request->vector[0]; /* this should be evenly distributed */ + +	build_radius_packet(request, user, NULL, &config); + +	add_int_attribute(request, PW_ACCT_STATUS_TYPE, status); + +	sprintf(recv_buffer, "%08d", (int) getpid()); +	add_attribute(request, PW_ACCT_SESSION_ID, (unsigned char *) recv_buffer, strlen(recv_buffer)); + +	add_int_attribute(request, PW_ACCT_AUTHENTIC, PW_AUTH_RADIUS); + +	if (status == PW_STATUS_START) { +		session_time = time(NULL); +	} else { +		add_int_attribute(request, PW_ACCT_SESSION_TIME, time(NULL) - session_time); +	} + +	retval = talk_radius(&config, request, response, NULL, NULL, 1); +	PAM_FAIL_CHECK; + +	/* oops! They don't have the right password.	Complain and die. */ +	if (response->code != PW_ACCOUNTING_RESPONSE) { +		retval = PAM_PERM_DENIED; +		goto error; +	} + +	retval = PAM_SUCCESS; + +error: + +	close(config.sockfd); +	cleanup(config.server); + +	return retval; +} + +PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, CONST char **argv) +{ +	return pam_private_session(pamh, flags, argc, argv, PW_STATUS_START); +} + +PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, CONST char **argv) +{ +	return pam_private_session(pamh, flags, argc, argv, PW_STATUS_STOP); +} + +#undef PAM_FAIL_CHECK +#define PAM_FAIL_CHECK if (retval != PAM_SUCCESS) {return retval; } +#define MAX_PASSWD_TRIES 3 + +PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, CONST char **argv) +{ +	CONST char *user; +	char *password = NULL; +	char *new_password = NULL; +	char *check_password = NULL; +	int ctrl; +	int retval = PAM_AUTHTOK_ERR; +	int attempts; + +	char recv_buffer[4096]; +	char send_buffer[4096]; +	AUTH_HDR *request = (AUTH_HDR *) send_buffer; +	AUTH_HDR *response = (AUTH_HDR *) recv_buffer; +	radius_conf_t config; + +	ctrl = _pam_parse(argc, argv, &config); + +	/* grab the user name */ +	retval = pam_get_user(pamh, &user, NULL); +	PAM_FAIL_CHECK; + +	/* check that they've entered something, and not too long, either */ +	if ((user == NULL) || (strlen(user) > MAXPWNAM)) { +		return PAM_USER_UNKNOWN; +	} + +	/* +	 * Get the IP address of the authentication server +	 * Then, open a socket, and bind it to a port +	 */ +	retval = initialize(&config, FALSE); +	PAM_FAIL_CHECK; + +	/* +	 * If there's no client id specified, use the service type, to help +	 * keep track of which service is doing the authentication. +	 */ +	if (!config.client_id) { +		retval = pam_get_item(pamh, PAM_SERVICE, (CONST void **) &config.client_id); +		PAM_FAIL_CHECK; +	} + +	/* now we've got a socket open, so we've got to clean it up on error */ +#undef PAM_FAIL_CHECK +#define PAM_FAIL_CHECK if (retval != PAM_SUCCESS) {goto error; } + +	/* grab the old password (if any) from the previous password layer */ +	retval = pam_get_item(pamh, PAM_OLDAUTHTOK, (CONST void **) &password); +	PAM_FAIL_CHECK; +	if(password) password = strdup(password); + +	/* grab the new password (if any) from the previous password layer */ +	retval = pam_get_item(pamh, PAM_AUTHTOK, (CONST void **) &new_password); +	PAM_FAIL_CHECK; +	if(new_password) new_password = strdup(new_password); + +	/* preliminary password change checks. */ +	if (flags & PAM_PRELIM_CHECK) { +		if (!password) {		/* no previous password: ask for one */ +			retval = rad_converse(pamh, PAM_PROMPT_ECHO_OFF, "Password: ", &password); +			PAM_FAIL_CHECK; +		} + +		/* +		 * We now check the password to see if it's the right one. +		 * If it isn't, we let the user try again. +		 * Note that RADIUS doesn't have any concept of 'root'.	The only way +		 * that root can change someone's password is to log into the RADIUS +		 * server, and and change it there. +		 */ + +		/* build and initialize the access request RADIUS packet */ +		request->code = PW_AUTHENTICATION_REQUEST; +		get_random_vector(request->vector); +		request->id = request->vector[0]; /* this should be evenly distributed */ + +		build_radius_packet(request, user, password, &config); +		add_int_attribute(request, PW_USER_SERVICE_TYPE, PW_AUTHENTICATE_ONLY); + +		retval = talk_radius(&config, request, response, password, NULL, 1); +		PAM_FAIL_CHECK; + +		/* oops! They don't have the right password.	Complain and die. */ +		if (response->code != PW_AUTHENTICATION_ACK) { +			_pam_forget(password); +			retval = PAM_PERM_DENIED; +			goto error; +		} + +		/* +		 * We're now sure it's the right user. +		 * Ask for their new password, if appropriate +		 */ + +		if (!new_password) {	/* not found yet: ask for it */ +			int new_attempts; +			attempts = 0; + +			/* loop, trying to get matching new passwords */ +			while (attempts++ < 3) { + +				/* loop, trying to get a new password */ +				new_attempts = 0; +				while (new_attempts++ < 3) { +					retval = rad_converse(pamh, PAM_PROMPT_ECHO_OFF, +							"New password: ", &new_password); +					PAM_FAIL_CHECK; + +					/* the old password may be short.	Check it, first. */ +					if (strcmp(password, new_password) == 0) { /* are they the same? */ +						rad_converse(pamh, PAM_ERROR_MSG, +						 "You must choose a new password.", NULL); +						_pam_forget(new_password); +						continue; +					} else if (strlen(new_password) < 6) { +						rad_converse(pamh, PAM_ERROR_MSG, "it's WAY too short", NULL); +						_pam_forget(new_password); +						continue; +					} + +					/* insert crypt password checking here */ + +					break;		/* the new password is OK */ +				} + +				if (new_attempts >= 3) { /* too many new password attempts: die */ +					retval = PAM_AUTHTOK_ERR; +					goto error; +				} + +				/* make sure of the password by asking for verification */ +				retval = rad_converse(pamh, PAM_PROMPT_ECHO_OFF, +						      "New password (again): ", &check_password); +				PAM_FAIL_CHECK; + +				retval = strcmp(new_password, check_password); +				_pam_forget(check_password); + +				/* if they don't match, don't pass them to the next module */ +				if (retval != 0) { +					_pam_forget(new_password); +					rad_converse(pamh, PAM_ERROR_MSG, +								 "You must enter the same password twice.", NULL); +					retval = PAM_AUTHTOK_ERR; +					goto error;		/* ??? maybe this should be a 'continue' ??? */ +				} + +				break;			/* everything's fine */ +			}	/* loop, trying to get matching new passwords */ + +			if (attempts >= 3) { /* too many new password attempts: die */ +				retval = PAM_AUTHTOK_ERR; +				goto error; +			} +		} /* now we have a new password which passes all of our tests */ + +		/* +		 * Solaris 2.6 calls pam_sm_chauthtok only ONCE, with PAM_PRELIM_CHECK +		 * set. +		 */ +#ifndef sun +		/* If told to update the authentication token, do so. */ +	} else if (flags & PAM_UPDATE_AUTHTOK) { +#endif + +		if (!password || !new_password) { /* ensure we've got passwords */ +			retval = PAM_AUTHTOK_ERR; +			goto error; +		} + +		/* build and initialize the password change request RADIUS packet */ +		request->code = PW_PASSWORD_REQUEST; +		get_random_vector(request->vector); +		request->id = request->vector[0]; /* this should be evenly distributed */ + +		/* the secret here can not be know to the user, so it's the new password */ +		_pam_forget(config.server->secret); +		config.server->secret = strdup(password); /* it's free'd later */ + +		build_radius_packet(request, user, new_password, &config); +		add_password(request, PW_OLD_PASSWORD, password, password); + +		retval = talk_radius(&config, request, response, new_password, password, 1); +		PAM_FAIL_CHECK; + +		/* Whew! Done password changing, check for password acknowledge */ +		if (response->code != PW_PASSWORD_ACK) { +			retval = PAM_AUTHTOK_ERR; +			goto error; +		} +	} + +	/* +	 * Send the passwords to the next stage if preliminary checks fail, +	 * or if the password change request fails. +	 */ +	if ((flags & PAM_PRELIM_CHECK) || (retval != PAM_SUCCESS)) { +	error: + +		/* If there was a password pass it to the next layer */ +		if (password && *password) { +			pam_set_item(pamh, PAM_OLDAUTHTOK, password); +		} + +		if (new_password && *new_password) { +			pam_set_item(pamh, PAM_AUTHTOK, new_password); +		} +	} + +	if (ctrl & PAM_DEBUG_ARG) { +		_pam_log(LOG_DEBUG, "password change %s", retval==PAM_SUCCESS ? "succeeded" : "failed"); +	} + +	close(config.sockfd); +	cleanup(config.server); + +	_pam_forget(password); +	_pam_forget(new_password); +	return retval; +} + +/* + *	Do nothing for account management. This is apparently needed by + *	some programs. + */ +PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh,int flags,int argc,CONST char **argv) +{ +	int retval; +	retval = PAM_SUCCESS; +	return retval; +} + +#ifdef PAM_STATIC + +/* static module data */ + +struct pam_module _pam_radius_modstruct = { +	"pam_radius_auth", +	pam_sm_authenticate, +	pam_sm_setcred, +	pam_sm_acct_mgmt, +	pam_sm_open_session, +	pam_sm_close_session, +	pam_sm_chauthtok, +}; +#endif + diff --git a/src/pam_radius_auth.h b/src/pam_radius_auth.h new file mode 100644 index 0000000..7241117 --- /dev/null +++ b/src/pam_radius_auth.h @@ -0,0 +1,126 @@ +#ifndef PAM_RADIUS_H +#define PAM_RADIUS_H + +#include "config.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/resource.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> +#include <stdlib.h> +#include <syslog.h> +#include <stdarg.h> +#include <utmp.h> +#include <time.h> +#include <netinet/in.h> +#include <netdb.h> +#include <fcntl.h> + +#if defined(HAVE_SECURITY_PAM_APPL_H) +#  include <security/pam_appl.h> +#elif defined(HAVE_PAM_PAM_APPL_H) +#  include <pam/pam_appl.h> +#endif + +#if defined(HAVE_SECURITY_PAM_MODULES_H) +#  include <security/pam_modules.h> +#elif defined(HAVE_PAM_PAM_APPL_H) +#  include <pam/pam_modules.h> +#else +#  error security/pam_modules.h or pam/pam_modules.h required +#endif + + +#include "radius.h" +#include "md5.h" + + +/************************************************************************* + * Additional RADIUS definitions + *************************************************************************/ + +/* Per-attribute structure */ +typedef struct attribute_t { +	unsigned char attribute; +	unsigned char length; +	unsigned char data[1]; +} attribute_t; + +typedef struct radius_server_t { +	struct radius_server_t *next; +	struct in_addr ip; +	uint16_t port; +	char *hostname; +	char *secret; +	int timeout; +	int accounting; +} radius_server_t; + +typedef struct radius_conf_t { +	radius_server_t *server; +	int retries; +	int localifdown; +	char *client_id; +	int accounting_bug; +	int sockfd; +	int debug; +} radius_conf_t; + + +/************************************************************************* + * Platform specific defines + *************************************************************************/ + +#ifdef sun +#define PAM_EXTERN extern +/* + *  On older versions of Solaris, you may have to change this to: + *  #define CONST + */ +#define CONST const +#else +#define CONST const +#endif + +/************************************************************************* + * Useful macros and defines + *************************************************************************/ + +#define _pam_forget(X) if (X) {memset(X, 0, strlen(X));free(X);X = NULL;} +#ifndef _pam_drop +#define _pam_drop(X) if (X) {free(X);X = NULL;} +#endif + +#define PAM_DEBUG_ARG      1 +#define PAM_SKIP_PASSWD    2 +#define PAM_USE_FIRST_PASS 4 +#define PAM_TRY_FIRST_PASS 8 +#define PAM_RUSER_ARG      16 + + +/* Module defines */ +#ifndef BUFFER_SIZE +#define BUFFER_SIZE      1024 +#endif /* BUFFER_SIZE */ +#define MAXPWNAM 253    /* maximum user name length. Server dependent, +                         * this is the default value +                         */ +#define MAXPASS 128     /* max password length. Again, depends on server +                         * compiled in. This is the default. +                         */ +#ifndef CONF_FILE       /* the configuration file holding the server secret */ +#define CONF_FILE       "/etc/raddb/server" +#endif /* CONF_FILE */ + +#ifndef FALSE +#define FALSE 0 +#undef TRUE +#define TRUE !FALSE +#endif + +#endif /* PAM_RADIUS_H */ diff --git a/src/radius.h b/src/radius.h new file mode 100644 index 0000000..bcfe547 --- /dev/null +++ b/src/radius.h @@ -0,0 +1,230 @@ +/* + * + *	RADIUS + *	Remote Authentication Dial In User Service + * + * + *	Livingston Enterprises, Inc. + *	6920 Koll Center Parkway + *	Pleasanton, CA   94566 + * + *	Copyright 1992 Livingston Enterprises, Inc. + * + *	Permission to use, copy, modify, and distribute this software for any + *	purpose and without fee is hereby granted, provided that this + *	copyright and permission notice appear on all copies and supporting + *	documentation, the name of Livingston Enterprises, Inc. not be used + *	in advertising or publicity pertaining to distribution of the + *	program without specific prior permission, and notice be given + *	in supporting documentation that copying and distribution is by + *	permission of Livingston Enterprises, Inc. + * + *	Livingston Enterprises, Inc. makes no representations about + *	the suitability of this software for any purpose.  It is + *	provided "as is" without express or implied warranty. + * + */ + +/* + *	@(#)radius.h	1.9 11/14/94 + */ +#ifndef RADIUS_H +#define RADIUS_H + +#define AUTH_VECTOR_LEN		16 +#define AUTH_PASS_LEN		16 +#define AUTH_STRING_LEN		128	/* maximum of 254 */ + +typedef struct pw_auth_hdr { +	uint8_t		code; +	uint8_t		id; +	uint16_t	length; +	uint8_t		vector[AUTH_VECTOR_LEN]; +	uint8_t		data[2]; +} AUTH_HDR; + +#define AUTH_HDR_LEN			20 +#define CHAP_VALUE_LENGTH		16 + +#define PW_AUTH_UDP_PORT		1645 +#define PW_ACCT_UDP_PORT		1646 + +#define PW_TYPE_STRING			0 +#define PW_TYPE_INTEGER			1 +#define PW_TYPE_IPADDR			2 +#define PW_TYPE_DATE			3 + + +#define	PW_AUTHENTICATION_REQUEST	1 +#define	PW_AUTHENTICATION_ACK		2 +#define	PW_AUTHENTICATION_REJECT	3 +#define	PW_ACCOUNTING_REQUEST		4 +#define	PW_ACCOUNTING_RESPONSE		5 +#define	PW_ACCOUNTING_STATUS		6 +#define PW_PASSWORD_REQUEST		7 +#define PW_PASSWORD_ACK			8 +#define PW_PASSWORD_REJECT		9 +#define	PW_ACCOUNTING_MESSAGE		10 +#define PW_ACCESS_CHALLENGE		11 + +#define	PW_USER_NAME			1 +#define	PW_PASSWORD			2 +#define	PW_CHAP_PASSWORD		3 +#define	PW_NAS_IP_ADDRESS	       	4 +#define	PW_NAS_PORT_ID			5 +#define	PW_USER_SERVICE_TYPE		6 +#define	PW_FRAMED_PROTOCOL		7 +#define	PW_FRAMED_ADDRESS		8 +#define	PW_FRAMED_NETMASK		9 +#define	PW_FRAMED_ROUTING		10 +#define	PW_FRAMED_FILTER_ID		11 +#define	PW_FRAMED_MTU			12 +#define	PW_FRAMED_COMPRESSION		13 +#define	PW_LOGIN_HOST			14 +#define	PW_LOGIN_SERVICE		15 +#define	PW_LOGIN_TCP_PORT		16 +#define PW_OLD_PASSWORD			17 +#define PW_REPLY_MESSAGE		18 +#define PW_CALLBACK_NUMBER     		19 +#define PW_CALLBACK_ID			20 +#define PW_EXPIRATION			21 +#define PW_FRAMED_ROUTE			22 +#define PW_FRAMED_IPXNET		23 +#define PW_STATE			24 +#define PW_CLASS                        25      /* string */ +#define PW_VENDOR_SPECIFIC              26      /* vendor */ +#define PW_SESSION_TIMEOUT              27      /* integer */ +#define PW_IDLE_TIMEOUT                 28      /* integer */ +#define PW_TERMINATION_ACTION           29      /* integer */ +#define PW_CALLED_STATION_ID            30      /* string */ +#define PW_CALLING_STATION_ID           31      /* string */ +#define PW_NAS_IDENTIFIER               32      /* string */ +#define PW_PROXY_STATE                  33      /* string */ +#define PW_LOGIN_LAT_SERVICE            34      /* string */ +#define PW_LOGIN_LAT_NODE               35      /* string */ +#define PW_LOGIN_LAT_GROUP              36      /* string */ +#define PW_FRAMED_APPLETALK_LINK        37      /* integer */ +#define PW_FRAMED_APPLETALK_NETWORK     38      /* integer */ +#define PW_FRAMED_APPLETALK_ZONE        39      /* string */ + +#define PW_ACCT_STATUS_TYPE		40 +#define PW_ACCT_DELAY_TIME		41 +#define PW_ACCT_INPUT_OCTETS		42 +#define PW_ACCT_OUTPUT_OCTETS		43 +#define PW_ACCT_SESSION_ID		44 +#define PW_ACCT_AUTHENTIC		45 +#define PW_ACCT_SESSION_TIME		46 + +#define PW_CHAP_CHALLENGE               60      /* string */ +#define PW_NAS_PORT_TYPE                61      /* integer */ +#define PW_PORT_LIMIT                   62      /* integer */ +#define PW_LOGIN_LAT_PORT               63      /* string */ +#define PW_PROMPT                       64      /* integer */ + +/* + *	INTEGER TRANSLATIONS + */ + +/*	USER TYPES	*/ + +#define	PW_LOGIN_USER			1 +#define	PW_FRAMED_USER			2 +#define	PW_DIALBACK_LOGIN_USER	3 +#define	PW_DIALBACK_FRAMED_USER	4 +#define PW_OUTBOUND_USER		5 +#define PW_SHELL_USER			6 + +/*	FRAMED PROTOCOLS	*/ + +#define	PW_PPP				1 +#define	PW_SLIP				2 + +/*	FRAMED ROUTING VALUES	*/ + +#define	PW_NONE				0 +#define	PW_BROADCAST			1 +#define	PW_LISTEN			2 +#define	PW_BROADCAST_LISTEN		3 + +/*	NAS PORT TYPES */ +#define PW_NAS_PORT_TYPE_VIRTUAL	5 + +/*	FRAMED COMPRESSION TYPES	*/ + +#define	PW_VAN_JACOBSEN_TCP_IP		1 + +/*	LOGIN SERVICES	*/ + +#define	PW_TELNET			0 +#define	PW_RLOGIN			1 +#define	PW_TCP_CLEAR			2 +#define	PW_PORTMASTER			3 +#define PW_AUTHENTICATE_ONLY            8 + +/*	AUTHENTICATION LEVEL	*/ + +#define PW_AUTH_NONE			0 +#define PW_AUTH_RADIUS			1 +#define PW_AUTH_LOCAL			2 + +/*	STATUS TYPES	*/ + +#define PW_STATUS_START			1 +#define PW_STATUS_STOP			2 +#define PW_STATUS_ALIVE			3 + +/* Default Database File Names */ + +#define RADIUS_DIR		"/etc/raddb" +#define RADACCT_DIR		"/usr/adm/radacct" + +#define RADIUS_DICTIONARY	"dictionary" +#define RADIUS_CLIENTS		"clients" +#define RADIUS_USERS		"users" +#define RADIUS_HOLD		"holdusers" +#define RADIUS_LOG		"logfile" + +/* Server data structures */ + +typedef struct dict_attr { +	char			name[32]; +	int			value; +	int			type; +	struct dict_attr	*next; +} DICT_ATTR; + +typedef struct dict_value { +	char			attrname[32]; +	char			name[32]; +	int			value; +	struct dict_value	*next; +} DICT_VALUE; + +typedef struct value_pair { +	char			name[32]; +	int			attribute; +	int			type; +	uint32_t		lvalue; +	char			strvalue[AUTH_STRING_LEN]; +	struct value_pair	*next; +} VALUE_PAIR; + +typedef struct auth_req { +	uint32_t		ipaddr; +	uint16_t		udp_port; +	uint8_t			id; +	uint8_t			code; +	uint8_t			vector[16]; +	uint8_t			secret[16]; +	VALUE_PAIR		*request; +	int			child_pid;	/* Process ID of child */ +	uint32_t		timestamp; +	struct auth_req		*next;		/* Next active request */ +} AUTH_REQ; + +#define SECONDS_PER_DAY		86400 +#define MAX_REQUEST_TIME	30 +#define CLEANUP_DELAY		5 +#define MAX_REQUESTS		100 + +#endif /* RADIUS_H */ | 
