]> mj.ucw.cz Git - subauth.git/commitdiff
Apache: An initial attempt at authn module
authorMartin Mares <mj@ucw.cz>
Sun, 6 Aug 2017 19:53:52 +0000 (21:53 +0200)
committerMartin Mares <mj@ucw.cz>
Sun, 6 Aug 2017 19:53:52 +0000 (21:53 +0200)
Makefile
apache/Makefile [new file with mode: 0644]
apache/mod_authn_subauth.c [new file with mode: 0644]
configure
default.cfg
pam/pam_subauth.c

index 830d7ecf5fcdf3449401ddc808be5b829da56314..71af79bcd9c6118ed5b4dc82faceb8bcd308bc46 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -24,6 +24,10 @@ include $(s)/server/Makefile
 include $(s)/client/Makefile
 include $(s)/pam/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
 
 # And finally the default rules of the build system
 include $(BUILDSYS)/Makebottom
 
diff --git a/apache/Makefile b/apache/Makefile
new file mode 100644 (file)
index 0000000..4383fe5
--- /dev/null
@@ -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 (file)
index 0000000..21108e2
--- /dev/null
@@ -0,0 +1,217 @@
+/*
+ *     Apache Module for the Sub-authentication Daemon
+ *
+ *     (c) 2017 Martin Mares <mj@ucw.cz>
+ */
+
+#include <ucw/lib.h>
+#include <ucw/mempool.h>
+#include <ucw/string.h>
+#include <ucw/trans.h>
+#include <ucw-json/json.h>
+
+#include <ap_config.h>
+#include <httpd.h>
+#include <http_config.h>
+#include <http_core.h>
+#include <http_log.h>
+#include <http_request.h>
+#include <mod_auth.h>
+
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#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,
+};
index 15aa37eed96d318dc744e664f6fb28d605c92c97..37920e208eae18ccd0fb006dac6d1df8b9283586 100755 (executable)
--- a/configure
+++ b/configure
@@ -31,16 +31,31 @@ use UCW::Configure;
 Init($srcdir, 'default.cfg');
 Log "### Configuring subauthd ###\n\n";
 Include Get("CONFIG");
 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
 
 # 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";
 Finish();
 
 Log "\nConfigured, run `make' to build everything.\n";
index f2062aff9e4c661cead049c566db5d13cb097fed..ba6d6f8510c3c02c8c282b93d7e5e923cf7f88c1 100644 (file)
@@ -1,4 +1,4 @@
-# You can specify default configuration here:
-# Set("SOME_SYMBOL");
+# Build Apache module
+Set("CONFIG_APACHE_MOD");
 
 1;
 
 1;
index d6d0f88e5bcc86fea80d12f3b47bfc48aefeae43..2cb32b3e9dc45264bf523d4beb145aca81571aea 100644 (file)
@@ -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 fastbuf rp_fb;
   fbbuf_init_read(&rp_fb, rp_buf, rp_len, 0);
 
+  struct json_node *reply = NULL;
   TRANS_TRY
     {
   TRANS_TRY
     {
-      ctx->reply = json_parse(ctx->json, &rp_fb);
+      reply = json_parse(ctx->json, &rp_fb);
     }
   TRANS_CATCH(x)
     {
     }
   TRANS_CATCH(x)
     {
@@ -93,12 +94,14 @@ static struct json_node *run_command(struct context *ctx, struct json_node *requ
     }
   TRANS_END;
 
     }
   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;
     }
 
     {
       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:
 fail3:
   free(rp_buf);
 fail2: