diff -uNr cistron-1.6.3-stable/doc/README.tunnel cistron-1.6.3-new/doc/README.tunnel --- cistron-1.6.3-stable/doc/README.tunnel Thu Jan 1 01:00:00 1970 +++ cistron-1.6.3-new/doc/README.tunnel Sun Jun 18 20:30:24 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 -uNr cistron-1.6.3-stable/raddb/dictionary.tunnel cistron-1.6.3-new/raddb/dictionary.tunnel --- cistron-1.6.3-stable/raddb/dictionary.tunnel Sun Sep 19 00:10:40 1999 +++ cistron-1.6.3-new/raddb/dictionary.tunnel Mon Aug 14 16:14:51 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 -uNr cistron-1.6.3-stable/raddb/realms cistron-1.6.3-new/raddb/realms --- cistron-1.6.3-stable/raddb/realms Tue Oct 19 23:08:25 1999 +++ cistron-1.6.3-new/raddb/realms Mon Jun 19 13:23:34 2000 @@ -14,15 +14,17 @@ # 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 +# - local_auth do authentication locally; only proxy acct. +# - local_acct do accounting locally; only proxy auth. # # 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 #bla.com LOCAL hints diff -uNr cistron-1.6.3-stable/src/attrprint.c cistron-1.6.3-new/src/attrprint.c --- cistron-1.6.3-stable/src/attrprint.c Sun Sep 19 00:10:40 1999 +++ cistron-1.6.3-new/src/attrprint.c Sun Jun 18 18:54:15 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; @@ -141,22 +152,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 -uNr cistron-1.6.3-stable/src/cache.c cistron-1.6.3-new/src/cache.c --- cistron-1.6.3-stable/src/cache.c Wed Apr 5 22:09:43 2000 +++ cistron-1.6.3-new/src/cache.c Wed Jun 7 12:59:03 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]; @@ -167,7 +170,7 @@ } /* End if */ fclose(passwd); - #if !defined(NOSHADOW) +#if !defined(NOSHADOW) if( (shadow= fopen(SHADOWFILE, "r")) == NULL) { log(L_ERR, "HASH: Can't open file %s: %s", SHADOWFILE, strerror(errno)); return -1; diff -uNr cistron-1.6.3-stable/src/conf.h cistron-1.6.3-new/src/conf.h --- cistron-1.6.3-stable/src/conf.h Thu Mar 30 17:24:59 2000 +++ cistron-1.6.3-new/src/conf.h Mon Aug 14 16:10:12 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 -uNr cistron-1.6.3-stable/src/dict.c cistron-1.6.3-new/src/dict.c --- cistron-1.6.3-stable/src/dict.c Tue Sep 28 14:30:08 1999 +++ cistron-1.6.3-new/src/dict.c Sun Jun 18 21:44:54 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 -uNr cistron-1.6.3-stable/src/files.c cistron-1.6.3-new/src/files.c --- cistron-1.6.3-stable/src/files.c Tue Apr 11 22:52:21 2000 +++ cistron-1.6.3-new/src/files.c Fri Jun 23 11:33:30 2000 @@ -186,7 +186,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; @@ -492,6 +494,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; } @@ -752,7 +765,7 @@ { VALUE_PAIR *vp; VALUE_PAIR *c = NULL; - int n; + int n = 0; /* * See if a password is present. Return right away @@ -836,7 +849,7 @@ VALUE_PAIR *reply_tmp; PAIR_LIST *pl = NULL, *last = NULL, *t; int lineno = 0; - int old_lineno; + int old_lineno = 0; int parsecode; /* @@ -1311,13 +1324,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; @@ -1328,7 +1342,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; } @@ -1339,6 +1355,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) { #if 0 /* Be quiet. */ @@ -1389,6 +1439,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))) == @@ -1396,12 +1480,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)); @@ -1554,7 +1646,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); @@ -1977,8 +2069,8 @@ char buffer[256]; char realm[32]; char hostnm[128]; - char opts1[32], opts2[32]; - char *p; + char opts[64], oldopts2[32]; + char *s; int lineno = 0; REALM *c; @@ -1997,21 +2089,27 @@ lineno++; if (buffer[0] == '#' || buffer[0] == '\n') continue; - opts1[0] = opts2[0] = 0; - if (sscanf(buffer, "%31s%127s%31s%31s", - realm, hostnm, opts1, opts2) < 2) { + opts[0] = 0; oldopts2[0] = 0; + if (sscanf(buffer, "%31s%127s%64s%32s", + realm, hostnm, opts, oldopts2) < 2) { log(L_ERR, "%s[%d]: syntax error", file, lineno); continue; } + /* concatenate opts and oldopts2, for backwards + compatibility */ + if (*oldopts2) { + strcat(opts, ","); + strcat(opts, oldopts2); + } if ((c = malloc(sizeof(REALM))) == NULL) { log(L_CONS|L_ERR, "%s[%d]: out of memory", file, lineno); return -1; } - if ((p = strchr(hostnm, ':')) != NULL) { - *p++ = 0; - c->auth_port = atoi(p); + if ((s = strchr(hostnm, ':')) != NULL) { + *s++ = 0; + c->auth_port = atoi(s); c->acct_port = c->auth_port + 1; } else { c->auth_port = auth_port; @@ -2021,11 +2119,46 @@ 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 */ - c->striprealm = strcmp(opts1, "nostrip") != 0 && - strcmp(opts2, "nostrip") != 0; - c->dohints = strcmp(opts1, "hints") == 0 || - strcmp(opts2, "hints") == 0; + /* + * Now parse the options string. It is formatted + * as a comma-separated string of flags. + * + * The fact that there will never be any spaces + * in the options string (sscanf), makes life + * a little easier here... + */ + c->dohints = 0; c->striprealm = 1; + memset(&(c->flags), 0, sizeof(c->flags)); + s = strtok(opts, ","); + while(s) { + if (strcmp(s, "nostrip") == 0) { + /* Boolean flag, means don't strip the + realm when proxying */ + c->striprealm = 0; + } + else if (strcmp(s, "hints") == 0) { + /* Boolean flag, says to process hints + file */ + c->dohints = 1; + } + else if (strcmp(s, "local_auth") == 0) { + /* Boolean flag, says to do authentication + locally, but to proxy accounting */ + c->flags.local_auth = 1; + } + else if (strcmp(s, "local_acct") == 0) { + /* Boolean flag, says to do accounting + locally, but to proxy authentication */ + c->flags.local_acct = 1; + } + else { + log(L_ERR|L_CONS, "%s[%d]: invalid option %s", + file, lineno, s); + return -1; + } + s = strtok(NULL, ","); + } + c->next = realms; realms = c; } diff -uNr cistron-1.6.3-stable/src/proxy.c cistron-1.6.3-new/src/proxy.c --- cistron-1.6.3-stable/src/proxy.c Thu Apr 6 16:27:29 2000 +++ cistron-1.6.3-new/src/proxy.c Mon Jun 19 11:31:22 2000 @@ -288,9 +288,15 @@ realmname++; /* - * The special server LOCAL ? + * 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 striprealm, act accordingly + * and return. */ - if (strcmp(realm->server, "LOCAL") == 0) { + 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 && @@ -421,6 +427,8 @@ total_length = AUTH_HDR_LEN; + /* Do tag support here just as in rad_send_reply() */ + /* * Put all the attributes into a buffer. */ @@ -468,12 +476,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); /* @@ -484,25 +521,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; } diff -uNr cistron-1.6.3-stable/src/radius.c cistron-1.6.3-new/src/radius.c --- cistron-1.6.3-stable/src/radius.c Sun Apr 2 17:58:14 2000 +++ cistron-1.6.3-new/src/radius.c Fri Jul 7 14:58:12 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 @@ -440,6 +625,20 @@ attribute, attrlen, length); } else { + /* 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 -= attr->flags.len_disp; + + if ( attrlen <= 0 ) { + DEBUG("attribute %d too short after adjustment, %d - %d <= 0", + attribute, attrlen, attr->flags.len_disp); + /* This _is_ critical, as we would get an endless loop */ + break; + } + if ((pair = (VALUE_PAIR *)malloc(sizeof(VALUE_PAIR))) == (VALUE_PAIR *)NULL) { log(L_ERR|L_CONS, "no memory"); @@ -453,13 +652,28 @@ pair->next = (VALUE_PAIR *)NULL; pair->operator = PW_OPERATOR_EQUAL; pair->strvalue[0] = '\0'; + /* Handle flags */ + pair->flags = attr->flags; switch (attr->type) { 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; @@ -472,8 +686,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 -uNr cistron-1.6.3-stable/src/radiusd.h cistron-1.6.3-new/src/radiusd.h --- cistron-1.6.3-stable/src/radiusd.h Thu Mar 2 14:24:45 2000 +++ cistron-1.6.3-new/src/radiusd.h Mon Jun 19 11:32:24 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,6 +118,13 @@ struct nas *next; } NAS; +typedef struct realm_flags { + char local_auth; + char local_acct; + char resv0; + char resv1; +} REALM_FLAGS; + typedef struct realm { char realm[64]; char server[64]; @@ -117,6 +133,7 @@ int acct_port; int striprealm; int dohints; + REALM_FLAGS flags; struct realm *next; } REALM; @@ -151,6 +168,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; @@ -187,6 +216,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 *); @@ -203,7 +233,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); @@ -230,6 +259,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 -uNr cistron-1.6.3-stable/src/radtest.c cistron-1.6.3-new/src/radtest.c --- cistron-1.6.3-stable/src/radtest.c Sun Apr 2 17:58:14 2000 +++ cistron-1.6.3-new/src/radtest.c Fri Jul 7 15:01:03 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); } 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));