1 /** Valuepair functions that are radiusd-specific and as such do not belong in
4 * @file main/valuepair.c
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24 * Copyright 2000,2006 The FreeRADIUS server project
25 * Copyright 2000 Alan DeKok <aland@ox.org>
28 #include <freeradius-devel/ident.h>
31 #include <freeradius-devel/radiusd.h>
32 #include <freeradius-devel/rad_assert.h>
34 #ifdef HAVE_PCREPOSIX_H
35 #include <pcreposix.h>
41 * For POSIX Regular expressions.
42 * (0) Means no extended regular expressions.
43 * REG_EXTENDED means use extended regular expressions.
46 #define REG_EXTENDED (0)
55 const FR_NAME_NUMBER pair_lists[] = {
56 { "request", PAIR_LIST_REQUEST },
57 { "reply", PAIR_LIST_REPLY },
58 { "config", PAIR_LIST_CONTROL },
59 { "control", PAIR_LIST_CONTROL },
61 { "proxy-request", PAIR_LIST_PROXY_REQUEST },
62 { "proxy-reply", PAIR_LIST_PROXY_REPLY },
65 { "coa", PAIR_LIST_COA },
66 { "coa-reply", PAIR_LIST_COA_REPLY },
67 { "disconnect", PAIR_LIST_DM },
68 { "disconnect-reply", PAIR_LIST_DM_REPLY },
74 unsigned int attribute;
75 unsigned int otherattr;
76 void *instance; /* module instance */
77 RAD_COMPARE_FUNC compare;
80 static struct cmp *cmp;
82 /** Compares check and vp by value.
84 * Does not call any per-attribute comparison function, but does honour
85 * check.operator. Basically does "vp.value check.op check.value".
87 * @param request Current request
88 * @param check rvalue, and operator
91 int radius_compare_vps(REQUEST *request, VALUE_PAIR *check, VALUE_PAIR *vp)
96 * Check for =* and !* and return appropriately
98 if (check->operator == T_OP_CMP_TRUE) return 0;
99 if (check->operator == T_OP_CMP_FALSE) return 1;
102 if (check->operator == T_OP_REG_EQ) {
107 regmatch_t rxmatch[REQUEST_MAX_REGEX + 1];
109 snprintf(name, sizeof(name), "%%{%s}", check->name);
110 radius_xlat(value, sizeof(value), name, request, NULL);
113 * Include substring matches.
115 compare = regcomp(®, check->vp_strvalue, REG_EXTENDED);
118 regerror(compare, ®, buffer, sizeof(buffer));
120 RDEBUG("Invalid regular expression %s: %s",
121 check->vp_strvalue, buffer);
124 compare = regexec(®, value, REQUEST_MAX_REGEX + 1,
129 * Add %{0}, %{1}, etc.
131 for (i = 0; i <= REQUEST_MAX_REGEX; i++) {
133 char buffer[sizeof(check->vp_strvalue)];
136 * Didn't match: delete old
137 * match, if it existed.
139 if ((compare != 0) ||
140 (rxmatch[i].rm_so == -1)) {
141 p = request_data_get(request, request,
142 REQUEST_DATA_REGEX | i);
156 * Copy substring into buffer.
158 memcpy(buffer, value + rxmatch[i].rm_so,
159 rxmatch[i].rm_eo - rxmatch[i].rm_so);
160 buffer[rxmatch[i].rm_eo - rxmatch[i].rm_so] = '\0';
163 * Copy substring, and add it to
166 * Note that we don't check
167 * for out of memory, which is
168 * the only error we can get...
171 request_data_add(request, request,
172 REQUEST_DATA_REGEX | i,
175 if (compare == 0) return 0;
179 if (check->operator == T_OP_REG_NE) {
184 regmatch_t rxmatch[REQUEST_MAX_REGEX + 1];
186 snprintf(name, sizeof(name), "%%{%s}", check->name);
187 radius_xlat(value, sizeof(value), name, request, NULL);
190 * Include substring matches.
192 compare = regcomp(®, (char *)check->vp_strvalue,
196 regerror(compare, ®, buffer, sizeof(buffer));
198 RDEBUG("Invalid regular expression %s: %s",
199 check->vp_strvalue, buffer);
202 compare = regexec(®, value, REQUEST_MAX_REGEX + 1,
206 if (compare != 0) return 0;
213 * Tagged attributes are equal if and only if both the
214 * tag AND value match.
216 if (check->flags.has_tag) {
217 ret = ((int) vp->flags.tag) - ((int) check->flags.tag);
218 if (ret != 0) return ret;
222 * Not a regular expression, compare the types.
224 switch(check->type) {
227 * Ascend binary attributes can be treated
228 * as opaque objects, I guess...
230 case PW_TYPE_ABINARY:
233 if (vp->length != check->length) {
234 ret = 1; /* NOT equal */
237 ret = memcmp(vp->vp_strvalue, check->vp_strvalue,
242 ret = strcmp((char *)vp->vp_strvalue,
243 (char *)check->vp_strvalue);
248 case PW_TYPE_INTEGER:
249 ret = vp->vp_integer - check->vp_integer;
252 case PW_TYPE_INTEGER64:
254 * Don't want integer overflow!
256 if (vp->vp_integer64 < check->vp_integer64) {
258 } else if (vp->vp_integer64 > check->vp_integer64) {
266 if (vp->vp_signed < check->vp_signed) {
268 } else if (vp->vp_signed > check->vp_signed) {
276 ret = vp->vp_date - check->vp_date;
280 ret = ntohl(vp->vp_ipaddr) - ntohl(check->vp_ipaddr);
283 case PW_TYPE_IPV6ADDR:
284 ret = memcmp(&vp->vp_ipv6addr, &check->vp_ipv6addr,
285 sizeof(vp->vp_ipv6addr));
288 case PW_TYPE_IPV6PREFIX:
289 ret = memcmp(&vp->vp_ipv6prefix, &check->vp_ipv6prefix,
290 sizeof(vp->vp_ipv6prefix));
294 ret = memcmp(&vp->vp_ifid, &check->vp_ifid,
295 sizeof(vp->vp_ifid));
306 /** Compare check and vp. May call the attribute compare function.
308 * Unlike radius_compare_vps() this function will call any attribute-specific
309 * comparison function.
311 * @param req Current request
312 * @param request value pairs in the reqiest
315 * @param reply_pairs value pairs in the reply
318 int radius_callback_compare(REQUEST *req, VALUE_PAIR *request,
319 VALUE_PAIR *check, VALUE_PAIR *check_pairs,
320 VALUE_PAIR **reply_pairs)
325 * Check for =* and !* and return appropriately
327 if (check->operator == T_OP_CMP_TRUE) return 0;
328 if (check->operator == T_OP_CMP_FALSE) return 1;
331 * See if there is a special compare function.
333 * FIXME: use new RB-Tree code.
335 for (c = cmp; c; c = c->next) {
336 if ((c->attribute == check->attribute) &&
337 (check->vendor == 0)) {
338 return (c->compare)(c->instance, req, request, check,
339 check_pairs, reply_pairs);
343 if (!request) return -1; /* doesn't exist, don't compare it */
345 return radius_compare_vps(req, check, request);
349 /** Find a comparison function for two attributes.
353 int radius_find_compare(unsigned int attribute)
357 for (c = cmp; c; c = c->next) {
358 if (c->attribute == attribute) {
367 /** See what attribute we want to compare with.
371 static int otherattr(unsigned int attribute)
375 for (c = cmp; c; c = c->next) {
376 if (c->attribute == attribute) {
384 /** Register a function as compare function.
387 * @param other_attr we want to compare with. Normally this is the
389 * You can set this to:
390 * - -1 The same as attribute.
391 * - 0 Always call compare function, not tied to request attribute.
392 * - >0 Attribute to compare with. For example, PW_GROUP in a check
393 * item needs to be compared with PW_USER_NAME in the incoming request.
394 * @param func comparison function
395 * @param instance argument to comparison function
398 int paircompare_register(unsigned int attribute, int other_attr,
399 RAD_COMPARE_FUNC func, void *instance)
403 paircompare_unregister(attribute, func);
405 c = rad_malloc(sizeof(struct cmp));
408 c->attribute = attribute;
409 c->otherattr = other_attr;
410 c->instance = instance;
417 /** Unregister comparison function for an attribute
419 * @param attribute attribute to unregister for.
420 * @param func comparison function to remove.
423 void paircompare_unregister(unsigned int attribute, RAD_COMPARE_FUNC func)
425 struct cmp *c, *last;
428 for (c = cmp; c; c = c->next) {
429 if (c->attribute == attribute && c->compare == func) {
435 if (c == NULL) return;
438 last->next = c->next;
446 /** Compare two pair lists except for the password information.
448 * For every element in "check" at least one matching copy must be present
451 * @param req Current request
452 * @param request request valuepairs
453 * @param check check/control valuepairs
454 * @param[in,out] reply reply value pairs
456 * @return 0 on match.
458 int paircompare(REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check,
461 VALUE_PAIR *check_item;
462 VALUE_PAIR *auth_item;
468 for (check_item = check;
470 check_item = check_item->next) {
472 * If the user is setting a configuration value,
473 * then don't bother comparing it to any attributes
474 * sent to us by the user. It ALWAYS matches.
476 if ((check_item->operator == T_OP_SET) ||
477 (check_item->operator == T_OP_ADD)) {
481 switch (check_item->attribute) {
483 * Attributes we skip during comparison.
484 * These are "server" check items.
486 case PW_CRYPT_PASSWORD:
490 case PW_SESSION_TYPE:
491 case PW_STRIP_USER_NAME:
496 * IF the password attribute exists, THEN
497 * we can do comparisons against it. If not,
498 * then the request did NOT contain a
499 * User-Password attribute, so we CANNOT do
500 * comparisons against it.
502 * This hack makes CHAP-Password work..
504 case PW_USER_PASSWORD:
505 if (check_item->operator == T_OP_CMP_EQ) {
506 DEBUG("WARNING: Found User-Password == \"...\".");
507 DEBUG("WARNING: Are you sure you don't mean Cleartext-Password?");
508 DEBUG("WARNING: See \"man rlm_pap\" for more information.");
510 if (pairfind(request, PW_USER_PASSWORD, 0) == NULL) {
517 * See if this item is present in the request.
519 other = otherattr(check_item->attribute);
524 while (auth_item != NULL) {
525 if ((auth_item->attribute ==
526 (unsigned int) other) ||
530 auth_item = auth_item->next;
535 * Not found, it's not a match.
537 if (auth_item == NULL) {
539 * Didn't find it. If we were *trying*
540 * to not find it, then we succeeded.
542 if (check_item->operator == T_OP_CMP_FALSE) {
550 * Else we found it, but we were trying to not
551 * find it, so we failed.
553 if (check_item->operator == T_OP_CMP_FALSE) {
559 * We've got to xlat the string before doing
562 if (check_item->flags.do_xlat) {
564 char buffer[sizeof(check_item->vp_strvalue)];
566 check_item->flags.do_xlat = 0;
567 rcode = radius_xlat(buffer, sizeof(buffer),
568 check_item->vp_strvalue,
572 * Parse the string into a new value.
574 pairparsevalue(check_item, buffer);
578 * OK it is present now compare them.
580 compare = radius_callback_compare(req, auth_item, check_item,
583 switch (check_item->operator) {
586 radlog(L_INFO, "Invalid operator for item %s: "
587 "reverting to '=='", check_item->name);
592 if (compare != 0) result = -1;
596 if (compare == 0) result = -1;
600 if (compare >= 0) result = -1;
604 if (compare <= 0) result = -1;
608 if (compare > 0) result = -1;
612 if (compare < 0) result = -1;
618 if (compare != 0) result = -1;
621 } /* switch over the operator of the check item */
624 * This attribute didn't match, but maybe there's
625 * another of the same attribute, which DOES match.
627 if ((result != 0) && (other >= 0)) {
628 auth_item = auth_item->next;
633 } /* for every entry in the check item list */
638 /** Move pairs, replacing/over-writing them, and doing xlat.
640 * Move attributes from one list to the other if not already present.
642 void pairxlatmove(REQUEST *req, VALUE_PAIR **to, VALUE_PAIR **from)
644 VALUE_PAIR **tailto, *i, *j, *next;
645 VALUE_PAIR *tailfrom = NULL;
649 * Point "tailto" to the end of the "to" list.
652 for (i = *to; i; i = i->next) {
657 * Loop over the "from" list.
659 for (i = *from; i; i = next) {
663 * Don't move 'fallthrough' over.
665 if (i->attribute == PW_FALL_THROUGH) {
671 * We've got to xlat the string before moving
674 if (i->flags.do_xlat) {
676 char buffer[sizeof(i->vp_strvalue)];
678 i->flags.do_xlat = 0;
679 rcode = radius_xlat(buffer, sizeof(buffer),
684 * Parse the string into a new value.
686 pairparsevalue(i, buffer);
689 found = pairfind(*to, i->attribute, i->vendor);
690 switch (i->operator) {
693 * If a similar attribute is found,
696 case T_OP_SUB: /* -= */
698 if (!i->vp_strvalue[0] ||
699 (strcmp((char *)found->vp_strvalue,
700 (char *)i->vp_strvalue) == 0)) {
701 pairdelete(to, found->attribute,
705 * 'tailto' may have been
709 for (j = *to; j; j = j->next) {
719 * Add it, if it's not already there.
721 case T_OP_EQ: /* = */
724 continue; /* with the loop */
729 * If a similar attribute is found,
730 * replace it with the new one. Otherwise,
731 * add the new one to the list.
733 case T_OP_SET: /* := */
738 memcpy(found, i, sizeof(*found));
746 * FIXME: Add support for <=, >=, <, >
748 * which will mean (for integers)
749 * 'make the attribute the smaller, etc'
753 * Add the new element to the list, even
754 * if similar ones already exist.
757 case T_OP_ADD: /* += */
762 tailfrom->next = next;
768 * If ALL of the 'to' attributes have been deleted,
769 * then ensure that the 'tail' is updated to point
780 } /* loop over the 'from' list */
783 /** Create a pair and add it to a particular list of VPs
785 * Note that this function ALWAYS returns. If we're OOM, then it causes the
788 VALUE_PAIR *radius_paircreate(UNUSED REQUEST *request, VALUE_PAIR **vps,
789 unsigned int attribute, unsigned int vendor, int type)
793 vp = paircreate(attribute, vendor, type);
795 radlog(L_ERR, "No memory!");
796 rad_assert("No memory" == NULL);
800 if (vps) pairadd(vps, vp);
805 /** Create a pair, and add it to a particular list of VPs
807 * Note that this function ALWAYS returns. If we're OOM, then it causes the
810 * @param[in] request current request.
811 * @param[in] vps to modify.
812 * @param[in] attribute name.
813 * @param[in] value attribute value.
814 * @param[in] operator fr_tokens value.
815 * @return a new VALUE_PAIR.
817 VALUE_PAIR *radius_pairmake(UNUSED REQUEST *request, VALUE_PAIR **vps,
818 const char *attribute, const char *value,
823 vp = pairmake(attribute, value, operator);
824 if (!vp) return NULL;
826 if (vps) pairadd(vps, vp);
831 /** Print a single valuepair to stderr or error log.
833 * @param[in] vp list to print.
835 void debug_pair(VALUE_PAIR *vp)
837 if (!vp || !debug_flag || !fr_log_fp) return;
839 vp_print(fr_log_fp, vp);
842 /** Print a list of valuepairs to stderr or error log.
844 * @param[in] vp to print.
846 void debug_pair_list(VALUE_PAIR *vp)
848 if (!vp || !debug_flag || !fr_log_fp) return;
851 vp_print(fr_log_fp, vp);
857 /** Resolve attribute pair_lists_t value to an attribute list.
859 * The value returned is a pointer to the pointer of the HEAD of the list
860 * in the REQUEST. If the head of the list changes, the pointer will still
863 * @param[in] request containing the target lists.
864 * @param[in] list pair_list_t value to resolve to VALUE_PAIR list.
865 * Will be NULL if list name couldn't be resolved.
867 VALUE_PAIR **radius_list(REQUEST *request, pair_lists_t list)
870 case PAIR_LIST_UNKNOWN:
874 case PAIR_LIST_REQUEST:
875 return &request->packet->vps;
877 case PAIR_LIST_REPLY:
878 return &request->reply->vps;
880 case PAIR_LIST_CONTROL:
881 return &request->config_items;
884 case PAIR_LIST_PROXY_REQUEST:
885 return &request->proxy->vps;
887 case PAIR_LIST_PROXY_REPLY:
888 return &request->proxy_reply->vps;
893 (request->coa->proxy->code == PW_COA_REQUEST)) {
894 return &request->coa->proxy->vps;
898 case PAIR_LIST_COA_REPLY:
899 if (request->coa && /* match reply with request */
900 (request->coa->proxy->code == PW_COA_REQUEST) &&
901 request->coa->proxy_reply) {
902 return &request->coa->proxy_reply->vps;
908 (request->coa->proxy->code == PW_DISCONNECT_REQUEST)) {
909 return &request->coa->proxy->vps;
913 case PAIR_LIST_DM_REPLY:
914 if (request->coa && /* match reply with request */
915 (request->coa->proxy->code == PW_DISCONNECT_REQUEST) &&
916 request->coa->proxy_reply) {
917 return &request->coa->proxy->vps;
926 /** Resolve attribute name to a list.
928 * Check the name string for qualifiers that specify a list and return
929 * an pair_lists_t value for that list. This value may be passed to
930 * radius_list, along with the current request, to get a pointer to the
931 * actual list in the request.
933 * If qualifiers were consumed, write a new pointer into name to the
934 * char after the last qualifier to be consumed.
936 * radius_list_name should be called before passing a name string that
937 * may contain qualifiers to dict_attrbyname.
939 * @see dict_attrbyname
941 * @param[in,out] name of attribute.
942 * @param[in] unknown the list to return if no qualifiers were found.
943 * @return PAIR_LIST_UNKOWN if qualifiers couldn't be resolved to a list.
945 pair_lists_t radius_list_name(const char **name, pair_lists_t unknown)
947 const char *p = *name;
950 /* This should never be a NULL pointer or zero length string */
951 rad_assert(name && *name);
960 return fr_substr2int(pair_lists, p, PAIR_LIST_UNKNOWN, (q - p));
963 /** Resolve attribute name to a request.
965 * Check the name string for qualifiers that reference a parent request and
966 * write the pointer to this request to 'request'.
968 * If qualifiers were consumed, write a new pointer into name to the
969 * char after the last qualifier to be consumed.
971 * radius_ref_request should be called before radius_list_name.
973 * @see radius_list_name
974 * @param[in,out] name of attribute.
975 * @param[in,out] request current request.
976 * @return FALSE if qualifiers found but not in a tunnel, else TRUE.
978 int radius_ref_request(const char **name, REQUEST **request)
980 rad_assert(name && *name);
981 rad_assert(request && *request);
983 if (strncmp(*name, "outer.", 6) == 0) {
984 if (!(*request)->parent) {
987 *request = (*request)->parent;
994 /** Return a VP from the specified request.
996 * @param request current request.
997 * @param name attribute name including qualifiers.
998 * @param vp_p where to write the pointer to the resolved VP.
999 * Will be NULL if the attribute couldn't be resolved.
1000 * @return False if either the attribute or qualifier were invalid, else true
1002 int radius_get_vp(REQUEST *request, const char *name, VALUE_PAIR **vp_p)
1007 const DICT_ATTR *da;
1011 if (!radius_ref_request(&name, &request)) {
1012 RDEBUG("WARNING: Attribute name refers to outer request"
1013 " but not in a tunnel.");
1014 return TRUE; /* Discuss, we don't actually know if
1015 the attrname was valid... */
1018 list = radius_list_name(&name, PAIR_LIST_REQUEST);
1019 if (list == PAIR_LIST_UNKNOWN) {
1020 RDEBUG("ERROR: Invalid list qualifier");
1024 da = dict_attrbyname(name);
1026 RDEBUG("ERROR: Attribute \"%s\" unknown", name);
1030 vps = radius_list(request, list);
1034 * May not may not be found, but it *is* a known name.
1036 *vp_p = pairfind(*vps, da->attr, da->vendor);