1 /* Copyright (C) 2008 Board of Trustees, Leland Stanford Jr. University.
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:
10 * The above copyright notice and this permission notice shall be included in
11 * all copies or substantial portions of the Software.
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
22 #include "vlog-socket.h"
29 #include <sys/socket.h>
31 #include <sys/types.h>
33 #include "fatal-signal.h"
34 #include "poll-loop.h"
38 #ifndef SCM_CREDENTIALS
42 static int make_unix_socket(bool nonblock, bool passcred,
43 const char *bind_path, const char *connect_path);
45 /* Server for Vlog control connection. */
47 struct poll_waiter *waiter;
52 static void poll_server(int fd, short int events, void *server_);
54 /* Start listening for connections from clients and processing their
55 * requests. 'path' may be:
57 * - NULL, in which case the default socket path is used. (Only one
58 * Vlog_server_socket per process can use the default path.)
60 * - A name that does not start with '/', in which case it is appended to
61 * the default socket path.
63 * - An absolute path (starting with '/') that gives the exact name of
64 * the Unix domain socket to listen on.
66 * Returns 0 if successful, otherwise a positive errno value. If successful,
67 * sets '*serverp' to the new vlog_server, otherwise to NULL. */
69 vlog_server_listen(const char *path, struct vlog_server **serverp)
71 struct vlog_server *server = xmalloc(sizeof *server);
73 if (path && path[0] == '/') {
74 server->path = xstrdup(path);
76 server->path = xasprintf("/tmp/vlogs.%ld%s",
77 (long int) getpid(), path ? path : "");
80 server->fd = make_unix_socket(true, true, server->path, NULL);
85 fprintf(stderr, "Could not initialize vlog configuration socket: %s\n",
86 strerror(-server->fd));
93 server->waiter = poll_fd_callback(server->fd, POLLIN, poll_server, server);
101 /* Destroys 'server' and stops listening for connections. */
103 vlog_server_close(struct vlog_server *server)
106 poll_cancel(server->waiter);
108 unlink(server->path);
109 fatal_signal_remove_file_to_unlink(server->path);
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)
120 #ifdef SCM_CREDENTIALS
121 /* Read a message and control messages from 'fd'. */
122 char cred_buf[CMSG_SPACE(sizeof(struct ucred))];
127 struct cmsghdr* cmsg;
129 iov.iov_base = cmd_buf;
130 iov.iov_len = cmd_buf_size - 1;
132 memset(&msg, 0, sizeof msg);
134 msg.msg_namelen = sizeof *un;
137 msg.msg_control = cred_buf;
138 msg.msg_controllen = sizeof cred_buf;
140 n = recvmsg(server->fd, &msg, 0);
141 *un_len = msg.msg_namelen;
147 /* Ensure that the message has credentials ensuring that it was sent
148 * from the same user who started us, or by root. */
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;
162 for (i = 0; i < n_fds; i++) {
168 fprintf(stderr, "vlog: config message lacks credentials\n");
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());
177 #else /* !SCM_CREDENTIALS */
183 /* Receive a message. */
185 n = recvfrom(server->fd, cmd_buf, cmd_buf_size - 1, 0,
186 (struct sockaddr *) un, &len);
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",
200 if (!S_ISSOCK(s.st_mode)) {
201 fprintf(stderr, "vlog: config message not from a socket\n");
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");
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());
215 #endif /* !SCM_CREDENTIALS */
218 /* Processes incoming requests for 'server'. */
220 poll_server(int fd UNUSED, short int events, void *server_)
222 struct vlog_server *server = server_;
225 struct sockaddr_un un;
230 error = recv_with_creds(server, cmd_buf, sizeof cmd_buf, &un, &un_len);
232 if (error != EAGAIN && error != EWOULDBLOCK) {
233 fprintf(stderr, "vlog: reading configuration socket: %s",
237 } else if (error < 0) {
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();
248 reply = xstrdup("nak");
250 sendto(server->fd, reply, strlen(reply), 0,
251 (struct sockaddr*) &un, un_len);
254 server->waiter = poll_fd_callback(server->fd, POLLIN, poll_server, server);
257 /* Client for Vlog control connection. */
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.
271 * Returns 0 if successful, otherwise a positive errno value. If successful,
272 * sets '*clientp' to the new vlog_client, otherwise to NULL. */
274 vlog_client_connect(const char *path, struct vlog_client **clientp)
276 struct vlog_client *client;
279 client = xmalloc(sizeof *client);
280 client->connect_path = (path[0] == '/'
282 : xasprintf("/tmp/vlogs.%s", path));
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);
293 free(client->connect_path);
294 free(client->bind_path);
301 /* Destroys 'client'. */
303 vlog_client_close(struct vlog_client *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);
315 /* Sends 'request' to the server socket that 'client' is connected to. Returns
316 * 0 if successful, otherwise a positive errno value. */
318 vlog_client_send(struct vlog_client *client, const char *request)
320 #ifdef SCM_CREDENTIALS
323 char buf[CMSG_SPACE(sizeof cred)];
325 struct cmsghdr* cmsg;
332 iov.iov_base = (void*) request;
333 iov.iov_len = strlen(request);
335 memset(&msg, 0, sizeof msg);
338 msg.msg_control = buf;
339 msg.msg_controllen = sizeof buf;
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;
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 */
353 return nbytes == strlen(request) ? 0 : ENOBUFS;
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. */
364 vlog_client_recv(struct vlog_client *client, char **reply)
375 nfds = poll(&pfd, 1, 1000);
378 } else if (nfds < 0) {
382 nbytes = read(client->fd, buffer, sizeof buffer - 1);
386 buffer[nbytes] = '\0';
387 *reply = xstrdup(buffer);
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. */
396 vlog_client_transact(struct vlog_client *client,
397 const char *request, char **reply)
401 /* Retry up to 3 times. */
402 for (i = 0; i < 3; ++i) {
403 int error = vlog_client_send(client, request);
408 error = vlog_client_recv(client, reply);
409 if (error != ETIMEDOUT) {
417 /* Returns the path of the server socket to which 'client' is connected. The
418 * caller must not modify or free the returned string. */
420 vlog_client_target(const struct vlog_client *client)
422 return client->connect_path;
425 /* Helper functions. */
427 /* Stores in '*un' a sockaddr_un that refers to file 'name'. Stores in
428 * '*un_len' the size of the sockaddr_un. */
430 make_sockaddr_un(const char *name, struct sockaddr_un* un, socklen_t *un_len)
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);
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
445 * Returns the socket's fd if successful, otherwise a negative errno value. */
447 make_unix_socket(bool nonblock, bool passcred UNUSED,
448 const char *bind_path, const char *connect_path)
453 fd = socket(PF_UNIX, SOCK_DGRAM, 0);
459 int flags = fcntl(fd, F_GETFL, 0);
463 if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
469 struct sockaddr_un un;
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));
476 fatal_signal_add_file_to_unlink(bind_path);
477 if (bind(fd, (struct sockaddr*) &un, un_len)
478 || fchmod(fd, S_IRWXU)) {
484 struct sockaddr_un un;
486 make_sockaddr_un(connect_path, &un, &un_len);
487 if (connect(fd, (struct sockaddr*) &un, un_len)) {
492 #ifdef SCM_CREDENTIALS
495 if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &enable, sizeof(enable))) {
505 fatal_signal_remove_file_to_unlink(bind_path);