Skip to content

Commit ea053b0

Browse files
committed
server config UPDATE hidden unix socket path
1 parent 94de46b commit ea053b0

File tree

11 files changed

+510
-104
lines changed

11 files changed

+510
-104
lines changed

doc/libnetconf.doc

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -304,8 +304,8 @@
304304
* - ::nc_server_set_capab_withdefaults()
305305
* - ::nc_server_set_capability()
306306
* - ::nc_server_endpt_count()
307-
* - ::nc_server_add_endpt_unix_socket_listen()
308-
* - ::nc_server_del_endpt_unix_socket()
307+
* - ::nc_server_set_unix_socket_path()
308+
* - ::nc_server_get_unix_socket_path()
309309
*
310310
* Server Configuration
311311
* ===
@@ -383,7 +383,7 @@
383383
* You may create this data yourself or by using ::nc_server_config_add_ssh_hostkey().
384384
*
385385
* It is important to decide whether the users that can connect to the SSH server should be obtained from the configuration or from the system.
386-
* If the YANG feature *local-users-supported* is enabled (the default), then the authorized users are derived from the configuration.
386+
* If the YANG feature *local-users-supported* is enabled (the default), then the authorized users are derived from the configuration.
387387
* When a client connects to the server, he must be found in the configuration and he must authenticate to **all** of his configured authentication methods.
388388
* If the feature is disabled, then the system will be used to try to authenticate the client via one of the three
389389
* methods - publickey, keyboard-interactive or password (only one of them has to succeed).
@@ -493,6 +493,28 @@
493493
* - ::nc_server_config_add_tls_ctn()
494494
* - ::nc_server_config_del_tls_ctn()
495495
*
496+
* UNIX Socket
497+
* ===========
498+
*
499+
* A UNIX socket endpoint can be established using one of two mechanisms:
500+
*
501+
* 1) **Cleartext Path**: The filesystem path is explicitly stored in the configuration.
502+
* To use this, pass a valid path string to ::nc_server_config_add_unix_socket().
503+
*
504+
* 2) **Hidden Path**: The filesystem path is managed via the API and is not visible
505+
* in the YANG configuration. To use this, pass NULL as the path argument to
506+
* ::nc_server_config_add_unix_socket(). The actual runtime path must then be set
507+
* using ::nc_server_set_unix_socket_path().
508+
*
509+
* Security Recommendation
510+
* -----------------------
511+
* The **Hidden Path** (Option 2) is strongly recommended.
512+
*
513+
* If Cleartext paths are enabled, any user with permission to modify the server
514+
* configuration can change the UNIX socket path via YANG. This allows them to
515+
* force the server to create or overwrite arbitrary files on the filesystem
516+
* with the privileges of the server process.
517+
*
496518
* FD
497519
* ==
498520
*

modules/[email protected]

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ module libnetconf2-netconf-server {
5151
description "Second revision.";
5252
}
5353

54+
// Features
55+
56+
feature unix-socket-path {
57+
description
58+
"Indicates that the server supports configuration of the UNIX socket path.";
59+
}
60+
5461
// Identities
5562

5663
/*
@@ -310,17 +317,31 @@ module libnetconf2-netconf-server {
310317
and listen for incoming NETCONF connections. Client authentication
311318
is based on the connecting process's effective user ID.";
312319

313-
leaf path {
314-
type string {
315-
length "1..107";
316-
}
320+
choice socket-path-config {
317321
mandatory true;
318322
description
319-
"Filesystem path where the UNIX socket will be created.
320-
The parent directory
321-
must exist and be writable by the NETCONF server process.
323+
"Selects how the UNIX domain socket path is determined.";
324+
case socket-path {
325+
if-feature "unix-socket-path";
326+
leaf socket-path {
327+
type string {
328+
length "1..107";
329+
}
330+
description
331+
"The explicit filesystem path where the UNIX socket will be bonded.
332+
The parent directory must exist and be writable by the server process.
322333
323-
Example: /var/run/netconf.sock";
334+
Example: /var/run/netconf.sock";
335+
}
336+
}
337+
case hidden-path {
338+
leaf hidden-path {
339+
type empty;
340+
description
341+
"Indicates that the UNIX socket path is not configured via YANG, but is instead
342+
determined by internal server API settings.";
343+
}
344+
}
324345
}
325346

326347
container socket-permissions {

src/server_config.c

Lines changed: 114 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@ nc_server_config_free(struct nc_server_config *config)
360360
struct nc_ch_client *ch_client;
361361
struct nc_ch_endpt *ch_endpt;
362362
LY_ARRAY_COUNT_TYPE i, j;
363+
const char *socket_path = NULL;
363364

364365
if (!config) {
365366
return;
@@ -375,14 +376,23 @@ nc_server_config_free(struct nc_server_config *config)
375376
LY_ARRAY_FOR(config->endpts, i) {
376377
endpt = &config->endpts[i];
377378

379+
if (endpt->ti == NC_TI_UNIX) {
380+
/* get the socket path before freeing the name */
381+
socket_path = nc_server_unix_get_socket_path(endpt);
382+
}
383+
378384
free(endpt->name);
379385

380386
/* free binds */
381387
LY_ARRAY_FOR(endpt->binds, j) {
382-
free(endpt->binds[j].address);
383388
if (endpt->binds[j].sock != -1) {
384389
close(endpt->binds[j].sock);
390+
if (socket_path) {
391+
/* remove the UNIX socket file */
392+
unlink(socket_path);
393+
}
385394
}
395+
free(endpt->binds[j].address);
386396
pthread_mutex_destroy(&endpt->bind_lock);
387397
}
388398
LY_ARRAY_FREE(endpt->binds);
@@ -2851,26 +2861,68 @@ config_tls(const struct lyd_node *node, enum nc_operation parent_op, struct nc_e
28512861
#endif /* NC_ENABLED_SSH_TLS */
28522862

28532863
static int
2854-
config_unix_path(const struct lyd_node *node, enum nc_operation parent_op, struct nc_endpt *endpt)
2864+
config_unix_socket_path(const struct lyd_node *node, enum nc_operation parent_op, struct nc_endpt *endpt)
28552865
{
28562866
enum nc_operation op;
28572867
struct nc_bind *bind;
2868+
struct nc_server_unix_opts *opts;
28582869

28592870
NC_NODE_GET_OP(node, parent_op, &op);
28602871

2872+
opts = endpt->opts.unix;
2873+
28612874
if (op == NC_OP_DELETE) {
28622875
/* the endpoint must have a single binding, so we can just free it,
28632876
* the socket will be closed in ::nc_server_config_free() */
28642877
assert(endpt->binds);
28652878
free(endpt->binds[0].address);
28662879
LY_ARRAY_FREE(endpt->binds);
2880+
2881+
/* also clear the cleartext path flag */
2882+
opts->path_type = NC_UNIX_SOCKET_PATH_UNKNOWN;
28672883
} else if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) {
28682884
/* the endpoint must not have any bindings yet, so we can just create one */
28692885
assert(!endpt->binds);
28702886
LY_ARRAY_NEW_RET(LYD_CTX(node), endpt->binds, bind, 1);
28712887
bind->address = strdup(lyd_get_value(node));
28722888
NC_CHECK_ERRMEM_RET(!bind->address, 1);
28732889
bind->sock = -1;
2890+
2891+
/* also set the cleartext path flag */
2892+
opts->path_type = NC_UNIX_SOCKET_PATH_FILE;
2893+
}
2894+
2895+
return 0;
2896+
}
2897+
2898+
static int
2899+
config_unix_hidden_path(const struct lyd_node *node, enum nc_operation parent_op, struct nc_endpt *endpt)
2900+
{
2901+
enum nc_operation op;
2902+
struct nc_bind *bind;
2903+
struct nc_server_unix_opts *opts;
2904+
2905+
NC_NODE_GET_OP(node, parent_op, &op);
2906+
2907+
opts = endpt->opts.unix;
2908+
2909+
if (op == NC_OP_DELETE) {
2910+
/* the endpoint must have a single binding, so we can just free it,
2911+
* the socket will be closed in ::nc_server_config_free() */
2912+
assert(endpt->binds);
2913+
LY_ARRAY_FREE(endpt->binds);
2914+
2915+
/* also clear the hidden path flag */
2916+
opts->path_type = NC_UNIX_SOCKET_PATH_UNKNOWN;
2917+
} else if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) {
2918+
/* the endpoint must not have any bindings yet, so we can just create one
2919+
* and since the path is hidden, there is no address to set */
2920+
assert(!endpt->binds);
2921+
LY_ARRAY_NEW_RET(LYD_CTX(node), endpt->binds, bind, 1);
2922+
bind->sock = -1;
2923+
2924+
/* also set the hidden path flag */
2925+
opts->path_type = NC_UNIX_SOCKET_PATH_HIDDEN;
28742926
}
28752927

28762928
return 0;
@@ -3117,7 +3169,7 @@ config_unix_client_auth(const struct lyd_node *node, enum nc_operation parent_op
31173169
static int
31183170
config_unix(const struct lyd_node *node, enum nc_operation parent_op, struct nc_endpt *endpt)
31193171
{
3120-
struct lyd_node *n;
3172+
struct lyd_node *n, *socket_path = NULL, *hidden_path = NULL;
31213173
enum nc_operation op;
31223174

31233175
NC_NODE_GET_OP(node, parent_op, &op);
@@ -3135,9 +3187,15 @@ config_unix(const struct lyd_node *node, enum nc_operation parent_op, struct nc_
31353187
endpt->opts.unix->gid = (gid_t)-1;
31363188
}
31373189

3138-
/* config path */
3139-
NC_CHECK_RET(nc_lyd_find_child(node, "path", 1, &n));
3140-
NC_CHECK_RET(config_unix_path(n, op, endpt));
3190+
/* config mandatory unix socket path choice => only one of them can be present */
3191+
NC_CHECK_RET(nc_lyd_find_child(node, "socket-path", 0, &socket_path));
3192+
NC_CHECK_RET(nc_lyd_find_child(node, "hidden-path", 0, &hidden_path));
3193+
if (socket_path) {
3194+
NC_CHECK_RET(config_unix_socket_path(socket_path, op, endpt));
3195+
} else {
3196+
assert(hidden_path);
3197+
NC_CHECK_RET(config_unix_hidden_path(hidden_path, op, endpt));
3198+
}
31413199

31423200
/* config socket permissions */
31433201
NC_CHECK_RET(nc_lyd_find_child(node, "socket-permissions", 1, &n));
@@ -4962,6 +5020,49 @@ nc_server_config_libnetconf2_netconf_server(const struct lyd_node *tree, int is_
49625020
return rc;
49635021
}
49645022

5023+
/**
5024+
* @brief Check if two server endpoint bindings match.
5025+
*
5026+
* They match if they use the same transport protocol, address and port.
5027+
*
5028+
* @param[in] e1 First server endpoint.
5029+
* @param[in] b1 First server endpoint binding.
5030+
* @param[in] e2 Second server endpoint.
5031+
* @param[in] b2 Second server endpoint binding.
5032+
* @return 1 if they match, 0 otherwise.
5033+
*/
5034+
static int
5035+
nc_server_config_bindings_match(const struct nc_endpt *e1, const struct nc_bind *b1,
5036+
const struct nc_endpt *e2, const struct nc_bind *b2)
5037+
{
5038+
const char *addr1, *addr2;
5039+
5040+
if (e1->ti != e2->ti) {
5041+
/* different transport protocols */
5042+
return 0;
5043+
}
5044+
5045+
if (e1->ti == NC_TI_UNIX) {
5046+
/* UNIX sockets may have hidden or cleartext addresses */
5047+
addr1 = nc_server_unix_get_socket_path(e1);
5048+
addr2 = nc_server_unix_get_socket_path(e2);
5049+
} else {
5050+
addr1 = b1->address;
5051+
addr2 = b2->address;
5052+
}
5053+
if (!addr1 || !addr2) {
5054+
/* unable to get the address */
5055+
return 0;
5056+
}
5057+
5058+
if (strcmp(addr1, addr2) || (b1->port != b2->port)) {
5059+
/* different addresses or ports */
5060+
return 0;
5061+
}
5062+
5063+
return 1;
5064+
}
5065+
49655066
/**
49665067
* @brief Atomically starts listening on new sockets and reuses existing ones.
49675068
*
@@ -4989,7 +5090,7 @@ nc_server_config_reconcile_sockets_listen(struct nc_server_config *old_cfg,
49895090
found = 0;
49905091
LY_ARRAY_FOR(old_cfg->endpts, struct nc_endpt, old_endpt) {
49915092
LY_ARRAY_FOR(old_endpt->binds, struct nc_bind, old_bind) {
4992-
if (!strcmp(new_bind->address, old_bind->address) && (new_bind->port == old_bind->port)) {
5093+
if (nc_server_config_bindings_match(new_endpt, new_bind, old_endpt, old_bind)) {
49935094
/* match found, reuse the socket */
49945095
new_bind->sock = old_bind->sock;
49955096
found = 1;
@@ -5510,6 +5611,8 @@ nc_server_config_unix_dup(const struct nc_server_unix_opts *src, struct nc_serve
55105611
*dst = calloc(1, sizeof **dst);
55115612
NC_CHECK_ERRMEM_RET(!*dst, 1);
55125613

5614+
(*dst)->path_type = src->path_type;
5615+
55135616
(*dst)->mode = src->mode;
55145617
(*dst)->uid = src->uid;
55155618
(*dst)->gid = src->gid;
@@ -5739,8 +5842,10 @@ nc_server_config_dup(const struct nc_server_config *src, struct nc_server_config
57395842
/* binds */
57405843
LN2_LY_ARRAY_CREATE_GOTO_WRAP(dst_endpt->binds, LY_ARRAY_COUNT(src_endpt->binds), rc, cleanup);
57415844
LY_ARRAY_FOR(src_endpt->binds, j) {
5742-
dst_endpt->binds[j].address = strdup(src_endpt->binds[j].address);
5743-
NC_CHECK_ERRMEM_GOTO(!dst_endpt->binds[j].address, rc = 1, cleanup);
5845+
if (src_endpt->binds[j].address) {
5846+
dst_endpt->binds[j].address = strdup(src_endpt->binds[j].address);
5847+
NC_CHECK_ERRMEM_GOTO(!dst_endpt->binds[j].address, rc = 1, cleanup);
5848+
}
57445849
dst_endpt->binds[j].port = src_endpt->binds[j].port;
57455850

57465851
/* mark the socket as uninitialized, it will be reassigned in ::nc_server_config_reconcile_sockets_listen() */

src/server_config_util.c

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -238,12 +238,31 @@ nc_server_config_add_unix_socket(const struct ly_ctx *ctx, const char *endpt_nam
238238
{
239239
int rc = 0;
240240
char *path_fmt = NULL;
241+
struct lys_module *mod;
242+
const char *path_type_str;
241243

242-
NC_CHECK_ARG_RET(NULL, ctx, path, config, 1);
244+
NC_CHECK_ARG_RET(NULL, ctx, config, 1);
243245

244-
/* create the path to the socket's path */
246+
if (!path) {
247+
/* creating a hidden UNIX socket path set by other means */
248+
path_type_str = "hidden-path";
249+
} else {
250+
/* creating a standard UNIX socket path */
251+
path_type_str = "socket-path";
252+
253+
/* check if the 'unix-socket-path' feature is enabled in the libnetconf2-netconf-server module */
254+
mod = ly_ctx_get_module_implemented(ctx, "libnetconf2-netconf-server");
255+
NC_CHECK_RET(!mod, 1);
256+
if (lys_feature_value(mod, "unix-socket-path")) {
257+
ERR(NULL, "Unable to set UNIX socket path ('unix-socket-path' feature not enabled in "
258+
"'libnetconf2-netconf-server' module).");
259+
return 1;
260+
}
261+
}
262+
263+
/* create the path to UNIX socket or just the empty leaf for hidden path */
245264
NC_CHECK_ERR_RET(asprintf(&path_fmt, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/"
246-
"libnetconf2-netconf-server:unix/path", endpt_name) == -1, ERRMEM, 1);
265+
"libnetconf2-netconf-server:unix/%s", endpt_name, path_type_str) == -1, ERRMEM, 1);
247266
NC_CHECK_GOTO(rc = nc_server_config_create(ctx, config, path, path_fmt), cleanup);
248267

249268
if (!mode && !owner && !group) {

src/session_client.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1899,7 +1899,7 @@ nc_accept_callhome(int timeout, struct ly_ctx *ctx, struct nc_session **session)
18991899
return -1;
19001900
}
19011901

1902-
ret = nc_sock_accept_binds(client_opts.ch_binds, client_opts.ch_bind_count, &client_opts.ch_bind_lock, timeout,
1902+
ret = nc_sock_accept_binds(NULL, client_opts.ch_binds, client_opts.ch_bind_count, &client_opts.ch_bind_lock, timeout,
19031903
&host, &port, &idx, &sock);
19041904
if (ret < 1) {
19051905
free(host);

0 commit comments

Comments
 (0)