Patch for Livingston's RADIUS 2.0

Cristian Gafton (gafton@sorosis.ro)
Fri, 22 Nov 1996 10:23:09 +0200 (EET)

Hi all,

Here is a patch for the official 2.0 release of the Livingston RADIUS
server.

What it does:

1. MD5 encrypted passwords in /etc/passwd or /etc/shadow are now
recognized;

2. It adds another local server option for configuring access based
on user's group membership. Now one can write something like:

DEFAULT Auth-Type = System, GROUP = "students"
Service-Type = Login-User,
Login-Host = 192.168.100.5,
Login-Service = Telnet

DEFAULT Auth-Type = System, GROUP = "teachers"
Service-Type = Login-User,
Login-Host = 192.168.100.27,
Login-Service = Telnet

to have students automatically open the telnet connection to their
machine, and teachers alike. Of course this is a rude example from all
the possibilities and nice things which can be done using this new option
:-)

The patch is tested now only on Linux, but it should work okay on many
other platforms. Please test it out if you like, and if it doesn't work
for you on another platform and you have the knowledge please try to make
it work, I would like this patch to be more generic...

Compiling new MD5 stuff will require a -DMD5_PASS to compiler args. For
the GROUP= thing, the dictionary must be upgraded too.

Best regards,

Cristian Gafton

--
--------------------------------------------------------------------
Cristian Gafton                                    gafton@sorosis.ro
Computers & Communications Center              Network Administrator
35 Moara de Foc St., Iasi 6600, ROMANIA           Tel: +40-32-252938
http://www.cccis.ro                               Fax: +40-32-252933
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
UNIX is user friendly. It's just selective about who its friends are.

diff -urN radius-2.0/Linux/Makefile radius-2.01/Linux/Makefile --- radius-2.0/Linux/Makefile Thu Oct 31 07:45:50 1996 +++ radius-2.01/Linux/Makefile Fri Nov 22 09:47:28 1996 @@ -1,9 +1,9 @@ SRC_DIR = ../radius/src -CC = cc -COPT = -O -CPPFLAGS = -I$(SRC_DIR) -DNOSHADOW -DNDBM +CC = gcc +COPT = -O6 +CPPFLAGS = -I$(SRC_DIR) -DMD5_PASS -DNDBM LIBS = -ldb include $(SRC_DIR)/make.inc diff -urN radius-2.0/radius/raddb/dictionary radius-2.01/radius/raddb/dictionary --- radius-2.0/radius/raddb/dictionary Thu Oct 24 01:56:22 1996 +++ radius-2.01/radius/raddb/dictionary Fri Nov 22 07:52:25 1996 @@ -108,6 +108,7 @@ ATTRIBUTE Termination-Menu 1002 string ATTRIBUTE Prefix 1003 string ATTRIBUTE Suffix 1004 string +ATTRIBUTE GROUP 1005 string # # Integer Translations diff -urN radius-2.0/radius/raddb/users radius-2.01/radius/raddb/users --- radius-2.0/radius/raddb/users Thu Oct 24 01:56:22 1996 +++ radius-2.01/radius/raddb/users Fri Nov 22 09:57:27 1996 @@ -106,11 +106,28 @@ Framed-IP-Address = 255.255.255.254, Framed-MTU = 1500 + +# +# The following section illustrates how to use the new feature of selecting +# users by their group membership. +# +# Feature added for RADIUS 2.0 by Cristian Gafton <gafton@sorosis.ro> +# + +DEFAULT Auth-Type = System, GROUP = "students" + Service-Type = Login-User, + Login-Host = 192.168.100.5, + Login-Service = Telnet + +DEFAULT Auth-Type = System, GROUP = "teachers" + Service-Type = Login-User, + Login-Host = 192.168.100.27, + Login-Service = Telnet + # # Anything else uses Rlogin to the host set for that port # DEFAULT Auth-Type = System Service-Type = Login-User, Login-Service = Rlogin - diff -urN radius-2.0/radius/src/make.inc radius-2.01/radius/src/make.inc --- radius-2.0/radius/src/make.inc Thu Oct 31 06:12:55 1996 +++ radius-2.01/radius/src/make.inc Fri Nov 22 09:48:21 1996 @@ -5,12 +5,14 @@ CFLAGS = $(COPT) $(CPPFLAGS) HEADERS = $(SRC_DIR)/conf.h $(SRC_DIR)/md5.h $(SRC_DIR)/radius.h \ - $(SRC_DIR)/users.h $(SRC_DIR)/sdacmvls.h \ - $(SRC_DIR)/sdconf.h $(SRC_DIR)/sdi_athd.h \ - $(SRC_DIR)/sdi_size.h $(SRC_DIR)/sdi_type.h + $(SRC_DIR)/users.h +# $(SRC_DIR)/sdacmvls.h \ +# $(SRC_DIR)/sdconf.h $(SRC_DIR)/sdi_athd.h \ +# $(SRC_DIR)/sdi_size.h $(SRC_DIR)/sdi_type.h RADD_OBJS = dict.o users.o util.o md5.o attrprint.o \ - acct.o version.o menu.o log.o $(XOBJS) + acct.o version.o menu.o log.o $(XOBJS) \ + md5_crypt.o TEST_OBJS = users.o dict.o attrprint.o util.o log.o version.o @@ -54,3 +56,5 @@ $(CC) -c $(CFLAGS) $(SRC_DIR)/log.c -o $@ securid.o: $(SRC_DIR)/securid.c $(CC) -c $(CFLAGS) $(SRC_DIR)/securid.c -o $@ +md5_crypt.o: $(SRC_DIR)/md5_crypt.c + $(CC) -c $(CFLAGS) $(SRC_DIR)/md5_crypt.c -o $@ diff -urN radius-2.0/radius/src/md5_crypt.c radius-2.01/radius/src/md5_crypt.c --- radius-2.0/radius/src/md5_crypt.c Thu Jan 1 02:00:00 1970 +++ radius-2.01/radius/src/md5_crypt.c Fri Nov 22 10:04:44 1996 @@ -0,0 +1,148 @@ + +#include <string.h> +#include "md5.h" + +static unsigned char itoa64[] = /* 0 ... 63 => ascii - 64 */ + "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + +static void +to64(char *s, unsigned long v, int n) +{ + while (--n >= 0) { + *s++ = itoa64[v&0x3f]; + v >>= 6; + } +} + +/* + * i64c - convert an integer to a radix 64 character + */ +int i64c(int i) +{ + if (i < 0) + return ('.'); + else if (i > 63) + return ('z'); + if (i == 0) + return ('.'); + if (i == 1) + return ('/'); + if (i >= 2 && i <= 11) + return ('0' - 2 + i); + if (i >= 12 && i <= 37) + return ('A' - 12 + i); + if (i >= 38 && i <= 63) + return ('a' - 38 + i); + return ('\0'); +} + +/* + * UNIX password + * + * Use MD5 for what it is best at... + */ + +char * 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; + + MD5Init(&ctx); + + /* The password first, since that is what is most unknown */ + MD5Update(&ctx,(unsigned const char *)pw,strlen(pw)); + + /* Then our magic string */ + MD5Update(&ctx,(unsigned const char *)magic,strlen(magic)); + + /* Then the raw salt */ + MD5Update(&ctx,(unsigned const char *)sp,sl); + + /* Then just as many characters of the MD5(pw,salt,pw) */ + MD5Init(&ctx1); + MD5Update(&ctx1,(unsigned const char *)pw,strlen(pw)); + MD5Update(&ctx1,(unsigned const char *)sp,sl); + MD5Update(&ctx1,(unsigned const char *)pw,strlen(pw)); + MD5Final(final,&ctx1); + for(pl = strlen(pw); pl > 0; pl -= 16) + 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) + MD5Update(&ctx, (unsigned const char *)final+j, 1); + else + MD5Update(&ctx, (unsigned const char *)pw+j, 1); + + /* Now make the output string */ + strcpy(passwd,magic); + strncat(passwd,sp,sl); + strcat(passwd,"$"); + + 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++) { + MD5Init(&ctx1); + if(i & 1) + MD5Update(&ctx1,(unsigned const char *)pw,strlen(pw)); + else + MD5Update(&ctx1,(unsigned const char *)final,16); + + if(i % 3) + MD5Update(&ctx1,(unsigned const char *)sp,sl); + + if(i % 7) + MD5Update(&ctx1,(unsigned const char *)pw,strlen(pw)); + + if(i & 1) + MD5Update(&ctx1,(unsigned const char *)final,16); + else + MD5Update(&ctx1,(unsigned const char *)pw,strlen(pw)); + 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 -urN radius-2.0/radius/src/radius.h radius-2.01/radius/src/radius.h --- radius-2.0/radius/src/radius.h Thu Oct 31 05:12:35 1996 +++ radius-2.01/radius/src/radius.h Fri Nov 22 09:49:52 1996 @@ -117,6 +117,7 @@ #define PW_TERMINATION_MENU 1002 #define PW_PREFIX 1003 #define PW_SUFFIX 1004 +#define PW_GROUP 1005 /* @@ -178,7 +179,7 @@ /* Default Database File Names */ #define RADIUS_DIR "/etc/raddb" -#define RADACCT_DIR "/usr/adm/radacct" +#define RADACCT_DIR "/var/log/radius" #define RADIUS_DICTIONARY "dictionary" #define RADIUS_CLIENTS "clients" diff -urN radius-2.0/radius/src/radiusd.c radius-2.01/radius/src/radiusd.c --- radius-2.0/radius/src/radiusd.c Thu Oct 31 05:12:35 1996 +++ radius-2.01/radius/src/radiusd.c Fri Nov 22 09:34:55 1996 @@ -1214,6 +1214,7 @@ case PW_PREFIX: case PW_SUFFIX: + case PW_GROUP: break; case PW_EXPIRATION: @@ -1769,6 +1770,7 @@ struct passwd *getpwnam(); char *encpw; char *crypt(); + char *crypt_md5(); char *encrypted_pass; #if !defined(NOSHADOW) #if defined(M_UNIX) @@ -1802,7 +1804,12 @@ #endif /* !NOSHADOW */ /* Run encryption algorythm */ - encpw = crypt(passwd, encrypted_pass); +#ifdef MD5_PASS + if (strncmp(encrypted_pass, "$1$", 3) == 0) + encpw = crypt_md5(passwd, encrypted_pass); + else +#endif /* MD5_PASS */ + encpw = crypt(passwd, encrypted_pass); /* Check it */ if(strcmp(encpw, encrypted_pass)) { diff -urN radius-2.0/radius/src/users.c radius-2.01/radius/src/users.c --- radius-2.0/radius/src/users.c Thu Oct 31 05:12:35 1996 +++ radius-2.01/radius/src/users.c Fri Nov 22 10:05:43 1996 @@ -231,6 +231,15 @@ check_first = (VALUE_PAIR *)NULL; continue; } + + /* + * verify user group if it is a local user + */ + if (user_auth_group(auth_name, check_first) != 0) { + pairfree(check_first); + check_first = (VALUE_PAIR *)NULL; + continue; + } mode = FIND_MODE_REPLY; } } @@ -314,6 +323,33 @@ return(0); } +/************************************************************************* + * + * Function: user_auth_group + * + * Purpose: If 'GROUP=' pair is present, check if the username is a + * member of that group + * + * Author: Cristian Gafton, <gafton@sorosis.ro>. (c) 1996. + * + *************************************************************************/ + +int +user_auth_group(auth_name, check_first) +char *auth_name; +VALUE_PAIR *check_first; +{ + VALUE_PAIR *fix; + VALUE_PAIR *get_attribute(); + + if ((fix = get_attribute(check_first, PW_GROUP)) == (VALUE_PAIR *)NULL) + return(-1); + + if (is_on_group(auth_name, fix->strvalue)) + return(0); + return(1); +} + #define PARSE_MODE_NAME 0 #define PARSE_MODE_EQUAL 1 #define PARSE_MODE_VALUE 2 @@ -709,4 +745,71 @@ } else { fclose((FILE *)user_desc); } +} + + +/************************************************************************* + * + * Function: is_on_group + * + * Purpose: test if a requested user is on a 'GROUP=' according to local + * passwd database + * + *************************************************************************/ + +#include <grp.h> + +/* checks if a user is on a list of members of a group */ +static int is_on_list(char * const *list, const char *member) +{ + while (*list) { + if (strcmp(*list, member) == 0) + return 1; + list++; + } + return 0; +} + +int is_on_group(const char *user_name, const char *group_name) +{ + struct passwd *pwd; + struct group *grp, *pgrp; + char uname[AUTH_STRING_LEN], gname[AUTH_STRING_LEN]; + + if (!strlen(user_name)) + return 0; + if (!strlen(group_name)) + return 0; + bzero(uname, sizeof(uname)); + strncpy(uname, user_name, AUTH_STRING_LEN); + bzero(gname, sizeof(gname)); + strncpy(gname, group_name, AUTH_STRING_LEN); + + setpwent(); + pwd = getpwnam(uname); + endpwent(); + if (!pwd) + return 0; + + /* the info about this group */ + setgrent(); + grp = getgrnam(gname); + endgrent(); + if (!grp) + return 0; + + /* first check: is a member of the group_name group ? */ + if (is_on_list(grp->gr_mem, uname)) + return 1; + + /* next check: user primary group is group_name ? */ + setgrent(); + pgrp = getgrgid(pwd->pw_gid); + endgrent(); + if (!pgrp) + return 0; + if (!strcmp(pgrp->gr_name, gname)) + return 1; + + return 0; }