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