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