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