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