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