/* Copyright (C) 2009 Intel Corporation Author: Andi Kleen Simple event-driven unix network server for client access. Process commands and buffer output. mcelog is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2. mcelog is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should find a copy of v2 of the GNU General Public License somewhere on your Linux system; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define _GNU_SOURCE 1 #include #include #include #include #include #include #include #include #include #include #include #include #include "mcelog.h" #include "server.h" #include "eventloop.h" #include "config.h" #include "memdb.h" #include "memutil.h" #include "paths.h" #include "page.h" #define PAIR(x) x, sizeof(x)-1 struct clientcon { char *inbuf; /* 0 terminated */ char *inptr; char *outbuf; size_t outcur; size_t outlen; }; static char *client_path = SOCKET_PATH; static int initial_ping_timeout = 2; static struct config_cred acc = { .uid = 0, .gid = -1U }; static void free_outbuf(struct clientcon *cc) { free(cc->outbuf); cc->outbuf = NULL; cc->outcur = cc->outlen = 0; } static void free_inbuf(struct clientcon *cc) { free(cc->inbuf); cc->inbuf = NULL; cc->inptr = NULL; } static void free_cc(struct clientcon *cc) { free(cc->outbuf); cc->outbuf = NULL; free(cc->inbuf); cc->inbuf = NULL; free(cc); cc = NULL; } static void sendstring(int fd, char *str) { (void)send(fd, str, strlen(str), MSG_DONTWAIT|MSG_NOSIGNAL); } static void dispatch_dump(FILE *fh, char *s) { char *p; enum printflags printflags = 0; while ((p = strsep(&s, " ")) != NULL) { if (!strcmp(p, "dump")) ; else if (!strcmp(p, "bios")) printflags |= DUMP_BIOS; else if (!strcmp(p, "all")) printflags |= DUMP_ALL; else fprintf(fh, "Unknown dump parameter\n"); } dump_memory_errors(fh, printflags); fprintf(fh, "done\n"); } static void dispatch_pages(FILE *fh) { dump_page_errors(fh); fprintf(fh, "done\n"); } static void dispatch_commands(char *line, FILE *fh) { char *s; while ((s = strsep(&line, "\n")) != NULL) { while (isspace(*s)) line++; if (!strncmp(s, "dump", 4)) dispatch_dump(fh, s); else if (!strncmp(s, "pages", 5)) dispatch_pages(fh); else if (!strcmp(s, "ping")) fprintf(fh, "pong\n"); else if (*s != 0) fprintf(fh, "Unknown command\n"); } } /* assumes commands don't cross records */ static void process_cmd(struct clientcon *cc) { FILE *fh; assert(cc->outbuf == NULL); fh = open_memstream(&cc->outbuf, &cc->outlen); if (!fh) Enomem(); cc->outcur = 0; dispatch_commands(cc->inbuf, fh); if (ferror(fh) || fclose(fh) != 0) Enomem(); } /* check if client is allowed to access */ static int access_check(int fd, struct msghdr *msg) { struct cmsghdr *cmsg; struct ucred *uc; /* check credentials */ cmsg = CMSG_FIRSTHDR(msg); if (cmsg == NULL || cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_CREDENTIALS) { Eprintf("Did not receive credentials over client unix socket %p\n", cmsg); return -1; } uc = (struct ucred *)CMSG_DATA(cmsg); if (uc->uid == 0 || (acc.uid != -1U && uc->uid == acc.uid) || (acc.gid != -1U && uc->gid == acc.gid)) return 0; Eprintf("rejected client access from pid:%u uid:%u gid:%u\n", uc->pid, uc->uid, uc->gid); sendstring(fd, "permission denied\n"); return -1; } /* retrieve commands from client */ static int client_input(int fd, struct clientcon *cc) { char ctlbuf[CMSG_SPACE(sizeof(struct ucred))]; struct iovec miov; struct msghdr msg = { .msg_iov = &miov, .msg_iovlen = 1, .msg_control = ctlbuf, .msg_controllen = sizeof(ctlbuf), }; int n, n2; assert(cc->inbuf == NULL); if (ioctl(fd, FIONREAD, &n) < 0) return -1; if (n == 0) return 0; cc->inbuf = xalloc_nonzero(n + 1); cc->inbuf[n] = 0; cc->inptr = cc->inbuf; miov.iov_base = cc->inbuf; miov.iov_len = n; n2 = recvmsg(fd, &msg, 0); if (n2 < n) return -1; return access_check(fd, &msg) == 0 ? n : -1; } /* process input/out on client socket */ static void client_event(struct pollfd *pfd, void *data) { int events = pfd->revents; struct clientcon *cc = (struct clientcon *)data; int n; if (events & ~(POLLIN|POLLOUT)) /* error/close */ goto error; if (events & POLLOUT) { if (cc->outcur < cc->outlen) { n = send(pfd->fd, cc->outbuf + cc->outcur, cc->outlen - cc->outcur, MSG_DONTWAIT|MSG_NOSIGNAL); if (n < 0) { /* EAGAIN here? but should not happen */ goto error; } cc->outcur += n; } if (cc->outcur == cc->outlen) free_outbuf(cc); } if (events & POLLIN) { n = client_input(pfd->fd, cc); if (n < 0) goto error; process_cmd(cc); free_inbuf(cc); } pfd->events = cc->outbuf ? POLLOUT : POLLIN; return; error: if (pfd->revents & POLLERR) SYSERRprintf("error while reading from client"); close(pfd->fd); unregister_pollcb(pfd); free_cc(cc); } /* accept a new client */ static void client_accept(struct pollfd *pfd, void *data) { struct clientcon *cc = NULL; int nfd = accept(pfd->fd, NULL, 0); int on; if (nfd < 0) { SYSERRprintf("accept failed on client socket"); return; } on = 1; if (setsockopt(nfd, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)) < 0) { SYSERRprintf("Cannot enable credentials passing on client socket"); goto cleanup; } cc = xalloc(sizeof(struct clientcon)); if (register_pollcb(nfd, POLLIN, client_event, cc) < 0) { sendstring(nfd, "mcelog server too busy\n"); goto cleanup; } return; cleanup: free(cc); cc = NULL; close(nfd); } static void server_config(void) { char *s; long v; config_cred("server", "client", &acc); if ((s = config_string("server", "socket-path")) != NULL) client_path = s; if (config_number("server", "initial-ping-timeout", "%u", &v) == 0) initial_ping_timeout = v; } static sigjmp_buf ping_timeout_ctx; static void ping_timeout(int sig) { siglongjmp(ping_timeout_ctx, 1); } /* server still running? */ static int server_ping(struct sockaddr_un *un) { struct sigaction oldsa; struct sigaction sa = { .sa_handler = ping_timeout }; int ret, n; char buf[10]; int fd = socket(PF_UNIX, SOCK_STREAM, 0); if (fd < 0) return 0; sigaction(SIGALRM, &sa, &oldsa); if (sigsetjmp(ping_timeout_ctx, 1) == 0) { ret = -1; alarm(initial_ping_timeout); if (connect(fd, un, sizeof(struct sockaddr_un)) < 0) goto cleanup; if (write(fd, PAIR("ping\n")) < 0) goto cleanup; if ((n = read(fd, buf, 10)) < 0) goto cleanup; if (n == 5 && !memcmp(buf, "pong\n", 5)) ret = 0; } else ret = -1; cleanup: sigaction(SIGALRM, &oldsa, NULL); alarm(0); close(fd); return ret; } void server_setup(void) { int fd; struct sockaddr_un adr; int on; server_config(); if (client_path[0] == 0) return; if (strlen(client_path) >= sizeof(adr.sun_path) - 1) { Eprintf("Client socket path `%s' too long for unix socket", client_path); return; } memset(&adr, 0, sizeof(struct sockaddr_un)); adr.sun_family = AF_UNIX; strncpy(adr.sun_path, client_path, sizeof(adr.sun_path) - 1); if (access(client_path, F_OK) == 0) { if (server_ping(&adr) == 0) { Eprintf("mcelog server already running\n"); exit(1); } unlink(client_path); } fd = socket(PF_UNIX, SOCK_STREAM, 0); if (fd < 0) { SYSERRprintf("cannot open listening socket"); return; } if (bind(fd, (struct sockaddr *)&adr, sizeof(struct sockaddr_un)) < 0) { SYSERRprintf("Cannot bind to client unix socket `%s'", client_path); goto cleanup; } listen(fd, 10); /* Set SO_PASSCRED to avoid race with client connecting too fast */ /* Ignore error for old kernels */ on = 1; setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)); register_pollcb(fd, POLLIN, client_accept, NULL); return; cleanup: close(fd); exit(1); }