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