diff -urN radiusd-cistron-1.6.4-orig/doc/README.tunnel radiusd-cistron-1.6.4-taggattr-realmopt-v4/doc/README.tunnel
--- radiusd-cistron-1.6.4-orig/doc/README.tunnel	Thu Jan  1 01:00:00 1970
+++ radiusd-cistron-1.6.4-taggattr-realmopt-v4/doc/README.tunnel	Mon Sep 11 15:02:22 2000
@@ -0,0 +1,199 @@
+
+        TUNNEL SUPPORT: TAGGED & ENCRYPTED ATTRIBUTES
+
+
+0. Introduction
+
+More and more NASes are able to set up L2TP or L2F tunnels these
+days. The L2F (layer 2 forwarding) protocol and the newer L2TP
+(layer two tunneling protocol) allows a complete PPP session
+to be re-framed in e.g. UDP and forwarded a the device that will
+actually handle the PPP session: do LCP and IPCP negotiation,
+assign IP addresses, apply filters, and so on.
+
+This can be used for e.g. roaming dial-up access: you dial into a
+NAS, the NAS recognises your realm, sets up a tunnel to your home
+site / ISP and forwards your PPP session to that tunnel endpoint,
+where you'll get authenticated and authorised as if you were
+directly connected to the tunnel endpoint.
+
+
+1. Tunnels and Radius
+
+Obviously, the NAS needs to know the tunnel endpoint that belongs
+to a specific user realm. Also obviously, managing a table like
+this on each NAS in your network, is not really a nice job.
+
+So how can RADIUS help here? By keeping the realm-to-tunnel
+mapping table on the RADIUS server, and telling the NASes on 
+request what tunneling parameters need to be applied to a specific 
+session.
+
+The IETF draft that has the details on the new RADIUS attributes
+to carry these tunnel parameters is draft-ietf-radius-tunnel-auth-
+xx.txt (09 is the latest version, 2000-06-07). Reading it before
+using the new attributes is really a good idea.
+
+
+2. Tunneling attributes
+
+The nasty thing about these new attributes is that they contain
+a sort of, but not really, optional numeric attribute tag field,
+which is inserted before, or overlaps with, the value field.
+
+(It's a bit of a mess, really, and the purpose isn't all that 
+clear either - you'll have to use your imagination a bit before 
+you really think of a good use for these tags. And afther that, 
+you'll be left with a vague feeling like 'there must be a better 
+way to do that, but I can't really think of one right now...'. 
+But that may just have been me; read the ietf draft and see for 
+yourself).
+
+Anyway, the NAS vendors like them, and it's better to use these
+attributes than a vendor specific solution anyway, so I've added
+tagged attribute support to Cistron RADIUS.
+
+Another thing is the fact that L2TP tunnels are to be
+authenticated themselves using a shared secret. The RADIUS 
+attribute to tell the NAS the secret for authenticating to the 
+other tunnel endpoint must, for obvious reasons, be encrypted. 
+This is also specified in the draft for that attribute.
+
+
+3. Changes to Cistron RADIUS
+
+There are a couple of changes with respect to Cistron's
+configuration files.
+
+
+* Dictionary files:
+
+  The optional fourth field of the attribute definition lines in 
+  the dictionary in which the vendor could be specified has been
+  changed to a more general optional options field.
+
+  Multiple options are allowed, separated by commas, but be sure
+  not to add any whitespace anywhere in the options field.
+
+  Currently recognised options are:
+
+  -	has_tag		Boolean option; specifies whether this
+	or		attribute can have a numeric tag.
+	has_tag=	(Numeric version: specify 0 (default)
+			 for no or something !=0 for yes.)
+	
+  -	encrypt=	Numeric option; specifies the encryption
+			method for this attribute.
+			Valid values currently are:
+				0 - no encryption (default)
+				1 - do 'Tunnel-Password'-style
+				    encryption per draft-ietf
+				    radius-tunnel-auth-09.
+			Of course, other methods may be added if
+			a new standard emerges.
+
+  -	len+=		Numeric option; specifies an adjustment
+	or		to the length field of this attribute to
+	len-=		provide interoperability with RFC
+			violating NASes that use different
+			(plain wrong, that is) length fields in 
+			their communications.
+			If you specify a positive value after
+			len+, it means that the NAS sends and
+			expects a length that is <value> higher
+			than the RFC specifies for this attribute.
+			This difference is is used in three
+			places:
+			- it's substracted from the length
+			  field on receipt of an access request 
+			  or accounting packet;
+			- it's added to the length field again
+			  when sending a proxied request, to
+			  emulate the NASes behaviour and thus
+			  stay as transparent as possible;
+			- it's added to the length field when
+			  replying to the NAS.
+
+			Note that if an adjustment is specified
+			for a vendor-specific attribute, only the
+			length field encapsulated in the VSA is
+			adjusted, not the VSA length itself.
+			I don't know whether those strange NASes
+			expect things this way or the whole VSA
+			adjusted, as luckily I don't operate any
+			of these brain-dead NASes.
+
+  -	vend=		String option; specifies the vendor for
+			a vendor-specific attribute.
+
+  -	<vendor>	'Boolean' option; specifies the vendor 
+			in the old way, for backwards 
+			compatibility. This is tried after the
+			normal options, so a vendor called
+			'has_tag' can only be specified using
+			the new syntax ;-)
+
+
+* Users file:
+
+  Check- and reply items having attributes for which the has_tag
+  flag is present in the dictionary, may optionally have a tag
+  specified. If no tag is specified for such an attribute, the
+  parser will assume 0 as the tag.
+
+  The syntax for a tagged attribute is:
+
+	attribute[:tag] operator value
+
+  An asterisk ('*') may also be specified as the tag for 
+  check items. This indicates that the check item is to be 
+  compared to a matching received attribute, regardless of 
+  its tag. Otherwise, an attribute with a different tag is
+  considered a different attribute. 
+
+  This is a little different from e.g. Merit's implementation, 
+  where a tag is really considered part of the value of the 
+  attribute. This is only true if you look at how the tags 
+  are sent in a radius packet; if you look at the introduction
+  in the ietf draft, you can see that a tag effectively says 
+  something about attributes, not about values:
+
+  'Multiple instances of each of the attributes defined below 
+  may be included in a single RADIUS packet. In this case, 
+  the attributes to be applied to any given tunnel SHOULD all 
+  contain the same value in their respective Tag fields; 
+  otherwise, the Tag field SHOULD NOT be used.  
+
+  If the RADIUS server returns attributes describing multiple 
+  tunnels then the tunnels SHOULD be interpreted by the tunnel 
+  initiator as alternatives and the server SHOULD include an 
+  instance of the Tunnel-Preference Attribute in the set of 
+  Attributes pertaining to each alternative tunnel.  
+
+  Similarly, if the RADIUS client includes multiple sets of 
+  tunnel Attributes in an Access-Request packet, all the 
+  Attributes pertaining to a given tunnel SHOULD contain the 
+  same value in their respective Tag fiels and each set SHOULD 
+  include an appropriately valued instance of the 
+  Tunnel-Preference Attribute.' 
+
+  It's rather vague, in my opinion, so for those who prefer
+  Merit's syntax (attribute = :tag:value), I've provided
+  an option in conf.h to switch between the two.
+
+  Note that the selected syntax for the users file is also
+  used as the output format in the accounting detail files.
+
+That's it, basically. If you'd like more details, look in
+radius.c (rad_send_reply, encrypt_attr_style_1, radrecv),
+files.c (userparse, mainly), dict.c and attrprint.c.
+
+If you have any suggestions for improvement, or dislike my
+implementation for any reason, please don't hestitate to 
+contact me at emile@evbergen.xs4all.nl.
+
+(Miquel / Alan, this especially applies to you, of course! ;-).
+
+
+Emile van Bergen.
+
diff -urN radiusd-cistron-1.6.4-orig/raddb/dictionary.tunnel radiusd-cistron-1.6.4-taggattr-realmopt-v4/raddb/dictionary.tunnel
--- radiusd-cistron-1.6.4-orig/raddb/dictionary.tunnel	Sun Sep 19 00:10:40 1999
+++ radiusd-cistron-1.6.4-taggattr-realmopt-v4/raddb/dictionary.tunnel	Fri Sep 15 10:38:18 2000
@@ -10,13 +10,15 @@
 #
 #	Tunneling Attributes
 #
-ATTRIBUTE	Tunnel-Type			64	integer
-ATTRIBUTE	Tunnel-Medium-Type		65	integer
-ATTRIBUTE	Acct-Tunnel-Client-Endpoint	66	string
-ATTRIBUTE	Tunnel-Server-Endpoint		67	string
-ATTRIBUTE	Acct-Tunnel-Connection-Id	68	string
-ATTRIBUTE	Tunnel-Password			69	string
-ATTRIBUTE	Private-Group-Id		75	integer
+ATTRIBUTE       Tunnel-Type                     64      integer         has_tag
+ATTRIBUTE       Tunnel-Medium-Type              65      integer         has_tag
+ATTRIBUTE       Acct-Tunnel-Client-Endpoint     66      string          has_tag
+ATTRIBUTE       Tunnel-Server-Endpoint          67      string          has_tag
+ATTRIBUTE       Acct-Tunnel-Connection-Id       68      string          has_tag
+ATTRIBUTE       Tunnel-Password                 69      string          has_tag,encrypt=1
+ATTRIBUTE       Tunnel-Private-Group-Id         81      string          has_tag
+ATTRIBUTE       Tunnel-Assignment-Id            82      string          has_tag
+ATTRIBUTE       Tunnel-Preference               83      integer         has_tag
 
 VALUE		Framed-Protocol		PPTP			9
 
diff -urN radiusd-cistron-1.6.4-orig/raddb/realms radiusd-cistron-1.6.4-taggattr-realmopt-v4/raddb/realms
--- radiusd-cistron-1.6.4-orig/raddb/realms	Tue Aug  8 19:29:56 2000
+++ radiusd-cistron-1.6.4-taggattr-realmopt-v4/raddb/realms	Mon Sep 11 17:41:10 2000
@@ -8,7 +8,6 @@
 #		file to accept responses for that realm.  See doc/README.proxy
 #		and the 'users' file for more information.
 #
-#
 #		Description of the fields:
 #
 #		* The first field is a realm name.
@@ -19,17 +18,25 @@
 #		  as port + 1.
 #		  If this field is set to LOCAL, the request is processed
 #		  normally without sending it to a remote radius server.
-#		* Extra fields with options can follow. Currently
-#		  defined options:
+#		* An extra field with comma-separated options can follow. 
+#		  Currently defined options:
 #		  - nostrip	do not strip @realm from the username
 #		  - hints	use username as after "hints" processing
-#		  - noacct	do not proxy accounting type packets
-#		  - noauth	do not proxy authentication type packets
+#		  - local_auth	do authentication locally; only proxy
+#				accounting type packets. (was noauth)
+#		  - local_acct	do accounting locally; only proxy
+#				authentication type packets. (was noacct)
+#		  N.B. The old format using two options fields also still 
+#		  works, providing backwards compatibility.
+#		  N.B.II. noauth / noacct instead of local_auth / local_acct
+#		  are also still recognised, but the new keywords are a little
+#		  clearer, I think.
 #
 
 # Realm                 Remote server [:port]		Options
 #----------------	---------------------		-------
-#isp2.com		radius.isp2.com			nostrip hints
+#isp2.com		radius.isp2.com			nostrip,hints
 #company.com		radius.company.com:1600
+#special.com		accthost.special.com		local_auth,nostrip
 #bla.com		LOCAL				hints
 
diff -urN radiusd-cistron-1.6.4-orig/src/attrprint.c radiusd-cistron-1.6.4-taggattr-realmopt-v4/src/attrprint.c
--- radiusd-cistron-1.6.4-orig/src/attrprint.c	Wed Aug 16 16:18:51 2000
+++ radiusd-cistron-1.6.4-taggattr-realmopt-v4/src/attrprint.c	Thu Sep 14 15:22:42 2000
@@ -84,10 +84,21 @@
 	UINT4		vendor;
 	int		i, left;
 
+	if (pair->flags.has_tag) {
+#ifdef MERIT_TAG_FORMAT
+		fprintf(fd, "%s = :%d:", pair->name,pair->flags.tag);
+#else
+		fprintf(fd, "%s:%d = ", pair->name,pair->flags.tag);
+#endif
+	}
+	else {
+		fprintf(fd, "%s = ",pair->name);
+	}
+
 	switch(pair->type) {
 
 	case PW_TYPE_STRING:
-		fprintf(fd, "%s = \"", pair->name);
+		fputc('"',fd);
 		ptr = (u_char *)pair->strvalue;
 		if (pair->attribute != PW_VENDOR_SPECIFIC) {
 			left = pair->length;
@@ -155,22 +166,22 @@
 	case PW_TYPE_INTEGER:
 		dval = dict_valget(pair->lvalue, pair->name);
 		if(dval != (DICT_VALUE *)NULL) {
-			fprintf(fd, "%s = %s", pair->name, dval->name);
+			fprintf(fd, "%s", dval->name);
 		}
 		else {
-			fprintf(fd, "%s = %ld", pair->name, (long)pair->lvalue);
+			fprintf(fd, "%ld", (long)pair->lvalue);
 		}
 		break;
 
 	case PW_TYPE_IPADDR:
 		ipaddr2str(buffer, pair->lvalue);
-		fprintf(fd, "%s = %s", pair->name, buffer);
+		fprintf(fd, "%s", buffer);
 		break;
 
 	case PW_TYPE_DATE:
 		strftime(buffer, sizeof(buffer), "%b %e %Y",
 					localtime((time_t *)&pair->lvalue));
-		fprintf(fd, "%s = \"%s\"", pair->name, buffer);
+		fprintf(fd, "\"%s\"", buffer);
 		break;
 
 	default:
diff -urN radiusd-cistron-1.6.4-orig/src/cache.c radiusd-cistron-1.6.4-taggattr-realmopt-v4/src/cache.c
--- radiusd-cistron-1.6.4-orig/src/cache.c	Wed Aug 30 15:35:49 2000
+++ radiusd-cistron-1.6.4-taggattr-realmopt-v4/src/cache.c	Mon Sep 11 15:02:22 2000
@@ -46,7 +46,10 @@
  * in memory.  Returns -1 on failure, 0 on success.
  */
 int buildHashTable(void) {
-	FILE *passwd, *shadow;
+	FILE *passwd;
+#if !defined(NOSHADOW)
+	FILE *shadow;
+#endif
 	char buffer[BUFSIZE];
 	char idtmp[10];
 	char username[MAXUSERNAME];
diff -urN radiusd-cistron-1.6.4-orig/src/conf.h radiusd-cistron-1.6.4-taggattr-realmopt-v4/src/conf.h
--- radiusd-cistron-1.6.4-orig/src/conf.h	Thu Mar 30 17:24:59 2000
+++ radiusd-cistron-1.6.4-taggattr-realmopt-v4/src/conf.h	Fri Sep 15 10:57:13 2000
@@ -71,3 +71,18 @@
  */
 #define COMPAT_1543
 
+/*
+ *      Choose between two formats for tagged attributes - this is reflected
+ *      when parsing the user file and when outputting accounting data.
+ *
+ *      Default format is attr:tag = value, as the draft says that
+ *      tags can be used to associate multiple different attributes or
+ *      distinguish between multiple instances of the same attribute.
+ *
+ *      Merit associates a tag with the value, which reflects the hackish
+ *      way the tag is put in the packet. Their format is attr = :tag:value
+ *      and is used if the next define is uncommented.
+ */
+/* #define MERIT_TAG_FORMAT */
+
+
diff -urN radiusd-cistron-1.6.4-orig/src/dict.c radiusd-cistron-1.6.4-taggattr-realmopt-v4/src/dict.c
--- radiusd-cistron-1.6.4-orig/src/dict.c	Tue Sep 28 14:30:08 1999
+++ radiusd-cistron-1.6.4-taggattr-realmopt-v4/src/dict.c	Thu Sep 14 15:22:32 2000
@@ -113,12 +113,14 @@
 	char	valstr[64];
 	char	attrstr[64];
 	char	typestr[64];
-	char	vendorstr[64];
+	char	optstr[64];	/* options, incl. vendors */
 	int	line_no;
+	ATTR_FLAGS	flags;	/* parsed options */
 	DICT_ATTR	*attr;
 	DICT_VALUE	*dval;
 	DICT_VENDOR	*v;
 	char	buffer[256];
+	char	*s, *c;		/* temp */
 	int	value;
 	int	type;
 	int	vendor;
@@ -178,9 +180,9 @@
 		if (is_attrib) {
 			/* Read the ATTRIBUTE line */
 			vendor = 0;
-			vendorstr[0] = 0;
+			optstr[0] = 0;
 			if(sscanf(buffer, "%63s%63s%63s%63s%63s", dummystr,
-				namestr, valstr, typestr, vendorstr) < 4) {
+				namestr, valstr, typestr, optstr) < 4) {
 				log(L_ERR,
 			"%s: Invalid attribute on line %d of dictionary\n",
 					progname, line_no);
@@ -193,13 +195,13 @@
 			 *	We might need to add USR to the list of
 			 *	vendors first.
 			 */
-			if (is_nmc && vendorstr[0] == 0) {
+			if (is_nmc && optstr[0] == 0) {
 				if (!vendor_usr_seen) {
 					if (addvendor("USR", VENDORPEC_USR) < 0)
 						return -1;
 					vendor_usr_seen = 1;
 				}
-				strcpy(vendorstr, "USR");
+				strcpy(optstr, "vend=USR");
 			}
 #endif
 
@@ -243,15 +245,73 @@
 				return(-1);
 			}
 
-			for (v = dictionary_vendors; v; v = v->next) {
-				if (strcmp(vendorstr, v->vendorname) == 0)
-					vendor = v->vendorcode;
-			}
-			if (vendorstr[0] && !vendor) {
-				log(L_ERR|L_CONS,
-			"dict_init: unknown vendor %s on line %d of dictionary",
-				vendorstr, line_no);
-				return -1;
+			/*
+			 * Now parse the options string. It is formatted
+			 * as a comma-separated string of flags and
+			 * optionally values. A vendor is used as a flag
+			 * without a value to prevent breaking existing 
+			 * dictionaries, though it would be nicer to use
+			 * 'vend=xyz' instead, which is also supported.
+			 *
+			 * The fact that there will never be any spaces
+			 * in the options string (sscanf), makes life 	
+			 * a little easier here...
+			 */
+			memset(&flags, 0, sizeof(flags));
+			s = strtok(optstr, ",");
+			while(s) {
+				if (strcmp(s, "has_tag") == 0 ||
+				    strcmp(s, "has_tag=1") == 0) {
+					/* Boolean flag, means this is a
+					   tagged attribute */
+					flags.has_tag = 1;
+				}
+				else if (strncmp(s, "len+=", 5) == 0 ||
+					 strncmp(s, "len-=", 5) == 0) {
+					/* Length difference, to accomodate
+					   braindead NASes & their vendors */
+					flags.len_disp = strtol(s + 5, &c, 0);
+					if (*c) {
+dict_opterr:
+						log(L_CONS|L_ERR, "dict_init: "
+				"invalid option %s on dictionary line %d",
+							s, line_no);
+						return -1;
+					}
+					if (s[3] == '-') {
+						flags.len_disp = -flags.len_disp;
+					}
+				}
+				else if (strncmp(s, "encrypt=", 8) == 0) {
+					/* Encryption method, defaults to 0 (none).
+					   Currently valid is just type 1,
+					   Tunnel-Password style, which can only
+					   be applied to strings. */
+					flags.encrypt = strtol(s + 8, &c, 0);
+					if (*c) {
+						/* shortcut if non-numeric */
+						goto dict_opterr;
+					}
+				}
+				else {
+					/* Must be a vendor 'flag'... */
+					if (strncmp(s, "vend=", 5) == 0) {
+						/* New format */
+						s += 5;
+					}
+
+					for (v = dictionary_vendors; v; v = v->next) {
+						if (strcmp(s, v->vendorname) == 0)
+							vendor = v->vendorcode;
+					}
+					if (!vendor) {
+						log(L_ERR|L_CONS, "dict_init: "
+				"unknown vendor %s on line %d of dictionary",
+							s, line_no);
+						return -1;
+					}
+				}
+				s = strtok(NULL, ",");
 			}
 
 #ifdef COMPAT_1543
@@ -289,6 +349,7 @@
 			strNcpy(attr->name, namestr, sizeof(attr->name));
 			attr->value = value;
 			attr->type = type;
+			attr->flags = flags;
 			if (vendor)
 				attr->value |= (vendor << 16);
 
diff -urN radiusd-cistron-1.6.4-orig/src/files.c radiusd-cistron-1.6.4-taggattr-realmopt-v4/src/files.c
--- radiusd-cistron-1.6.4-orig/src/files.c	Mon Aug 21 20:39:12 2000
+++ radiusd-cistron-1.6.4-taggattr-realmopt-v4/src/files.c	Fri Sep 15 11:00:23 2000
@@ -189,7 +189,9 @@
 		 */
 		if ((i->attribute != PW_HINT) &&
 		    (i->attribute != PW_FRAMED_ROUTE) &&
-		    (pairfind(*to, i->attribute) != NULL)) {
+		    (pairfind(*to, i->attribute) != NULL) &&
+		     (i->flags.has_tag == 0 || 
+		     (i->flags.tag == (*to)->flags.tag))) {
 			DEBUG2("WARNING: Duplicate attribute %s is being ignored!", i->name);
 			tailfrom = i;
 			continue;
@@ -496,6 +498,17 @@
 					if (auth_item->attribute !=
 					    check_item->attribute)
 						continue;
+					/*
+					 * If it's a tagged attr, and 
+					 * the check item doesn't say 'any', 
+					 * and the tags don't match, then
+					 * consider this a different attribute.
+					 */ 
+					if (check_item->flags.has_tag &&
+					    check_item->flags.tag != TAG_ANY &&
+					    check_item->flags.tag != 
+					    auth_item->flags.tag) 
+						continue;
 			}
 			break;
 		}
@@ -756,7 +769,7 @@
 {
 	VALUE_PAIR	*vp;
 	VALUE_PAIR	*c = NULL;
-	int		n;
+	int		n = 0; 
 
 	/*
 	 *	See if a password is present. Return right away
@@ -840,7 +853,7 @@
 	VALUE_PAIR	*reply_tmp;
 	PAIR_LIST	*pl = NULL, *last = NULL, *t;
 	int		lineno = 0;
-	int		old_lineno;
+	int		old_lineno = 0;
 	int		parsecode;
 
 	*err = 1;
@@ -1320,13 +1333,14 @@
 	int		x;
 	char		attrstr[256];
 	char		valstr[256];
-	char		*s;
+	char		*s, *c;
 	DICT_ATTR	*attr = NULL;
 	DICT_VALUE	*dval;
 	VALUE_PAIR	*pair, *pair2;
 	struct tm	*tm;
 	time_t		timeval;
-	int		operator;
+	int		operator = 0;
+	int		tmptag = 0;
 	int		rcode;
 
 	rcode = USERPARSE_EOS;
@@ -1337,7 +1351,9 @@
 			rcode = USERPARSE_COMMA;
 		}
 
-		if(*buffer == ' ' || *buffer == '\t' || *buffer == ',') {
+		/* Changed - skip commas only in PARSE_MODE_NAME */
+		if (*buffer == ' ' || *buffer == '\t' || 
+		    (mode == PARSE_MODE_NAME && *buffer == ',')) {
 			buffer++;
 			continue;
 		}
@@ -1348,6 +1364,40 @@
 		case PARSE_MODE_NAME:
 			/* Attribute Name */
 			fieldcpy(attrstr, sizeof(attrstr), &buffer);
+
+#ifndef MERIT_TAG_FORMAT
+			/* 
+			 * Handle tagged attributes in our own format,
+			 * attribute:tag.
+			 */ 
+			tmptag = 0;
+			s = strrchr(attrstr, ':'); c = NULL;
+			if (s && s[1]) {
+				/* Colon found with something behind it */
+				if (s[1] == '*' && s[2] == 0) {
+					/* wildcard */
+					tmptag = TAG_ANY;     /* special value */
+					c = s + 2;		/* fake strtol */
+				}
+				else {
+					tmptag = strtol(s + 1, &c, 0);
+				}
+				if (c && *c == 0 && 
+				    (TAG_VALID(tmptag) || tmptag == TAG_ANY)) { 
+					/* Valid tag behind the colon */
+					/* Strip the colon and the tag */
+					*s = 0;
+				} else {
+					/* Garbage behind it - ignore the whole
+					   thing; don't even strip the colon. An
+					   error will be flagged by dict_attrfind
+					   anyway. */
+					tmptag = 0;
+				}
+			}
+#endif
+
+			/* Now look up the attribute */
 			if((attr = dict_attrfind(attrstr)) ==
 						(DICT_ATTR *)NULL) {
 				log(L_ERR|L_CONS, "Unknown attribute \"%s\"",
@@ -1397,6 +1447,40 @@
 			break;
 
 		case PARSE_MODE_VALUE:
+#ifdef MERIT_TAG_FORMAT
+			/*
+			 * Handle Merit-style tagged attribute syntax:
+			 * attr = :tag:value.
+			 * This is done here, before fieldcpy, to allow 
+			 * e.g. String-Attr = :1:"hello" - otherwise
+			 * it would have been String-Attr = ":1:hello",
+			 * which doesn't exactly Merit's syntax.
+			 */ 
+			tmptag = 0;
+			if (attr->flags.has_tag && *buffer == ':') {
+				/* Tag allowed and colon as first char */
+				if (buffer[1] == '*') {
+					/* wildcard */
+					tmptag = TAG_ANY;     /* special value */
+					c = buffer + 2;		/* fake strtol */
+				}
+				else {
+					/* see if there's a value there */
+					tmptag = strtol(buffer + 1, &c, 0);
+				}
+				if (c && *c == ':' &&
+				    (TAG_VALID(tmptag) || tmptag == TAG_ANY)) { 
+					/* Valid tag and a colon behind it:
+					   set buffer to point past that colon */
+					buffer = c + 1;
+				}
+				else {
+					/* Garbled tag - ignore the whole
+					   thing and leave buffer untouched. */
+					tmptag = 0;
+				}
+			}
+#endif
 			/* Value */
 			fieldcpy(valstr, sizeof(valstr), &buffer);
 			if((pair = (VALUE_PAIR *)malloc(sizeof(VALUE_PAIR))) ==
@@ -1404,12 +1488,20 @@
 				log(L_CONS|L_ERR, "no memory");
 				exit(1);
 			}
+
 			/* No need for strncpy - same size */
 			strcpy(pair->name, attr->name);
 			pair->attribute = attr->value;
 			pair->type = attr->type;
+			pair->flags = attr->flags;
 			pair->operator = operator;
 
+			/* Fill in the tag - better do it via tmptag, not via 
+			   attr->flags itself, in the case of our own syntax. 
+			   Keep 'attr' sort of readonly, because it really 
+			   belongs to the dictionary, so to speak. */
+			pair->flags.tag = tmptag;
+
 			/* _Always_ copy value as string. */
 			strNcpy(pair->strvalue, valstr, sizeof(pair->strvalue));
 
@@ -1560,7 +1652,7 @@
 	/*
 	 *	Normally, we should return 0 here, but we
 	 *	support the old * stuff.
-	 *	FIXME: this doesn't support realsm yet, while
+	 *	FIXME: this doesn't support realms yet, while
 	 *	presufcmp does!
 	 */
 	len = strlen(name);
@@ -1989,9 +2081,8 @@
 	char	buffer[256];
 	char	realm[32];
 	char	hostnm[128];
-	char	opts[5][32];
+	char	opts[96], oldopts2[32];
 	char	*p;
-	int	i;
 	int	lineno = 0;
 	REALM	*c;
 
@@ -2006,23 +2097,24 @@
 		lineno++;
 		if (buffer[0] == '#' || buffer[0] == '\n')
 			continue;
-		opts[0][0] = opts[1][0] = 0;
-		opts[2][0] = opts[3][0] = 0;
-		opts[4][0] = 0;
-		if (sscanf(buffer, "%31s%127s%31s%31s%31s%31s%31s",
-		    realm, hostnm, opts[0], opts[1],
-		    	opts[2], opts[3], opts[4]) < 2) {
+		opts[0] = oldopts2[0] = 0;
+		if (sscanf(buffer, "%31s%127s%63s%31s",
+		    realm, hostnm, opts, oldopts2) < 2) {
 			log(L_ERR, "%s[%d]: syntax error", file, lineno);
 			return -1;
 		}
+		/* concatenate opts and oldopts2, for backwards
+		   compatibility */
+		if (*oldopts2) {
+			strcat(opts, ",");
+			strcat(opts, oldopts2);	 /* there's room enough */
+		}
 		if ((c = malloc(sizeof(REALM))) == NULL) {
 			log(L_CONS|L_ERR, "%s[%d]: out of memory",
 				file, lineno);
 			return -1;
 		}
 		memset(c, 0, sizeof(REALM));
-		c->striprealm = 1;
-		c->dohints = 0;
 
 		if ((p = strchr(hostnm, ':')) != NULL) {
 			*p++ = 0;
@@ -2036,19 +2128,53 @@
 			c->ipaddr = get_ipaddr(hostnm);
 		strNcpy(c->realm, realm, sizeof(c->realm));
 		strNcpy(c->server, hostnm, sizeof(c->server));
-
-		/* Yes we could do this more intelligently */
-		for(i = 0; i < 3; i++) {
-			if (strcmp(opts[i], "nostrip") == 0)
-				c->striprealm = 0;
-			if (strcmp(opts[i], "hints") == 0)
-				c->dohints = 1;
-			if (strcmp(opts[i], "noacct") == 0)
-				c->acct_port = 0;
-			if (strcmp(opts[i], "noauth") == 0)
-				c->auth_port = 0;
-			if (strcmp(opts[i], "trusted") == 0)
-				c->trusted = 0;
+		/*
+		 * Now parse the options string as a comma-
+		 * separated string of flags.
+		 *
+		 * Note that all options in 'flags' default to 0.
+		 *
+		 * The fact that there will never be any spaces
+		 * in the options string (sscanf), makes life 	
+		 * a little easier here...
+		 */
+		p = strtok(opts, ",");
+		while(p) {
+			if (strcmp(p, "nostrip") == 0) {
+				/* Boolean flag, means don't strip the
+				   realm when proxying */
+				c->flags.nostrip = 1;
+			}
+			else if (strcmp(p, "hints") == 0) {
+				/* Boolean flag, says to process hints
+				   file */
+				c->flags.dohints = 1;
+			}
+			else if (strcmp(p, "local_auth") == 0 ||
+				 strcmp(p, "noauth") == 0) {
+				/* Boolean flag, says to do authentication
+				   locally, but to proxy accounting */
+				c->flags.local_auth = 1;
+			}
+			else if (strcmp(p, "local_acct") == 0 ||
+				 strcmp(p, "noacct") == 0) {
+				/* Boolean flag, says to do accounting
+				   locally, but to proxy authentication */
+				c->flags.local_acct = 1;
+			}
+			else if (strcmp(p, "trusted") == 0) {
+				/* Boolean flag, says to trust the remote
+				   radius server, i.e. relay all reply items. */
+				/* Mike, your old line (trusted = 0) could never
+				   have worked, as it was initialised with 0 too? */
+				c->flags.trusted = 1;
+			}
+			else {
+				log(L_ERR|L_CONS, "%s[%d]: invalid option %s", 
+					file, lineno, p);
+				return -1;
+			}
+			p = strtok(NULL, ",");
 		}
 		c->next = realms;
 		realms = c;
diff -urN radiusd-cistron-1.6.4-orig/src/proxy.c radiusd-cistron-1.6.4-taggattr-realmopt-v4/src/proxy.c
--- radiusd-cistron-1.6.4-orig/src/proxy.c	Mon Aug 21 20:39:12 2000
+++ radiusd-cistron-1.6.4-taggattr-realmopt-v4/src/proxy.c	Mon Sep 11 16:50:37 2000
@@ -292,19 +292,29 @@
 	 *	If "hints" was not set, we have to use the original
 	 *	username as it was before hints processing.
 	 */
-	if (!realm->dohints && authreq->username[0])
+	if (!realm->flags.dohints && authreq->username[0])
 		strNcpy(namepair->strvalue, authreq->username,
 			sizeof(namepair->strvalue));
 	if ((realmname = strrchr(namepair->strvalue, '@')) != NULL) 
 		realmname++;
 
 	/*
-	 *	The special server LOCAL ?
-	 */
-	if (strcmp(realm->server, "LOCAL") == 0) {
+	 *	The special server LOCAL, or
+	 *	an auth req with realm flags local_auth, or
+	 *	an acct req with realm flags local_acct?
+	 *	Then look at nostrip, act accordingly
+	 *	and return.
+	 *	Mike, I changed it back to my way to have the
+	 *	new options behave _exactly_ the same way as LOCAL.
+	 *	(And I prefer the more generic flags above the 
+	 *	'port = 0' way of specifying things.)
+	 */
+	if (strcmp(realm->server, "LOCAL") == 0 ||
+	    (realm->flags.local_auth && authreq->code == PW_AUTHENTICATION_REQUEST) ||
+	    (realm->flags.local_acct && authreq->code == PW_ACCOUNTING_REQUEST)) {
 		/* Same size */
 		strcpy(namepair->strvalue, saved_username);
-		if (realm->striprealm &&
+		if (!realm->flags.nostrip &&
 		    ((realmname = strrchr(namepair->strvalue, '@')) != NULL)) {
 			*realmname = 0;
 			namepair->length = strlen(namepair->strvalue);
@@ -332,7 +342,7 @@
 	}
 
 	if (realmname != NULL) {
-		if (realm->striprealm)
+		if (!realm->flags.nostrip)
 			realmname[-1] = 0;
 		strNcpy(authreq->realm, realmname, sizeof(authreq->realm));
 	} else
@@ -438,6 +448,8 @@
 
 	total_length = AUTH_HDR_LEN;
 
+	/* Do tag support here just as in rad_send_reply() */
+
 	/*
 	 *	Put all the attributes into a buffer.
 	 */
@@ -485,12 +497,41 @@
 
 		switch (vp->type) {
 			case PW_TYPE_STRING:
+				/* 
+				 * vp->length is the length of the string value;
+				 * len is the length of the string field in the
+				 * packet. Normally, these are the same, but
+				 * if a tag is present, only len will reflect this.
+				 */
+				len = vp->length;
+
+				if (vp->flags.has_tag &&
+				    /* the following additional requirement is strange,
+				       but it's really what the draft says: if the tag
+				       is zero or >0x1f, don't insert it. It's really
+				       in-band, so to speak. */
+				    TAG_VALID(vp->flags.tag)) {
+					len++;
+				}
+				/*
+				 * Substract the 'correction' (uche) value to
+				 * the length field in the packet again to 
+				 * make the request really look like the original one.
+				 */
 #ifdef ATTRIB_NMC
 				if (vendorpec != VENDORPEC_USR)
 #endif
-					*ptr++ = vp->length + 2;
-				if (length_ptr) *length_ptr += vp->length + 2;
-				total_length += 2 + vp->length;
+					*ptr++ = len + 2 - vp->flags.len_disp;
+				if (length_ptr) *length_ptr += len + 2; 
+						/* += len + 2 - vp->flags.len_disp; */
+						/* See remark in radius.c */
+
+				/* Re-insert the tag before the string */
+				if (vp->flags.has_tag && TAG_VALID(vp->flags.tag)) {
+					*ptr++ = vp->flags.tag;
+				}
+
+				/* Use the original length of the string value */
 				memcpy(ptr, vp->strvalue, vp->length);
 
 				/*
@@ -501,25 +542,49 @@
 						pw_digest, ptr, vp->length);
 
 				ptr += vp->length;
+				total_length += len + 2;
 				break;
+
 			case PW_TYPE_INTEGER:
 			case PW_TYPE_DATE:
 			case PW_TYPE_IPADDR:
+				len = sizeof(UINT4) + 2;
+				if (vp->type == PW_TYPE_IPADDR ||
+				    vp->type == PW_TYPE_DATE) {
+					/* For IPADDRs / DATEs, _insert_ the tag.
+					   Not that tags are defined yet for this 
+					   type, but just to be consistent :) */
+					len += vp->flags.has_tag;
+				}
+#ifdef ATTRIB_NMC
+				if (vendorpec != VENDORPEC_USR)
+#endif
+					*ptr++ = len - vp->flags.len_disp;
+				if (length_ptr) *length_ptr += len;
+						/* += len - vp->flags.len_disp; */
+	
+				if (vp->flags.has_tag) {
+					if (vp->type == PW_TYPE_IPADDR ||
+					    vp->type == PW_TYPE_DATE) {
+						/* For IPADDRs / DATEs, _insert_ the tag */
+						*ptr++ = vp->flags.tag;
+					} else {
+						/* For INTEGERs, _the MSB_ is the tag */
+						vp->lvalue = (vp->lvalue & 0xffffff) |
+								((vp->flags.tag & 0xff) << 24);
+					}
+				}
 				/*
 				 *	FIXME: this works for IPADDR because
 				 *	it is still stored internally in
 				 *	hostorder (ugh).
 				 */
-#ifdef ATTRIB_NMC
-				if (vendorpec != VENDORPEC_USR)
-#endif
-					*ptr++ = sizeof(UINT4) + 2;
-				if (length_ptr) *length_ptr += sizeof(UINT4)+ 2;
 				lvalue = htonl(vp->lvalue);
 				memcpy(ptr, &lvalue, sizeof(UINT4));
 				ptr += sizeof(UINT4);
-				total_length += sizeof(UINT4) + 2;
+				total_length += len;
 				break;
+
 			default:
 				break;
 		}
@@ -668,10 +733,11 @@
 
 	/*
 	 *	Only allow some attributes to be propagated from
-	 *	the remote server back to the NAS, for security.
+	 *	the remote server back to the NAS, for security,
+	 *	unless the remote server is designated 'trusted'.
 	 */
 	if (oldreq->realm && (realm = realm_find(oldreq->realm)) &&
-	    realm->trusted) {
+	    realm->flags.trusted) {
 		allowed_pairs = authreq->request;
 	} else {
 		allowed_pairs = NULL;
diff -urN radiusd-cistron-1.6.4-orig/src/radius.c radiusd-cistron-1.6.4-taggattr-realmopt-v4/src/radius.c
--- radiusd-cistron-1.6.4-orig/src/radius.c	Wed Aug 16 16:18:51 2000
+++ radiusd-cistron-1.6.4-taggattr-realmopt-v4/src/radius.c	Thu Sep 28 11:13:51 2000
@@ -141,34 +141,119 @@
 			 *	should NOT be needed. In fact I have no
 			 *	idea if it is needed :)
 			 */
-			if (reply->length == 0 && reply->strvalue[0] != 0)
+			if (reply->length == 0 && reply->strvalue[0] != 0) {
 				reply->length = strlen(reply->strvalue);
+			}
+			if (reply->length >= AUTH_STRING_LEN) {
+				reply->length = AUTH_STRING_LEN - 1;
+			}
+
+			/*
+			 * If the flags indicate a encrypted response 
+			 * attribute, do it here, as it may depend on authreq.
+			 * Also, I don't want to go through the reply list
+			 * another time, only for transformation purposes
+			 * like this.
+			 */
+			switch(reply->flags.encrypt)
+			{
+			case 0:
+				/* Normal, cleartext. */
+				break;
+			case 1:
+				/* 
+				 * Style: Tunnel-Password (see draft-ietf-
+				 * radius-tunnel-auth-06.txt).
+				 * Input: authenticator and shared secret 
+				 * from auth-req, cleartext pw from reply.
+				 * Output: encrypted pw in reply.
+				 */
+				encrypt_attr_style_1(authreq->secret,
+					authreq->vector, reply);
+				break;
 
+			default:
+				/* Unknown style - don't send the cleartext! */
+				reply->length = 5;
+				memcpy(reply->strvalue, "sorry", reply->length); 
+			}
+
+			/* 
+			 * reply->length is the length of the string value;
+			 * len is the length of the string field in the
+			 * packet. Normally, these are the same, but
+			 * if a tag is present, only len will reflect this.
+			 */
 			len = reply->length;
-			if (len >= AUTH_STRING_LEN) {
-				len = AUTH_STRING_LEN - 1;
+
+			if (reply->flags.has_tag &&
+			    /* the following additional requirement is strange,
+			       but it's really what the draft says: if the tag
+			       is zero or >0x1f, don't insert it. It's really
+			       in-band, so to speak. */
+			    TAG_VALID(reply->flags.tag)) {
+				len++;
 			}
+
+			/*
+			 * Add an optional 'correction' (uche) value to
+			 * the length field in the packet to 'support'
+			 * RFC-violating NASes. Only to take a selling
+			 * argument away from some commercial radius
+			 * servers.
+			 */
 #ifdef ATTRIB_NMC
 			if (vendorpec != VENDORPEC_USR)
 #endif
-				*ptr++ = len + 2;
-			if (length_ptr) *length_ptr += len + 2;
-			memcpy(ptr, reply->strvalue,len);
-			ptr += len;
+				*ptr++ = len + 2 + reply->flags.len_disp;
+			if (length_ptr) *length_ptr += len + 2; 
+					/* += len + 2 + reply->flags.len_disp; */
+				/* I don't know _what_ these types expect -
+				   only the 'internal' length adjusted, or
+				   the total vsa length as well? I assume the
+				   former... */
+
+			/* Insert the tag before the string */
+			if (reply->flags.has_tag && TAG_VALID(reply->flags.tag)) {
+				*ptr++ = reply->flags.tag;
+			}
+
+			/* Use the original length of the string value */
+			memcpy(ptr, reply->strvalue, reply->length);
+			ptr += reply->length;
 			total_length += len + 2;
 			break;
 
 		case PW_TYPE_INTEGER:
 		case PW_TYPE_IPADDR:
+			len = sizeof(UINT4) + 2;
+			if (reply->type == PW_TYPE_IPADDR) {
+				/* For IPADDRs, _insert_ the tag.
+				   Not that tags are defined yet for this 
+				   type, but just to be consistent :) */
+				len += reply->flags.has_tag;
+			}
 #ifdef ATTRIB_NMC
 			if (vendorpec != VENDORPEC_USR)
 #endif
-				*ptr++ = sizeof(UINT4) + 2;
-			if (length_ptr) *length_ptr += sizeof(UINT4)+ 2;
+				*ptr++ = len + reply->flags.len_disp;
+			if (length_ptr) *length_ptr += len;
+					/* += len + reply->flags.len_disp; */
+
+			if (reply->flags.has_tag) {
+				if (reply->type == PW_TYPE_IPADDR) {
+					/* For IPADDRs, _insert_ the tag */
+					*ptr++ = reply->flags.tag;
+				} else {
+					/* For INTEGERs, _the MSB_ is the tag */
+					reply->lvalue = (reply->lvalue & 0xffffff) |
+							((reply->flags.tag & 0xff) << 24);
+				}
+			}
 			lvalue = htonl(reply->lvalue);
 			memcpy(ptr, &lvalue, sizeof(UINT4));
 			ptr += sizeof(UINT4);
-			total_length += sizeof(UINT4) + 2;
+			total_length += len;
 			break;
 
 		default:
@@ -313,6 +398,106 @@
 	return memcmp(digest, authreq->vector, AUTH_VECTOR_LEN) ? 2 : 0;
 }
 
+
+/*
+ *	Encrypt a string attribute, style 1.
+ *
+ *	See draft-ietf-radius-tunnel-auth-06 for details. Currently
+ *	probably only useful for Tunnel-Password, but why make it
+ *	a special case, now we have a generic mechanism in place
+ *	anyway?
+ *
+ *	It's optimized for speed, but it could probably be better...
+ */
+
+#define	CLEAR_STRING_LEN	256 	/* The draft says it is */
+#define	SECRET_LEN		32	/* max. in client.c */
+#define	MD5_LEN			16
+#define	SALT_LEN		2
+
+void encrypt_attr_style_1(u_char *secret, u_char *vector, VALUE_PAIR *reply)
+{
+	u_char clear_buf[CLEAR_STRING_LEN];
+	u_char work_buf[SECRET_LEN + AUTH_VECTOR_LEN + SALT_LEN];
+	u_char digest[MD5_LEN];
+	u_char *i,*o;
+	u_short salt;
+	int clear_len;
+	int work_len;
+	int secret_len;
+	int n;
+
+	/* Copy up to the first 255 bytes of the original cleartext, 
+	   padding it with zeroes and inserting a length octet in front. 
+           This will be the string we'll actually be processing here. */
+	clear_len = reply->length;
+	if (clear_len > CLEAR_STRING_LEN - 1) 
+		clear_len = CLEAR_STRING_LEN - 1;
+	/* write the original length byte */
+	*clear_buf = clear_len;
+	/* copy the buffer */
+	memcpy(clear_buf + 1, reply->strvalue, clear_len);
+	/* now include the length byte - it really counts from now on */
+	clear_len++;	
+	/* and do the padding to a multiple of 16 bytes - 1 chunk. */
+	if (clear_len & 15) 
+		memset(clear_buf + clear_len, 0, 16 - (clear_len & 15));
+
+	/* input */
+	i = clear_buf;
+	clear_len = (clear_len >> 4) + 1;	/* chunks to process */
+
+	/* output */
+	o = reply->strvalue;
+	reply->length = sizeof(salt);		/* starting length */
+
+	/*
+	 * Fill in salt. Must be unique per attribute that uses it
+	 * in the same packet, and the most significant bit must be set - 
+	 * see the draft mentioned above for details.
+	 *
+	 * FIXME: this _may_ be too simple. For now we just take
+	 * the reply pointer, which is different between attributes,
+	 * xor-ed with the first longword of the vector to make it
+	 * a little more unique.
+	 *
+	 * And, sizeof(long) always == sizeof(void*) in our part of the
+	 * universe, right? (e.g. OSF/Alpha, SunOS/Sparc, Linux/x86 or Alpha)
+	 */
+	salt = htons( ( ((long)reply ^ *(long *)vector) & 0xffff ) | 0x8000 );
+	memcpy(o, &salt, sizeof(salt));
+	o += sizeof(salt);
+
+	/* Create a first working buffer to calc the MD5 hash over */
+	secret_len = strlen(secret);	/* already limited by read_clients */
+#if 0
+	printf("secret = %s, vector = 0x%08Lx %08Lx, salt = %02x\n",
+		secret, ((long long *)vector)[0], ((long long *)vector)[1],
+		ntohs(salt));
+#endif
+	memcpy(work_buf, secret, secret_len);
+	memcpy(work_buf + secret_len, vector, AUTH_VECTOR_LEN);
+	memcpy(work_buf + secret_len + AUTH_VECTOR_LEN, &salt, sizeof(salt));
+	work_len = secret_len + AUTH_VECTOR_LEN + sizeof(salt);
+
+	for( ; clear_len; clear_len--) {
+		/* get the digest */
+		md5_calc(digest, work_buf, work_len);
+
+		/* xor the clear text with it to get the output chunk and next
+		   working buffer */
+		for(n = 0; n < 16; n++) {
+			*(work_buf + secret_len + n) = *o++ = *i++ ^ digest[n];
+		}
+
+		/* This is the size of the next working buffer */
+		work_len = secret_len + 16;
+		/* Increment the reply length */
+		reply->length += 16;
+	}
+}
+
+
 /*
  *	Receive UDP client requests, build an authorization request
  *	structure, and attach attribute-value pairs contained in
@@ -443,9 +628,24 @@
 		} else {
 			strcpy(pair->name, attr->name);
 			pair->type = attr->type;
+			pair->flags = attr->flags;
 		}
 
-		if ( attrlen >= AUTH_STRING_LEN-1 ) {
+		/* FIXME: I assume the length-braindead NASes also _send_ wrong
+		   lengths?
+		   Note that vendorlen isn't fixed up, as I assume that the
+		   external vsa length is still sent normally. See also
+		   rad_send_reply(). */
+		attrlen -= pair->flags.len_disp;
+
+		if ( attrlen <= 0 ) {
+			DEBUG("attribute %d too short after adjustment, %d - %d <= 0",
+			      attribute, attrlen, pair->flags.len_disp);
+			free(pair);
+			/* This is critical, as we would get an endless loop */
+			break;
+		}
+		else if ( attrlen >= AUTH_STRING_LEN-1 ) {
 			DEBUG("attribute %d too long, %d >= %d", attribute,
 				attrlen, AUTH_STRING_LEN);
 			free(pair);
@@ -467,7 +667,20 @@
 			case PW_TYPE_STRING:
 				/* attrlen always < AUTH_STRING_LEN */
 				memset(pair->strvalue, 0, AUTH_STRING_LEN);
-				memcpy(pair->strvalue, ptr, attrlen);
+
+                                /* Support tags */
+                                if (pair->flags.has_tag && TAG_VALID(*ptr)) {
+                                        pair->flags.tag = *ptr;
+                                        pair->length--;
+                                        memcpy(pair->strvalue, ptr + 1, pair->length);
+                                        /* We don't want to touch ptr or attrlen,
+                                           that's why it's done this way. */
+                                }
+                                else {
+                                        memcpy(pair->strvalue, ptr, attrlen);
+                                        pair->flags.tag = 0;
+                                }
+
 				debug_pair(stdout, pair);
 				if(first_pair == (VALUE_PAIR *)NULL) {
 					first_pair = pair;
@@ -480,8 +693,26 @@
 			
 			case PW_TYPE_INTEGER:
 			case PW_TYPE_IPADDR:
-				memcpy(&lvalue, ptr, sizeof(UINT4));
+				/* Keep length correct under all circumstances (e.g.
+				   a tagged ip address would have attrlen == 5) */
+				pair->length = sizeof(UINT4);
+
+				/* Support inserted tags for ip addresses */
+				if (pair->flags.has_tag && pair->type == PW_TYPE_IPADDR) {
+					pair->flags.tag = *ptr;
+					memcpy(&lvalue, ptr + 1, sizeof(UINT4));
+				}
+				else {
+					memcpy(&lvalue, ptr, sizeof(UINT4));
+				}
 				pair->lvalue = ntohl(lvalue);
+
+				/* Support tag in MSB for integers */
+				if (pair->flags.has_tag && pair->type == PW_TYPE_INTEGER) {
+					pair->flags.tag = (pair->lvalue >> 24) & 0xff;
+					pair->lvalue &= 0xffffff;
+				}
+
 				debug_pair(stdout, pair);
 				if(first_pair == (VALUE_PAIR *)NULL) {
 					first_pair = pair;
diff -urN radiusd-cistron-1.6.4-orig/src/radiusd.h radiusd-cistron-1.6.4-taggattr-realmopt-v4/src/radiusd.h
--- radiusd-cistron-1.6.4-orig/src/radiusd.h	Mon Aug 21 20:39:12 2000
+++ radiusd-cistron-1.6.4-taggattr-realmopt-v4/src/radiusd.h	Mon Sep 11 16:52:19 2000
@@ -36,11 +36,19 @@
 
 /* Server data structures */
 
+typedef struct attr_flags {
+	char			has_tag;	/* attribute allows tags */
+	signed char		tag;
+	char			encrypt;	/* encryption method */
+	signed char		len_disp;	/* length displacement */
+} ATTR_FLAGS;
+
 typedef struct dict_attr {
 	char			name[32];
 	int			value;
 	int			type;
 	int			vendor;
+	ATTR_FLAGS		flags;
 	struct dict_attr	*next;
 } DICT_ATTR;
 
@@ -65,6 +73,7 @@
 	int			length; /* of strvalue */
 	UINT4			lvalue;
 	int			operator;
+	ATTR_FLAGS		flags;
 	char			strvalue[AUTH_STRING_LEN];
 	struct value_pair	*next;
 } VALUE_PAIR;
@@ -109,15 +118,25 @@
 	struct nas		*next;
 } NAS;
 
+typedef struct realm_flags {
+	char			nostrip;
+	char			dohints;
+	char			local_auth;
+	char			local_acct;
+
+	char			trusted;
+	char			resv0;
+	char			resv1;
+	char			resv2;
+} REALM_FLAGS;
+
 typedef struct realm {
 	char			realm[64];
 	char			server[64];
 	UINT4			ipaddr;
 	int			auth_port;
 	int			acct_port;
-	int			striprealm;
-	int			dohints;
-	int			trusted;
+	REALM_FLAGS		flags;
 	struct realm		*next;
 } REALM;
 
@@ -152,6 +171,18 @@
 #define VENDOR(x) (x >> 16)
 
 /*
+ * This defines for tagged string attrs whether the tag
+ * is actually inserted or not...! Stupid IMHO, but
+ * that's what the draft says...
+ */
+#define TAG_VALID(x)	((x) > 0 && (x) < 0x20)
+/* 
+ * This defines a TAG_ANY, the value for the tag if
+ * a wildcard ('*') was specified in a check item.
+ */
+#define TAG_ANY		-128	 /* SCHAR_MIN */
+
+/*
  *	Global variables.
  */
 extern char		*recv_buffer;
@@ -190,6 +221,7 @@
 /* attrprint.c */
 void		fprint_attr_list(FILE *, VALUE_PAIR *);
 void		fprint_attr_val(FILE *, VALUE_PAIR *);
+void		debug_pair(FILE *, VALUE_PAIR *);
 
 /* dict.c */
 int		dict_init(char *);
@@ -206,7 +238,6 @@
 
 /* radiusd.c */
 int		radius_exec_program(char *, VALUE_PAIR *, VALUE_PAIR **, int, char **user_msg);
-void		debug_pair(FILE *, VALUE_PAIR *);
 int		log_err (char *);
 void		sig_cleanup(int);
 
@@ -233,6 +264,7 @@
 AUTH_REQ	*radrecv (UINT4, u_short, u_char *, int);
 int		calc_digest (u_char *, AUTH_REQ *);
 int		calc_acctdigest(u_char *digest, AUTH_REQ *authreq);
+void		encrypt_attr_style_1(u_char *secret, u_char *vector, VALUE_PAIR *reply);
 
 /* files.c */
 int		user_find(char *name, VALUE_PAIR *,
diff -urN radiusd-cistron-1.6.4-orig/src/radtest.c radiusd-cistron-1.6.4-taggattr-realmopt-v4/src/radtest.c
--- radiusd-cistron-1.6.4-orig/src/radtest.c	Wed Aug 16 16:18:51 2000
+++ radiusd-cistron-1.6.4-taggattr-realmopt-v4/src/radtest.c	Mon Sep 11 15:02:22 2000
@@ -50,6 +50,18 @@
 #define TEST_VENDOR	1
 #define TEST_USR	1
 
+#define TEST_TAGS	1
+
+
+#ifndef MIN
+#define MIN(a,b)	(((a)<(b)) ? (a) : (b))
+#endif
+
+#ifndef MAX
+#define MAX(a,b)	(((a)>(b)) ? (a) : (b))
+#endif
+
+
 int		i_send_buffer[2048];
 int		i_recv_buffer[2048];
 char		*send_buffer = (char *)i_send_buffer;
@@ -84,6 +96,10 @@
 	VALUE_PAIR	*prev;
 	VALUE_PAIR	*pair;
 	AUTH_REQ	*authreq;
+#if 1
+	int		l,i;
+	u_char		*s;
+#endif
 
 	/*
 	 *	Pre-allocate the new request data structure
@@ -119,6 +135,19 @@
 	first_pair = (VALUE_PAIR *)NULL;
 	prev = (VALUE_PAIR *)NULL;
 
+#if 1	
+	printf("Headerless packet dump (length %d):\n",length);
+	for(l=0,s=ptr; l<length; l+=16) {
+		printf("%05x  ",l);
+		for(i=0; i<MIN(length-l,16); i++) printf("%02x ",s[i]);
+		for( ; i<16; i++) printf("   "); 
+		printf("  ");
+		for(i=0; i<MIN(length-l,16); i++) putchar(s[i]>31 && s[i]<127 ? s[i] : '.');
+		putchar('\n');
+		s+=16;
+	}
+#endif
+
 	while(length > 0) {
 
 		attribute = *ptr++;
@@ -136,6 +165,13 @@
 				attrlen, AUTH_STRING_LEN-1);
 		}
 		else {
+			/* Adjust length back again */
+			attrlen -= attr->flags.len_disp;
+			if (attrlen < 0) {
+				length = 0;
+				continue;
+			}
+
 			if((pair = (VALUE_PAIR *)malloc(sizeof(VALUE_PAIR))) ==
 						(VALUE_PAIR *)NULL) {
 				fprintf(stderr, "%s: no memory\n",
@@ -146,14 +182,23 @@
 			strcpy(pair->name, attr->name);
 			pair->attribute = attr->value;
 			pair->type = attr->type;
+			pair->flags = attr->flags;
 			pair->next = (VALUE_PAIR *)NULL;
 
 			switch(attr->type) {
 
 			case PW_TYPE_STRING:
-				memcpy(pair->strvalue, ptr, attrlen);
-				pair->strvalue[attrlen] = '\0';
 				pair->length = attrlen;
+				if (pair->flags.has_tag && TAG_VALID(*ptr)) {
+					pair->flags.tag = *ptr;
+					pair->length--;
+					memcpy(pair->strvalue, ptr + 1, pair->length);
+				}
+				else {
+					memcpy(pair->strvalue, ptr, pair->length);
+					pair->flags.tag = 0;
+				}
+				pair->strvalue[pair->length] = '\0';
 				if(first_pair == (VALUE_PAIR *)NULL) {
 					first_pair = pair;
 				}
@@ -165,8 +210,21 @@
 			
 			case PW_TYPE_INTEGER:
 			case PW_TYPE_IPADDR:
-				memcpy(&lvalue, ptr, sizeof(UINT4));
+				pair->length = sizeof(UINT4);
+				if (pair->flags.has_tag && pair->type == PW_TYPE_IPADDR) {
+					pair->flags.tag = *ptr;
+					memcpy(&lvalue, ptr + 1, sizeof(UINT4));
+				}
+				else {
+					memcpy(&lvalue, ptr, sizeof(UINT4));
+				}
 				pair->lvalue = ntohl(lvalue);
+
+				if (pair->flags.has_tag && pair->type == PW_TYPE_INTEGER) {
+					pair->flags.tag = (pair->lvalue >> 24) & 0xff;
+					pair->lvalue &= 0xffffff;
+				}
+
 				if(first_pair == (VALUE_PAIR *)NULL) {
 					first_pair = pair;
 				}
@@ -181,7 +239,6 @@
 				free(pair);
 				break;
 			}
-
 		}
 		ptr += attrlen;
 		length -= attrlen + 2;
@@ -208,7 +265,7 @@
 	totallen = ntohs(auth->length);
 
 	if(totallen != length) {
-		printf("Received invalid reply length from server (want %d/ got %d)\n", totallen, length);
+		printf("Received invalid reply length from server (want %d/got %d)\n", totallen, length);
 		exit(1);
 	}
 
@@ -220,7 +277,7 @@
 	md5_calc(calc_digest, (char *)auth, length + secretlen);
 
 	if(memcmp(reply_digest, calc_digest, AUTH_VECTOR_LEN) != 0) {
-		printf("Warning: Received invalid reply digest from server\n");
+		printf("WARNING: Received invalid reply digest from server\n");
 	}
 
 	authreq = test_radrecv(host, udp_port, buffer, length);
@@ -466,6 +523,15 @@
 	total_length += 14;
 #endif
 
+#if TEST_TAGS
+	*ptr++ = 83;	/* Tunnel-Preference */
+	*ptr++ = 6;	/* length */
+	ui = htonl(123 + (2 << 24));	/* value 123, tag 2 */
+	memcpy(ptr, &ui, sizeof(UINT4));
+	ptr += sizeof(UINT4);
+	total_length += 6;
+#endif
+
 	*ptr++ = PW_NAS_IP_ADDRESS;
 	*ptr++ = 6;
 	ui = htonl(nas_ipaddr);
@@ -507,7 +573,7 @@
 	sin->sin_port = htons(svc_port);
 
 	printf("Sending request to server %s, port %d.\n", server, svc_port);
-	for (i = 0; i < 10; i++) {
+	for (i = result = 0; i < 10; i++) {
 		if (i > 0) printf("Re-sending request.\n");
 		sendto(sockfd, (char *)auth, total_length, 0,
 			&saremote, sizeof(struct sockaddr_in));
