diff options
Diffstat (limited to 'open-vm-tools/vgauth/serviceImpl/netPosix.c')
-rw-r--r-- | open-vm-tools/vgauth/serviceImpl/netPosix.c | 513 |
1 files changed, 513 insertions, 0 deletions
diff --git a/open-vm-tools/vgauth/serviceImpl/netPosix.c b/open-vm-tools/vgauth/serviceImpl/netPosix.c new file mode 100644 index 00000000..4350f449 --- /dev/null +++ b/open-vm-tools/vgauth/serviceImpl/netPosix.c @@ -0,0 +1,513 @@ +/********************************************************* + * Copyright (C) 2011-2015 VMware, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation version 2.1 and no later version. + * + * This program 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 Lesser GNU General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + *********************************************************/ + +/** + * @file netPosix.c -- + * + * Networking interfaces for posix systems. + */ + + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <unistd.h> +#include <sys/unistd.h> +#include <sys/un.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <fcntl.h> +#include "serviceInt.h" +#include "VGAuthProto.h" + + +/* + ****************************************************************************** + * ServiceNetworkListen -- */ /** + * + * Creates the UNIX domain socket and starts listening on it. + * + * @param[in] conn The ServiceConnection describing the connection. + * @param[in] makeSecure If set, the new pipe is restricted in access to + * the userName in the ServiceConnection. + * + * @return VGAUTH_E_OK on success, VGAuthError on failure + * + ****************************************************************************** + */ + +VGAuthError +ServiceNetworkListen(ServiceConnection *conn, // IN/OUT + gboolean makeSecure) +{ + int sock = -1; + struct sockaddr_un sockaddr; + int ret; + VGAuthError err = VGAUTH_E_OK; + mode_t mode; + struct stat stbuf; + uid_t uid; + gid_t gid; + char *socketDir = NULL; + + /* + * For some reason, this is simply hardcoded in sys/un.h + */ +#ifndef UNIX_PATH_MAX +#define UNIX_PATH_MAX 108 +#endif + + ASSERT(strlen(conn->pipeName) < UNIX_PATH_MAX); + + conn->sock = -1; + + /* + * Make sure the socket dir exists. In theory this is only ever done once, + * but something could clobber it. + */ + socketDir = g_path_get_dirname(SERVICE_PUBLIC_PIPE_NAME); + ASSERT(socketDir != NULL); + + /* + * Punt if its there but not a directory. + * + * g_file_test() on a symlink will return true for IS_DIR + * if it's a link pointing to a directory, since glib + * uses stat() instead of lstat(). + */ + if (g_file_test(socketDir, G_FILE_TEST_EXISTS) && + (!g_file_test(socketDir, G_FILE_TEST_IS_DIR) || + g_file_test(socketDir, G_FILE_TEST_IS_SYMLINK))) { + err = VGAUTH_E_COMM; + Warning("%s: socket dir path '%s' already exists as a non-directory; " + "aborting\n", __FUNCTION__, socketDir); + goto abort; + } + + /* + * XXX May want to add some security checks here. + */ + if (!g_file_test(socketDir, G_FILE_TEST_EXISTS)) { + ret = ServiceFileMakeDirTree(socketDir, 0755); + if (ret < 0) { + err = VGAUTH_E_COMM; + Warning("%s: failed to create socket dir '%s' %d\n", + __FUNCTION__, socketDir, ret); + goto abort; + } + Log("%s: Created socket directory '%s'\n", __FUNCTION__, socketDir); + } + + sock = socket(PF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + err = VGAUTH_E_COMM; + Warning("%s: socket() failed, %d\n", __FUNCTION__, errno); + goto abort; + } + + sockaddr.sun_family = PF_UNIX; + + g_unlink(conn->pipeName); + + g_strlcpy(sockaddr.sun_path, conn->pipeName, UNIX_PATH_MAX); + + ret = bind(sock, (struct sockaddr *) &sockaddr, sizeof sockaddr); + if (ret < 0) { + err = VGAUTH_E_COMM; + Warning("%s: bind(%s) failed, %d\n", __FUNCTION__, conn->pipeName, errno); + goto abort; + } + + /* + * Adjust security as needed. + */ + if (stat(conn->pipeName, &stbuf) < 0) { + err = VGAUTH_E_COMM; + Warning("%s: stat(%s) failed, %d\n", __FUNCTION__, conn->pipeName, errno); + goto abort; + } + + mode = stbuf.st_mode; + mode &= ~(S_IRWXU | S_IRWXG | S_IRWXO); + if (makeSecure) { + mode = (S_IRUSR | S_IWUSR); + } else { + mode = (S_IRUSR | S_IWUSR | + S_IRGRP | S_IWGRP | + S_IROTH | S_IWOTH); + } + + if (chmod(conn->pipeName, mode) < 0) { + err = VGAUTH_E_COMM; + Warning("%s: chmod(%s) failed, %d\n", __FUNCTION__, conn->pipeName, errno); + goto abort; + } + + if (makeSecure) { + err = UsercheckLookupUser(conn->userName, &uid, &gid); + if (err != VGAUTH_E_OK) { + err = VGAUTH_E_NO_SUCH_USER; + Warning("%s: failed to get uid/gid for user '%s'\n", + __FUNCTION__, conn->userName); + goto abort; + } + if (chown(conn->pipeName, uid, gid) < 0) { + err = VGAUTH_E_COMM; + Warning("%s: chown(%s) failed, %d\n", __FUNCTION__, conn->pipeName, errno); + goto abort; + } + } + + if (fcntl(sock, F_SETFL, O_NONBLOCK) < 0) { + err = VGAUTH_E_COMM; + Warning("%s: fcntl() failed, %d\n", __FUNCTION__, errno); + goto abort; + } + + if (listen(sock, 32) < 0) { + err = VGAUTH_E_COMM; + Warning("%s: listen() failed, %d\n", __FUNCTION__, errno); + goto abort; + } + + conn->sock = sock; + + return VGAUTH_E_OK; + +abort: + g_free(socketDir); + if (sock >= 0) { + close(sock); + } + return err; +} + + +/* + ****************************************************************************** + * ServiceNetworkAcceptConnection -- */ /** + * + * Accepts a connection on a socket. + * + * @param[in] connIn The connection that owns the socket being + * accept()ed. + * @param[out] connOut The new connection. + * + * @return VGAUTH_E_OK on success, VGAuthError on failure + * + ****************************************************************************** + */ + +VGAuthError +ServiceNetworkAcceptConnection(ServiceConnection *connIn, + ServiceConnection *connOut) +{ + int newfd; + struct sockaddr_un sockaddr; + int addrlen = sizeof(struct sockaddr_un); + VGAuthError err = VGAUTH_E_OK; + + memset(&sockaddr, 0, addrlen); + newfd = accept(connIn->sock, (struct sockaddr *) &sockaddr, &addrlen); + if (newfd < 0) { + err = VGAUTH_E_COMM; + Warning("%s: accept() failed, %d\n", __FUNCTION__, errno); + goto abort; + } + + Debug("%s: got new connection on '%s', sock %d\n", + __FUNCTION__, connIn->pipeName, newfd); + + connOut->sock = newfd; + + return VGAUTH_E_OK; +abort: + return err; +} + + +/* + ****************************************************************************** + * ServiceNetworkCloseConnection -- */ /** + * + * Closes the network connection. + * + * @param[in] conn The connection to be closed. + * + ****************************************************************************** + */ + +void +ServiceNetworkCloseConnection(ServiceConnection *conn) +{ + if (conn->sock != -1) { + close(conn->sock); + } + conn->sock = -1; +} + + +/* + ****************************************************************************** + * ServiceNetworkRemoveListenPipe -- */ /** + * + * Closes the listening connection's pipe. + * + * @param[in] conn The listen connection owning the pipe to be removed. + * + ****************************************************************************** + */ + +void +ServiceNetworkRemoveListenPipe(ServiceConnection *conn) +{ + ServiceFileUnlinkFile(conn->pipeName); +} + + +/* + ****************************************************************************** + * ServiceNetworkReadData -- */ /** + * + * Reads some data off the wire. + * + * @param[in] conn The connection. + * @param[out] len How much data was read. + * @param[out] data The data. + * + * @return VGAUTH_E_OK on success, VGAuthError on failure + * + ****************************************************************************** + */ + +VGAuthError +ServiceNetworkReadData(ServiceConnection *conn, + gsize *len, // OUT + gchar **data) // OUT +{ + VGAuthError err = VGAUTH_E_OK; + int ret; +#if NETWORK_FORCE_TINY_PACKETS +#define READ_BUFSIZE 1 +#else +#define READ_BUFSIZE 10240 +#endif + char buf[READ_BUFSIZE]; + + *len = 0; + *data = NULL; + + do { + ret = recv(conn->sock, buf, sizeof(buf), 0); + if (ret == 0) { + Debug("%s: EOF on socket\n", __FUNCTION__); + conn->eof = TRUE; + return err; + } + } while (ret == -1 && errno == EINTR); + + if (ret < 0) { + Warning("%s: error %d reading from socket\n", __FUNCTION__, errno); + return VGAUTH_E_COMM; + } + + *data = g_strndup(buf, ret); + *len = ret; + + return err; +} + + +/* + ****************************************************************************** + * ServiceNetworkWriteData -- */ /** + * + * Writes some data on the wire. + * + * @param[in] conn The connection. + * @param[in] len How much data to write. + * @param[in] data The data. + * + * @return VGAUTH_E_OK on success, VGAuthError on failure + * + ****************************************************************************** + */ + +VGAuthError +ServiceNetworkWriteData(ServiceConnection *conn, + gsize len, + gchar *data) +{ + VGAuthError err = VGAUTH_E_OK; + gsize sent = 0; + int ret; + + if (len == 0) { + Debug("%s: asked to send %d bytes; bad caller?\n", __FUNCTION__, (int) len); + return err; + } + + /* + * XXX Potential DOS issue here. This could wedge if the socket + * gets full and is never drained (client asks for QueryMappedCerts, + * reply is huge, client suspends before we start sending it, + * buffer fills, service loops.) + * + * A couple potential fixes: + * - instead of writing, toss it on a queue and watch the writable + * flag on the sock and dump when we can. + * - have the current code just give up and declare the client dead + * if we get EAGAIN too many times in a row. + * + * Second fix is simpler, but might bite us in a dev situation if + * we sit in a breakpoint too long. + */ + + do { +retry: +#if NETWORK_FORCE_TINY_PACKETS + ret = send(conn->sock, data + sent, 1, 0); +#else + ret = send(conn->sock, data + sent, len - sent, 0); +#endif + if (ret < 0) { + if (EINTR == errno) { + goto retry; + } + Warning("%s: send() failed, errno %d\n", __FUNCTION__, errno); + return VGAUTH_E_COMM; + } + sent += ret; + } while (sent < len); + + return err; +} + + +/*****************************************************************************/ + +#ifdef SUPPORT_TCP + +/* + * XXX this is untested, and may not even compile, but is + * left as a starting point for an external client. + */ + +VGAuthError +ServiceNetworkAcceptTCPConnection(ServiceConnection *connIn, + ServiceConnection *connOut) +{ + int newfd; + struct sockaddr_in sockaddr; + int addrlen = sizeof(sockaddr); + char *ipaddr = NULL; + VGAuthError err = VGAUTH_E_OK; + + memset(&sockaddr, 0, sizeof(sockaddr)); + newfd = accept(connIn->sock, (struct sockaddr *) &sockaddr, &addrlen); + if (newfd < 0) { + err = VGAUTH_E_COMM; + Warning("%s: accept() failed, %d\n", __FUNCTION__, errno); + goto abort; + } + + ipaddr = inet_ntoa(sockaddr.sin_addr); + Debug("%s: got new connection from %s\n", __FUNCTION__, ipaddr); + + connOut->sock = newfd; + + return VGAUTH_E_OK; +abort: + return err; +} + + +VGAuthError +ServiceNetworkTCPListen(ServiceConnection *conn) // IN/OUT +{ + int sock; + struct sockaddr_in sockaddr; + int ret; + VGAuthError err = VGAUTH_E_OK; + int fd; + struct protoent protobuf; + char *buf; + struct protoent *proto_ent; + + buf = g_malloc(2048); + if (getprotobyname_r("TCP", &protobuf, buf, 2048, &proto_ent)) { + err = VGAUTH_E_COMM; + Warning("%s: getprotobyname_r() failed, %d\n", __FUNCTION__, errno); + g_free(buf); + goto abort; + } + + + sock = socket(AF_INET, SOCK_STREAM, proto_ent->p_proto); + if (sock < 0) { + err = VGAUTH_E_COMM; + Warning("%s: socket() failed, %d\n", __FUNCTION__, errno); + g_free(buf); + goto abort; + } + + g_free(buf); + + memset(&sockaddr, 0, sizeof(sockaddr)); + sockaddr.sin_family = AF_INET; + sockaddr.sin_port = htons(conn->port); + sockaddr.sin_addr.s_addr = htonl(INADDR_ANY); + +#ifdef SO_REUSEADDR + { + int one = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(int)); + } +#endif + + ret = bind(sock, (struct sockaddr *) &sockaddr, sizeof sockaddr); + + if (ret < 0) { + err = VGAUTH_E_COMM; + Warning("%s: bind() failed, %d\n", __FUNCTION__, errno); + goto abort; + } + + if (fcntl(sock, F_SETFL, O_NONBLOCK) < 0) { + err = VGAUTH_E_COMM; + Warning("%s: fcntl() failed, %d\n", __FUNCTION__, errno); + goto abort; + } + + if (listen(sock, 32) < 0) { + err = VGAUTH_E_COMM; + Warning("%s: listen() failed, %d\n", __FUNCTION__, errno); + goto abort; + } + + conn->sock = sock; + + return VGAUTH_E_OK; + +abort: + close(sock); + return err; +} + +#endif // SUPPORT_TCP |