/** Convert CONFIG_PAIR to VALUE_PAIR_MAP. * * Treats the left operand as a * @verbatim<request>.<list>.<attribute>@endverbatim reference and the right * operand as a module specific value. * * The left operand will be pre-parsed into request ref, dst list, and da, * the right operand will be left as a string. * * Return must be freed with radius_mapfree. * * @param[in] cp to convert to map. * @param[in] request_def The default request to insert unqualified * attributes into. * @param[in] list_def The default list to insert unqualified attributes into. * @return VALUE_PAIR_MAP if successful or NULL on error. */ VALUE_PAIR_MAP *radius_cp2map(CONF_PAIR *cp, request_refs_t request_def, pair_lists_t list_def) { VALUE_PAIR_MAP *map; const char *attr; const char *value; map = rad_malloc(sizeof(VALUE_PAIR_MAP)); memset(map, 0, sizeof(VALUE_PAIR_MAP)); attr = cf_pair_attr(cp); map->dst = radius_attr2tmpl(attr, request_def, list_def); if (!map->dst){ goto error; } value = cf_pair_value(cp); if (!value) { radlog(L_ERR, "Missing attribute name"); goto error; } map->src = radius_str2tmpl(value); if (!map->src) { goto error; } map->op_token = cf_pair_operator(cp); /* * Infer whether we need to expand the mapping values * The old style attribute map allowed the user to specify * whether the LDAP value should be expanded. * We can't really support that easily, but equivalent * functionality should be available with %{eval:} */ switch (cf_pair_value_type(cp)) { case T_BARE_WORD: case T_SINGLE_QUOTED_STRING: map->src->do_xlat = FALSE; break; case T_BACK_QUOTED_STRING: case T_DOUBLE_QUOTED_STRING: map->src->do_xlat = TRUE; break; default: rad_assert(0); goto error; } return map; error: radius_mapfree(&map); return NULL; }
/** Modify user's object in LDAP * * Process a modifcation map to update a user object in the LDAP directory. * * @param inst rlm_ldap instance. * @param request Current request. * @param section that holds the map to process. * @return one of the RLM_MODULE_* values. */ static rlm_rcode_t user_modify(ldap_instance_t *inst, REQUEST *request, ldap_acct_section_t *section) { rlm_rcode_t rcode = RLM_MODULE_OK; ldap_rcode_t status; ldap_handle_t *conn = NULL; LDAPMod *mod_p[LDAP_MAX_ATTRMAP + 1], mod_s[LDAP_MAX_ATTRMAP]; LDAPMod **modify = mod_p; char *passed[LDAP_MAX_ATTRMAP * 2]; int i, total = 0, last_pass = 0; char *expanded[LDAP_MAX_ATTRMAP]; int last_exp = 0; char const *attr; char const *value; char const *dn; /* * Build our set of modifications using the update sections in * the config. */ CONF_ITEM *ci; CONF_PAIR *cp; CONF_SECTION *cs; FR_TOKEN op; char path[MAX_STRING_LEN]; char *p = path; rad_assert(section); /* * Locate the update section were going to be using */ if (section->reference[0] != '.') { *p++ = '.'; } if (radius_xlat(p, (sizeof(path) - (p - path)) - 1, request, section->reference, NULL, NULL) < 0) { goto error; } ci = cf_reference_item(NULL, section->cs, path); if (!ci) { goto error; } if (!cf_item_is_section(ci)){ REDEBUG("Reference must resolve to a section"); goto error; } cs = cf_section_sub_find(cf_itemtosection(ci), "update"); if (!cs) { REDEBUG("Section must contain 'update' subsection"); goto error; } /* * Iterate over all the pairs, building our mods array */ for (ci = cf_item_find_next(cs, NULL); ci != NULL; ci = cf_item_find_next(cs, ci)) { bool do_xlat = false; if (total == LDAP_MAX_ATTRMAP) { REDEBUG("Modify map size exceeded"); goto error; } if (!cf_item_is_pair(ci)) { REDEBUG("Entry is not in \"ldap-attribute = value\" format"); goto error; } /* * Retrieve all the information we need about the pair */ cp = cf_itemtopair(ci); value = cf_pair_value(cp); attr = cf_pair_attr(cp); op = cf_pair_operator(cp); if (!value || (*value == '\0')) { RDEBUG("Empty value string, skipping attribute \"%s\"", attr); continue; } switch (cf_pair_value_type(cp)) { case T_BARE_WORD: case T_SINGLE_QUOTED_STRING: break; case T_BACK_QUOTED_STRING: case T_DOUBLE_QUOTED_STRING: do_xlat = true; break; default: rad_assert(0); goto error; } if (op == T_OP_CMP_FALSE) { passed[last_pass] = NULL; } else if (do_xlat) { char *exp = NULL; if (radius_axlat(&exp, request, value, NULL, NULL) <= 0) { RDEBUG("Skipping attribute \"%s\"", attr); talloc_free(exp); continue; } expanded[last_exp++] = exp; passed[last_pass] = exp; /* * Static strings */ } else { memcpy(&(passed[last_pass]), &value, sizeof(passed[last_pass])); } passed[last_pass + 1] = NULL; mod_s[total].mod_values = &(passed[last_pass]); last_pass += 2; switch (op) { /* * T_OP_EQ is *NOT* supported, it is impossible to * support because of the lack of transactions in LDAP */ case T_OP_ADD: mod_s[total].mod_op = LDAP_MOD_ADD; break; case T_OP_SET: mod_s[total].mod_op = LDAP_MOD_REPLACE; break; case T_OP_SUB: case T_OP_CMP_FALSE: mod_s[total].mod_op = LDAP_MOD_DELETE; break; #ifdef LDAP_MOD_INCREMENT case T_OP_INCRM: mod_s[total].mod_op = LDAP_MOD_INCREMENT; break; #endif default: REDEBUG("Operator '%s' is not supported for LDAP modify operations", fr_int2str(fr_tokens, op, "<INVALID>")); goto error; } /* * Now we know the value is ok, copy the pointers into * the ldapmod struct. */ memcpy(&(mod_s[total].mod_type), &attr, sizeof(mod_s[total].mod_type)); mod_p[total] = &(mod_s[total]); total++; } if (total == 0) { rcode = RLM_MODULE_NOOP; goto release; } mod_p[total] = NULL; conn = mod_conn_get(inst, request); if (!conn) return RLM_MODULE_FAIL; dn = rlm_ldap_find_user(inst, request, &conn, NULL, false, NULL, &rcode); if (!dn || (rcode != RLM_MODULE_OK)) { goto error; } status = rlm_ldap_modify(inst, request, &conn, dn, modify); switch (status) { case LDAP_PROC_SUCCESS: break; case LDAP_PROC_REJECT: case LDAP_PROC_BAD_DN: rcode = RLM_MODULE_INVALID; break; default: rcode = RLM_MODULE_FAIL; break; }; release: error: /* * Free up any buffers we allocated for xlat expansion */ for (i = 0; i < last_exp; i++) { talloc_free(expanded[i]); } mod_conn_release(inst, conn); return rcode; }
/** Convert CONFIG_PAIR (which may contain refs) to value_pair_map_t. * * Treats the left operand as an attribute reference * @verbatim<request>.<list>.<attribute>@endverbatim * * Treatment of left operand depends on quotation, barewords are treated as * attribute references, double quoted values are treated as expandable strings, * single quoted values are treated as literal strings. * * Return must be freed with talloc_free * * @param[in] ctx for talloc * @param[in] cp to convert to map. * @param[in] dst_request_def The default request to insert unqualified * attributes into. * @param[in] dst_list_def The default list to insert unqualified attributes * into. * @param[in] src_request_def The default request to resolve attribute * references in. * @param[in] src_list_def The default list to resolve unqualified attributes * in. * @return value_pair_map_t if successful or NULL on error. */ value_pair_map_t *radius_cp2map(TALLOC_CTX *ctx, CONF_PAIR *cp, request_refs_t dst_request_def, pair_lists_t dst_list_def, request_refs_t src_request_def, pair_lists_t src_list_def) { value_pair_map_t *map; char const *attr; char const *value; FR_TOKEN type; CONF_ITEM *ci = cf_pairtoitem(cp); if (!cp) return NULL; map = talloc_zero(ctx, value_pair_map_t); attr = cf_pair_attr(cp); value = cf_pair_value(cp); if (!value) { cf_log_err(ci, "Missing attribute value"); goto error; } map->dst = radius_attr2tmpl(map, attr, dst_request_def, dst_list_def); if (!map->dst) { cf_log_err(ci, "Syntax error in attribute definition"); goto error; } /* * Bare words always mean attribute references. */ type = cf_pair_value_type(cp); if (type == T_BARE_WORD) { if (*value == '&') { map->src = radius_attr2tmpl(map, value + 1, src_request_def, src_list_def); } else { if (!isdigit((int) *value) && ((strchr(value, ':') != NULL) || (dict_attrbyname(value) != NULL))) { map->src = radius_attr2tmpl(map, value, src_request_def, src_list_def); } if (map->src) { WDEBUG("%s[%d]: Please add '&' for attribute reference '%s = &%s'", cf_pair_filename(cp), cf_pair_lineno(cp), attr, value); } else { map->src = radius_str2tmpl(map, value, type); } } } else { map->src = radius_str2tmpl(map, value, type); } if (!map->src) { goto error; } map->op = cf_pair_operator(cp); map->ci = ci; /* * Lots of sanity checks for insane people... */ /* * We don't support implicit type conversion, * except for "octets" */ if (map->dst->da && map->src->da && (map->src->da->type != map->dst->da->type) && (map->src->da->type != PW_TYPE_OCTETS) && (map->dst->da->type != PW_TYPE_OCTETS)) { cf_log_err(ci, "Attribute type mismatch"); goto error; } /* * What exactly where you expecting to happen here? */ if ((map->dst->type == VPT_TYPE_ATTR) && (map->src->type == VPT_TYPE_LIST)) { cf_log_err(ci, "Can't copy list into an attribute"); goto error; } /* * Can't copy an xlat expansion or literal into a list, * we don't know what type of attribute we'd need * to create */ if ((map->dst->type == VPT_TYPE_LIST) && ((map->src->type == VPT_TYPE_XLAT) || (map->src->type == VPT_TYPE_LITERAL))) { cf_log_err(ci, "Can't copy value into list (we don't know which attribute to create)"); goto error; } /* * Depending on the attribute type, some operators are * disallowed. */ if (map->dst->type == VPT_TYPE_ATTR) { if ((map->op != T_OP_EQ) && (map->op != T_OP_CMP_EQ) && (map->op != T_OP_ADD) && (map->op != T_OP_SUB) && (map->op != T_OP_LE) && (map->op != T_OP_GE) && (map->op != T_OP_CMP_FALSE) && (map->op != T_OP_SET)) { cf_log_err(ci, "Invalid operator for attribute"); goto error; } } switch (map->src->type) { /* * Only += and -= operators are supported for list copy. */ case VPT_TYPE_LIST: switch (map->op) { case T_OP_SUB: case T_OP_ADD: break; default: cf_log_err(ci, "Operator \"%s\" not allowed " "for list copy", fr_int2str(fr_tokens, map->op, "<INVALID>")); goto error; } break; default: break; } return map; error: talloc_free(map); return NULL; }
/** Convert CONFIG_PAIR (which may contain refs) to value_pair_map_t. * * Treats the left operand as an attribute reference * @verbatim<request>.<list>.<attribute>@endverbatim * * Treatment of left operand depends on quotation, barewords are treated as * attribute references, double quoted values are treated as expandable strings, * single quoted values are treated as literal strings. * * Return must be freed with radius_mapfree. * * @param[in] cp to convert to map. * @param[in] dst_request_def The default request to insert unqualified * attributes into. * @param[in] dst_list_def The default list to insert unqualified attributes * into. * @param[in] src_request_def The default request to resolve attribute * references in. * @param[in] src_list_def The default list to resolve unqualified attributes * in. * @return value_pair_map_t if successful or NULL on error. */ value_pair_map_t *radius_cp2map(CONF_PAIR *cp, request_refs_t dst_request_def, pair_lists_t dst_list_def, request_refs_t src_request_def, pair_lists_t src_list_def) { value_pair_map_t *map; const char *attr; const char *value; FR_TOKEN type; CONF_ITEM *ci = cf_pairtoitem(cp); if (!cp) return NULL; map = rad_calloc(sizeof(value_pair_map_t)); attr = cf_pair_attr(cp); value = cf_pair_value(cp); if (!value) { cf_log_err(ci, "Missing attribute value"); goto error; } map->dst = radius_attr2tmpl(attr, dst_request_def, dst_list_def); if (!map->dst){ goto error; } /* * Bare words always mean attribute references. */ type = cf_pair_value_type(cp); map->src = type == T_BARE_WORD ? radius_attr2tmpl(value, src_request_def, src_list_def) : radius_str2tmpl(value, type); if (!map->src) { goto error; } map->op = cf_pair_operator(cp); map->ci = ci; /* * Lots of sanity checks for insane people... */ /* * We don't support implicit type conversion */ if (map->dst->da && map->src->da && (map->src->da->type != map->dst->da->type)) { cf_log_err(ci, "Attribute type mismatch"); goto error; } /* * What exactly where you expecting to happen here? */ if ((map->dst->type == VPT_TYPE_ATTR) && (map->src->type == VPT_TYPE_LIST)) { cf_log_err(ci, "Can't copy list into an attribute"); goto error; } switch (map->src->type) { /* * Only += and -= operators are supported for list copy. */ case VPT_TYPE_LIST: switch (map->op) { case T_OP_SUB: case T_OP_ADD: break; default: cf_log_err(ci, "Operator \"%s\" not allowed " "for list copy", fr_int2str(fr_tokens, map->op, "¿unknown?")); goto error; } break; /* * @todo add support for exec expansion. */ case VPT_TYPE_EXEC: cf_log_err(ci, "Exec values are not allowed"); break; default: break; } return map; error: radius_mapfree(&map); return NULL; }
/** Convert CONFIG_PAIR (which may contain refs) to value_pair_map_t. * * Treats the left operand as an attribute reference * @verbatim<request>.<list>.<attribute>@endverbatim * * Treatment of left operand depends on quotation, barewords are treated as * attribute references, double quoted values are treated as expandable strings, * single quoted values are treated as literal strings. * * Return must be freed with talloc_free * * @param[in] ctx for talloc * @param[in] cp to convert to map. * @param[in] dst_request_def The default request to insert unqualified * attributes into. * @param[in] dst_list_def The default list to insert unqualified attributes * into. * @param[in] src_request_def The default request to resolve attribute * references in. * @param[in] src_list_def The default list to resolve unqualified attributes * in. * @return value_pair_map_t if successful or NULL on error. */ value_pair_map_t *radius_cp2map(TALLOC_CTX *ctx, CONF_PAIR *cp, request_refs_t dst_request_def, pair_lists_t dst_list_def, request_refs_t src_request_def, pair_lists_t src_list_def) { value_pair_map_t *map; char const *attr; char const *value; FR_TOKEN type; CONF_ITEM *ci = cf_pairtoitem(cp); if (!cp) return NULL; map = talloc_zero(ctx, value_pair_map_t); map->op = cf_pair_operator(cp); map->ci = ci; attr = cf_pair_attr(cp); value = cf_pair_value(cp); if (!value) { cf_log_err(ci, "Missing attribute value"); goto error; } /* * LHS must always be an attribute reference. */ map->dst = radius_attr2tmpl(map, attr, dst_request_def, dst_list_def); if (!map->dst) { cf_log_err(ci, "Syntax error in attribute definition"); goto error; } /* * RHS might be an attribute reference. */ type = cf_pair_value_type(cp); map->src = radius_str2tmpl(map, value, type, src_request_def, src_list_def); if (!map->src) { goto error; } /* * Anal-retentive checks. */ if (debug_flag > 2) { if ((map->dst->type == VPT_TYPE_ATTR) && (*attr != '&')) { WARN("%s[%d]: Please change attribute reference to '&%s %s ...'", cf_pair_filename(cp), cf_pair_lineno(cp), attr, fr_int2str(fr_tokens, map->op, "<INVALID>")); } if ((map->src->type == VPT_TYPE_ATTR) && (*value != '&')) { WARN("%s[%d]: Please change attribute reference to '... %s &%s'", cf_pair_filename(cp), cf_pair_lineno(cp), fr_int2str(fr_tokens, map->op, "<INVALID>"), value); } } /* * Lots of sanity checks for insane people... */ /* * We don't support implicit type conversion, * except for "octets" */ if (map->dst->vpt_da && map->src->vpt_da && (map->src->vpt_da->type != map->dst->vpt_da->type) && (map->src->vpt_da->type != PW_TYPE_OCTETS) && (map->dst->vpt_da->type != PW_TYPE_OCTETS)) { cf_log_err(ci, "Attribute type mismatch"); goto error; } /* * What exactly where you expecting to happen here? */ if ((map->dst->type == VPT_TYPE_ATTR) && (map->src->type == VPT_TYPE_LIST)) { cf_log_err(ci, "Can't copy list into an attribute"); goto error; } /* * Depending on the attribute type, some operators are * disallowed. */ if (map->dst->type == VPT_TYPE_ATTR) { if ((map->op != T_OP_EQ) && (map->op != T_OP_CMP_EQ) && (map->op != T_OP_ADD) && (map->op != T_OP_SUB) && (map->op != T_OP_LE) && (map->op != T_OP_GE) && (map->op != T_OP_CMP_FALSE) && (map->op != T_OP_SET)) { cf_log_err(ci, "Invalid operator for attribute"); goto error; } /* * This will be an error in future versions of * the server. */ if ((map->op == T_OP_CMP_FALSE) && ((map->src->type != VPT_TYPE_LITERAL) || (strcmp(map->src->name, "ANY") != 0))) { WARN("%s[%d] Attribute deletion MUST use '!* ANY'", cf_pair_filename(cp), cf_pair_lineno(cp)); } } if (map->dst->type == VPT_TYPE_LIST) { /* * Only += and :=, and !* operators are supported * for lists. */ switch (map->op) { case T_OP_CMP_FALSE: if ((map->src->type != VPT_TYPE_LITERAL) || (strcmp(map->src->name, "ANY") != 0)) { cf_log_err(ci, "List deletion MUST use '!* ANY'"); goto error; } break; case T_OP_ADD: if ((map->src->type != VPT_TYPE_LIST) && (map->src->type != VPT_TYPE_EXEC)) { cf_log_err(ci, "Invalid source for list '+='"); goto error; } break; case T_OP_SET: if (map->src->type == VPT_TYPE_EXEC) { WARN("%s[%d] Please change ':=' to '=' for list assignment", cf_pair_filename(cp), cf_pair_lineno(cp)); break; } if (map->src->type != VPT_TYPE_LIST) { cf_log_err(ci, "Invalid source for ':=' operator"); goto error; } break; case T_OP_EQ: if (map->src->type != VPT_TYPE_EXEC) { cf_log_err(ci, "Invalid source for '=' operator"); goto error; } break; default: cf_log_err(ci, "Operator \"%s\" not allowed for list assignment", fr_int2str(fr_tokens, map->op, "<INVALID>")); goto error; } } return map; error: talloc_free(map); return NULL; }
/** Convert an 'update' config section into an attribute map. * * Uses 'name2' of section to set default request and lists. * Copied from map_afrom_cs, except that list assignments can have the RHS * be a bare word. * * @param[in] cs the update section * @param[out] out Where to store the head of the map. * @param[in] dst_list_def The default destination list, usually dictated by * the section the module is being called in. * @param[in] src_list_def The default source list, usually dictated by the * section the module is being called in. * @param[in] max number of mappings to process. * @return -1 on error, else 0. */ static int ldap_map_afrom_cs(value_pair_map_t **out, CONF_SECTION *cs, pair_lists_t dst_list_def, pair_lists_t src_list_def, unsigned int max) { char const *cs_list, *p; request_refs_t request_def = REQUEST_CURRENT; CONF_ITEM *ci; unsigned int total = 0; value_pair_map_t **tail, *map; TALLOC_CTX *ctx; *out = NULL; tail = out; if (!cs) return 0; /* * The first map has cs as the parent. * The rest have the previous map as the parent. */ ctx = cs; ci = cf_sectiontoitem(cs); cs_list = p = cf_section_name2(cs); if (cs_list) { request_def = radius_request_name(&p, REQUEST_CURRENT); if (request_def == REQUEST_UNKNOWN) { cf_log_err(ci, "Default request specified " "in mapping section is invalid"); return -1; } dst_list_def = fr_str2int(pair_lists, p, PAIR_LIST_UNKNOWN); if (dst_list_def == PAIR_LIST_UNKNOWN) { cf_log_err(ci, "Default list \"%s\" specified " "in mapping section is invalid", p); return -1; } } for (ci = cf_item_find_next(cs, NULL); ci != NULL; ci = cf_item_find_next(cs, ci)) { char const *attr; FR_TOKEN type; CONF_PAIR *cp; cp = cf_itemtopair(ci); type = cf_pair_value_type(cp); if (total++ == max) { cf_log_err(ci, "Map size exceeded"); goto error; } if (!cf_item_is_pair(ci)) { cf_log_err(ci, "Entry is not in \"attribute = value\" format"); goto error; } cp = cf_itemtopair(ci); /* * Look for "list: OP BARE_WORD". If it exists, * we can make the RHS a bare word. Otherwise, * just call map_afrom_cp() * * Otherwise, the map functions check the RHS of * list assignments, and complain that the RHS * isn't another list. */ attr = cf_pair_attr(cp); p = strrchr(attr, ':'); if (!p || (p[1] != '\0') || (type == T_DOUBLE_QUOTED_STRING)) { if (map_afrom_cp(ctx, &map, cp, request_def, dst_list_def, REQUEST_CURRENT, src_list_def) < 0) { goto error; } } else { ssize_t slen; char const *value; map = talloc_zero(ctx, value_pair_map_t); map->op = cf_pair_operator(cp); map->ci = cf_pairtoitem(cp); slen = tmpl_afrom_attr_str(ctx, &map->lhs, attr, request_def, dst_list_def); if (slen <= 0) { char *spaces, *text; fr_canonicalize_error(ctx, &spaces, &text, slen, attr); cf_log_err(ci, "Failed parsing list reference"); cf_log_err(ci, "%s", text); cf_log_err(ci, "%s^ %s", spaces, fr_strerror()); talloc_free(spaces); talloc_free(text); goto error; } if (map->lhs->type != TMPL_TYPE_LIST) { cf_log_err(map->ci, "Invalid list name"); goto error; } if (map->op != T_OP_ADD) { cf_log_err(map->ci, "Only '+=' operator is permitted for valuepair to list mapping"); goto error; } value = cf_pair_value(cp); if (!value) { cf_log_err(map->ci, "No value specified for list assignment"); goto error; } /* * the RHS type is a bare word or single * quoted string. We don't want it being * interpreted as a list or attribute * reference, so we force the RHS to be a * literal. */ slen = tmpl_afrom_str(ctx, &map->rhs, value, T_SINGLE_QUOTED_STRING, request_def, dst_list_def); if (slen <= 0) { char *spaces, *text; fr_canonicalize_error(ctx, &spaces, &text, slen, value); cf_log_err(ci, "Failed parsing string"); cf_log_err(ci, "%s", text); cf_log_err(ci, "%s^ %s", spaces, fr_strerror()); talloc_free(spaces); talloc_free(text); goto error; } /* * And unlike map_afrom_cp(), we do NOT * try to parse the RHS as a list * reference. It's a literal, and we * leave it as a literal. */ rad_assert(map->rhs->type == TMPL_TYPE_LITERAL); } ctx = *tail = map; tail = &(map->next); } return 0; error: TALLOC_FREE(*out); return -1; }