From d2a2ba7d4a2118ec3131bc2dfac5e32181026192 Mon Sep 17 00:00:00 2001 From: Martin Mares Date: Sun, 6 Aug 2017 21:53:52 +0200 Subject: [PATCH] Apache: An initial attempt at authn module --- Makefile | 4 + apache/Makefile | 17 +++ apache/mod_authn_subauth.c | 217 +++++++++++++++++++++++++++++++++++++ configure | 31 ++++-- default.cfg | 4 +- pam/pam_subauth.c | 7 +- 6 files changed, 268 insertions(+), 12 deletions(-) create mode 100644 apache/Makefile create mode 100644 apache/mod_authn_subauth.c diff --git a/Makefile b/Makefile index 830d7ec..71af79b 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,10 @@ include $(s)/server/Makefile include $(s)/client/Makefile include $(s)/pam/Makefile +ifdef CONFIG_APACHE_MOD +include $(s)/apache/Makefile +endif + # And finally the default rules of the build system include $(BUILDSYS)/Makebottom diff --git a/apache/Makefile b/apache/Makefile new file mode 100644 index 0000000..4383fe5 --- /dev/null +++ b/apache/Makefile @@ -0,0 +1,17 @@ +DIRS+=apache +APACHE2_RUNDIR=lib/apache2/modules +EXTRA_RUNDIRS+=$(APACHE2_RUNDIR) + +PROGS+=$(o)/apache/mod_authn_subauth.so + +$(o)/apache/mod_authn_subauth.so: $(o)/apache/mod_authn_subauth.oo +$(o)/apache/mod_authn_subauth.so: SO_RUNDIR=$(APACHE2_RUNDIR) +$(o)/apache/mod_authn_subauth.oo: CFLAGS+=$(APACHE2_CFLAGS) -Wno-redundant-decls + +.PHONY: install-apache +install-apache: + install -d -m 755 $(DESTDIR)$(INSTALL_APACHE2_MOD_DIR) $(DESTDIR)$(INSTALL_APACHE2_CONFIG_DIR)/mods-available + install -m 755 run/$(APACHE2_RUNDIR)/mod_authn_subauth.so $(DESTDIR)$(INSTALL_APACHE2_MOD_DIR)/ +# install -m 644 $(s)/apache2/etc/mod_authn_subauth.load $(DESTDIR)$(INSTALL_APACHE2_CONFIG_DIR)/mods-available/ + +INSTALL_TARGETS+=install-apache diff --git a/apache/mod_authn_subauth.c b/apache/mod_authn_subauth.c new file mode 100644 index 0000000..21108e2 --- /dev/null +++ b/apache/mod_authn_subauth.c @@ -0,0 +1,217 @@ +/* + * Apache Module for the Sub-authentication Daemon + * + * (c) 2017 Martin Mares + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "autoconf.h" + +struct dir_config { + const char *socket; + const char *zone; +}; + +AP_MODULE_DECLARE_DATA extern module authn_subauth_module; + +struct context { + request_rec *r; + struct dir_config *dir; + struct json_context *json; + struct json_node *reply; +}; + +static struct json_node *run_command(struct context *ctx, struct json_node *request) +{ + ctx->reply = NULL; + + int sk = socket(PF_UNIX, SOCK_SEQPACKET, 0); + if (sk < 0) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, "SubAuth cannot create socket"); + return NULL; + } + + const char *path = ctx->dir->socket ? : INSTALL_RUN_DIR "/subauthd.socket"; + struct sockaddr_un sun; + sun.sun_family = AF_UNIX; + if (strlen(path) >= sizeof(sun.sun_path)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, "SubAuth socket path too long"); + goto fail1; + } + strcpy(sun.sun_path, path); + + if (connect(sk, (struct sockaddr *) &sun, sizeof(sun)) < 0) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, "SubAuth cannot connect to server"); + goto fail1; + } + + struct fastbuf *rq_fb = fbgrow_create(4096); + json_write(ctx->json, rq_fb, request); + byte *rq_buf; + uint rq_len = fbgrow_get_buf(rq_fb, &rq_buf); + if (send(sk, rq_buf, rq_len, 0) < 0) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, "SubAuth error sending to server"); + goto fail2; + } + + uint rp_bufsize = 16384; + byte *rp_buf = xmalloc(rp_bufsize); + int rp_len = recv(sk, rp_buf, rp_bufsize, 0); + if (rp_len < 0) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, "SubAuth error receiving from server"); + goto fail3; + } + + struct fastbuf rp_fb; + fbbuf_init_read(&rp_fb, rp_buf, rp_len, 0); + + struct json_node *reply = NULL; + TRANS_TRY { + reply = json_parse(ctx->json, &rp_fb); + } TRANS_CATCH(x) { + goto fail3; + } TRANS_END; + + if (reply->type != JSON_OBJECT) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, "SubAuth malformed server reply: Top-level node is not an object"); + goto fail3; + } + + ctx->reply = reply; + +fail3: + free(rp_buf); +fail2: + bclose(rq_fb); +fail1: + close(sk); + return ctx->reply; +} + +static authn_status check_password(request_rec *r, const char *user, const char *password) +{ + struct dir_config *dir = ap_get_module_config(r->per_dir_config, &authn_subauth_module); + + if (!dir->zone) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "SubAuthZone not specified in the configuration"); + return AUTH_GENERAL_ERROR; + } + + authn_status status = AUTH_GENERAL_ERROR; + struct context ctx0 = { .r = r, .dir = dir }; + struct context *ctx = &ctx0; + + ctx->json = json_new(); + struct json_node *rq = json_new_object(ctx->json); + json_object_set(rq, "cmd", json_new_string(ctx->json, "login")); + json_object_set(rq, "zone", json_new_string(ctx->json, dir->zone)); + json_object_set(rq, "login", json_new_string(ctx->json, user)); + json_object_set(rq, "passwd", json_new_string(ctx->json, password)); + + struct json_node *rp = run_command(ctx, rq); + if (!rp) + goto done; + struct json_node *error = json_object_get(rp, "error"); + if (!error || error->type != JSON_STRING) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "SubAuth malformed server reply: No error status found"); + goto done; + } + if (!strcmp(error->string, "")) + status = AUTH_GRANTED; + else + status = AUTH_DENIED; + +done: + json_delete(ctx->json); + return status; +} + +static const authn_provider authn_subauth_provider = { + check_password, + NULL, // get_realm_hash +}; + +static void +register_hooks(apr_pool_t *p) +{ + ap_register_auth_provider(p, + AUTHN_PROVIDER_GROUP, + "subauth", + AUTHN_PROVIDER_VERSION, + &authn_subauth_provider, + AP_AUTH_INTERNAL_PER_CONF); +} + +/*** Configuration ***/ + +static void * +dir_create_config(apr_pool_t *p, char *dd UNUSED) +{ + struct dir_config *dir = apr_pcalloc(p, sizeof(*dir)); + return dir; +} + +static void * +dir_merge_config(apr_pool_t *p, void *base, void *over) +{ + struct dir_config *dir = apr_pcalloc(p, sizeof(*dir)); + struct dir_config *b = base; + struct dir_config *o = over; + dir->socket = o->socket ? : b->socket; + dir->zone = o->zone ? : b->zone; + return dir; +} + +static const char * +config_socket(cmd_parms *cmd UNUSED, void *d, const char *arg) +{ + struct dir_config *dir = d; + dir->socket = arg; + return NULL; +} + +static const char * +config_zone(cmd_parms *cmd UNUSED, void *d, const char *arg) +{ + struct dir_config *dir = d; + dir->zone = arg; + return NULL; +} + +static const command_rec cmds[] = { + AP_INIT_TAKE1("SubAuthSocket", config_socket, NULL, OR_AUTHCFG, "socket of subauth daemon"), + AP_INIT_TAKE1("SubAuthZone", config_zone, NULL, OR_AUTHCFG, "name of subauth zone"), + { NULL }, +}; + +/*** Module ***/ + +AP_DECLARE_MODULE(authn_subauth) = { + STANDARD20_MODULE_STUFF, + dir_create_config, + dir_merge_config, + NULL, // srv_create_config, + NULL, // srv_merge_config, + cmds, + register_hooks, +}; diff --git a/configure b/configure index 15aa37e..37920e2 100755 --- a/configure +++ b/configure @@ -31,16 +31,31 @@ use UCW::Configure; Init($srcdir, 'default.cfg'); Log "### Configuring subauthd ###\n\n"; Include Get("CONFIG"); -# What should be detected? -require UCW::Configure::Build; -require UCW::Configure::Paths; -require UCW::Configure::C; -require UCW::Configure::Pkg; +use UCW::Configure::Build; +use UCW::Configure::Paths; +use UCW::Configure::C; +use UCW::Configure::Pkg; # Get some libraries -UCW::Configure::Pkg::PkgConfig("libucw") or Fail("libucw is required"); -UCW::Configure::Pkg::PkgConfig("libucw-json") or Fail("libucw-json is required"); -UCW::Configure::Pkg::TrivConfig("libgcrypt", script => "libgcrypt-config", minversion => '1.6') or Fail("libgcrypt is required"); +PkgConfig("libucw") or Fail("libucw is required"); +PkgConfig("libucw-json") or Fail("libucw-json is required"); +TrivConfig("libgcrypt", script => "libgcrypt-config", minversion => '1.6') or Fail("libgcrypt is required"); + +if (IsSet("CONFIG_APACHE_MOD")) { + Log "Checking for apxs2 ... "; + my $cf = TryCmd("apxs2 -q CFLAGS"); + my $ei = TryCmd("apxs2 -q EXTRA_INCLUDES"); + my $id = TryCmd("apxs2 -q INCLUDEDIR"); + if (!defined($cf) || !defined($id) || !defined($ei)) { + Log "NO\n"; + Fail "Apache's apxs2 utility is required to build the Apache module."; + } + Log "YES\n"; + Set("APACHE2_CFLAGS" => "-g $cf $ei -I$id"); + Set("INSTALL_APACHE2_MOD_DIR" => '$(INSTALL_LIB_DIR)/apache2/modules'); + Set("INSTALL_APACHE2_CONFIG_DIR" => '$(INSTALL_PREFIX)etc/apache2'); +} + Finish(); Log "\nConfigured, run `make' to build everything.\n"; diff --git a/default.cfg b/default.cfg index f2062af..ba6d6f8 100644 --- a/default.cfg +++ b/default.cfg @@ -1,4 +1,4 @@ -# You can specify default configuration here: -# Set("SOME_SYMBOL"); +# Build Apache module +Set("CONFIG_APACHE_MOD"); 1; diff --git a/pam/pam_subauth.c b/pam/pam_subauth.c index d6d0f88..2cb32b3 100644 --- a/pam/pam_subauth.c +++ b/pam/pam_subauth.c @@ -83,9 +83,10 @@ static struct json_node *run_command(struct context *ctx, struct json_node *requ struct fastbuf rp_fb; fbbuf_init_read(&rp_fb, rp_buf, rp_len, 0); + struct json_node *reply = NULL; TRANS_TRY { - ctx->reply = json_parse(ctx->json, &rp_fb); + reply = json_parse(ctx->json, &rp_fb); } TRANS_CATCH(x) { @@ -93,12 +94,14 @@ static struct json_node *run_command(struct context *ctx, struct json_node *requ } TRANS_END; - if (ctx->reply->type != JSON_OBJECT) + if (reply->type != JSON_OBJECT) { pam_syslog(ctx->pamh, LOG_ERR, "Malformed reply: Top-level node is not an object"); goto fail3; } + ctx->reply = reply; + fail3: free(rp_buf); fail2: -- 2.39.5