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