]> mj.ucw.cz Git - subauth.git/blob - pam/pam_subauth.c
2cb32b3e9dc45264bf523d4beb145aca81571aea
[subauth.git] / pam / pam_subauth.c
1 /*
2  *      Sub-authentication Client
3  *
4  *      (c) 2017 Martin Mares <mj@ucw.cz>
5  */
6
7 #include <ucw/lib.h>
8 #include <ucw/mempool.h>
9 #include <ucw/string.h>
10 #include <ucw/trans.h>
11 #include <ucw-json/json.h>
12
13 #include <stdlib.h>
14 #include <sys/socket.h>
15 #include <sys/un.h>
16 #include <syslog.h>
17 #include <unistd.h>
18
19 #include "autoconf.h"
20
21 #define PAM_SM_AUTH
22 #include <security/pam_modules.h>
23 #include <security/pam_ext.h>
24
25 struct context {
26   pam_handle_t *pamh;
27   int debug;
28   int use_first_pass;
29   const char *socket_path;
30   const char *zone;
31   const char *user;
32   const char *passwd;
33   struct mempool *pool;
34   struct json_context *json;
35   struct json_node *reply;
36 };
37
38 static struct json_node *run_command(struct context *ctx, struct json_node *request)
39 {
40   ctx->reply = NULL;
41
42   int sk = socket(PF_UNIX, SOCK_SEQPACKET, 0);
43   if (sk < 0)
44     {
45       pam_syslog(ctx->pamh, LOG_ERR, "socket(PF_UNIX, SOCK_SEQPACKET): %m");
46       return NULL;
47     }
48
49   struct sockaddr_un sun;
50   sun.sun_family = AF_UNIX;
51   if (strlen(ctx->socket_path) >= sizeof(sun.sun_path))
52     {
53       pam_syslog(ctx->pamh, LOG_ERR, "Socket path too long");
54       goto fail1;
55     }
56   strcpy(sun.sun_path, ctx->socket_path);
57
58   if (connect(sk, (struct sockaddr *) &sun, sizeof(sun)) < 0)
59     {
60       pam_syslog(ctx->pamh, LOG_ERR, "Cannot connect to %s: %m", ctx->socket_path);
61       goto fail1;
62     }
63
64   struct fastbuf *rq_fb = fbgrow_create(4096);
65   json_write(ctx->json, rq_fb, request);
66   byte *rq_buf;
67   uint rq_len = fbgrow_get_buf(rq_fb, &rq_buf);
68   if (send(sk, rq_buf, rq_len, 0) < 0)
69     {
70       pam_syslog(ctx->pamh, LOG_ERR, "Cannot send request: %m");
71       goto fail2;
72     }
73
74   uint rp_bufsize = 16384;
75   byte *rp_buf = xmalloc(rp_bufsize);
76   int rp_len = recv(sk, rp_buf, rp_bufsize, 0);
77   if (rp_len < 0)
78     {
79       pam_syslog(ctx->pamh, LOG_ERR, "Cannot receive reply: %m");
80       goto fail3;
81     }
82
83   struct fastbuf rp_fb;
84   fbbuf_init_read(&rp_fb, rp_buf, rp_len, 0);
85
86   struct json_node *reply = NULL;
87   TRANS_TRY
88     {
89       reply = json_parse(ctx->json, &rp_fb);
90     }
91   TRANS_CATCH(x)
92     {
93       goto fail3;
94     }
95   TRANS_END;
96
97   if (reply->type != JSON_OBJECT)
98     {
99       pam_syslog(ctx->pamh, LOG_ERR, "Malformed reply: Top-level node is not an object");
100       goto fail3;
101     }
102
103   ctx->reply = reply;
104
105 fail3:
106   free(rp_buf);
107 fail2:
108   bclose(rq_fb);
109 fail1:
110   close(sk);
111   return ctx->reply;
112 }
113
114 static bool check_auth(struct context *ctx)
115 {
116   bool ok = 0;
117
118   ctx->json = json_new();
119   struct json_node *rq = json_new_object(ctx->json);
120   json_object_set(rq, "cmd", json_new_string(ctx->json, "login"));
121   json_object_set(rq, "zone", json_new_string(ctx->json, ctx->zone));
122   json_object_set(rq, "login", json_new_string(ctx->json, ctx->user));
123   json_object_set(rq, "passwd", json_new_string(ctx->json, ctx->passwd));
124
125   struct json_node *rp = run_command(ctx, rq);
126   if (!rp)
127     goto done;
128   struct json_node *error = json_object_get(rp, "error");
129   if (!error || error->type != JSON_STRING)
130     {
131       pam_syslog(ctx->pamh, LOG_ERR, "Malformed reply: No error status found");
132       goto done;
133     }
134   if (!strcmp(error->string, ""))
135     ok = 1;
136   else if (ctx->debug)
137     pam_syslog(ctx->pamh, LOG_DEBUG, "Server returned error: %s", error->string);
138
139 done:
140   json_delete(ctx->json);
141   return ok;
142 }
143
144 int pam_sm_authenticate(pam_handle_t *pamh, int flags UNUSED, int argc, const char **argv)
145 {
146   struct context ctx = {
147     .pamh = pamh,
148     .debug = 0,
149     .use_first_pass = 0,
150     .socket_path = INSTALL_RUN_DIR "/subauthd.socket",
151     .zone = "default",
152     .pool = mp_new(4096),
153   };
154
155   // Parse arguments
156   for (int i=0; i<argc; i++)
157     {
158       const char *arg = argv[i];
159       if (!strcmp(arg, "debug"))
160         ctx.debug = 1;
161       else if (!strcmp(arg, "use_first_pass"))
162         ctx.use_first_pass = 1;
163       else if (str_has_prefix(arg, "socket="))
164         ctx.socket_path = arg + 7;
165       else if (str_has_prefix(arg, "zone="))
166         ctx.zone = arg + 5;
167       else
168         pam_syslog(pamh, LOG_ERR, "Unrecognized argument %s", arg);
169     }
170
171   // Obtain user name
172   if (pam_get_user(pamh, &ctx.user, NULL) != PAM_SUCCESS)
173     {
174       pam_syslog(pamh, LOG_ERR, "Cannot retrieve login");
175       goto fail;
176     }
177
178   // Obtain password
179   if (ctx.use_first_pass)
180     {
181       if (pam_get_item(pamh, PAM_AUTHTOK, (const void **) &ctx.passwd) != PAM_SUCCESS)
182         goto fail;
183     }
184   else
185     {
186       char *passwd;
187       if (pam_prompt(pamh, PAM_PROMPT_ECHO_OFF, &passwd, "Password: ") != PAM_SUCCESS)
188         return PAM_AUTH_ERR;
189       ctx.passwd = mp_strdup(ctx.pool, passwd);
190       free(passwd);
191     }
192
193   if (ctx.debug)
194     pam_syslog(pamh, LOG_DEBUG, "Authenticating user=%s zone=%s via socket %s", ctx.user, ctx.zone, ctx.socket_path);
195
196   if (!check_auth(&ctx))
197     goto fail;
198
199   return PAM_SUCCESS;
200
201 fail:
202   mp_delete(ctx.pool);
203   return PAM_AUTH_ERR;
204 }
205
206 int pam_sm_setcred(pam_handle_t *pamh UNUSED, int flags UNUSED, int argc UNUSED, const char **argv UNUSED)
207 {
208   return PAM_SUCCESS;
209 }