/* * proxy_svr.c: root suid proxy server for Xen access to APIs with no * side effects from unauthenticated clients. * * Copyright (C) 2006, 2007, 2008 Red Hat, Inc. * * See COPYING.LIB for the License of this software * * Daniel Veillard */ #include #include #ifdef WITH_XEN #include #include #include #include #include #include #include #include #include "internal.h" #include "proxy_internal.h" #include "util.h" #include "xen_internal.h" #include "xend_internal.h" #include "xs_internal.h" #include "xen_unified.h" /* * This is provided in libvirt.c when the code is part of the library */ int debugFlag = 0; static int fdServer = -1; static int debug = 0; static int persist = 0; static int done = 0; #define MAX_CLIENT 64 static int nbClients = 0; /* client 0 is the unix listen socket */ static struct pollfd pollInfos[MAX_CLIENT + 1]; static virConnect conninfos; static virConnectPtr conn = &conninfos; static unsigned long xenVersion = 0; /************************************************************************ * * * Interfaces with the Xen hypervisor * * * ************************************************************************/ /** * proxyInitXen: * * Initialize the communication layer with Xen * * Returns 0 or -1 in case of error */ static int proxyInitXen(void) { int ret; unsigned long xenVersion2; xenUnifiedPrivatePtr priv; /* Allocate per-connection private data. */ priv = malloc (sizeof *priv); if (!priv) { fprintf(stderr, "Failed to allocate private data\n"); return(-1); } conn->privateData = priv; priv->handle = -1; priv->xendConfigVersion = -1; priv->type = -1; priv->len = -1; priv->addr = NULL; priv->xshandle = NULL; priv->proxy = -1; ret = xenHypervisorOpen(conn, NULL, NULL, 0); if (ret < 0) { fprintf(stderr, "Failed to open Xen hypervisor\n"); return(-1); } else { ret = xenHypervisorGetVersion(conn, &xenVersion); if (ret != 0) { fprintf(stderr, "Failed to get Xen hypervisor version\n"); return(-1); } } ret = xenDaemonOpen_unix(conn, "/var/lib/xend/xend-socket"); if (ret < 0) { fprintf(stderr, "Failed to connect to Xen daemon\n"); return(-1); } ret = xenStoreOpen(conn, NULL, NULL, VIR_CONNECT_RO); if (ret < 0) { fprintf(stderr, "Failed to open XenStore connection"); return (-1); } ret = xenDaemonGetVersion(conn, &xenVersion2); if (ret != 0) { fprintf(stderr, "Failed to get Xen daemon version\n"); return(-1); } if (debug) fprintf(stderr, "Connected to hypervisor %lu and daemon %lu\n", xenVersion, xenVersion2); if (xenVersion2 > xenVersion) xenVersion = xenVersion2; return(0); } /************************************************************************ * * * Processing of the unix socket to listen for clients * * * ************************************************************************/ /** * proxyCloseUnixSocket: * * close the unix socket * * Returns 0 or -1 in case of error */ static int proxyCloseUnixSocket(void) { int ret; if (fdServer < 0) return(0); ret = close(fdServer); if (debug > 0) fprintf(stderr, "closing unix socket %d: %d\n", fdServer, ret); fdServer = -1; pollInfos[0].fd = -1; return(ret); } /** * proxyListenUnixSocket: * @path: the filename for the socket * * create a new abstract socket based on that path and listen on it * * Returns the associated file descriptor or -1 in case of failure */ static int proxyListenUnixSocket(const char *path) { int fd; struct sockaddr_un addr; if (fdServer >= 0) return(fdServer); fd = socket(PF_UNIX, SOCK_STREAM, 0); if (fd < 0) { fprintf(stderr, "Failed to create unix socket"); return(-1); } /* * Abstract socket do not hit the filesystem, way more secure and * guaranteed to be atomic */ memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; addr.sun_path[0] = '\0'; strncpy(&addr.sun_path[1], path, (sizeof(addr) - 4) - 2); /* * now bind the socket to that address and listen on it */ if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { fprintf(stderr, "Failed to bind to socket %s\n", path); close(fd); return (-1); } if (listen(fd, 30 /* backlog */ ) < 0) { fprintf(stderr, "Failed to listen to socket %s\n", path); close(fd); return (-1); } if (debug > 0) fprintf(stderr, "opened and bound unix socket %d\n", fd); fdServer = fd; pollInfos[0].fd = fd; pollInfos[0].events = POLLIN | POLLERR | POLLHUP | POLLNVAL; return (fd); } /** * proxyAcceptClientSocket: * * Process a request to the unix socket * * Returns the filedescriptor of the new client or -1 in case of error */ static int proxyAcceptClientSocket(void) { int client; socklen_t client_addrlen; struct sockaddr client_addr; retry: client_addrlen = sizeof(client_addr); client = accept(pollInfos[0].fd, &client_addr, &client_addrlen); if (client < 0) { if (errno == EINTR) { if (debug > 0) fprintf(stderr, "accept connection on socket %d interrupted\n", pollInfos[0].fd); goto retry; } fprintf(stderr, "Failed to accept incoming connection on socket %d\n", pollInfos[0].fd); done = 1; return(-1); } if (nbClients >= MAX_CLIENT) { fprintf(stderr, "Too many client registered\n"); close(client); return(-1); } nbClients++; pollInfos[nbClients].fd = client; pollInfos[nbClients].events = POLLIN | POLLERR | POLLHUP | POLLNVAL; if (debug > 0) fprintf(stderr, "accept connection on socket %d for client %d\n", client, nbClients); return(client); } /************************************************************************ * * * Processing of client sockets * * * ************************************************************************/ /** * proxyCloseClientSocket: * @nr: client number * * Close the socket from that client, and recompact the pollInfo array * * Returns 0 in case of success and -1 in case of error */ static int proxyCloseClientSocket(int nr) { int ret; ret = close(pollInfos[nr].fd); if (ret != 0) fprintf(stderr, "Failed to close socket %d from client %d\n", pollInfos[nr].fd, nr); else if (debug > 0) fprintf(stderr, "Closed socket %d from client %d\n", pollInfos[nr].fd, nr); if (nr < nbClients) { memmove(&pollInfos[nr], &pollInfos[nr + 1], (nbClients - nr) * sizeof(pollInfos[0])); } nbClients--; return(ret); } /** * proxyCloseClientSockets: * * Close all the sockets from the clients */ static void proxyCloseClientSockets(void) { int i, ret; for (i = 1;i <= nbClients;i++) { ret = close(pollInfos[i].fd); if (ret != 0) fprintf(stderr, "Failed to close socket %d from client %d\n", pollInfos[i].fd, i); else if (debug > 0) fprintf(stderr, "Closed socket %d from client %d\n", pollInfos[i].fd, i); } nbClients = 0; } /** * proxyWriteClientSocket: * @nr: the client number * @req: pointer to the packet * * Send back a packet to the client. If it seems write would be blocking * then try to disconnect from it. * * Return 0 in case of success and -1 in case of error. */ static int proxyWriteClientSocket(int nr, virProxyPacketPtr req) { int ret; if ((nr <= 0) || (nr > nbClients) || (req == NULL) || (req->len < sizeof(virProxyPacket)) || (req->len > sizeof(virProxyFullPacket)) || (pollInfos[nr].fd < 0)) { fprintf(stderr, "write to client %d in error", nr); proxyCloseClientSocket(nr); return(-1); } ret = safewrite(pollInfos[nr].fd, (char *) req, req->len); if (ret < 0) { fprintf(stderr, "write %d bytes to socket %d from client %d failed\n", req->len, pollInfos[nr].fd, nr); proxyCloseClientSocket(nr); return(-1); } if (ret == 0) { if (debug) fprintf(stderr, "end of stream from client %d on socket %d\n", nr, pollInfos[nr].fd); proxyCloseClientSocket(nr); return(-1); } if (ret != req->len) { fprintf(stderr, "write %d of %d bytes to socket %d from client %d\n", ret, req->len, pollInfos[nr].fd, nr); proxyCloseClientSocket(nr); return(-1); } if (debug) fprintf(stderr, "wrote %d bytes to client %d on socket %d\n", ret, nr, pollInfos[nr].fd); return(0); } /** * proxyReadClientSocket: * @nr: the client number * * Process a read from a client socket */ static int proxyReadClientSocket(int nr) { virProxyFullPacket request; virProxyPacketPtr req = (virProxyPacketPtr) &request; int ret; char *xml, *ostype; retry: ret = read(pollInfos[nr].fd, req, sizeof(virProxyPacket)); if (ret < 0) { if (errno == EINTR) { if (debug > 0) fprintf(stderr, "read socket %d from client %d interrupted\n", pollInfos[nr].fd, nr); goto retry; } fprintf(stderr, "Failed to read socket %d from client %d\n", pollInfos[nr].fd, nr); proxyCloseClientSocket(nr); return(-1); } if (ret == 0) { if (debug) fprintf(stderr, "end of stream from client %d on socket %d\n", nr, pollInfos[nr].fd); proxyCloseClientSocket(nr); return(-1); } if (debug) fprintf(stderr, "read %d bytes from client %d on socket %d\n", ret, nr, pollInfos[nr].fd); if ((req->version != PROXY_PROTO_VERSION) || (req->len < sizeof(virProxyPacket)) || (req->len > sizeof(virProxyFullPacket))) goto comm_error; if (debug) fprintf(stderr, "Got command %d from client %d\n", req->command, nr); /* * complete reading the packet. * TODO: we should detect when blocking and abort connection if this happen */ if (req->len > ret) { int total, extra; char *base = (char *) &request; total = ret; while (total < req->len) { extra = req->len - total; retry2: ret = read(pollInfos[nr].fd, base + total, extra); if (ret < 0) { if (errno == EINTR) { if (debug > 0) fprintf(stderr, "read socket %d from client %d interrupted\n", pollInfos[nr].fd, nr); goto retry2; } fprintf(stderr, "Failed to read socket %d from client %d\n", pollInfos[nr].fd, nr); proxyCloseClientSocket(nr); return(-1); } if (ret == 0) { if (debug) fprintf(stderr, "end of stream from client %d on socket %d\n", nr, pollInfos[nr].fd); proxyCloseClientSocket(nr); return(-1); } total += ret; } } switch (req->command) { case VIR_PROXY_NONE: if (req->len != sizeof(virProxyPacket)) goto comm_error; break; case VIR_PROXY_VERSION: if (req->len != sizeof(virProxyPacket)) goto comm_error; req->data.larg = xenVersion; break; case VIR_PROXY_LIST: { int maxids; if (req->len != sizeof(virProxyPacket)) goto comm_error; maxids = sizeof(request.extra.arg) / sizeof(int); ret = xenHypervisorListDomains(conn, &request.extra.arg[0], maxids); if (ret < 0) { req->len = sizeof(virProxyPacket); req->data.arg = 0; } else { req->len = sizeof(virProxyPacket) + ret * sizeof(int); req->data.arg = ret; } break; } case VIR_PROXY_NUM_DOMAIN: if (req->len != sizeof(virProxyPacket)) goto comm_error; req->data.arg = xenHypervisorNumOfDomains(conn); break; case VIR_PROXY_MAX_MEMORY: if (req->len != sizeof(virProxyPacket)) goto comm_error; req->data.larg = xenHypervisorGetDomMaxMemory(conn, req->data.arg); break; case VIR_PROXY_DOMAIN_INFO: if (req->len != sizeof(virProxyPacket)) goto comm_error; memset(&request.extra.dinfo, 0, sizeof(virDomainInfo)); ret = xenHypervisorGetDomInfo(conn, req->data.arg, &request.extra.dinfo); if (ret < 0) { req->data.arg = -1; } else { req->len += sizeof(virDomainInfo); } break; case VIR_PROXY_LOOKUP_ID: { char *name = NULL; unsigned char uuid[VIR_UUID_BUFLEN]; int len; if (req->len != sizeof(virProxyPacket)) goto comm_error; if (xenDaemonDomainLookupByID(conn, req->data.arg, &name, uuid) < 0) { req->data.arg = -1; } else { len = strlen(name); if (len > 1000) { len = 1000; name[1000] = 0; } req->len += VIR_UUID_BUFLEN + len + 1; memcpy(&request.extra.str[0], uuid, VIR_UUID_BUFLEN); strcpy(&request.extra.str[VIR_UUID_BUFLEN], name); } free(name); break; } case VIR_PROXY_LOOKUP_UUID: { char **names; char **tmp; int ident, len; char *name = NULL; unsigned char uuid[VIR_UUID_BUFLEN]; if (req->len != sizeof(virProxyPacket) + VIR_UUID_BUFLEN) goto comm_error; /* * Xend API forces to collect the full domain list by names, and * then query each of them until the id is found */ names = xenDaemonListDomainsOld(conn); tmp = names; if (names != NULL) { while (*tmp != NULL) { ident = xenDaemonDomainLookupByName_ids(conn, *tmp, &uuid[0]); if (!memcmp(uuid, &request.extra.str[0], VIR_UUID_BUFLEN)) { name = *tmp; break; } tmp++; } } if (name == NULL) { /* not found */ req->data.arg = -1; req->len = sizeof(virProxyPacket); } else { len = strlen(name); if (len > 1000) { len = 1000; name[1000] = 0; } req->len = sizeof(virProxyPacket) + len + 1; strcpy(&request.extra.str[0], name); req->data.arg = ident; } free(names); break; } case VIR_PROXY_LOOKUP_NAME: { int ident; unsigned char uuid[VIR_UUID_BUFLEN]; if (req->len > sizeof(virProxyPacket) + 1000) goto comm_error; ident = xenDaemonDomainLookupByName_ids(conn, &request.extra.str[0], &uuid[0]); if (ident < 0) { /* not found */ req->data.arg = -1; req->len = sizeof(virProxyPacket); } else { req->len = sizeof(virProxyPacket) + VIR_UUID_BUFLEN; memcpy(&request.extra.str[0], uuid, VIR_UUID_BUFLEN); req->data.arg = ident; } break; } case VIR_PROXY_NODE_INFO: if (req->len != sizeof(virProxyPacket)) goto comm_error; /* * Hum, could we expect those information to be unmutable and * cache them ? Since it's probably an unfrequent call better * not make assumption and do the xend RPC each call. */ ret = xenDaemonNodeGetInfo(conn, &request.extra.ninfo); if (ret < 0) { req->data.arg = -1; req->len = sizeof(virProxyPacket); } else { req->data.arg = 0; req->len = sizeof(virProxyPacket) + sizeof(virNodeInfo); } break; case VIR_PROXY_GET_CAPABILITIES: if (req->len != sizeof(virProxyPacket)) goto comm_error; xml = xenHypervisorGetCapabilities (conn); if (!xml) { req->data.arg = -1; req->len = sizeof (virProxyPacket); } else { int xmllen = strlen (xml); if (xmllen > (int) sizeof (request.extra.str)) { req->data.arg = -2; req->len = sizeof (virProxyPacket); } else { req->data.arg = 0; memmove (request.extra.str, xml, xmllen); req->len = sizeof (virProxyPacket) + xmllen; } free (xml); } break; case VIR_PROXY_DOMAIN_XML: if (req->len != sizeof(virProxyPacket)) goto comm_error; /* * Ideally we should get the CPUs used by the domain * but that information is really node specific and it * rather hard to get from that code path. So proxy * users won't see CPU pinning (last NULL arg) */ xml = xenDaemonDomainDumpXMLByID(conn, request.data.arg, 0, NULL); if (!xml) { req->data.arg = -1; req->len = sizeof(virProxyPacket); } else { int xmllen = strlen(xml); if (xmllen > (int) sizeof(request.extra.str)) { req->data.arg = -2; req->len = sizeof(virProxyPacket); } else { req->data.arg = 0; memmove(&request.extra.str[0], xml, xmllen); req->len = sizeof(virProxyPacket) + xmllen; } free(xml); } break; case VIR_PROXY_DOMAIN_OSTYPE: if (req->len != sizeof(virProxyPacket)) goto comm_error; ostype = xenStoreDomainGetOSTypeID(conn, request.data.arg); if (!ostype) { req->data.arg = -1; req->len = sizeof(virProxyPacket); } else { int ostypelen = strlen(ostype); if (ostypelen > (int) sizeof(request.extra.str)) { req->data.arg = -2; req->len = sizeof(virProxyPacket); } else { req->data.arg = 0; memmove(&request.extra.str[0], ostype, ostypelen); req->len = sizeof(virProxyPacket) + ostypelen; } free(ostype); } break; default: goto comm_error; } ret = proxyWriteClientSocket(nr, req); return(ret); comm_error: fprintf(stderr, "Communication error with client %d: malformed packet\n", nr); proxyCloseClientSocket(nr); return(-1); } /************************************************************************ * * * Main loop processing * * * ************************************************************************/ /** * proxyProcessRequests: * * process requests and timers */ static void proxyProcessRequests(void) { int exit_timeout = 30; int ret, i; while (!done) { /* * wait for requests, with a one second timeout */ ret = poll(&pollInfos[0], nbClients + 1, 1000); if (ret == 0) { /* timeout */ if ((nbClients == 0) && (persist == 0)) { exit_timeout--; if (exit_timeout == 0) { done = 1; if (debug > 0) { fprintf(stderr, "Exiting after 30s without clients\n"); } } } else exit_timeout = 30; if (debug > 1) fprintf(stderr, "poll timeout\n"); continue; } else if (ret < 0) { if (errno == EINTR) { if (debug > 0) fprintf(stderr, "poll syscall interrupted\n"); continue; } fprintf(stderr, "poll syscall failed\n"); break; } /* * there have been I/O to process */ exit_timeout = 30; if (pollInfos[0].revents != 0) { if (pollInfos[0].revents & POLLIN) { proxyAcceptClientSocket(); } else { fprintf(stderr, "Got an error %d on incoming socket %d\n", pollInfos[0].revents, pollInfos[0].fd); break; } } /* * process the clients in reverse order since on error or disconnect * pollInfos is compacted to remove the given client. */ for (i = nbClients;i > 0;i--) { if (pollInfos[i].revents & POLLIN) { proxyReadClientSocket(i); } else if (pollInfos[i].revents != 0) { fprintf(stderr, "Got an error %d on client %d socket %d\n", pollInfos[i].revents, i, pollInfos[i].fd); proxyCloseClientSocket(i); } } } } /** * proxyMainLoop: * * main loop for the proxy, continually try to keep the unix socket * open, serve client requests, and process timing events. */ static void proxyMainLoop(void) { while (! done) { if (proxyListenUnixSocket(PROXY_SOCKET_PATH) < 0) break; proxyProcessRequests(); } proxyCloseClientSockets(); } /** * usage: * * dump on stdout information about the program */ static void usage(const char *progname) { printf("Usage: %s [-v] [-v]\n", progname); printf(" option -v increase the verbosity level for debugging\n"); printf("This is a proxy for xen services used by libvirt to offer\n"); printf("safe and fast status information on the Xen virtualization.\n"); printf("This need not be run manually it's started automatically.\n"); } /** * main: * * Check that we are running with root privileges, initialize the * connections to the daemon and or hypervisor, and then run the main loop */ int main(int argc, char **argv) { int i; if (!setlocale(LC_ALL, "")) { perror("setlocale"); return -1; } if (!bindtextdomain(GETTEXT_PACKAGE, LOCALEBASEDIR)) { perror("bindtextdomain"); return -1; } if (!textdomain(GETTEXT_PACKAGE)) { perror("textdomain"); return -1; } for (i = 1; i < argc; i++) { if (STREQ(argv[i], "-v")) { debug++; } else if (STREQ(argv[i], "-no-timeout")) { persist = 1; } else { usage(argv[0]); exit(1); } } if (geteuid() != 0) { fprintf(stderr, "%s must be run as root or suid\n", argv[0]); /* exit(1); */ } /* * setup a connection block */ memset(conn, 0, sizeof(conninfos)); conn->magic = VIR_CONNECT_MAGIC; /* * very fist thing, use the socket as an exclusive lock, this then * allow to do timed exits, avoiding constant CPU usage in case of * failure. */ if (proxyListenUnixSocket(PROXY_SOCKET_PATH) < 0) exit(0); if (proxyInitXen() == 0) proxyMainLoop(); sleep(1); proxyCloseUnixSocket(); exit(0); } #else /* WITHOUT_XEN */ int main(void) { fprintf(stderr, "libvirt was compiled without Xen support\n"); exit(1); } #endif /* WITH_XEN */