From: Denis Fondras Subject: radiusd_standard(8): add attributes to responses To: tech@openbsd.org Cc: yasuoka@openbsd.org Date: Thu, 18 Sep 2025 20:29:44 +0200 My provider requires additional attributes when authentifying users. The add-response-attribute configuration directive permits that. My additional config in radiud.conf : ``` module add-attributes "/usr/libexec/radiusd/radiusd_standard" { set add-response-attribute Tunnel-Type 3 set add-response-attribute Tunnel-Medium-Type 1 set add-response-attribute Tunnel-Server-Endpoint 192.0.2.1 } authenticate *@myrealm by file decorate-by strip-realm add-attributes ``` Denis Index: radiusd_standard.8 =================================================================== RCS file: /cvs/src/usr.sbin/radiusd/radiusd_standard.8,v diff -u -p -r1.4 radiusd_standard.8 --- radiusd_standard.8 4 Aug 2024 03:56:57 -0000 1.4 +++ radiusd_standard.8 18 Sep 2025 18:17:08 -0000 @@ -63,6 +63,19 @@ To specify a vendor attribute, specify the Vendor-Id in a decimal number for .Ar vendor . +.Pp +.It Cm add-response-attribute Oo Ar vendor Oc Ar type Oc Ar value +Add the specified attributes to response messages of Access-Request. +Specify +.Ar type +of the attribute in a decimal number or supported description +(Tunnel-Type, Tunnel-Medium-Type and Tunnel-Server-Endpoint) +followed by the +.Ar value . +To specify a vendor attribute, +specify the Vendor-Id +in a decimal number for +.Ar vendor . .El .Sh FILES .Bl -tag -width "/usr/libexec/radiusd/radiusd_standard" -compact Index: radiusd_standard.c =================================================================== RCS file: /cvs/src/usr.sbin/radiusd/radiusd_standard.c,v diff -u -p -r1.6 radiusd_standard.c --- radiusd_standard.c 2 Jul 2024 00:33:51 -0000 1.6 +++ radiusd_standard.c 18 Sep 2025 18:17:08 -0000 @@ -37,10 +37,15 @@ TAILQ_HEAD(attrs,attr); struct attr { - uint8_t type; - uint32_t vendor; - uint32_t vtype; - TAILQ_ENTRY(attr) next; + uint8_t type; + uint32_t vendor; + uint32_t vtype; + union { + uint32_t numeric; + char string[255]; + } value; + uint32_t len; + TAILQ_ENTRY(attr) next; }; struct module_standard { @@ -49,6 +54,7 @@ struct module_standard { bool strip_nt_domain; struct attrs remove_reqattrs; struct attrs remove_resattrs; + struct attrs add_resattrs; }; struct radius_const_str { @@ -80,6 +86,50 @@ static struct radius_const_str acct_status_type_consts[], acct_authentic_consts[], terminate_cause_consts[], tunnel_medium_type_consts[]; +static int parse_strip_atmark(char * const *, int, + struct module_standard *); +static int parse_strip_ntdom(char * const *, int, + struct module_standard *); +static int parse_rm_reqattr(char * const *, int, + struct module_standard *); +static int parse_rm_resattr(char * const *, int, + struct module_standard *); +static int parse_add_attr(char * const *, int, struct module_standard *); +static int parse_args(char * const *, struct attr *); + +struct command { + char *name; + int minargs; + int maxargs; + int (*func)(char * const *, int, struct module_standard *); +} commands[] = { + { "strip-atmark-realm", 1, 1, parse_strip_atmark }, + { "strip-nt-domain", 1, 1, parse_strip_ntdom }, + { "remove-request-attribute", 1, 2, parse_rm_reqattr }, + { "remove-response-attribute", 1, 2, parse_rm_resattr }, + { "add-response-attribute", 2, 3, parse_add_attr }, + { "_", 0, 0, NULL }, + { NULL, 0, 0, NULL }, +}; + +enum attribute_types { + T_NULL = 0, + T_NUM, + T_STRING +}; + +struct radius_attribute_type { + char *name; + u_int8_t val; + enum attribute_types type; + u_int8_t len; +} types[] = { + {"Tunnel-Type", 64, T_NUM, 4}, + {"Tunnel-Medium-Type", 65, T_NUM, 4}, + {"Tunnel-Server-Endpoint", 67, T_STRING, 255}, + {NULL, 0, T_NULL, 0} +}; + int main(int argc, char *argv[]) { @@ -95,6 +145,7 @@ main(int argc, char *argv[]) memset(&module_standard, 0, sizeof(module_standard)); TAILQ_INIT(&module_standard.remove_reqattrs); TAILQ_INIT(&module_standard.remove_resattrs); + TAILQ_INIT(&module_standard.add_resattrs); if ((module_standard.base = module_create( STDIN_FILENO, &module_standard, &handlers)) == NULL) @@ -120,96 +171,237 @@ main(int argc, char *argv[]) TAILQ_REMOVE(&module_standard.remove_resattrs, attr, next); freezero(attr, sizeof(struct attr)); } + while ((attr = TAILQ_FIRST(&module_standard.add_resattrs)) != NULL) { + TAILQ_REMOVE(&module_standard.add_resattrs, attr, next); + freezero(attr, sizeof(struct attr)); + } exit(EXIT_SUCCESS); } +static int +parse_args(char * const *argv, struct attr *attr) +{ + struct radius_attribute_type *att; + const char *errstr; + + attr->type = strtonum(argv[0], 0, 255, &errstr); + if (errstr) { + /* Then assume it is a string */ + if (strlcpy(attr->value.string, argv[0], 255) >= 255) + return (-1); + + for (att = types; att->name; att++) + if (strcmp(attr->value.string, att->name) == 0) + break; + if (att->name) + attr->type = att->val; + } + + for (att = types; att->name; att++) + if (att->val == attr->type) + break; + + if (att->name == NULL) + return (-1); + + if (att->type == T_NUM) { + attr->value.numeric = strtonum(argv[1], 0, UINT32_MAX, &errstr); + if (errstr) + return (-1); + } else if (att->type == T_STRING) { + if (strlcpy(attr->value.string, argv[1], 255) >= 255) + return (-1); + } + + return (0); +} + +static enum attribute_types +get_attribute_type(u_int8_t value) +{ + struct radius_attribute_type *att; + + for (att = types; att->name; att++) + if (att->val == value) + return att->type; + + return T_NULL; +} + +static uint32_t +get_attribute_len(u_int8_t value) +{ + struct radius_attribute_type *att; + + for (att = types; att->name; att++) + if (att->val == value) + return (att->len); + + return 0; +} + +static int +parse_strip_atmark(char * const *argv, int argc, struct module_standard *module) +{ + if (strcmp(argv[0], "true") == 0) + module->strip_atmark_realm = true; + else if (strcmp(argv[0], "false") == 0) + module->strip_atmark_realm = false; + else + return -1; + + return 0; +} + +static int +parse_strip_ntdom(char * const *argv, int argc, struct module_standard *module) +{ + if (strcmp(argv[0], "true") == 0) + module->strip_nt_domain = true; + else if (strcmp(argv[0], "false") == 0) + module->strip_nt_domain = false; + else + return -1; + + return 0; +} + +static int +parse_rm_reqattr(char * const *argv, int argc, struct module_standard *module) +{ + struct attr *attr; + const char *errstr; + + if ((attr = calloc(1, sizeof(struct attr))) == NULL) { + return -1; + } + if (argc == 1) { + attr->type = strtonum(argv[0], 0, 255, &errstr); + if (errstr == NULL && + attr->type != RADIUS_TYPE_VENDOR_SPECIFIC) { + TAILQ_INSERT_TAIL(&module->remove_reqattrs, + attr, next); + attr = NULL; + } + } else { + attr->vtype = RADIUS_TYPE_VENDOR_SPECIFIC; + attr->vendor = strtonum(argv[0], 0, UINT32_MAX, &errstr); + if (errstr == NULL) + attr->type = strtonum(argv[1], 0, 255, &errstr); + if (errstr == NULL) { + TAILQ_INSERT_TAIL(&module->remove_reqattrs, attr, + next); + attr = NULL; + } + } + freezero(attr, sizeof(struct attr)); + if (errstr == NULL) + return 0; + else + return -1; +} + +static int +parse_rm_resattr(char * const *argv, int argc, struct module_standard *module) +{ + struct attr *attr; + const char *errstr; + + if ((attr = calloc(1, sizeof(struct attr))) == NULL) { + return -1; + } + if (argc == 1) { + attr->type = strtonum(argv[0], 0, 255, &errstr); + if (errstr == NULL && + attr->type != RADIUS_TYPE_VENDOR_SPECIFIC) { + TAILQ_INSERT_TAIL(&module->remove_resattrs, + attr, next); + attr = NULL; + } + } else { + attr->vtype = RADIUS_TYPE_VENDOR_SPECIFIC; + attr->vendor = strtonum(argv[0], 0, UINT32_MAX, &errstr); + if (errstr == NULL) + attr->type = strtonum(argv[1], 0, 255, &errstr); + if (errstr == NULL) { + TAILQ_INSERT_TAIL(&module->remove_resattrs, attr, + next); + attr = NULL; + } + } + freezero(attr, sizeof(struct attr)); + if (errstr == NULL) + return 0; + else + return -1; +} + +static int +parse_add_attr(char * const *argv, int argc, struct module_standard *module) +{ + struct attr *attr; + const char *errstr; + + if ((attr = calloc(1, sizeof(struct attr))) == NULL) { + return -1; + } + + if (argc == 3) { + attr->vtype = RADIUS_TYPE_VENDOR_SPECIFIC; + attr->vendor = strtonum(argv[0], 0, UINT32_MAX, &errstr); + if (errstr != NULL) { + freezero(attr, sizeof(struct attr)); + return -1; + } + argv++; + } + + if (parse_args(argv, attr) != 0) { + freezero(attr, sizeof(struct attr)); + return -1; + } + + TAILQ_INSERT_TAIL(&module->add_resattrs, attr, + next); + + return 0; +} + static void module_standard_config_set(void *ctx, const char *name, int argc, char * const * argv) { struct module_standard *module = ctx; - struct attr *attr; - const char *errmsg = "none"; - const char *errstr; + struct command *cmd; - if (strcmp(name, "strip-atmark-realm") == 0) { - SYNTAX_ASSERT(argc == 1, - "`strip-atmark-realm' must have only one argment"); - if (strcmp(argv[0], "true") == 0) - module->strip_atmark_realm = true; - else if (strcmp(argv[0], "false") == 0) - module->strip_atmark_realm = false; - else - SYNTAX_ASSERT(0, - "`strip-atmark-realm' must `true' or `false'"); - } else if (strcmp(name, "strip-nt-domain") == 0) { - SYNTAX_ASSERT(argc == 1, - "`strip-nt-domain' must have only one argment"); - if (strcmp(argv[0], "true") == 0) - module->strip_nt_domain = true; - else if (strcmp(argv[0], "false") == 0) - module->strip_nt_domain = false; - else - SYNTAX_ASSERT(0, - "`strip-nt-domain' must `true' or `false'"); - } else if (strcmp(name, "remove-request-attribute") == 0 || - strcmp(name, "remove-response-attribute") == 0) { - struct attrs *attrs; - - if (strcmp(name, "remove-request-attribute") == 0) { - SYNTAX_ASSERT(argc == 1 || argc == 2, - "`remove-request-attribute' must have one or two " - "argment"); - attrs = &module->remove_reqattrs; - } else { - SYNTAX_ASSERT(argc == 1 || argc == 2, - "`remove-response-attribute' must have one or two " - "argment"); - attrs = &module->remove_resattrs; - } - if ((attr = calloc(1, sizeof(struct attr))) == NULL) { - module_send_message(module->base, IMSG_NG, - "Out of memory: %s", strerror(errno)); - } - if (argc == 1) { - attr->type = strtonum(argv[0], 0, 255, &errstr); - if (errstr == NULL && - attr->type != RADIUS_TYPE_VENDOR_SPECIFIC) { - TAILQ_INSERT_TAIL(attrs, attr, next); - attr = NULL; - } - } else { - attr->type = RADIUS_TYPE_VENDOR_SPECIFIC; - attr->vendor = strtonum(argv[0], 0, UINT32_MAX, - &errstr); - if (errstr == NULL) - attr->vtype = strtonum(argv[1], 0, 255, - &errstr); - if (errstr == NULL) { - TAILQ_INSERT_TAIL(attrs, attr, next); - attr = NULL; - } - } - freezero(attr, sizeof(struct attr)); - if (strcmp(name, "remove-request-attribute") == 0) - SYNTAX_ASSERT(attr == NULL, - "wrong number for `remove-request-attribute`"); - else - SYNTAX_ASSERT(attr == NULL, - "wrong number for `remove-response-attribute`"); - } else if (strncmp(name, "_", 1) == 0) - /* nothing */; /* ignore all internal messages */ - else { + for (cmd = commands; cmd->name; cmd++) + if (strncmp(cmd->name, name, strlen(cmd->name)) == 0) + break; + + if (cmd->name == NULL) { module_send_message(module->base, IMSG_NG, "Unknown config parameter name `%s'", name); return; } - module_send_message(module->base, IMSG_OK, NULL); - return; - syntax_error: - module_send_message(module->base, IMSG_NG, "%s", errmsg); + if (argc < cmd->minargs || argc > cmd->maxargs) { + module_send_message(module->base, IMSG_NG, + "Wrong number of parameters for `%s'", cmd->name); + return; + } + + if (cmd->func == NULL) + goto finish; + + if (cmd->func(argv, argc, module) == -1) { + module_send_message(module->base, IMSG_NG, + "Invalid attribute for `%s'", cmd->name); + return; + } + +finish: + module_send_message(module->base, IMSG_OK, NULL); } /* request message decoration */ @@ -268,7 +460,7 @@ module_standard_reqdeco(void *ctx, u_int radius_del_attr_all(radpkt, attr->type); else radius_del_vs_attr_all(radpkt, attr->vendor, - attr->vtype); + attr->type); } if (radpkt == NULL) { pkt = NULL; @@ -294,7 +486,65 @@ module_standard_resdeco(void *ctx, u_int struct module_standard *module = ctx; RADIUS_PACKET *radres = NULL; struct attr *attr; + uint32_t numeric; + int putres = 0; + TAILQ_FOREACH(attr, &module->add_resattrs, next) { + if (radres == NULL && + (radres = radius_convert_packet(res, reslen)) == NULL) { + syslog(LOG_ERR, + "%s: radius_convert_packet() failed: %m", __func__); + module_stop(module->base); + return; + } + if (attr->vtype == RADIUS_TYPE_VENDOR_SPECIFIC) { + switch (get_attribute_type(attr->type)) { + case T_NUM: + numeric = htonl(attr->value.numeric); + putres = radius_put_vs_raw_attr_cat(radres, + attr->vendor, attr->type, + &numeric, + get_attribute_len(attr->type)); + break; + case T_STRING: + putres = radius_put_vs_raw_attr_cat(radres, + attr->vendor, attr->type, + attr->value.string, + strlen(attr->value.string)); + break; + default: + syslog(LOG_ERR, + "%s: unknown attribute type", __func__); + module_stop(module->base); + return; + } + } else { + switch (get_attribute_type(attr->type)) { + case T_NUM: + numeric = htonl(attr->value.numeric); + putres = radius_put_raw_attr_cat(radres, + attr->type, &numeric, + get_attribute_len(attr->type)); + break; + case T_STRING: + putres = radius_put_raw_attr_cat(radres, + attr->type, attr->value.string, + strlen(attr->value.string)); + break; + default: + syslog(LOG_ERR, + "%s: unknown attribute type", __func__); + module_stop(module->base); + return; + } + } + if (putres == -1) { + syslog(LOG_ERR, + "%s: radius_put_*_raw_attr_cat failed: %m", __func__); + module_stop(module->base); + return; + } + } TAILQ_FOREACH(attr, &module->remove_resattrs, next) { if (radres == NULL && (radres = radius_convert_packet(res, reslen)) == NULL) { @@ -303,11 +553,11 @@ module_standard_resdeco(void *ctx, u_int module_stop(module->base); return; } - if (attr->type != RADIUS_TYPE_VENDOR_SPECIFIC) - radius_del_attr_all(radres, attr->type); - else + if (attr->vtype == RADIUS_TYPE_VENDOR_SPECIFIC) radius_del_vs_attr_all(radres, attr->vendor, - attr->vtype); + attr->type); + else + radius_del_attr_all(radres, attr->type); } if (radres == NULL) { res = NULL;