]> mj.ucw.cz Git - subauth.git/blob - pam/pam_subauth.c
d6d0f88e5bcc86fea80d12f3b47bfc48aefeae43
[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   TRANS_TRY
87     {
88       ctx->reply = json_parse(ctx->json, &rp_fb);
89     }
90   TRANS_CATCH(x)
91     {
92       goto fail3;
93     }
94   TRANS_END;
95
96   if (ctx->reply->type != JSON_OBJECT)
97     {
98       pam_syslog(ctx->pamh, LOG_ERR, "Malformed reply: Top-level node is not an object");
99       goto fail3;
100     }
101
102 fail3:
103   free(rp_buf);
104 fail2:
105   bclose(rq_fb);
106 fail1:
107   close(sk);
108   return ctx->reply;
109 }
110
111 static bool check_auth(struct context *ctx)
112 {
113   bool ok = 0;
114
115   ctx->json = json_new();
116   struct json_node *rq = json_new_object(ctx->json);
117   json_object_set(rq, "cmd", json_new_string(ctx->json, "login"));
118   json_object_set(rq, "zone", json_new_string(ctx->json, ctx->zone));
119   json_object_set(rq, "login", json_new_string(ctx->json, ctx->user));
120   json_object_set(rq, "passwd", json_new_string(ctx->json, ctx->passwd));
121
122   struct json_node *rp = run_command(ctx, rq);
123   if (!rp)
124     goto done;
125   struct json_node *error = json_object_get(rp, "error");
126   if (!error || error->type != JSON_STRING)
127     {
128       pam_syslog(ctx->pamh, LOG_ERR, "Malformed reply: No error status found");
129       goto done;
130     }
131   if (!strcmp(error->string, ""))
132     ok = 1;
133   else if (ctx->debug)
134     pam_syslog(ctx->pamh, LOG_DEBUG, "Server returned error: %s", error->string);
135
136 done:
137   json_delete(ctx->json);
138   return ok;
139 }
140
141 int pam_sm_authenticate(pam_handle_t *pamh, int flags UNUSED, int argc, const char **argv)
142 {
143   struct context ctx = {
144     .pamh = pamh,
145     .debug = 0,
146     .use_first_pass = 0,
147     .socket_path = INSTALL_RUN_DIR "/subauthd.socket",
148     .zone = "default",
149     .pool = mp_new(4096),
150   };
151
152   // Parse arguments
153   for (int i=0; i<argc; i++)
154     {
155       const char *arg = argv[i];
156       if (!strcmp(arg, "debug"))
157         ctx.debug = 1;
158       else if (!strcmp(arg, "use_first_pass"))
159         ctx.use_first_pass = 1;
160       else if (str_has_prefix(arg, "socket="))
161         ctx.socket_path = arg + 7;
162       else if (str_has_prefix(arg, "zone="))
163         ctx.zone = arg + 5;
164       else
165         pam_syslog(pamh, LOG_ERR, "Unrecognized argument %s", arg);
166     }
167
168   // Obtain user name
169   if (pam_get_user(pamh, &ctx.user, NULL) != PAM_SUCCESS)
170     {
171       pam_syslog(pamh, LOG_ERR, "Cannot retrieve login");
172       goto fail;
173     }
174
175   // Obtain password
176   if (ctx.use_first_pass)
177     {
178       if (pam_get_item(pamh, PAM_AUTHTOK, (const void **) &ctx.passwd) != PAM_SUCCESS)
179         goto fail;
180     }
181   else
182     {
183       char *passwd;
184       if (pam_prompt(pamh, PAM_PROMPT_ECHO_OFF, &passwd, "Password: ") != PAM_SUCCESS)
185         return PAM_AUTH_ERR;
186       ctx.passwd = mp_strdup(ctx.pool, passwd);
187       free(passwd);
188     }
189
190   if (ctx.debug)
191     pam_syslog(pamh, LOG_DEBUG, "Authenticating user=%s zone=%s via socket %s", ctx.user, ctx.zone, ctx.socket_path);
192
193   if (!check_auth(&ctx))
194     goto fail;
195
196   return PAM_SUCCESS;
197
198 fail:
199   mp_delete(ctx.pool);
200   return PAM_AUTH_ERR;
201 }
202
203 int pam_sm_setcred(pam_handle_t *pamh UNUSED, int flags UNUSED, int argc UNUSED, const char **argv UNUSED)
204 {
205   return PAM_SUCCESS;
206 }