+/*
+ * 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,
+};