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 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. + + - '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; l31 && 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));