]> mj.ucw.cz Git - subauth.git/blob - subauthd.c
Elementary client and passing of JSON back and forth
[subauth.git] / subauthd.c
1 /*
2  *      Sub-authentication Daemon
3  *
4  *      (c) 2017 Martin Mares <mj@ucw.cz>
5  */
6
7 #include <ucw/lib.h>
8 #include <ucw/conf.h>
9 #include <ucw/log.h>
10 #include <ucw/mainloop.h>
11 #include <ucw/opt.h>
12 #include <ucw/trans.h>
13 #include <ucw-json/json.h>
14
15 #include <errno.h>
16 #include <fcntl.h>
17 #include <sys/socket.h>
18 #include <sys/un.h>
19 #include <unistd.h>
20
21 #include "autoconf.h"
22
23 static char *socket_path = "subauthd.socket";
24 static uint max_connections = ~0U;
25
26 static struct main_file listen_socket;
27 static uint num_connections;
28
29 #define SOCKET_TIMEOUT 60000            // in ms
30 #define MAX_PACKET_SIZE 16384
31 #define MAX_OOB_DATA_SIZE 4096
32
33 struct client {
34   struct main_file socket;
35   struct main_timer timer;
36   int uid;
37   struct json_context *json;
38   struct json_node *request;
39   struct json_node *reply;
40 };
41
42 static byte packet_buffer[MAX_PACKET_SIZE];
43 static byte oob_data_buffer[MAX_OOB_DATA_SIZE];
44
45 static int socket_read_handler(struct main_file *fi);
46 static int socket_write_handler(struct main_file *fi);
47
48 static void client_close(struct client *c)
49 {
50   msg(L_INFO, "Closing connection");
51   file_del(&c->socket);
52   timer_del(&c->timer);
53   close(c->socket.fd);
54   json_delete(c->json);
55   xfree(c);
56   num_connections--;
57 }
58
59 static void cmd_error(struct client *c, const char *err)
60 {
61   json_object_set(c->reply, "error", json_new_string(c->json, err));
62 }
63
64 static void socket_timeout_handler(struct main_timer *tm)
65 {
66   struct client *c = tm->data;
67
68   msg(L_INFO, "Client timeout");
69
70   client_close(c);
71 }
72
73 static void try_send_reply(struct client *c)
74 {
75   struct fastbuf fb;
76   fbbuf_init_write(&fb, packet_buffer, MAX_PACKET_SIZE);
77
78   TRANS_TRY
79     {
80       json_write(c->json, &fb, c->reply);
81     }
82   TRANS_CATCH(x)
83     {
84       msg(L_ERROR, "Unable to construct reply, it is probably too long");
85       fbbuf_init_write(&fb, packet_buffer, MAX_PACKET_SIZE);
86       bputs(&fb, "{ \"error\": \"Reply too long\" }\n");
87     }
88   TRANS_END;
89
90   int len = fbbuf_count_written(&fb);
91   msg(L_INFO, "Sending reply of %d bytes", len);
92   if (send(c->socket.fd, packet_buffer, len, 0) < 0)
93     {
94       if (errno == EAGAIN || errno == EINTR)
95         {
96           msg(L_INFO, "Postponed send");
97           c->socket.write_handler = socket_write_handler;
98           file_chg(&c->socket);
99         }
100       msg(L_ERROR, "Client write error: %m");
101       client_close(c);
102     }
103   else
104     {
105       msg(L_INFO, "Reply sent");
106       c->socket.read_handler = socket_read_handler;
107       c->socket.write_handler = NULL;
108       file_chg(&c->socket);
109       timer_add_rel(&c->timer, SOCKET_TIMEOUT);
110     }
111 }
112
113 static void send_reply(struct client *c)
114 {
115   timer_add_rel(&c->timer, SOCKET_TIMEOUT);
116   try_send_reply(c);
117 }
118
119 static void received_packet(struct client *c, byte *pkt, int len)
120 {
121   json_reset(c->json);
122
123   struct fastbuf fb;
124   fbbuf_init_read(&fb, pkt, len, 0);
125
126   c->reply = json_new_object(c->json);
127
128   TRANS_TRY
129     {
130       c->request = json_parse(c->json, &fb);
131     }
132   TRANS_CATCH(x)
133     {
134       cmd_error(c, "Parse error");
135       send_reply(c);
136       return;
137     }
138   TRANS_END;
139
140   cmd_error(c, "OK");
141   send_reply(c);
142 }
143
144 static int socket_write_handler(struct main_file *fi)
145 {
146   struct client *c = fi->data;
147   try_send_reply(c);
148   return HOOK_IDLE;
149 }
150
151 static int socket_read_handler(struct main_file *fi)
152 {
153   struct client *c = fi->data;
154
155   struct iovec iov = {
156     .iov_base = packet_buffer,
157     .iov_len = MAX_PACKET_SIZE,
158   };
159
160   struct msghdr mh = {
161     .msg_iov = &iov,
162     .msg_iovlen = 1,
163     .msg_control = oob_data_buffer,
164     .msg_controllen = MAX_OOB_DATA_SIZE,
165   };
166
167   ssize_t len = recvmsg(fi->fd, &mh, 0);
168   if (len < 0)
169     {
170       if (errno != EAGAIN && errno != EINTR)
171         msg(L_ERROR, "Socket read: %m");
172       return HOOK_IDLE;
173     }
174
175   if (!len)
176     {
177       client_close(c);
178       return HOOK_IDLE;
179     }
180
181   struct ucred *cred = NULL;
182   for (struct cmsghdr *cm = CMSG_FIRSTHDR(&mh); cm; cm = CMSG_NXTHDR(&mh, cm))
183     {
184       if (cm->cmsg_level == SOL_SOCKET)
185         {
186           if (cm->cmsg_type == SCM_RIGHTS)
187             {
188               // We are not interested in receiving file descriptor, but despite
189               // that they could be attached to the message. If it happens, simply
190               // close them.
191               int *fdptr = (int *) CMSG_DATA(cm);
192               int nfd = cm->cmsg_len / sizeof(int);
193               for (int i=0; i<nfd; i++)
194                 close(fdptr[i]);
195             }
196           else if (cm->cmsg_type == SCM_CREDENTIALS)
197             {
198               ASSERT(cm->cmsg_len >= sizeof(cred));
199               cred = (struct ucred *) CMSG_DATA(cm);
200             }
201         }
202     }
203
204   if (!cred)
205     {
206       msg(L_ERROR, "Dropping message with no credentials");
207       return HOOK_RETRY;
208     }
209
210   msg(L_INFO, "Got message from UID %d", (int) cred->uid);
211   c->uid = cred->uid;
212
213   fi->read_handler = NULL;
214   file_chg(fi);
215
216   received_packet(c, packet_buffer, len);
217   return HOOK_RETRY;
218 }
219
220 static int listen_read_handler(struct main_file *fi)
221 {
222   struct sockaddr_un client;
223   socklen_t addr_len = sizeof(client);
224
225   int new_sk = accept(fi->fd, &client, &addr_len);
226   if (new_sk < 0)
227     {
228       if (errno != EAGAIN && errno != EINTR)
229         msg(L_ERROR, "Socket accept: %m");
230       return HOOK_IDLE;
231     }
232
233   if (num_connections >= max_connections)
234     {
235       msg(L_WARN, "Too many connections (you might need to increase MaxConnections)");
236       close(new_sk);
237       return HOOK_IDLE;
238     }
239   num_connections++;
240
241   msg(L_INFO, "Accepted connection");
242
243   if (fcntl(new_sk, F_SETFL, fcntl(new_sk, F_GETFL) | O_NONBLOCK) < 0)
244     die("Cannot set O_NONBLOCK: %m");
245
246   struct client *c = xmalloc_zero(sizeof(*c));
247   c->json = json_new();
248
249   c->socket.fd = new_sk;
250   c->socket.read_handler = socket_read_handler;
251   c->socket.data = c;
252   file_add(&c->socket);
253
254   c->timer.handler = socket_timeout_handler;
255   c->timer.data = c;
256   timer_add_rel(&c->timer, SOCKET_TIMEOUT);
257
258   return HOOK_RETRY;
259 }
260
261 static void init_socket(void)
262 {
263   int sk = socket(PF_UNIX, SOCK_SEQPACKET, 0);
264   if (sk < 0)
265     die("socket(PF_UNIX, SOCK_SEQPACKET): %m");
266
267   if (fcntl(sk, F_SETFL, fcntl(sk, F_GETFL) | O_NONBLOCK) < 0)
268     die("Cannot set O_NONBLOCK: %m");
269
270   struct sockaddr_un sun;
271   sun.sun_family = AF_UNIX;
272   if (strlen(socket_path) >= sizeof(sun.sun_path))
273     die("SocketPath too long");
274   strcpy(sun.sun_path, socket_path);
275
276   if (unlink(socket_path) < 0 && errno != ENOENT)
277     die("Cannot unlink old socket %s: %m", socket_path);
278
279   if (bind(sk, (struct sockaddr *) &sun, sizeof(sun)) < 0)
280     die("Cannot bind to %s: %m", socket_path);
281
282   if (listen(sk, 64) < 0)
283     die("listen(): %m");
284
285   int one;
286   if (setsockopt(sk, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0)
287     die("setsockopt(SO_PASSCRED): %m");
288
289   listen_socket.fd = sk;
290   listen_socket.read_handler = listen_read_handler;
291   file_add(&listen_socket);
292
293   msg(L_INFO, "Listening on %s", socket_path);
294 }
295
296 static struct cf_section daemon_config = {
297   CF_ITEMS {
298     CF_STRING("SocketPath", &socket_path),
299     CF_UINT("MaxConnections", &max_connections),
300     CF_END
301   }
302 };
303
304 static const struct opt_section options = {
305   OPT_ITEMS {
306     OPT_HELP("A sub-authentication daemon."),
307     OPT_HELP("Usage: subauthd [options]"),
308     OPT_HELP(""),
309     OPT_HELP("Options:"),
310     OPT_HELP_OPTION,
311     OPT_CONF_OPTIONS,
312     OPT_END
313   }
314 };
315
316 int main(int argc UNUSED, char **argv)
317 {
318   cf_def_file = CONFIG_DIR "/subauthd";
319   cf_declare_section("SubauthD", &daemon_config, 0);
320   opt_parse(&options, argv+1);
321
322   main_init();
323   init_socket();
324
325   main_loop();
326   return 0;
327 }