]> mj.ucw.cz Git - subauth.git/blob - server/subauthd.c
Server: Set correct permissions for the socket
[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   mp_delete(c->pool);           // This includes the connection structure
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 mempool *mp = mp_new(4096);
232   struct client *c = mp_alloc_zero(mp, sizeof(*c));
233   c->pool = mp;
234   c->json = json_new();
235
236   c->socket.fd = new_sk;
237   c->socket.read_handler = socket_read_handler;
238   c->socket.data = c;
239   file_add(&c->socket);
240
241   c->timer.handler = socket_timeout_handler;
242   c->timer.data = c;
243   timer_add_rel(&c->timer, SOCKET_TIMEOUT);
244
245   return HOOK_RETRY;
246 }
247
248 static void init_socket(void)
249 {
250   int sk = socket(PF_UNIX, SOCK_SEQPACKET, 0);
251   if (sk < 0)
252     die("socket(PF_UNIX, SOCK_SEQPACKET): %m");
253
254   if (fcntl(sk, F_SETFL, fcntl(sk, F_GETFL) | O_NONBLOCK) < 0)
255     die("Cannot set O_NONBLOCK: %m");
256
257   struct sockaddr_un sun;
258   sun.sun_family = AF_UNIX;
259   if (strlen(socket_path) >= sizeof(sun.sun_path))
260     die("SocketPath too long");
261   strcpy(sun.sun_path, socket_path);
262
263   if (unlink(socket_path) < 0 && errno != ENOENT)
264     die("Cannot unlink old socket %s: %m", socket_path);
265
266   if (bind(sk, (struct sockaddr *) &sun, sizeof(sun)) < 0)
267     die("Cannot bind to %s: %m", socket_path);
268
269   if (listen(sk, 64) < 0)
270     die("listen(): %m");
271
272   int one = 1;
273   if (setsockopt(sk, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0)
274     die("setsockopt(SO_PASSCRED): %m");
275
276   listen_socket.fd = sk;
277   listen_socket.read_handler = listen_read_handler;
278   file_add(&listen_socket);
279
280   if (chmod(socket_path, 0666) < 0)
281     die("Cannot chmod socket: %m");
282
283   msg(L_INFO, "Listening on %s", socket_path);
284 }
285
286 static char *zone_commit(void *z_)
287 {
288   struct auth_zone *z = z_;
289   if (!z->name)
290     return "A zone must have a name";
291   return NULL;
292 }
293
294 static struct cf_section zone_config = {
295   CF_TYPE(struct auth_zone),
296   CF_COMMIT(zone_commit),
297   CF_ITEMS {
298     CF_STRING("Name", PTR_TO(struct auth_zone, name)),
299     CF_STRING("Description", PTR_TO(struct auth_zone, desc)),
300     CF_UINT("AutoCreateAcct", PTR_TO(struct auth_zone, auto_create_acct)),
301     CF_UINT("AllowPasswd", PTR_TO(struct auth_zone, allow_passwd)),
302     CF_UINT("AllowTokens", PTR_TO(struct auth_zone, allow_tokens)),
303     CF_UINT("MaxTempValidity", PTR_TO(struct auth_zone, max_temp_validity)),
304     CF_END
305   }
306 };
307
308 static struct cf_section daemon_config = {
309   CF_ITEMS {
310     CF_STRING("SocketPath", &socket_path),
311     CF_UINT("MaxConnections", &max_connections),
312     CF_LIST("Zone", &zone_list, &zone_config),
313     CF_STRING("Database", &database_name),
314     CF_STRING("TempKeyFile", &temp_key_file),
315     CF_END
316   }
317 };
318
319 static const struct opt_section options = {
320   OPT_ITEMS {
321     OPT_HELP("A sub-authentication daemon."),
322     OPT_HELP("Usage: subauthd [options]"),
323     OPT_HELP(""),
324     OPT_HELP("Options:"),
325     OPT_HELP_OPTION,
326     OPT_CONF_OPTIONS,
327     OPT_END
328   }
329 };
330
331 int main(int argc UNUSED, char **argv)
332 {
333   umask(0077);
334
335   cf_def_file = CONFIG_DIR "/subauthd";
336   cf_declare_section("SubauthD", &daemon_config, 0);
337   opt_parse(&options, argv+1);
338
339   auth_init();
340   temp_init();
341   main_init();
342   init_socket();
343
344   main_loop();
345   return 0;
346 }