Move Autoconf's macro definitions into config.h.
[sliver-openvswitch.git] / lib / vlog-socket.c
1 /* Copyright (c) 2008 The Board of Trustees of The Leland Stanford
2  * Junior University
3  * 
4  * We are making the OpenFlow specification and associated documentation
5  * (Software) available for public use and benefit with the expectation
6  * that others will use, modify and enhance the Software and contribute
7  * those enhancements back to the community. However, since we would
8  * like to make the Software available for broadest use, with as few
9  * restrictions as possible permission is hereby granted, free of
10  * charge, to any person obtaining a copy of this Software to deal in
11  * the Software under the copyrights without restriction, including
12  * without limitation the rights to use, copy, modify, merge, publish,
13  * distribute, sublicense, and/or sell copies of the Software, and to
14  * permit persons to whom the Software is furnished to do so, subject to
15  * the following conditions:
16  * 
17  * The above copyright notice and this permission notice shall be
18  * included in all copies or substantial portions of the Software.
19  * 
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23  * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
24  * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
25  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
26  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27  * SOFTWARE.
28  * 
29  * The name and trademarks of copyright holder(s) may NOT be used in
30  * advertising or publicity pertaining to the Software or any
31  * derivatives without specific, written prior permission.
32  */
33
34 #include <config.h>
35 #include "vlog-socket.h"
36 #include <errno.h>
37 #include <sys/un.h>
38 #include <fcntl.h>
39 #include <poll.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <sys/socket.h>
43 #include <sys/stat.h>
44 #include <sys/types.h>
45 #include <unistd.h>
46 #include "fatal-signal.h"
47 #include "poll-loop.h"
48 #include "util.h"
49 #include "vlog.h"
50
51 #ifndef SCM_CREDENTIALS
52 #include <time.h>
53 #endif
54
55 static int make_unix_socket(bool nonblock, bool passcred,
56                             const char *bind_path, const char *connect_path);
57 \f
58 /* Server for Vlog control connection. */
59 struct vlog_server {
60     struct poll_waiter *waiter;
61     char *path;
62     int fd;
63 };
64
65 static void poll_server(int fd, short int events, void *server_);
66
67 /* Start listening for connections from clients and processing their
68  * requests.  'path' may be:
69  *
70  *      - NULL, in which case the default socket path is used.  (Only one
71  *        Vlog_server_socket per process can use the default path.)
72  *
73  *      - A name that does not start with '/', in which case it is appended to
74  *        the default socket path.
75  *
76  *      - An absolute path (starting with '/') that gives the exact name of
77  *        the Unix domain socket to listen on.
78  *
79  * Returns 0 if successful, otherwise a positive errno value.  If successful,
80  * sets '*serverp' to the new vlog_server, otherwise to NULL. */
81 int
82 vlog_server_listen(const char *path, struct vlog_server **serverp)
83 {
84     struct vlog_server *server = xmalloc(sizeof *server);
85
86     if (path && path[0] == '/') {
87         server->path = xstrdup(path);
88     } else {
89         server->path = xasprintf("/tmp/vlogs.%ld%s",
90                                  (long int) getpid(), path ? path : "");
91     }
92
93     server->fd = make_unix_socket(true, true, server->path, NULL);
94     if (server->fd < 0) {
95         int fd = server->fd;
96         free(server->path);
97         free(server);
98         fprintf(stderr, "Could not initialize vlog configuration socket: %s\n",
99                 strerror(-server->fd));
100         if (serverp) {
101             *serverp = NULL; 
102         }
103         return fd;
104     }
105
106     server->waiter = poll_fd_callback(server->fd, POLLIN, poll_server, server);
107
108     if (serverp) {
109         *serverp = server; 
110     }
111     return 0;
112 }
113
114 /* Destroys 'server' and stops listening for connections. */
115 void
116 vlog_server_close(struct vlog_server *server)
117 {
118     if (server) {
119         poll_cancel(server->waiter);
120         close(server->fd);
121         unlink(server->path);
122         fatal_signal_remove_file_to_unlink(server->path);
123         free(server->path);
124         free(server);
125     }
126 }
127
128 static int
129 recv_with_creds(const struct vlog_server *server,
130                 char *cmd_buf, size_t cmd_buf_size,
131                 struct sockaddr_un *un, socklen_t *un_len)
132 {
133 #ifdef SCM_CREDENTIALS
134     /* Read a message and control messages from 'fd'.  */
135     char cred_buf[CMSG_SPACE(sizeof(struct ucred))];
136     ssize_t n;
137     struct iovec iov;
138     struct msghdr msg;
139     struct ucred* cred;
140     struct cmsghdr* cmsg;
141
142     iov.iov_base = cmd_buf;
143     iov.iov_len = cmd_buf_size - 1;
144
145     memset(&msg, 0, sizeof msg);
146     msg.msg_name = un;
147     msg.msg_namelen = sizeof *un;
148     msg.msg_iov = &iov;
149     msg.msg_iovlen = 1;
150     msg.msg_control = cred_buf;
151     msg.msg_controllen = sizeof cred_buf;
152
153     n = recvmsg(server->fd, &msg, 0);
154     *un_len = msg.msg_namelen;
155     if (n < 0) {
156         return errno;
157     }
158     cmd_buf[n] = '\0';
159
160     /* Ensure that the message has credentials ensuring that it was sent
161      * from the same user who started us, or by root. */
162     cred = NULL;
163     for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
164          cmsg = CMSG_NXTHDR(&msg, cmsg)) {
165         if (cmsg->cmsg_level == SOL_SOCKET
166             && cmsg->cmsg_type == SCM_CREDENTIALS) {
167             cred = (struct ucred *) CMSG_DATA(cmsg);
168         } else if (cmsg->cmsg_level == SOL_SOCKET
169                    && cmsg->cmsg_type == SCM_RIGHTS) {
170             /* Anyone can send us fds.  If we don't close them, then that's
171              * a DoS: the sender can overflow our fd table. */
172             int* fds = (int *) CMSG_DATA(cmsg);
173             size_t n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof *fds;
174             size_t i;
175             for (i = 0; i < n_fds; i++) {
176                 close(fds[i]);
177             }
178         }
179     }
180     if (!cred) {
181         fprintf(stderr, "vlog: config message lacks credentials\n");
182         return -1;
183     } else if (cred->uid && cred->uid != getuid()) {
184         fprintf(stderr, "vlog: config message uid=%ld is not 0 or %ld\n",
185                 (long int) cred->uid, (long int) getuid());
186         return -1;
187     }
188
189     return 0;
190 #else /* !SCM_CREDENTIALS */
191     socklen_t len;
192     ssize_t n;
193     struct stat s;
194     time_t recent;
195
196     /* Receive a message. */
197     len = sizeof *un;
198     n = recvfrom(server->fd, cmd_buf, cmd_buf_size - 1, 0,
199                  (struct sockaddr *) un, &len);
200     *un_len = len;
201     if (n < 0) {
202         return errno;
203     }
204     cmd_buf[n] = '\0';
205
206     len -= offsetof(struct sockaddr_un, sun_path);
207     un->sun_path[len] = '\0';
208     if (stat(un->sun_path, &s) < 0) {
209         fprintf(stderr, "vlog: config message from inaccessible socket: %s\n",
210                 strerror(errno));
211         return -1;
212     }
213     if (!S_ISSOCK(s.st_mode)) {
214         fprintf(stderr, "vlog: config message not from a socket\n");
215         return -1;
216     }
217     recent = time(0) - 30;
218     if (s.st_atime < recent || s.st_ctime < recent || s.st_mtime < recent) {
219         fprintf(stderr, "vlog: config socket too old\n");
220         return -1;
221     }
222     if (s.st_uid && s.st_uid != getuid()) {
223         fprintf(stderr, "vlog: config message uid=%ld is not 0 or %ld\n",
224                 (long int) s.st_uid, (long int) getuid());
225         return -1;
226     }
227     return 0;
228 #endif /* !SCM_CREDENTIALS */
229 }
230
231 /* Processes incoming requests for 'server'. */
232 static void
233 poll_server(int fd UNUSED, short int events, void *server_)
234 {
235     struct vlog_server *server = server_;
236     for (;;) {
237         char cmd_buf[512];
238         struct sockaddr_un un;
239         socklen_t un_len;
240         char *reply;
241         int error;
242
243         error = recv_with_creds(server, cmd_buf, sizeof cmd_buf, &un, &un_len);
244         if (error > 0) {
245             if (error != EAGAIN && error != EWOULDBLOCK) {
246                 fprintf(stderr, "vlog: reading configuration socket: %s",
247                         strerror(errno));
248             }
249             break;
250         } else if (error < 0) {
251             continue;
252         }
253
254         /* Process message and send reply. */
255         if (!strncmp(cmd_buf, "set ", 4)) {
256             char *msg = vlog_set_levels_from_string(cmd_buf + 4);
257             reply = msg ? msg : xstrdup("ack");
258         } else if (!strcmp(cmd_buf, "list")) {
259             reply = vlog_get_levels();
260         } else {
261             reply = xstrdup("nak");
262         }
263         sendto(server->fd, reply, strlen(reply), 0,
264                (struct sockaddr*) &un, un_len);
265         free(reply);
266     }
267     server->waiter = poll_fd_callback(server->fd, POLLIN, poll_server, server);
268 }
269 \f
270 /* Client for Vlog control connection. */
271
272 struct vlog_client {
273     char *connect_path;
274     char *bind_path;
275     int fd;
276 };
277
278 /* Connects to a Vlog server socket.  If 'path' does not start with '/', then
279  * it start with a PID as a string.  If a non-null, non-absolute name was
280  * passed to Vlog_server_socket::listen(), then it must follow the PID in
281  * 'path'.  If 'path' starts with '/', then it must be an absolute path that
282  * gives the exact name of the Unix domain socket to connect to.
283  *
284  * Returns 0 if successful, otherwise a positive errno value.  If successful,
285  * sets '*clientp' to the new vlog_client, otherwise to NULL. */
286 int
287 vlog_client_connect(const char *path, struct vlog_client **clientp)
288 {
289     struct vlog_client *client;
290     int fd;
291
292     client = xmalloc(sizeof *client);
293     client->connect_path = (path[0] == '/'
294                             ? xstrdup(path)
295                             : xasprintf("/tmp/vlogs.%s", path));
296
297     client->bind_path = xasprintf("/tmp/vlog.%ld", (long int) getpid());
298     fd = make_unix_socket(false, false,
299                           client->bind_path, client->connect_path);
300
301     if (fd >= 0) {
302         client->fd = fd;
303         *clientp = client;
304         return 0;
305     } else {
306         free(client->connect_path);
307         free(client->bind_path);
308         free(client);
309         *clientp = NULL;
310         return errno;
311     }
312 }
313
314 /* Destroys 'client'. */
315 void
316 vlog_client_close(struct vlog_client *client)
317 {
318     if (client) {
319         unlink(client->bind_path);
320         fatal_signal_remove_file_to_unlink(client->bind_path);
321         free(client->bind_path);
322         free(client->connect_path);
323         close(client->fd);
324         free(client);
325     }
326 }
327
328 /* Sends 'request' to the server socket that 'client' is connected to.  Returns
329  * 0 if successful, otherwise a positive errno value. */
330 int
331 vlog_client_send(struct vlog_client *client, const char *request)
332 {
333 #ifdef SCM_CREDENTIALS
334     struct ucred cred;
335     struct iovec iov;
336     char buf[CMSG_SPACE(sizeof cred)];
337     struct msghdr msg;
338     struct cmsghdr* cmsg;
339     ssize_t nbytes;
340
341     cred.pid = getpid();
342     cred.uid = getuid();
343     cred.gid = getgid();
344
345     iov.iov_base = (void*) request;
346     iov.iov_len = strlen(request);
347
348     memset(&msg, 0, sizeof msg);
349     msg.msg_iov = &iov;
350     msg.msg_iovlen = 1;
351     msg.msg_control = buf;
352     msg.msg_controllen = sizeof buf;
353
354     cmsg = CMSG_FIRSTHDR(&msg);
355     cmsg->cmsg_level = SOL_SOCKET;
356     cmsg->cmsg_type = SCM_CREDENTIALS;
357     cmsg->cmsg_len = CMSG_LEN(sizeof cred);
358     memcpy(CMSG_DATA(cmsg), &cred, sizeof cred);
359     msg.msg_controllen = cmsg->cmsg_len;
360
361     nbytes = sendmsg(client->fd, &msg, 0);
362 #else /* !SCM_CREDENTIALS */
363     ssize_t nbytes = send(client->fd, request, strlen(request), 0);
364 #endif /* !SCM_CREDENTIALS */
365     if (nbytes > 0) {
366         return nbytes == strlen(request) ? 0 : ENOBUFS;
367     } else {
368         return errno;
369     }
370 }
371
372 /* Attempts to receive a response from the server socket that 'client' is
373  * connected to.  Returns 0 if successful, otherwise a positive errno value.
374  * If successful, sets '*reply' to the reply, which the caller must free,
375  * otherwise to NULL. */
376 int
377 vlog_client_recv(struct vlog_client *client, char **reply)
378 {
379     struct pollfd pfd;
380     int nfds;
381     char buffer[65536];
382     ssize_t nbytes;
383
384     *reply = NULL;
385
386     pfd.fd = client->fd;
387     pfd.events = POLLIN;
388     nfds = poll(&pfd, 1, 1000);
389     if (nfds == 0) {
390         return ETIMEDOUT;
391     } else if (nfds < 0) {
392         return errno;
393     }
394
395     nbytes = read(client->fd, buffer, sizeof buffer - 1);
396     if (nbytes < 0) {
397         return errno;
398     } else {
399         buffer[nbytes] = '\0';
400         *reply = xstrdup(buffer);
401         return 0;
402     }
403 }
404
405 /* Sends 'request' to the server socket and waits for a reply.  Returns 0 if
406  * successful, otherwise to a positive errno value.  If successful, sets
407  * '*reply' to the reply, which the caller must free, otherwise to NULL. */
408 int
409 vlog_client_transact(struct vlog_client *client,
410                      const char *request, char **reply)
411 {
412     int i;
413
414     /* Retry up to 3 times. */
415     for (i = 0; i < 3; ++i) {
416         int error = vlog_client_send(client, request);
417         if (error) {
418             *reply = NULL;
419             return error;
420         }
421         error = vlog_client_recv(client, reply);
422         if (error != ETIMEDOUT) {
423             return error;
424         }
425     }
426     *reply = NULL;
427     return ETIMEDOUT;
428 }
429
430 /* Returns the path of the server socket to which 'client' is connected.  The
431  * caller must not modify or free the returned string. */
432 const char *
433 vlog_client_target(const struct vlog_client *client)
434 {
435     return client->connect_path;
436 }
437 \f
438 /* Helper functions. */
439
440 /* Stores in '*un' a sockaddr_un that refers to file 'name'.  Stores in
441  * '*un_len' the size of the sockaddr_un. */
442 static void
443 make_sockaddr_un(const char *name, struct sockaddr_un* un, socklen_t *un_len)
444 {
445     un->sun_family = AF_UNIX;
446     strncpy(un->sun_path, name, sizeof un->sun_path);
447     un->sun_path[sizeof un->sun_path - 1] = '\0';
448     *un_len = (offsetof(struct sockaddr_un, sun_path)
449                 + strlen (un->sun_path) + 1);
450 }
451
452 /* Creates a Unix domain datagram socket that is bound to '*bind_path' (if
453  * 'bind_path' is non-null) and connected to '*connect_path' (if 'connect_path'
454  * is non-null).  If 'nonblock' is true, the socket is made non-blocking.  If
455  * 'passcred' is true, the socket is configured to receive SCM_CREDENTIALS
456  * control messages.
457  *
458  * Returns the socket's fd if successful, otherwise a negative errno value. */
459 static int
460 make_unix_socket(bool nonblock, bool passcred UNUSED,
461                  const char *bind_path, const char *connect_path)
462 {
463     int error;
464     int fd;
465
466     fd = socket(PF_UNIX, SOCK_DGRAM, 0);
467     if (fd < 0) {
468         return -errno;
469     }
470
471     if (nonblock) {
472         int flags = fcntl(fd, F_GETFL, 0);
473         if (flags == -1) {
474             goto error;
475         }
476         if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
477             goto error;
478         }
479     }
480
481     if (bind_path) {
482         struct sockaddr_un un;
483         socklen_t un_len;
484         make_sockaddr_un(bind_path, &un, &un_len);
485         if (unlink(un.sun_path) && errno != ENOENT) {
486             fprintf(stderr, "unlinking \"%s\": %s\n",
487                     un.sun_path, strerror(errno));
488         }
489         fatal_signal_add_file_to_unlink(bind_path);
490         if (bind(fd, (struct sockaddr*) &un, un_len)
491             || fchmod(fd, S_IRWXU)) {
492             goto error;
493         }
494     }
495
496     if (connect_path) {
497         struct sockaddr_un un;
498         socklen_t un_len;
499         make_sockaddr_un(connect_path, &un, &un_len);
500         if (connect(fd, (struct sockaddr*) &un, un_len)) {
501             goto error;
502         }
503     }
504
505 #ifdef SCM_CREDENTIALS
506     if (passcred) {
507         int enable = 1;
508         if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &enable, sizeof(enable))) {
509             goto error;
510         }
511     }
512 #endif
513
514     return fd;
515
516 error:
517     if (bind_path) {
518         fatal_signal_remove_file_to_unlink(bind_path);
519     }
520     error = errno;
521     close(fd);
522     return -error;
523 }