diff --git a/READMEs/README.lwsws.md b/READMEs/README.lwsws.md index 21c0fac19..d2006b353 100644 --- a/READMEs/README.lwsws.md +++ b/READMEs/README.lwsws.md @@ -227,6 +227,8 @@ See also "apply-listen-accept" below. - "`unix-socket`": "1" causes the unix socket specified in the interface option to be used instead of an INET socket + - "`unix-socket-perms`": "user:group" allows you to control the unix permissons on the listening unix socket. It's always get to `0600` mode, but you can control the user and group for the socket fd at creation time. This allows you to use unix user and groups to control who may open the other end of the unix socket on the local system. + - "`sts`": "1" causes lwsws to send a Strict Transport Security header with responses that informs the client he should never accept to connect to this address using http. This is needed to get the A+ security rating from SSL Labs for your server. - "`access-log`": "filepath" sets where apache-compatible access logs will be written diff --git a/include/libwebsockets/lws-context-vhost.h b/include/libwebsockets/lws-context-vhost.h index af9cf6bbb..c6e727cd6 100644 --- a/include/libwebsockets/lws-context-vhost.h +++ b/include/libwebsockets/lws-context-vhost.h @@ -649,6 +649,10 @@ struct lws_context_creation_info { * permissions. Like .uid but takes a string username. */ const char *groupname; /**< CONTEXT: string groupname for post-init * permissions. Like .gid but takes a string groupname. */ + const char *unix_socket_perms; /**< VHOST: if your vhost is listening + * on a unix socket, you can give a "username:groupname" string here + * to control the owner:group it's created with. It's always created + * with 0660 mode. */ /* Add new things just above here ---^ * This is part of the ABI, don't needlessly break compatibility diff --git a/lib/core-net/network.c b/lib/core-net/network.c index fc0ef71e1..1ae77fbb5 100644 --- a/lib/core-net/network.c +++ b/lib/core-net/network.c @@ -326,11 +326,38 @@ lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port, } #if defined(LWS_WITH_UNIX_SOCK) - if (LWS_UNIX_SOCK_ENABLED(vhost) && vhost->context->uid) - if (chown(serv_unix.sun_path, vhost->context->uid, - vhost->context->gid)) - lwsl_notice("%s: chown for unix skt %s failed\n", - __func__, serv_unix.sun_path); + if (LWS_UNIX_SOCK_ENABLED(vhost)) { + uid_t uid = vhost->context->uid; + gid_t gid = vhost->context->gid; + + if (vhost->unix_socket_perms) { + if (lws_plat_user_colon_group_to_ids( + vhost->unix_socket_perms, &uid, &gid)) { + lwsl_err("%s: Failed to translate %s\n", + __func__, vhost->unix_socket_perms); + return LWS_ITOSA_NOT_EXIST; + } + } + if (uid && gid) { + if (chown(serv_unix.sun_path, uid, gid)) { + lwsl_err("%s: failed to set %s perms %u:%u\n", + __func__, serv_unix.sun_path, + (unsigned int)uid, (unsigned int)gid); + + return LWS_ITOSA_NOT_EXIST; + } + lwsl_notice("%s: vh %s unix skt %s perms %u:%u\n", + __func__, vhost->name, serv_unix.sun_path, + (unsigned int)uid, (unsigned int)gid); + + if (chmod(serv_unix.sun_path, 0660)) { + lwsl_err("%s: failed to set %s to 0600 mode\n", + __func__, serv_unix.sun_path); + + return LWS_ITOSA_NOT_EXIST; + } + } + } #endif #ifndef LWS_PLAT_OPTEE diff --git a/lib/core-net/private.h b/lib/core-net/private.h index feade9f36..598c87355 100644 --- a/lib/core-net/private.h +++ b/lib/core-net/private.h @@ -394,6 +394,7 @@ struct lws_vhost { const char *iface; const char *listen_accept_role; const char *listen_accept_protocol; + const char *unix_socket_perms; void (*finalize)(struct lws_vhost *vh, void *arg); void *finalize_arg; diff --git a/lib/core-net/vhost.c b/lib/core-net/vhost.c index b7242e9f3..a3f962b1b 100644 --- a/lib/core-net/vhost.c +++ b/lib/core-net/vhost.c @@ -485,6 +485,7 @@ lws_create_vhost(struct lws_context *context, vh->finalize_arg = info->finalize_arg; vh->listen_accept_role = info->listen_accept_role; vh->listen_accept_protocol = info->listen_accept_protocol; + vh->unix_socket_perms = info->unix_socket_perms; LWS_FOR_EVERY_AVAILABLE_ROLE_START(ar) if (ar->init_vhost) diff --git a/lib/core/private.h b/lib/core/private.h index 526cf8e20..e7d9e4471 100644 --- a/lib/core/private.h +++ b/lib/core/private.h @@ -593,6 +593,11 @@ lws_plat_init(struct lws_context *context, LWS_EXTERN int lws_plat_drop_app_privileges(struct lws_context *context); +#if defined(LWS_WITH_UNIX_SOCK) +int +lws_plat_user_colon_group_to_ids(const char *u_colon_g, uid_t *puid, gid_t *pgid); +#endif + LWS_EXTERN int lws_check_byte_utf8(unsigned char state, unsigned char c); LWS_EXTERN int LWS_WARN_UNUSED_RESULT diff --git a/lib/plat/unix/unix-caps.c b/lib/plat/unix/unix-caps.c index 7666f89c5..2ae3a6302 100644 --- a/lib/plat/unix/unix-caps.c +++ b/lib/plat/unix/unix-caps.c @@ -43,6 +43,45 @@ _lws_plat_apply_caps(int mode, const cap_value_t *cv, int count) } #endif +int +lws_plat_user_colon_group_to_ids(const char *u_colon_g, uid_t *puid, gid_t *pgid) +{ + char *colon = strchr(u_colon_g, ':'), u[33]; + struct passwd *p; + struct group *g; + int ulen; + + if (!colon) + return 1; + + ulen = lws_ptr_diff(colon, u_colon_g); + if (ulen < 2 || ulen > (int)sizeof(u) - 1) + return 1; + + memcpy(u, u_colon_g, ulen); + u[ulen] = '\0'; + + colon++; + + g = getgrnam(colon); + if (!g) { + lwsl_err("%s: unknown group '%s'\n", __func__, colon); + + return 1; + } + *pgid = g->gr_gid; + + p = getpwnam(u); + if (!p) { + lwsl_err("%s: unknown group '%s'\n", __func__, u); + + return 1; + } + *puid = p->pw_uid; + + return 0; +} + int lws_plat_drop_app_privileges(struct lws_context *context) { @@ -105,7 +144,7 @@ lws_plat_drop_app_privileges(struct lws_context *context) return 1; } - lwsl_notice("%s: effective group %s\n", __func__, + lwsl_notice("%s: effective group '%s'\n", __func__, g->gr_name); } else lwsl_info("%s: not changing group\n", __func__); diff --git a/lib/roles/http/server/lejp-conf.c b/lib/roles/http/server/lejp-conf.c index 66d9270bb..ef07ead16 100644 --- a/lib/roles/http/server/lejp-conf.c +++ b/lib/roles/http/server/lejp-conf.c @@ -67,6 +67,7 @@ static const char * const paths_vhosts[] = { "vhosts[].port", "vhosts[].interface", "vhosts[].unix-socket", + "vhosts[].unix-socket-perms", "vhosts[].sts", "vhosts[].host-ssl-key", "vhosts[].host-ssl-cert", @@ -133,6 +134,7 @@ enum lejp_vhost_paths { LEJPVP_PORT, LEJPVP_INTERFACE, LEJPVP_UNIXSKT, + LEJPVP_UNIXSKT_PERMS, LEJPVP_STS, LEJPVP_HOST_SSL_KEY, LEJPVP_HOST_SSL_CERT, @@ -593,6 +595,9 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) else a->info->options &= ~(LWS_SERVER_OPTION_UNIX_SOCK); return 0; + case LEJPVP_UNIXSKT_PERMS: + a->info->unix_socket_perms = a->p; + break; case LEJPVP_STS: if (arg_to_bool(ctx->buf)) a->info->options |= LWS_SERVER_OPTION_STS;