--- /dev/null
+/*
+ * Sub-authentication Client
+ *
+ * (c) 2017 Martin Mares <mj@ucw.cz>
+ */
+
+// FIXME: Includes
+
+#include <ucw/lib.h>
+#include <ucw/mempool.h>
+#include <ucw/string.h>
+#include <ucw/trans.h>
+#include <ucw-json/json.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <syslog.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "autoconf.h"
+
+#define PAM_SM_AUTH
+#include <security/pam_modules.h>
+#include <security/pam_ext.h>
+
+struct context {
+ pam_handle_t *pamh;
+ int debug;
+ int use_first_pass;
+ const char *socket_path;
+ const char *zone;
+ const char *user;
+ const char *passwd;
+ struct mempool *pool;
+ 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)
+ {
+ pam_syslog(ctx->pamh, LOG_ERR, "socket(PF_UNIX, SOCK_SEQPACKET): %m");
+ return NULL;
+ }
+
+ struct sockaddr_un sun;
+ sun.sun_family = AF_UNIX;
+ if (strlen(ctx->socket_path) >= sizeof(sun.sun_path))
+ {
+ pam_syslog(ctx->pamh, LOG_ERR, "Socket path too long");
+ goto fail1;
+ }
+ strcpy(sun.sun_path, ctx->socket_path);
+
+ if (connect(sk, (struct sockaddr *) &sun, sizeof(sun)) < 0)
+ {
+ pam_syslog(ctx->pamh, LOG_ERR, "Cannot connect to %s: %m", ctx->socket_path);
+ 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)
+ {
+ pam_syslog(ctx->pamh, LOG_ERR, "Cannot send request: %m");
+ 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)
+ {
+ pam_syslog(ctx->pamh, LOG_ERR, "Cannot receive reply: %m");
+ goto fail3;
+ }
+
+ struct fastbuf rp_fb;
+ fbbuf_init_read(&rp_fb, rp_buf, rp_len, 0);
+
+ TRANS_TRY
+ {
+ ctx->reply = json_parse(ctx->json, &rp_fb);
+ }
+ TRANS_CATCH(x)
+ {
+ goto fail3;
+ }
+ TRANS_END;
+
+ if (ctx->reply->type != JSON_OBJECT)
+ {
+ pam_syslog(ctx->pamh, LOG_ERR, "Malformed reply: Top-level node is not an object");
+ goto fail3;
+ }
+
+fail3:
+ free(rp_buf);
+fail2:
+ bclose(rq_fb);
+fail1:
+ close(sk);
+ return ctx->reply;
+}
+
+static bool check_auth(struct context *ctx)
+{
+ bool ok = 0;
+
+ 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, ctx->zone));
+ json_object_set(rq, "login", json_new_string(ctx->json, ctx->user));
+ json_object_set(rq, "passwd", json_new_string(ctx->json, ctx->passwd));
+
+ 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)
+ {
+ pam_syslog(ctx->pamh, LOG_ERR, "Malformed reply: No error status found");
+ goto done;
+ }
+ if (!strcmp(error->string, ""))
+ ok = 1;
+ else if (ctx->debug)
+ pam_syslog(ctx->pamh, LOG_DEBUG, "Server returned error: %s", error->string);
+
+done:
+ json_delete(ctx->json);
+ return ok;
+}
+
+int pam_sm_authenticate(pam_handle_t *pamh, int flags UNUSED, int argc, const char **argv)
+{
+ struct context ctx = {
+ .pamh = pamh,
+ .debug = 0,
+ .use_first_pass = 0,
+ .socket_path = INSTALL_RUN_DIR "/subauthd.socket",
+ .zone = "default",
+ .pool = mp_new(4096),
+ };
+
+ // Parse arguments
+ for (int i=0; i<argc; i++)
+ {
+ const char *arg = argv[i];
+ if (!strcmp(arg, "debug"))
+ ctx.debug = 1;
+ else if (!strcmp(arg, "use_first_pass"))
+ ctx.use_first_pass = 1;
+ else if (str_has_prefix(arg, "socket="))
+ ctx.socket_path = arg + 7;
+ else if (str_has_prefix(arg, "zone="))
+ ctx.zone = arg + 5;
+ else
+ pam_syslog(pamh, LOG_ERR, "Unrecognized argument %s", arg);
+ }
+
+ // Obtain user name
+ if (pam_get_user(pamh, &ctx.user, NULL) != PAM_SUCCESS)
+ {
+ pam_syslog(pamh, LOG_ERR, "Cannot retrieve login");
+ goto fail;
+ }
+
+ // Obtain password
+ if (ctx.use_first_pass)
+ {
+ if (pam_get_item(pamh, PAM_AUTHTOK, (const void **) &ctx.passwd) != PAM_SUCCESS)
+ goto fail;
+ }
+ else
+ {
+ char *passwd;
+ if (pam_prompt(pamh, PAM_PROMPT_ECHO_OFF, &passwd, "Password: ") != PAM_SUCCESS)
+ return PAM_AUTH_ERR;
+ ctx.passwd = mp_strdup(ctx.pool, passwd);
+ free(passwd);
+ }
+
+ if (ctx.debug)
+ pam_syslog(pamh, LOG_DEBUG, "Authenticating user=%s zone=%s via socket %s", ctx.user, ctx.zone, ctx.socket_path);
+
+ if (!check_auth(&ctx))
+ goto fail;
+
+ return PAM_SUCCESS;
+
+fail:
+ mp_delete(ctx.pool);
+ return PAM_AUTH_ERR;
+}
+
+int pam_sm_setcred(pam_handle_t *pamh UNUSED, int flags UNUSED, int argc UNUSED, const char **argv UNUSED)
+{
+ return PAM_SUCCESS;
+}