diff options
author | Igor Pashev <pashev.igor@gmail.com> | 2014-10-26 12:33:50 +0400 |
---|---|---|
committer | Igor Pashev <pashev.igor@gmail.com> | 2014-10-26 12:33:50 +0400 |
commit | 47e6e7c84f008a53061e661f31ae96629bc694ef (patch) | |
tree | 648a07f3b5b9d67ce19b0fd72e8caa1175c98f1a /src/pmproxy | |
download | pcp-debian/3.9.10.tar.gz |
Debian 3.9.10debian/3.9.10debian
Diffstat (limited to 'src/pmproxy')
-rw-r--r-- | src/pmproxy/GNUmakefile | 50 | ||||
-rw-r--r-- | src/pmproxy/client.c | 260 | ||||
-rw-r--r-- | src/pmproxy/pmproxy.c | 543 | ||||
-rw-r--r-- | src/pmproxy/pmproxy.h | 48 | ||||
-rw-r--r-- | src/pmproxy/pmproxy.options | 31 | ||||
-rw-r--r-- | src/pmproxy/pmproxy.service.in | 14 | ||||
-rw-r--r-- | src/pmproxy/rc_pmproxy | 301 | ||||
-rw-r--r-- | src/pmproxy/util.c | 97 |
8 files changed, 1344 insertions, 0 deletions
diff --git a/src/pmproxy/GNUmakefile b/src/pmproxy/GNUmakefile new file mode 100644 index 0000000..a519c75 --- /dev/null +++ b/src/pmproxy/GNUmakefile @@ -0,0 +1,50 @@ +# +# Copyright (c) 2014 Red Hat. +# Copyright (c) 2000-2002 Silicon Graphics, Inc. All Rights Reserved. +# +# This program 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; either version 2 of the License, or (at your +# option) any 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 GNU General Public License +# for more details. +# + +TOPDIR = ../.. +include $(TOPDIR)/src/include/builddefs + +CMDTARGET = pmproxy$(EXECSUFFIX) +HFILES = pmproxy.h +CFILES = pmproxy.c client.c util.c + +LLDLIBS = $(PCPLIB) +LDIRT = pmproxy.log pmproxy.service + +LCFLAGS += $(PIECFLAGS) +LLDFLAGS += $(PIELDFLAGS) + +default: $(CMDTARGET) pmproxy.service + +pmproxy.service: pmproxy.service.in + $(SED) -e 's;@path@;'$(PCP_RC_DIR)';' $< > $@ + +include $(BUILDRULES) + +install: default + $(INSTALL) -m 755 -d `dirname $(PCP_PMPROXYOPTIONS_PATH)` + $(INSTALL) -m 644 pmproxy.options $(PCP_PMPROXYOPTIONS_PATH) + $(INSTALL) -m 755 rc_pmproxy $(PCP_RC_DIR)/pmproxy +ifeq ($(ENABLE_SYSTEMD),true) + $(INSTALL) -m 644 pmproxy.service $(PCP_SYSTEMDUNIT_DIR)/pmproxy.service +endif + $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BINADM_DIR)/$(CMDTARGET) + $(INSTALL) -m 775 -o $(PCP_USER) -g $(PCP_USER) -d $(PCP_LOG_DIR)/pmproxy + +default_pcp : default + +install_pcp : install + +pmproxy.o client.o: pmproxy.h diff --git a/src/pmproxy/client.c b/src/pmproxy/client.c new file mode 100644 index 0000000..f45534b --- /dev/null +++ b/src/pmproxy/client.c @@ -0,0 +1,260 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved. + * + * This program 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; either version 2 of the License, or (at your + * option) any 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 GNU General Public License + * for more details. + */ + +#include "pmproxy.h" + +#define MIN_CLIENTS_ALLOC 8 + +ClientInfo *client; +int nClients; /* Number in array, (not all in use) */ +int maxReqPortFd; /* highest request port fd */ +int maxSockFd; /* largest fd for a client */ +__pmFdSet sockFds; /* for client select() */ + +static int +NewClient(void) +{ + int i; + static int clientSize; + + for (i = 0; i < nClients; i++) + if (!client[i].status.connected) + break; + + if (i == clientSize) { + int j, sz; + + clientSize = clientSize ? clientSize * 2 : MIN_CLIENTS_ALLOC; + sz = sizeof(ClientInfo) * clientSize; + client = (ClientInfo *) realloc(client, sz); + if (client == NULL) { + __pmNoMem("NewClient", sz, PM_RECOV_ERR); + Shutdown(); + exit(1); + } + for (j = i; j < clientSize; j++) { + client[j].addr = NULL; + } + } + client[i].addr = __pmSockAddrAlloc(); + if (client[i].addr == NULL) { + __pmNoMem("NewClient", __pmSockAddrSize(), PM_RECOV_ERR); + Shutdown(); + exit(1); + } + if (i >= nClients) + nClients = i + 1; + return i; +} + +/* MY_BUFLEN needs to big enough to hold "hostname port" */ +#define MY_BUFLEN (MAXHOSTNAMELEN+10) +#define MY_VERSION "pmproxy-server 1\n" + +/* Establish a new socket connection to a client */ +ClientInfo * +AcceptNewClient(int reqfd) +{ + int i; + int fd; + __pmSockLen addrlen; + int ok = 0; + char buf[MY_BUFLEN]; + char *bp; + char *endp; + char *abufp; + + i = NewClient(); + addrlen = __pmSockAddrSize(); + fd = __pmAccept(reqfd, client[i].addr, &addrlen); + if (fd == -1) { + __pmNotifyErr(LOG_ERR, "AcceptNewClient(%d) __pmAccept failed: %s", + reqfd, netstrerror()); + Shutdown(); + exit(1); + } + __pmSetSocketIPC(fd); + if (fd > maxSockFd) + maxSockFd = fd; + __pmFD_SET(fd, &sockFds); + + client[i].fd = fd; + client[i].pmcd_fd = -1; + client[i].status.connected = 1; + client[i].pmcd_hostname = NULL; + + /* + * version negotiation (converse to negotiate_proxy() logic in + * libpcp + * + * __pmRecv client version message + * __pmSend my server version message + * __pmRecv pmcd hostname and pmcd port + */ + for (bp = buf; bp < &buf[MY_BUFLEN]; bp++) { + if (__pmRecv(fd, bp, 1, 0) != 1) { + *bp = '\0'; /* null terminate what we have */ + bp = &buf[MY_BUFLEN]; /* flag error */ + break; + } + /* end of line means no more ... */ + if (*bp == '\n' || *bp == '\r') { + *bp = '\0'; + break; + } + } + if (bp < &buf[MY_BUFLEN]) { + /* looks OK so far ... is this a version we can support? */ + if (strcmp(buf, "pmproxy-client 1") == 0) { + client[i].version = 1; + ok = 1; + } + } + + if (!ok) { + abufp = __pmSockAddrToString(client[i].addr); + __pmNotifyErr(LOG_WARNING, "Bad version string from client at %s", abufp); + free(abufp); + fprintf(stderr, "AcceptNewClient: bad version string was \""); + for (bp = buf; *bp && bp < &buf[MY_BUFLEN]; bp++) + fputc(*bp & 0xff, stderr); + fprintf(stderr, "\"\n"); + DeleteClient(&client[i]); + return NULL; + } + + if (__pmSend(fd, MY_VERSION, strlen(MY_VERSION), 0) != strlen(MY_VERSION)) { + abufp = __pmSockAddrToString(client[i].addr); + __pmNotifyErr(LOG_WARNING, "AcceptNewClient: failed to send version " + "string (%s) to client at %s\n", MY_VERSION, abufp); + free(abufp); + DeleteClient(&client[i]); + return NULL; + } + + for (bp = buf; bp < &buf[MY_BUFLEN]; bp++) { + if (__pmRecv(fd, bp, 1, 0) != 1) { + *bp = '\0'; /* null terminate what we have */ + bp = &buf[MY_BUFLEN]; /* flag error */ + break; + } + /* end of line means no more ... */ + if (*bp == '\n' || *bp == '\r') { + *bp = '\0'; + break; + } + } + if (bp < &buf[MY_BUFLEN]) { + /* looks OK so far ... get hostname and port */ + for (bp = buf; *bp && *bp != ' '; bp++) + ; + if (bp != buf) { + *bp = '\0'; + client[i].pmcd_hostname = strdup(buf); + if (client[i].pmcd_hostname == NULL) + __pmNoMem("PMCD.hostname", strlen(buf), PM_FATAL_ERR); + bp++; + client[i].pmcd_port = (int)strtoul(bp, &endp, 10); + if (*endp != '\0') { + abufp = __pmSockAddrToString(client[i].addr); + __pmNotifyErr(LOG_WARNING, "AcceptNewClient: bad pmcd port " + "\"%s\" from client at %s", bp, abufp); + free(abufp); + DeleteClient(&client[i]); + return NULL; + } + } + /* error, fall through */ + } + + if (client[i].pmcd_hostname == NULL) { + abufp = __pmSockAddrToString(client[i].addr); + __pmNotifyErr(LOG_WARNING, "AcceptNewClient: failed to get PMCD " + "hostname (%s) from client at %s", buf, abufp); + free(abufp); + DeleteClient(&client[i]); + return NULL; + } + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) { + /* + * note error message gets appended to once pmcd connection is + * made in ClientLoop() + */ + abufp = __pmSockAddrToString(client[i].addr); + fprintf(stderr, "AcceptNewClient [%d] fd=%d from %s to %s (port %s)", + i, fd, abufp, client[i].pmcd_hostname, bp); + free(abufp); + } +#endif + + return &client[i]; +} + +void +DeleteClient(ClientInfo *cp) +{ + int i; + + for (i = 0; i < nClients; i++) + if (cp == &client[i]) + break; + + if (i == nClients) { + fprintf(stderr, "DeleteClient: Botch: tried to delete non-existent client @" PRINTF_P_PFX "%p\n", cp); + return; + } + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "DeleteClient [%d]\n", i); +#endif + + if (cp->fd >= 0) { + __pmFD_CLR(cp->fd, &sockFds); + __pmCloseSocket(cp->fd); + } + if (cp->pmcd_fd >= 0) { + __pmFD_CLR(cp->pmcd_fd, &sockFds); + __pmCloseSocket(cp->pmcd_fd); + } + if (i == nClients-1) { + i--; + while (i >= 0 && !client[i].status.connected) + i--; + nClients = (i >= 0) ? i + 1 : 0; + } + if (cp->fd == maxSockFd || cp->pmcd_fd == maxSockFd) { + maxSockFd = maxReqPortFd; + for (i = 0; i < nClients; i++) { + if (cp->status.connected == 0) + continue; + if (client[i].fd > maxSockFd) + maxSockFd = client[i].fd; + if (client[i].pmcd_fd > maxSockFd) + maxSockFd = client[i].pmcd_fd; + } + } + __pmSockAddrFree(cp->addr); + cp->addr = NULL; + cp->status.connected = 0; + cp->fd = -1; + cp->pmcd_fd = -1; + if (cp->pmcd_hostname != NULL) { + free(cp->pmcd_hostname); + cp->pmcd_hostname = NULL; + } +} diff --git a/src/pmproxy/pmproxy.c b/src/pmproxy/pmproxy.c new file mode 100644 index 0000000..b359199 --- /dev/null +++ b/src/pmproxy/pmproxy.c @@ -0,0 +1,543 @@ +/* + * Copyright (c) 2012-2014 Red Hat. + * Copyright (c) 2002 Silicon Graphics, Inc. All Rights Reserved. + * + * This program 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; either version 2 of the License, or (at your + * option) any 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 GNU General Public License + * for more details. + */ + +#include "pmproxy.h" +#include <sys/stat.h> +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif + +#define MAXPENDING 5 /* maximum number of pending connections */ +#define FDNAMELEN 40 /* maximum length of a fd description */ +#define STRINGIFY(s) #s +#define TO_STRING(s) STRINGIFY(s) + +static char *FdToString(int); + +static int timeToDie; /* For SIGINT handling */ +static char *logfile = "pmproxy.log"; /* log file name */ +static int run_daemon = 1; /* run as a daemon, see -f */ +static char *fatalfile = "/dev/tty";/* fatal messages at startup go here */ +static char *username; +static char *certdb; /* certificate DB path (NSS) */ +static char *dbpassfile; /* certificate DB password file */ +static char *hostname; + +static void +DontStart(void) +{ + FILE *tty; + FILE *log; + + __pmNotifyErr(LOG_ERR, "pmproxy not started due to errors!\n"); + + if ((tty = fopen(fatalfile, "w")) != NULL) { + fflush(stderr); + fprintf(tty, "NOTE: pmproxy not started due to errors! "); + if ((log = fopen(logfile, "r")) != NULL) { + int c; + fprintf(tty, "Log file \"%s\" contains ...\n", logfile); + while ((c = fgetc(log)) != EOF) + fputc(c, tty); + fclose(log); + } + else + fprintf(tty, "Log file \"%s\" has vanished!\n", logfile); + fclose(tty); + } + exit(1); +} + +static pmLongOptions longopts[] = { + PMAPI_OPTIONS_HEADER("General options"), + PMOPT_DEBUG, + PMOPT_HELP, + PMAPI_OPTIONS_HEADER("Service options"), + { "foreground", 0, 'f', 0, "run in the foreground" }, + { "username", 1, 'U', "USER", "in daemon mode, run as named user [default pcp]" }, + PMAPI_OPTIONS_HEADER("Configuration options"), + { "certdb", 1, 'C', "PATH", "path to NSS certificate database" }, + { "passfile", 1, 'P', "PATH", "password file for certificate database access" }, + { "", 1, 'L', "BYTES", "maximum size for PDUs from clients [default 65536]" }, + PMAPI_OPTIONS_HEADER("Connection options"), + { "interface", 1, 'i', "ADDR", "accept connections on this IP address" }, + { "port", 1, 'p', "N", "accept connections on this port" }, + PMAPI_OPTIONS_HEADER("Diagnostic options"), + { "log", 1, 'l', "PATH", "redirect diagnostics and trace output" }, + { "", 1, 'x', "PATH", "fatal messages at startup sent to file [default /dev/tty]" }, + PMAPI_OPTIONS_END +}; + +static pmOptions opts = { + .short_options = "C:D:fi:l:L:p:P:U:x:?", + .long_options = longopts, +}; + +static void +ParseOptions(int argc, char *argv[], int *nports) +{ + int c; + int sts; + int usage = 0; + + while ((c = pmgetopt_r(argc, argv, &opts)) != EOF) { + switch (c) { + + case 'C': /* path to NSS certificate database */ + certdb = opts.optarg; + break; + + case 'D': /* debug flag */ + if ((sts = __pmParseDebug(opts.optarg)) < 0) { + pmprintf("%s: unrecognized debug flag specification (%s)\n", + pmProgname, opts.optarg); + opts.errors++; + } else { + pmDebug |= sts; + } + break; + + case 'f': /* foreground, i.e. do _not_ run as a daemon */ + run_daemon = 0; + break; + + case 'i': + /* one (of possibly several) interfaces for client requests */ + __pmServerAddInterface(opts.optarg); + break; + + case 'l': + /* log file name */ + logfile = opts.optarg; + break; + + case 'L': /* Maximum size for PDUs from clients */ + sts = (int)strtol(opts.optarg, NULL, 0); + if (sts <= 0) { + pmprintf("%s: -L requires a positive value\n", pmProgname); + opts.errors++; + } else { + __pmSetPDUCeiling(sts); + } + break; + + case 'p': + if (__pmServerAddPorts(opts.optarg) < 0) { + pmprintf("%s: -p requires a positive numeric argument (%s)\n", + pmProgname, opts.optarg); + opts.errors++; + } else { + *nports += 1; + } + break; + + case 'P': /* password file for certificate database access */ + dbpassfile = opts.optarg; + break; + + case 'U': /* run as user username */ + username = opts.optarg; + break; + + case 'x': + fatalfile = opts.optarg; + break; + + case '?': + usage = 1; + break; + + default: + opts.errors++; + break; + } + } + + if (usage || opts.errors || opts.optind < argc) { + pmUsageMessage(&opts); + if (usage) + exit(0); + DontStart(); + } +} + +static void +CleanupClient(ClientInfo *cp, int sts) +{ +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) { + int i; + for (i = 0; i < nClients; i++) { + if (cp == &client[i]) + break; + } + fprintf(stderr, "CleanupClient: client[%d] fd=%d %s (%d)\n", + i, cp->fd, pmErrStr(sts), sts); + } +#endif + + DeleteClient(cp); +} + +static int +VerifyClient(ClientInfo *cp, __pmPDU *pb) +{ + int i, sts, flags = 0, sender = 0, credcount = 0; + __pmPDUHdr *header = (__pmPDUHdr *)pb; + __pmHashCtl attrs = { 0 }; /* TODO */ + __pmCred *credlist; + + /* first check that this is a credentials PDU */ + if (header->type != PDU_CREDS) + return PM_ERR_IPC; + + /* now decode it and if secure connection requested, set it up */ + if ((sts = __pmDecodeCreds(pb, &sender, &credcount, &credlist)) < 0) + return sts; + + for (i = 0; i < credcount; i++) { + if (credlist[i].c_type == CVERSION) { + __pmVersionCred *vcp = (__pmVersionCred *)&credlist[i]; + flags = vcp->c_flags; + break; + } + } + if (credlist != NULL) + free(credlist); + + /* need to ensure both the pmcd and client channel use flags */ + + if (sts >= 0 && flags) + sts = __pmSecureServerHandshake(cp->fd, flags, &attrs); + + /* send credentials PDU through to pmcd now (order maintained) */ + if (sts >= 0) + sts = __pmXmitPDU(cp->pmcd_fd, pb); + + /* + * finally perform any additional handshaking needed with pmcd. + * Do not initialize NSS again. + */ + if (sts >= 0 && flags) + sts = __pmSecureClientHandshake(cp->pmcd_fd, + flags | PDU_FLAG_NO_NSS_INIT, + hostname, &attrs); + + return sts; +} + +/* Determine which clients (if any) have sent data to the server and handle it + * as required. + */ +void +HandleInput(__pmFdSet *fdsPtr) +{ + int i, sts; + __pmPDU *pb; + ClientInfo *cp; + + /* input from clients */ + for (i = 0; i < nClients; i++) { + if (!client[i].status.connected || !__pmFD_ISSET(client[i].fd, fdsPtr)) + continue; + + cp = &client[i]; + + sts = __pmGetPDU(cp->fd, LIMIT_SIZE, 0, &pb); + if (sts <= 0) { + CleanupClient(cp, sts); + continue; + } + + /* We *must* see a credentials PDU as the first PDU */ + if (!cp->status.allowed) { + sts = VerifyClient(cp, pb); + __pmUnpinPDUBuf(pb); + if (sts < 0) { + CleanupClient(cp, sts); + continue; + } + cp->status.allowed = 1; + continue; + } + + sts = __pmXmitPDU(cp->pmcd_fd, pb); + __pmUnpinPDUBuf(pb); + if (sts <= 0) { + CleanupClient(cp, sts); + continue; + } + } + + /* input from pmcds */ + for (i = 0; i < nClients; i++) { + if (!client[i].status.connected || + !__pmFD_ISSET(client[i].pmcd_fd, fdsPtr)) + continue; + + cp = &client[i]; + + sts = __pmGetPDU(cp->pmcd_fd, ANY_SIZE, 0, &pb); + if (sts <= 0) { + CleanupClient(cp, sts); + continue; + } + + sts = __pmXmitPDU(cp->fd, pb); + __pmUnpinPDUBuf(pb); + if (sts <= 0) { + CleanupClient(cp, sts); + continue; + } + } +} + +/* Called to shutdown pmproxy in an orderly manner */ +void +Shutdown(void) +{ + int i; + + for (i = 0; i < nClients; i++) + if (client[i].status.connected) + __pmCloseSocket(client[i].fd); + __pmServerCloseRequestPorts(); + __pmSecureServerShutdown(); + __pmNotifyErr(LOG_INFO, "pmproxy Shutdown\n"); + fflush(stderr); +} + +void +SignalShutdown(void) +{ + __pmNotifyErr(LOG_INFO, "pmproxy caught SIGINT or SIGTERM\n"); + Shutdown(); + exit(0); +} + +static void +CheckNewClient(__pmFdSet * fdset, int rfd, int family) +{ + ClientInfo *cp; + + if (__pmFD_ISSET(rfd, fdset)) { + if ((cp = AcceptNewClient(rfd)) == NULL) + /* failed to negotiate, already cleaned up */ + return; + + /* establish a new connection to pmcd */ + if ((cp->pmcd_fd = __pmAuxConnectPMCDPort(cp->pmcd_hostname, cp->pmcd_port)) < 0) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + /* append to message started in AcceptNewClient() */ + fprintf(stderr, " oops!\n" + "__pmAuxConnectPMCDPort(%s,%d) failed: %s\n", + cp->pmcd_hostname, cp->pmcd_port, + pmErrStr(-oserror())); +#endif + CleanupClient(cp, -oserror()); + } + else { + if (cp->pmcd_fd > maxSockFd) + maxSockFd = cp->pmcd_fd; + __pmFD_SET(cp->pmcd_fd, &sockFds); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + /* append to message started in AcceptNewClient() */ + fprintf(stderr, " fd=%d\n", cp->pmcd_fd); +#endif + } + } +} + +/* Loop, synchronously processing requests from clients. */ +static void +ClientLoop(void) +{ + int i, sts; + int maxFd; + __pmFdSet readableFds; + + for (;;) { + /* Figure out which file descriptors to wait for input on. Keep + * track of the highest numbered descriptor for the select call. + */ + readableFds = sockFds; + maxFd = maxSockFd + 1; + + sts = __pmSelectRead(maxFd, &readableFds, NULL); + + if (sts > 0) { + if (pmDebug & DBG_TRACE_APPL0) + for (i = 0; i <= maxSockFd; i++) + if (__pmFD_ISSET(i, &readableFds)) + fprintf(stderr, "__pmSelectRead(): from %s fd=%d\n", + FdToString(i), i); + __pmServerAddNewClients(&readableFds, CheckNewClient); + HandleInput(&readableFds); + } + else if (sts == -1 && neterror() != EINTR) { + __pmNotifyErr(LOG_ERR, "ClientLoop select: %s\n", netstrerror()); + break; + } + if (timeToDie) { + SignalShutdown(); + break; + } + } +} + +#ifdef IS_MINGW +static void +SigIntProc(int s) +{ + SignalShutdown(); +} +#else +static void +SigIntProc(int s) +{ + signal(SIGINT, SigIntProc); + signal(SIGTERM, SigIntProc); + timeToDie = 1; +} +#endif + +static void +SigBad(int sig) +{ + if (pmDebug & DBG_TRACE_DESPERATE) { + __pmNotifyErr(LOG_ERR, "Unexpected signal %d ...\n", sig); + fprintf(stderr, "\nDumping to core ...\n"); + fflush(stderr); + } + _exit(sig); +} + +/* + * Hostname extracted and cached for later use during protocol negotiations + */ +static void +GetProxyHostname(void) +{ + __pmHostEnt *hep; + char host[MAXHOSTNAMELEN]; + + if (gethostname(host, MAXHOSTNAMELEN) < 0) { + __pmNotifyErr(LOG_ERR, "%s: gethostname failure\n", pmProgname); + DontStart(); + } + host[MAXHOSTNAMELEN-1] = '\0'; + + hep = __pmGetAddrInfo(host); + if (hep == NULL) { + __pmNotifyErr(LOG_ERR, "%s: __pmGetAddrInfo failure\n", pmProgname); + DontStart(); + } else { + hostname = __pmHostEntGetName(hep); + if (!hostname) { /* no reverse DNS lookup for local hostname */ + hostname = strdup(host); + if (!hostname) /* out of memory, we're having a bad day!?! */ + __pmNoMem("PMPROXY.hostname", strlen(host), PM_FATAL_ERR); + } + __pmHostEntFree(hep); + } +} + +int +main(int argc, char *argv[]) +{ + int sts; + int nport = 0; + char *envstr; + + umask(022); + __pmGetUsername(&username); + __pmSetInternalState(PM_STATE_PMCS); + + if ((envstr = getenv("PMPROXY_PORT")) != NULL) + nport = __pmServerAddPorts(envstr); + ParseOptions(argc, argv, &nport); + if (nport == 0) + __pmServerAddPorts(TO_STRING(PROXY_PORT)); + GetProxyHostname(); + + if (run_daemon) { + fflush(stderr); + StartDaemon(argc, argv); + __pmServerCreatePIDFile(PM_SERVER_PROXY_SPEC, 0); + } + + __pmSetSignalHandler(SIGHUP, SIG_IGN); + __pmSetSignalHandler(SIGINT, SigIntProc); + __pmSetSignalHandler(SIGTERM, SigIntProc); + __pmSetSignalHandler(SIGBUS, SigBad); + __pmSetSignalHandler(SIGSEGV, SigBad); + + /* Open request ports for client connections */ + if ((sts = __pmServerOpenRequestPorts(&sockFds, MAXPENDING)) < 0) + DontStart(); + maxReqPortFd = maxSockFd = sts; + + __pmOpenLog(pmProgname, logfile, stderr, &sts); + /* close old stdout, and force stdout into same stream as stderr */ + fflush(stdout); + close(fileno(stdout)); + if (dup(fileno(stderr)) == -1) { + fprintf(stderr, "Warning: dup() failed: %s\n", pmErrStr(-oserror())); + } + + fprintf(stderr, "pmproxy: PID = %" FMT_PID, getpid()); + fprintf(stderr, ", PDU version = %u\n", PDU_VERSION); + __pmServerDumpRequestPorts(stderr); + fflush(stderr); + + /* lose root privileges if we have them */ + __pmSetProcessIdentity(username); + + if (__pmSecureServerSetup(certdb, dbpassfile) < 0) + DontStart(); + + /* all the work is done here */ + ClientLoop(); + + Shutdown(); + exit(0); +} + +/* Convert a file descriptor to a string describing what it is for. */ +static char * +FdToString(int fd) +{ + static char fdStr[FDNAMELEN]; + static char *stdFds[4] = {"*UNKNOWN FD*", "stdin", "stdout", "stderr"}; + int i; + + if (fd >= -1 && fd < 3) + return stdFds[fd + 1]; + if (__pmServerRequestPortString(fd, fdStr, FDNAMELEN) != NULL) + return fdStr; + for (i = 0; i < nClients; i++) { + if (client[i].status.connected && fd == client[i].fd) { + sprintf(fdStr, "client[%d] client socket", i); + return fdStr; + } + if (client[i].status.connected && fd == client[i].pmcd_fd) { + sprintf(fdStr, "client[%d] pmcd socket", i); + return fdStr; + } + } + return stdFds[0]; +} diff --git a/src/pmproxy/pmproxy.h b/src/pmproxy/pmproxy.h new file mode 100644 index 0000000..df6005c --- /dev/null +++ b/src/pmproxy/pmproxy.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * Copyright (c) 2002 Silicon Graphics, Inc. All Rights Reserved. + * + * This program 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; either version 2 of the License, or (at your + * option) any 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 GNU General Public License + * for more details. + */ +#ifndef _PROXY_H +#define _PROXY_H + +#include "pmapi.h" +#include "impl.h" + +/* The table of clients, used by pmproxy */ +typedef struct { + int fd; /* client socket descriptor */ + int version; /* proxy-client protocol version */ + struct { /* Status of connection to client */ + unsigned int connected : 1; /* Client connected, socket level */ + unsigned int allowed : 1; /* Creds seen, OK to talk to pmcd */ + } status; + char *pmcd_hostname; /* PMCD hostname */ + int pmcd_port; /* PMCD port */ + int pmcd_fd; /* PMCD socket file descriptor */ + __pmSockAddr *addr; /* address of client */ +} ClientInfo; + +extern ClientInfo *client; /* Array of clients */ +extern int nClients; /* Number of entries in array */ +extern int maxReqPortFd; /* highest request port fd */ +extern int maxSockFd; /* largest fd for a clients + * and pmcd connections */ +extern __pmFdSet sockFds; /* for select() */ + +/* prototypes */ +extern ClientInfo *AcceptNewClient(int); +extern void DeleteClient(ClientInfo *); +extern void StartDaemon(int, char **); +extern void Shutdown(void); + +#endif /* _PROXY_H */ diff --git a/src/pmproxy/pmproxy.options b/src/pmproxy/pmproxy.options new file mode 100644 index 0000000..bd83e0c --- /dev/null +++ b/src/pmproxy/pmproxy.options @@ -0,0 +1,31 @@ +# command-line options and environment variables for pmproxy +# uncomment/edit lines as required + +# run in the foreground +# -f + +# restrict PCP monitoring client connections to these interfaces +# -i gateway-public-0.foo.bar.com +# -i gateway-public-1.foo.bar.com + +# make log go someplace else +# -l /some/place/else + +# maximum incoming PDU size (default 64KB) +# -L 16384 + +# assume identity of some user other than "pcp" +# -U nobody + +# emergency messages before logfile created +# -x /tmp/desperate.log + +# setting of environment variables for pmproxy + +# port for incomining connections from PCP monitoring clients +# PMPROXY_PORT=44322 + +# timeouts for interactions with pmcd on behalf of clients +# PMCD_CONNECT_TIMEOUT=10 +# PMCD_RECONNECT_TIMEOUT=10,20,30 +# PMCD_REQUEST_TIMEOUT=10 diff --git a/src/pmproxy/pmproxy.service.in b/src/pmproxy/pmproxy.service.in new file mode 100644 index 0000000..078f126 --- /dev/null +++ b/src/pmproxy/pmproxy.service.in @@ -0,0 +1,14 @@ +[Unit] +Description=Proxy for Performance Metrics Collector Daemon +Documentation=man:pmproxy(8) +Wants=avahi-daemon.service +After=network.target avahi-daemon.service + +[Service] +Type=oneshot +ExecStart=@path@/pmproxy start +ExecStop=@path@/pmproxy stop +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target diff --git a/src/pmproxy/rc_pmproxy b/src/pmproxy/rc_pmproxy new file mode 100644 index 0000000..5886219 --- /dev/null +++ b/src/pmproxy/rc_pmproxy @@ -0,0 +1,301 @@ +#! /bin/sh +# +# Copyright (c) 2005 Silicon Graphics, Inc. All Rights Reserved. +# +# This program 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; either version 2 of the License, or (at your +# option) any 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 GNU General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Start or Stop the Performance Co-Pilot (PCP) proxy daemon for pmcd +# +# The following is for chkconfig on RedHat based systems +# chkconfig: 2345 95 05 +# description: pmproxy is the pmcd proxy daemon for the Performance Co-Pilot (PCP) +# +# The following is for insserv(1) based systems, +# e.g. SuSE, where chkconfig is a perl script. +### BEGIN INIT INFO +# Provides: pmproxy +# Required-Start: $remote_fs +# Should-Start: $local_fs $network $syslog $time $pmcd +# Required-Stop: $remote_fs +# Should-Stop: $local_fs $network $syslog $pmcd +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Control pmproxy (the pmcd proxy daemon for PCP) +# Description: Configure and control pmproxy (the pmcd proxy daemon for the Performance Co-Pilot) +### END INIT INFO + +. $PCP_DIR/etc/pcp.env +. $PCP_SHARE_DIR/lib/rc-proc.sh + +PMPROXY=$PCP_BINADM_DIR/pmproxy +PMPROXYOPTS=$PCP_PMPROXYOPTIONS_PATH +RUNDIR=$PCP_LOG_DIR/pmproxy +pmprog=$PCP_RC_DIR/pmproxy +prog=$PCP_RC_DIR/`basename $0` + +tmp=`mktemp -d /var/tmp/pcp.XXXXXXXXX` || exit 1 +status=1 +trap "rm -rf $tmp; exit \$status" 0 1 2 3 15 + +if [ $pmprog = $prog ] +then + VERBOSE_CTL=on +else + VERBOSE_CTL=off +fi + +case "$PCP_PLATFORM" +in + mingw) + # nothing we can usefully do here, skip the test + # + ;; + + *) + # standard Unix/Linux style test + # + ID=id + test -f /usr/xpg4/bin/id && ID=/usr/xpg4/bin/id + + IAM=`$ID -u 2>/dev/null` + if [ -z "$IAM" ] + then + # do it the hardway + # + IAM=`$ID | sed -e 's/.*uid=//' -e 's/(.*//'` + fi + ;; +esac + +_shutdown() +{ + # Is pmproxy running? + # + _get_pids_by_name pmproxy >$tmp/tmp + if [ ! -s $tmp/tmp ] + then + [ "$1" = verbose ] && echo "$pmprog: pmproxy not running" + return 0 + fi + + # Send pmproxy a SIGTERM, which is noted as a pending shutdown. + # When finished the currently active request, pmproxy will close any + # connections and then exit. + # Wait for pmproxy to terminate. + # + pmsignal -a -s TERM pmproxy > /dev/null 2>&1 + $ECHO $PCP_ECHO_N "Waiting for pmproxy to terminate ...""$PCP_ECHO_C" + gone=0 + for i in 1 2 3 4 5 6 + do + sleep 3 + _get_pids_by_name pmproxy >$tmp/tmp + if [ ! -s $tmp/tmp ] + then + gone=1 + break + fi + + # If pmproxy doesn't go in 15 seconds, SIGKILL and sleep 1 more time + # to allow any clients reading from pmproxy sockets to fail so that + # socket doesn't end up in TIME_WAIT or somesuch. + # + if [ $i = 5 ] + then + $ECHO + echo "Process ..." + $PCP_PS_PROG $PCP_PS_ALL_FLAGS >$tmp/ps + sed 1q $tmp/ps + for pid in `cat $tmp/tmp` + do + $PCP_AWK_PROG <$tmp/ps "\$2 == $pid { print }" + done + echo "$prog: Warning: Forcing pmproxy to terminate!" + pmsignal -a -s KILL pmproxy > /dev/null 2>&1 + else + $ECHO $PCP_ECHO_N ".""$PCP_ECHO_C" + fi + done + if [ $gone != 1 ] # It just WON'T DIE, give up. + then + echo "Process ..." + cat $tmp/tmp + echo "$prog: Warning: pmproxy won't die!" + exit + fi + $RC_STATUS -v + $PCP_BINADM_DIR/pmpost "stop pmproxy from $pmprog" +} + +_usage() +{ + echo "Usage: $pmprog [-v] {start|restart|condrestart|stop|status|reload|force-reload}" +} + +while getopts v c +do + case $c + in + v) # force verbose + VERBOSE_CTL=on + ;; + + *) + _usage + exit 1 + ;; + esac +done +shift `expr $OPTIND - 1` + +if [ $VERBOSE_CTL = on ] +then # For a verbose startup and shutdown + ECHO=$PCP_ECHO_PROG +else # For a quiet startup and shutdown + ECHO=: +fi + +if [ "$IAM" != 0 -a "$1" != "status" ] +then + if [ -n "$PCP_DIR" ] + then + : running in a non-default installation, do not need to be root + else + echo "$prog:"' +Error: You must be root (uid 0) to start or stop the PCP pmproxy daemon.' + exit + fi +fi + +# First reset status of this service +$RC_RESET + +# Return values acc. to LSB for all commands but status: +# 0 - success +# 1 - misc error +# 2 - invalid or excess args +# 3 - unimplemented feature (e.g. reload) +# 4 - insufficient privilege +# 5 - program not installed +# 6 - program not configured +# +# Note that starting an already running service, stopping +# or restarting a not-running service as well as the restart +# with force-reload (in case signalling is not supported) are +# considered a success. +case "$1" in + + 'start'|'restart'|'condrestart'|'reload'|'force-reload') + if [ "$1" = "condrestart" ] && ! is_chkconfig_on pmproxy + then + status=0 + exit + fi + + _shutdown quietly + + # pmproxy messages should go to stderr, not the GUI notifiers + # + unset PCP_STDERR + + if [ -x $PMPROXY ] + then + if [ ! -f $PCP_PMPROXYCONF_PATH ] + then + echo "$prog:"' +Error: pmproxy control file "$PCP_PMPROXYCONF_PATH" is missing, cannot start pmproxy.' + exit + fi + if [ ! -d "$RUNDIR" ] + then + mkdir -p -m 775 "$RUNDIR" + chown $PCP_USER:$PCP_GROUP "$RUNDIR" + fi + cd $RUNDIR + + # salvage the previous versions of any pmproxy + # + if [ -f pmproxy.log ] + then + rm -f pmproxylog.log.prev + mv pmproxy.log pmproxy.log.prev + fi + + $ECHO $PCP_ECHO_N "Starting pmproxy ..." "$PCP_ECHO_C" + + # options file processing ... + # only consider lines which start with a hyphen + # get rid of the -f option + # ensure multiple lines concat onto 1 line + OPTS=`sed <$PMPROXYOPTS 2>/dev/null \ + -e '/^[^-]/d' \ + -e 's/^/ /' \ + -e 's/$/ /' \ + -e 's/ -f / /g' \ + -e 's/^ //' \ + -e 's/ $//' \ + | tr '\012' ' ' ` + + # environment stuff + # + eval `sed -e 's/"/\\"/g' $PMPROXYOPTS \ + | awk -F= ' +BEGIN { exports="" } +/^[A-Z]/ && NF == 2 { exports=exports" "$1 + printf "%s=${%s:-\"%s\"}\n", $1, $1, $2 + } +END { if (exports != "") print "export", exports }'` + + $PMPROXY $OPTS + $RC_STATUS -v + + $PCP_BINADM_DIR/pmpost "start pmproxy from $pmprog" + + fi + status=0 + ;; + + 'stop') + _shutdown + status=0 + ;; + + 'status') + # NOTE: $RC_CHECKPROC returns LSB compliant status values. + $ECHO $PCP_ECHO_N "Checking for pmproxy:" "$PCP_ECHO_C" + if [ -r /etc/rc.status ] + then + # SuSE + $RC_CHECKPROC $PMPROXY + $RC_STATUS -v + status=$? + else + # not SuSE + $RC_CHECKPROC $PMPROXY + status=$? + if [ $status -eq 0 ] + then + $ECHO running + else + $ECHO stopped + fi + fi + ;; + + *) + _usage + ;; +esac + diff --git a/src/pmproxy/util.c b/src/pmproxy/util.c new file mode 100644 index 0000000..c22ac8a --- /dev/null +++ b/src/pmproxy/util.c @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2002 Silicon Graphics, Inc. All Rights Reserved. + * Copyright (c) 2009 Aconex. All Rights Reserved. + * + * This program 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; either version 2 of the License, or (at your + * option) any 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 GNU General Public License + * for more details. + */ + +#include "pmproxy.h" + + +#ifdef IS_MINGW + +void +StartDaemon(int argc, char **argv) +{ + PROCESS_INFORMATION piProcInfo; + STARTUPINFO siStartInfo; + LPTSTR cmdline = NULL; + int i, sz = 3; /* -f\0 */ + + for (i = 0; i < argc; i++) + sz += strlen(argv[i]) + 1; + if ((cmdline = malloc(sz)) == NULL) { + __pmNotifyErr(LOG_ERR, "StartDaemon: no memory"); + exit(1); + } + for (sz = i = 0; i < argc; i++) + sz += sprintf(cmdline, "%s ", argv[i]); + sprintf(cmdline + sz, "-f"); + + ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION)); + ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); + siStartInfo.cb = sizeof(STARTUPINFO); + + if (0 == CreateProcess( + NULL, cmdline, + NULL, NULL, /* process and thread attributes */ + FALSE, /* inherit handles */ + CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW | DETACHED_PROCESS, + NULL, /* environment (from parent) */ + NULL, /* current directory */ + &siStartInfo, /* STARTUPINFO pointer */ + &piProcInfo)) { /* receives PROCESS_INFORMATION */ + __pmNotifyErr(LOG_ERR, "StartDaemon: CreateProcess"); + /* but keep going */ + } + else { + /* parent, let her exit, but avoid ugly "Log finished" messages */ + fclose(stderr); + exit(0); + } +} + +#else + +/* Based on Stevens (Unix Network Programming, p.83) */ +void +StartDaemon(int argc, char **argv) +{ + int childpid; + + (void)argc; (void)argv; + +#if defined(HAVE_TERMIO_SIGNALS) + signal(SIGTTOU, SIG_IGN); + signal(SIGTTIN, SIG_IGN); + signal(SIGTSTP, SIG_IGN); +#endif + + if ((childpid = fork()) < 0) + __pmNotifyErr(LOG_ERR, "StartDaemon: fork"); + /* but keep going */ + else if (childpid > 0) { + /* parent, let her exit, but avoid ugly "Log finished" messages */ + fclose(stderr); + exit(0); + } + + /* not a process group leader, lose controlling tty */ + if (setsid() == -1) + __pmNotifyErr(LOG_WARNING, "StartDaemon: setsid"); + /* but keep going */ + + close(0); + /* don't close other fd's -- we know that only good ones are open! */ + + /* don't chdir("/") -- we still need to open pmcd.log */ +} +#endif |