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