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