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