Download raw body.
radiusd_standard(8): add attributes to responses
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;
radiusd_standard(8): add attributes to responses