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