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