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