[Libvir] Preliminary patch to support remote driver / libvirtd

This patch is just for discussion. It's not in a state to be applied, even if it were accepted (which is a long-shot at present anyway). When looking at the patch, a good starting point is to search for "Architecture and notes" and read from there. Supports: * remote driver (just does the "open", "close", "type" and "version" calls at present as a proof of concept) * TLS transport (built using GnuTLS) * SSH transport (forks external ssh process) * TCP transport (unencrypted - just for testing) * Unix domain socket transport * arbitrary external program / shell script transport * IPv6-ready on client & server I've tested all the transports, and in limited tests they all seem to work. ie. You really can do: virsh -c remote:tls:server version Shortcomings in this version: * in "open" call, name must be non-NULL (this is just a bug) * doesn't actually invoke libvirt on the server side; just prints out messages and returns dummy values * "ssh" not recognised as a service name by getaddrinfo, so you must always give a port number, ie. remote:ssh:server:22 * /tmp/socket should be cleaned up when the server exits Potential problems: * SunRPC is stateless so we need to hand out a cookie to represent the virConnectPtr handle on the server side. However if the client dies without explicitly calling close, we have no way to know, and so the cookie/handle on the server side lives forever. * There's some confusion about the level of abstraction. At the moment I'm abstracting at the driver level, but that may be wrong and possibly I should be abstracting at the level of vir* calls. On the other hand, there's not a huge amount of difference. * Security: Is it safe for libvirt to be connecting to arbitrary TCP sockets? Is it safe for libvirt to be able to run arbitrary programs? Rich. -- Emerging Technologies, Red Hat http://et.redhat.com/~rjones/ 64 Baker Street, London, W1U 7DF Mobile: +44 7866 314 421 "[Negative numbers] darken the very whole doctrines of the equations and make dark of the things which are in their nature excessively obvious and simple" (Francis Maseres FRS, mathematician, 1759) ? src/libvirtd.c ? src/remote_internal.c ? src/remote_internal.h ? src/remote_rpc.x ? src/sunrpc/README ? src/sunrpc/clnt_ext.c ? src/sunrpc/clnt_ext.h ? src/sunrpc/clnt_gnutls.c ? src/sunrpc/clnt_gnutls.h ? src/sunrpc/clnt_tcp2.c ? src/sunrpc/clnt_tcp2.h ? src/sunrpc/create_xid.c ? src/sunrpc/svc_gnutls.c ? src/sunrpc/svc_gnutls.h ? src/sunrpc/svc_tcp2.c ? src/sunrpc/svc_tcp2.h Index: config.h.in =================================================================== RCS file: /data/cvs/libvirt/config.h.in,v retrieving revision 1.7 diff -u -r1.7 config.h.in --- config.h.in 31 Oct 2006 10:25:13 -0000 1.7 +++ config.h.in 30 Jan 2007 17:22:07 -0000 @@ -28,6 +28,9 @@ /* Define to 1 if you have the <inttypes.h> header file. */ #undef HAVE_INTTYPES_H +/* Define to 1 if you have the `gnutls' library (-lgnutls). */ +#undef HAVE_LIBGNUTLS + /* Define to 1 if you have the <memory.h> header file. */ #undef HAVE_MEMORY_H Index: configure.in =================================================================== RCS file: /data/cvs/libvirt/configure.in,v retrieving revision 1.52 diff -u -r1.52 configure.in --- configure.in 22 Jan 2007 15:31:00 -0000 1.52 +++ configure.in 30 Jan 2007 17:22:07 -0000 @@ -146,6 +146,11 @@ AC_SUBST(LIBXML_CONFIG) AC_SUBST(LIBXML_MIN_VERSION) +dnl GnuTLS library +AC_CHECK_LIB(gnutls, gnutls_handshake, + [], + [AC_MSG_ERROR([gnutls library not found])]) + dnl virsh libraries AC_CHECK_LIB(curses, initscr, [VIRSH_LIBS="$VIRSH_LIBS -lcurses"], Index: include/libvirt/virterror.h =================================================================== RCS file: /data/cvs/libvirt/include/libvirt/virterror.h,v retrieving revision 1.17 diff -u -r1.17 virterror.h --- include/libvirt/virterror.h 8 Nov 2006 16:55:20 -0000 1.17 +++ include/libvirt/virterror.h 30 Jan 2007 17:22:07 -0000 @@ -46,7 +46,8 @@ VIR_FROM_DOM, /* Error when operating on a domain */ VIR_FROM_RPC, /* Error in the XML-RPC code */ VIR_FROM_PROXY, /* Error in the proxy code */ - VIR_FROM_CONF /* Error in the configuration file handling */ + VIR_FROM_CONF, /* Error in the configuration file handling */ + VIR_FROM_REMOTE /* Error from remote driver */ } virErrorDomain; @@ -113,7 +114,8 @@ VIR_ERR_PARSE_FAILED, /* failed to parse a conf file */ VIR_ERR_CONF_SYNTAX, /* failed to parse the syntax of a conf file */ VIR_ERR_WRITE_FAILED, /* failed to write a conf file */ - VIR_ERR_XML_DETAIL /* detail of an XML error */ + VIR_ERR_XML_DETAIL, /* detail of an XML error */ + VIR_ERR_RPC /* some sort of RPC error */ } virErrorNumber; /** Index: src/.cvsignore =================================================================== RCS file: /data/cvs/libvirt/src/.cvsignore,v retrieving revision 1.2 diff -u -r1.2 .cvsignore --- src/.cvsignore 5 Jul 2006 21:52:52 -0000 1.2 +++ src/.cvsignore 30 Jan 2007 17:22:07 -0000 @@ -5,3 +5,8 @@ *.lo *.la virsh +libvirtd +remote_rpc_clnt.c +remote_rpc_svc.c +remote_rpc_xdr.c +remote_rpc.h \ No newline at end of file Index: src/Makefile.am =================================================================== RCS file: /data/cvs/libvirt/src/Makefile.am,v retrieving revision 1.31 diff -u -r1.31 Makefile.am --- src/Makefile.am 26 Jan 2007 11:54:29 -0000 1.31 +++ src/Makefile.am 30 Jan 2007 17:22:07 -0000 @@ -28,15 +28,41 @@ driver.h \ proxy_internal.c proxy_internal.h \ conf.c conf.h \ - xm_internal.c xm_internal.h + xm_internal.c xm_internal.h \ + sunrpc/create_xid.c remote_rpc_xdr.c \ + remote_rpc_clnt.c remote_rpc.h \ + sunrpc/clnt_ext.h sunrpc/clnt_gnutls.h \ + sunrpc/clnt_tcp2.h sunrpc/clnt_ext.c \ + sunrpc/clnt_gnutls.c sunrpc/clnt_tcp2.c \ + remote_internal.c remote_internal.h bin_PROGRAMS = virsh +sbin_PROGRAMS = libvirtd virsh_SOURCES = virsh.c console.c console.h virsh_LDFLAGS = virsh_DEPENDENCIES = $(DEPS) virsh_LDADD = $(LDADDS) $(VIRSH_LIBS) +libvirtd_SOURCES = \ + remote_rpc_svc.c \ + sunrpc/svc_gnutls.c sunrpc/svc_gnutls.h \ + sunrpc/svc_tcp2.c sunrpc/svc_tcp2.h \ + libvirtd.c +libvirtd_LDFLAGS = +libvirtd_DEPENDENCIES = $(DEPS) +libvirtd_LDADD = $(LDADDS) remote_rpc_xdr.o + +# Build client and server stubs. +# 'rpcgen' program comes with glibc. +# This is convoluted because we need to build the server stubs +# without main() (-m option), but you can't just do that in a +# simple way. +remote_rpc_clnt.c remote_rpc.h remote_rpc_svc.c remote_rpc_xdr.c: remote_rpc.x + rpcgen remote_rpc.x + rm -f remote_rpc_svc.c + rpcgen -m remote_rpc.x > remote_rpc_svc.c + # # target to ease building test programs # Index: src/driver.h =================================================================== RCS file: /data/cvs/libvirt/src/driver.h,v retrieving revision 1.16 diff -u -r1.16 driver.h --- src/driver.h 22 Jan 2007 16:21:27 -0000 1.16 +++ src/driver.h 30 Jan 2007 17:22:07 -0000 @@ -22,7 +22,8 @@ VIR_DRV_XEN_DAEMON = 3, VIR_DRV_TEST = 4, VIR_DRV_XEN_PROXY = 5, - VIR_DRV_XEN_XM = 6 + VIR_DRV_XEN_XM = 6, + VIR_DRV_REMOTE = 7 } virDrvNo; Index: src/internal.h =================================================================== RCS file: /data/cvs/libvirt/src/internal.h,v retrieving revision 1.28 diff -u -r1.28 internal.h --- src/internal.h 23 Jan 2007 14:39:45 -0000 1.28 +++ src/internal.h 30 Jan 2007 17:22:07 -0000 @@ -117,6 +117,13 @@ struct sockaddr_un addr_un; /* the unix address */ struct sockaddr_in addr_in; /* the inet address */ + /* driver private data + * (Ought to replace the above ad-hoc Xen data, IMHO anyway. + * Currently only the 'remote' driver uses this. + * - RWMJ). + */ + void *private; + /* error stuff */ virError err; /* the last error */ virErrorFunc handler; /* associated handlet */ Index: src/libvirt.c =================================================================== RCS file: /data/cvs/libvirt/src/libvirt.c,v retrieving revision 1.53 diff -u -r1.53 libvirt.c --- src/libvirt.c 23 Jan 2007 14:39:45 -0000 1.53 +++ src/libvirt.c 30 Jan 2007 17:22:09 -0000 @@ -30,6 +30,7 @@ #include "xs_internal.h" #include "xm_internal.h" #include "proxy_internal.h" +#include "remote_internal.h" #include "xml.h" #include "test.h" @@ -79,6 +80,7 @@ xenStoreRegister(); xenXMRegister(); testRegister(); + remoteRegister (); return(0); } Index: src/virsh.c =================================================================== RCS file: /data/cvs/libvirt/src/virsh.c,v retrieving revision 1.47 diff -u -r1.47 virsh.c --- src/virsh.c 28 Jan 2007 19:47:36 -0000 1.47 +++ src/virsh.c 30 Jan 2007 17:22:10 -0000 @@ -2760,7 +2760,7 @@ end = end ? : argc; /* standard (non-command) options */ - while ((arg = getopt_long(end, argv, "d:hqtcv", opt, &idx)) != -1) { + while ((arg = getopt_long(end, argv, "d:hqtc:v", opt, &idx)) != -1) { switch (arg) { case 'd': ctl->debug = atoi(optarg); Index: src/virterror.c =================================================================== RCS file: /data/cvs/libvirt/src/virterror.c,v retrieving revision 1.19 diff -u -r1.19 virterror.c --- src/virterror.c 8 Nov 2006 16:55:20 -0000 1.19 +++ src/virterror.c 30 Jan 2007 17:22:10 -0000 @@ -268,6 +268,9 @@ case VIR_FROM_RPC: dom = "XML-RPC "; break; + case VIR_FROM_REMOTE: + dom = "Remote "; + break; } if ((err->dom != NULL) && (err->code != VIR_ERR_INVALID_DOMAIN)) { domain = err->dom->name; @@ -582,6 +585,12 @@ else errmsg = "%s"; break; + case VIR_ERR_RPC: + if (info == NULL) + errmsg = _("RPC error"); + else + errmsg = "%s"; + break; } return (errmsg); } --- /tmp/newfile 2007-01-30 17:23:46.000000000 +0000 +++ src/libvirtd.c 2007-01-30 16:58:42.000000000 +0000 @@ -0,0 +1,257 @@ +/* + * libvirtd: This is a small server to be used in conjunction with + * the "remote" driver (remote_internal.c). + * + * Copyright (C) 2007 Red Hat, Inc. + * + * See COPYING.LIB for the license of this software. + * + * Richard Jones <rjones@redhat.com> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <malloc.h> +#include <time.h> +#include <rpc/rpc.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#include <errno.h> +#include <gnutls/gnutls.h> + +#include "sunrpc/svc_gnutls.h" +#include "sunrpc/svc_tcp2.h" +#include "remote_internal.h" +#include "remote_rpc.h" + +// XXX Replace with variables from a configuration file +// XXX TCP services should be disabled by default +#define listen_tls 1 +#define listen_tcp 1 +#define listen_unix 1 + +// XXX Still to decide where these certificates should be located. +#define KEYFILE "newkey.pem" +#define CERTFILE "newcert.pem" +#define CAFILE "demoCA/cacert.pem" +//#define CRLFILE "crl.pem" + +// This is autogenerated, in remote_rpc_svc.c +extern void libvirtremote_1 (struct svc_req *rqstp, register SVCXPRT *transp); + +/*----------------------------------------------------------------------*/ +/* Server side of the remote procedure calls. */ + +int * +remote_rpc_open_1_svc (char **name, + struct svc_req *req __attribute__((unused))) +{ + static int retcode = 0; + + // Before this is going to work, we will have to assign a cookie + // to each caller to represent their server-side virConnectPtr. + // XXX XXX XXX + //virConnectOpen (name); + + printf ("libvirtd: open: name = %s\n", *name); + return &retcode; +} + +int * +remote_rpc_close_1_svc (void *p __attribute__((unused)), + struct svc_req *req __attribute__((unused))) +{ + static int retcode = 0; + + printf ("libvirtd: close\n"); + return &retcode; +} + +char ** +remote_rpc_type_1_svc (void *p __attribute__((unused)), + struct svc_req *req __attribute__((unused))) +{ + static char *dummy = "remote"; + + printf ("libvirtd: type\n"); + return &dummy; +} + +struct version_ret * +remote_rpc_version_1_svc (void *p __attribute__((unused)), + struct svc_req *req __attribute__((unused))) +{ + static struct version_ret ret = { .retcode = 0, .hvVer = 1000000 }; + + printf ("libvirtd: version\n"); + return &ret; +} + +/*----------------------------------------------------------------------*/ +/* Main function. */ + +static gnutls_certificate_credentials_t x509_cred; +static gnutls_dh_params_t dh_params; + +static void generate_dh_params (void); +static int make_sockets (int *fds, int max_fds, int *nfds_r, + const char *service); + +int +main (int argc, char *argv[]) +{ + if (!listen_tls || !listen_tcp || !listen_unix) { + fprintf (stderr, "libvirtd: you need to enable at least one service in the configuration file\n"); + exit (1); + } + + if (listen_tls) { + /* Initialise GnuTLS. */ + gnutls_global_init (); + + gnutls_certificate_allocate_credentials (&x509_cred); + gnutls_certificate_set_x509_trust_file (x509_cred, CAFILE, + GNUTLS_X509_FMT_PEM); + + // gnutls_certificate_set_x509_crl_file (x509_cred, CRLFILE, + // GNUTLS_X509_FMT_PEM); + + gnutls_certificate_set_x509_key_file (x509_cred, CERTFILE, KEYFILE, + GNUTLS_X509_FMT_PEM); + + generate_dh_params (); + gnutls_certificate_set_dh_params (x509_cred, dh_params); + + int fds[2]; + int nfds = 0; + if (make_sockets (fds, 2, &nfds, LIBVIRTD_GNUTLS_PORT) == -1) + exit (1); + + int i; + for (i = 0; i < nfds; ++i) { + SVCXPRT *transp = svcgnutls_create (fds[i], 0, 0, x509_cred); + if (!transp) { + fprintf (stderr, "libvirtd: cannot create TLS service\n"); + exit (1); + } + + /* Because final arg is 0, this will not register with portmap. */ + if (!svc_register (transp, LIBVIRTREMOTE, LIBVIRTREMOTE_VERS1, + libvirtremote_1, 0)) { + fprintf (stderr, "libvirtd: unable to register (LIBVIRTREMOTE, LIBVIRTREMOTE_VERS1, 0)\n"); + exit (1); + } + } + } + + if (listen_tcp) { + int fds[2]; + int nfds = 0; + if (make_sockets (fds, 2, &nfds, LIBVIRTD_TCP_PORT) == -1) + exit (1); + + int i; + for (i = 0; i < nfds; ++i) { + SVCXPRT *transp = svctcp2_create (fds[i], 0, 0); + if (!transp) { + fprintf (stderr, "libvirtd: cannot create TCP service\n"); + exit (1); + } + + /* Because final arg is 0, this will not register with portmap. */ + if (!svc_register (transp, LIBVIRTREMOTE, LIBVIRTREMOTE_VERS1, + libvirtremote_1, 0)) { + fprintf (stderr, "libvirtd: unable to register (LIBVIRTREMOTE, LIBVIRTREMOTE_VERS1, 0)\n"); + exit (1); + } + } + } + + if (listen_unix) { + char *sockname = LIBVIRTD_UNIX_SOCKET; + int sock = RPC_ANYSOCK; + SVCXPRT *transp = svcunix_create (sock, 0, 0, sockname); + if (!transp) { + fprintf (stderr, "libvirtd: cannot create Unix domain socket service\n"); + exit (1); + } + + /* Because final arg is 0, this will not register with portmap. */ + if (!svc_register (transp, LIBVIRTREMOTE, LIBVIRTREMOTE_VERS1, + libvirtremote_1, 0)) { + fprintf (stderr, "libvirtd: unable to register (LIBVIRTREMOTE, LIBVIRTREMOTE_VERS1, 0)\n"); + exit (1); + } + } + + svc_run (); + fprintf (stderr, "libvirtd: svc_run should not return\n"); + exit (1); +} + +// XXX DH_BITS has to match the value define in svc_gnutls.c +#define DH_BITS 1024 + +static void +generate_dh_params (void) +{ + /* Generate Diffie Hellman parameters - for use with DHE + * kx algorithms. These should be discarded and regenerated + * once a day, once a week or once a month. Depending on the + * security requirements. + */ + gnutls_dh_params_init (&dh_params); + gnutls_dh_params_generate2 (dh_params, DH_BITS); +} + +// See: http://people.redhat.com/drepper/userapi-ipv6.html +static int +make_sockets (int *fds, int max_fds, int *nfds_r, const char *service) +{ + struct addrinfo *ai; + struct addrinfo hints; + memset (&hints, 0, sizeof hints); + hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; + hints.ai_socktype = SOCK_STREAM; + + int e = getaddrinfo (NULL, service, &hints, &ai); + if (e != 0) { + fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (e)); + return -1; + } + + struct addrinfo *runp = ai; + while (runp && *nfds_r < max_fds) { + fds[*nfds_r] = socket (runp->ai_family, runp->ai_socktype, + runp->ai_protocol); + if (fds[*nfds_r] == -1) { + perror ("socket"); + return -1; + } + + int opt = 1; + setsockopt (fds[*nfds_r], SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt); + + if (bind (fds[*nfds_r], runp->ai_addr, runp->ai_addrlen) == -1) { + if (errno != EADDRINUSE) { + perror ("bind"); + return -1; + } + close (fds[*nfds_r]); + } + else { + if (listen (fds[*nfds_r], SOMAXCONN) == -1) { + perror ("listen"); + return -1; + } + ++*nfds_r; + } + runp = runp->ai_next; + } + + freeaddrinfo (ai); + + return 0; +} --- /tmp/newfile 2007-01-30 17:23:46.000000000 +0000 +++ src/remote_internal.c 2007-01-30 16:31:59.000000000 +0000 @@ -0,0 +1,663 @@ +/* + * remote_internal.c: driver to provide access to libvirtd running + * on a remote machine. + * + * Copyright (C) 2007 Red Hat, Inc. + * + * See COPYING.LIB for the license of this software. + * + * Richard Jones <rjones@redhat.com> + */ + +/* Architecture and notes: + * + * virConnectOpen ("remote:....") invokes this driver. Depending + * on the exact contents of the ellipsis "...." in the name string + * we will try some method to connect to a libvirtd daemon, running + * on a remote machine (or sometimes running on the local machine). + * + * All other vir* calls made on this connection are forwarded + * to the libvirtd daemon which carries out the requested action. + * So for example if you call virDomainCreateLinux, then the + * domain gets created on the remote machine, and virConnectListDomains + * lists domains running on the remote machine. + * + * Connections can be authenticated and encrypted -- it depends + * on the transport selected by the name string. + * + * The current implementation uses SunRPC layered over one of: + * - GnuTLS (an SSL/TLS library providing enterprise-level + * authentication and encryption) + * - a local Unix domain socket + * - ssh or another external program such as rsh + * - a plain TCP socket (unencrypted, not recommended for production) + * + * See http://et.redhat.com/~rjones/secure_rpc for an insight into + * the thinking that went into the selection of SunRPC. In + * the future we may use a different RPC system - for example + * XML-RPC would be a logical choice - so for now you should regard + * the protocol used as private and subject to change in future + * versions of libvirt without notice. + * + * The name string selects the transport to use and the type of + * virtualisation at the remote end. The general format is: + * + * "remote:<protocol>:<path> var=value var=value ..." + * + * Some examples: + * + * "remote:unix:/var/run/libvirtd/socket" + * "remote:tls:myxenserver" + * "remote:ssh:myserver name=qemud" + * "remote:ssh:myserver command=/opt/openssh/bin/ssh" + * + * The <protocol> is one of: tls, unix, ssh, ext or tcp. + * The <path> is protocol specific: + * + * Protocol Path-format + * ----------------------------------------- + * tls hostname[:port] + * unix Path to local socket + * ssh hostname[:port] + * ext Name or path of external program + * tcp hostname[:port] + * + * For tls, the default port is 16514. For tcp, the default port is + * 16509 (but note that tcp is almost never enabled because it is + * insecure - it's only there for testing). + * + * For ssh: The default port for ssh is 22. You should configure ssh + * so that it doesn't ask for a password (eg. using ssh-agent). The + * remote server should have a recent version of the the netcat program + * installed as 'nc', and the remote libvirtd must be configured to + * listen on a Unix domain socket. The following full command is run: + * ssh -p $port $hostname nc -U /var/run/libvirtd/socket + * + * For ext: Only the command you specify is run. It is up to you to + * write this command so that it somehow makes a connection to a + * remote libvirtd, and passes input and output over its stdin/stdout. + * + * The var=value pairs provide optional extra information: + * + * Variable Protocols Meaning + * ----------------------------------------- + * name (all) Name used in remote virConnectOpen + * (default is NULL). + * command ssh Name or path of external program (instead + * of "ssh"). + * + * The value is %-escaped (just like URL encoding), so if you want it + * to contain a literal space use "%20" or "+", if you want it to have + * a literal + character use "%2b", and for a literal % character use "%25". + * + * To provide some forwards compatibility, variables which are not + * understood are ignored (but a warning is printed on stderr). + * + * Several shorthand syntaxes are available: + * + * "remote:/var/run/libvirtd/socket" connect to Unix domain socket + * "remote://server" connect to TLS socket on server + * "remote://server:9000" connect to TLS server port 9000 + * + * For the details of the implementation of SunRPC over GnuTLS, etc. + * please see http://et.redhat.com/~rjones/secure_rpc which contains + * simple code samples which will allow you to understand what's + * going on here. + * + * - Richard Jones <rjones@redhat.com> + */ + +#define _GNU_SOURCE + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <rpc/rpc.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <netdb.h> +#include <assert.h> +#include <gnutls/gnutls.h> + +#include "sunrpc/clnt_ext.h" +#include "sunrpc/clnt_gnutls.h" +#include "sunrpc/clnt_tcp2.h" + +#include "internal.h" +#include "driver.h" +#include "remote_internal.h" +#include "remote_rpc.h" + +#define VERSION 1 /* Doesn't really mean anything. */ +#define DEBUG 1 /* Enable verbose messages on stderr. */ + +#define MAGIC 999 /* private_data->magic if OK */ +#define DEAD 998 /* private_data->magic if dead/closed */ + +struct private_data { + int magic; /* Should be MAGIC or DEAD. */ + int sock; /* Socket. */ + CLIENT *cl; /* SunRPC client. */ +}; + +#define GET_PRIVATE(conn,retcode) \ + struct private_data *private = (struct private_data *) (conn)->private; \ + assert (private); \ + if (private->magic == DEAD) { \ + error (conn, VIR_ERR_INVALID_ARG, \ + "tried to use a closed or uninitialised handle"); \ + return (retcode); \ + } \ + assert (private->magic == MAGIC) + +#define CAFILE "demoCA/cacert.pem" /* XXX */ + +static gnutls_certificate_credentials_t xcred; + +static void +initialise_gnutls (void) +{ + static int initialised = 0; + + if (initialised) return; + + gnutls_global_init (); + + /* X509 stuff */ + gnutls_certificate_allocate_credentials (&xcred); + + /* sets the trusted cas file + */ + gnutls_certificate_set_x509_trust_file (xcred, CAFILE, GNUTLS_X509_FMT_PEM); + + initialised = 1; +} + +enum protocol { + proto_unknown, + proto_tls, + proto_unix, + proto_ssh, + proto_ext, + proto_tcp +}; + +#define whitespace " \t\n" + +static char *unescape (const char *); +static int parse_hostname_port (const char *, char **hostname_r, + char **port_r, const char *default_port); +static void error (virConnectPtr conn, virErrorNumber code, const char *info); + +static int +remote_open (virConnectPtr conn, const char *name, + int flags __attribute__((unused))) +{ + int retcode = -1; /* Return code from this function. */ + enum protocol proto = proto_unknown; + char *path = 0; /* or hostname */ + char *port = 0; + char *remote_name = 0; /* Name to use at remote end of connection. */ + char *command = 0; /* External command. */ + char **cmd_argv = 0; /* External command argv[] array. */ + struct private_data private = { .magic = DEAD }; + /* Private data - copied to conn->private at the + * end of this function. + */ + + initialise_gnutls (); + + if (strncasecmp (name, "remote:", 7) != 0) + return -1; /* Not for me. */ + + /* Split the name at whitespace and parse it. */ + const char *p; + for (p = name; *p;) { + size_t len = strcspn (p, whitespace); + char *token = strndup (p, len); +#if DEBUG + fprintf (stderr, "token = %s\n", token); +#endif + if (p == name) { + /* First parameter is remote:protocol:path or a shortcut. */ + if (strncasecmp (token, "remote://", 9) == 0) { + proto = proto_tls; + if (parse_hostname_port (token+9, &path, &port, LIBVIRTD_GNUTLS_PORT) + == -1) { + error (conn, VIR_ERR_INVALID_ARG, + "remote_open: cannot parse port number"); + goto failed; + } + } else if (strncasecmp (token, "remote:/", 8) == 0) { + proto = proto_unix; + path = strdup (token+7); // include the initial slash + } else if (strncasecmp (token, "remote:tls:", 11) == 0) { + proto = proto_tls; + if (parse_hostname_port (token+11, &path, &port, LIBVIRTD_GNUTLS_PORT) + == -1) { + error (conn, VIR_ERR_INVALID_ARG, + "remote_open: cannot parse port number"); + goto failed; + } + } else if (strncasecmp (token, "remote:unix:", 12) == 0) { + proto = proto_unix; + path = strdup (token+12); + } else if (strncasecmp (token, "remote:ssh:", 11) == 0) { + proto = proto_ssh; + if (parse_hostname_port (token+11, &path, &port, "ssh") == -1) { + error (conn, VIR_ERR_INVALID_ARG, + "remote_open: cannot parse port number"); + goto failed; + } + command = strdup ("ssh"); + } else if (strncasecmp (token, "remote:ext:", 11) == 0) { + proto = proto_ext; + command = strdup (token+11); + } else if (strncasecmp (token, "remote:tcp:", 11) == 0) { + proto = proto_tcp; + if (parse_hostname_port (token+11, &path, &port, LIBVIRTD_TCP_PORT) + == -1) { + error (conn, VIR_ERR_INVALID_ARG, + "remote_open: cannot parse port number"); + goto failed; + } + } else { + error (conn, VIR_ERR_INVALID_ARG, + "remote_open: expecting 'remote:protocol:path' - please read the manual for the remote driver / libvirtd"); + goto failed; + } + } else { + /* Variable=value. Value is URL-escaped. */ + char *value = strchr (token, '='); + if (!value) { + error (conn, VIR_ERR_INVALID_ARG, + "remote_open: expecting 'variable=value'"); + goto failed; + } + *value++ = '\0'; /* So now token = variable, value = value. */ + /* Unescape value - this always makes a copy. */ + value = unescape (value); + + if (strcasecmp (token, "name") == 0) { + if (remote_name) { + free (remote_name); + fprintf (stderr, "libvir: warning: multiple name parameters. All but final will be ignored.\n"); + } + remote_name = value; + } else if (strcasecmp (token, "command") == 0) { + if (command) free (command); + command = value; + } else + /* For forwards compatibility, just warn about variables we + * don't understand. + */ + fprintf (stderr, "libvir: warning: variable '%s' ignored\n", token); + } + + free (token); + + /* Skip to next token. */ + p += len; + p += strspn (p, whitespace); + } + + /* Connect to the remote service. */ + switch (proto) { + case proto_unknown: + abort (); /* Internal error in this function. */ + + case proto_tls: + case proto_tcp: { + // http://people.redhat.com/drepper/userapi-ipv6.html + struct addrinfo *res, *r; + struct addrinfo hints; + memset (&hints, 0, sizeof hints); + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_ADDRCONFIG; + int e = getaddrinfo (path, port, &hints, &res); + if (e != 0) { + error (conn, VIR_ERR_INVALID_ARG, gai_strerror (e)); + goto failed; + } + + /* Try to connect to each returned address in turn. */ + for (r = res; r; r = r->ai_next) { + private.sock = RPC_ANYSOCK; + private.cl = + proto == proto_tls + ? clntgnutls_create (r->ai_family, r->ai_addr, r->ai_addrlen, xcred, + LIBVIRTREMOTE, LIBVIRTREMOTE_VERS1, + &private.sock, 0, 0) + : clnttcp2_create (r->ai_family, r->ai_addr, r->ai_addrlen, + LIBVIRTREMOTE, LIBVIRTREMOTE_VERS1, + &private.sock, 0, 0); + if (private.cl) + goto tcp_connected; + } + + freeaddrinfo (res); + error (conn, VIR_ERR_RPC, + clnt_spcreateerror ("could not create SunRPC client")); + goto failed; + + tcp_connected: + freeaddrinfo (res); + + // NB. All versioning is done by SunRPC so we don't need to worry + // that we are connected to an incompatible daemon. + break; + } + + case proto_unix: { + // 108 is hard-coded into the header files as well. +#define UNIX_PATH_MAX 108 + struct sockaddr_un addr; + memset (&addr, 0, sizeof addr); + addr.sun_family = AF_UNIX; + strncpy (addr.sun_path, path, UNIX_PATH_MAX); + + private.sock = RPC_ANYSOCK; + private.cl = + clntunix_create (&addr, + LIBVIRTREMOTE, LIBVIRTREMOTE_VERS1, + &private.sock, 0, 0); + if (!private.cl) { + error (conn, VIR_ERR_RPC, + clnt_spcreateerror ("could not create SunRPC client")); + goto failed; + } + + break; + } + + case proto_ssh: + // Generate the final command argv[] array. + // ssh -p $port $hostname nc -U $socket [NULL] + cmd_argv = malloc (8 * sizeof (char *)); + cmd_argv[0] = strdup (command); + cmd_argv[1] = strdup ("-p"); + cmd_argv[2] = strdup (port); + cmd_argv[3] = strdup (path); + cmd_argv[4] = strdup ("nc"); + cmd_argv[5] = strdup ("-U"); + cmd_argv[6] = strdup (LIBVIRTD_UNIX_SOCKET); + cmd_argv[7] = 0; + /*FALLTHROUGH*/ + case proto_ext: + private.sock = RPC_ANYSOCK; + private.cl = + clntext_create (command, cmd_argv, + LIBVIRTREMOTE, LIBVIRTREMOTE_VERS1, + &private.sock, 0, 0); + if (!private.cl) { + error (conn, VIR_ERR_RPC, + clnt_spcreateerror ("could not create SunRPC client")); + goto failed; + } + } + + // Send name (make the actual driver open RPC). + int *retcode_ptr = remote_rpc_open_1 (&remote_name, private.cl); + if (!retcode_ptr || *retcode_ptr == -1) { + error (conn, VIR_ERR_RPC, + clnt_sperror (private.cl, "remote_rpc_open")); + clnt_destroy (private.cl); + goto failed; + } + + conn->private = malloc (sizeof private); + if (!conn->private) { + error (conn, VIR_ERR_NO_MEMORY, "malloc"); + clnt_destroy (private.cl); + goto failed; + } + private.magic = MAGIC; + memcpy (conn->private, &private, sizeof private); + + retcode = 0; /* Success. */ + /*FALLTHROUGH*/ + + failed: + if (path) free (path); + if (port) free (port); + if (remote_name) free (remote_name); + if (command) free (command); + if (cmd_argv) { + char **a = cmd_argv; + while (*a) { free (*a); a++; } + free (cmd_argv); + } + + return retcode; +} + +/* Un-%-escape the argument string. Note that this always makes + * a copy, and that is intentional. + */ +static inline int +xdigit (char c) +{ + switch (c) { + case '0'...'9': return c - '0'; + case 'a'...'f': return c - 'a' + 10; + case 'A'...'F': return c - 'A' + 10; + default: return -1; + } +} + +static char * +unescape (const char *str) +{ + // Returned string will always be same length or shorter than input. + int n = strlen (str); + + char *ret = malloc (n+1); + if (ret == 0) return 0; // although _I_ think we should abort(). + + int i; + char *p = ret; + for (i = 0; i < n; ++i) { + if (str[i] == '+') { + *p++ = ' '; + } else if (str[i] == '%') { + if (i+2 < n) { + int c1 = xdigit (str[i+1]), c2 = xdigit (str[i+2]); + if (c1 == -1 || c2 == -1) { + fprintf (stderr, "remote_open: incorrect %%-hex sequence in name\n"); + return 0; + } + *p++ = xdigit (c1) << 4 | xdigit (c2); + } else { + fprintf (stderr, "remote_open: short %%-hex sequence in name\n"); + return 0; + } + } else + *p++ = str[i]; + } + *p = '\0'; + return ret; +} + +/* Parse a string which may be either "hostname" or "hostname:port". + * The hostname may contain colons (eg. if it's an IPv6 name). + * Note that this always makes a copy of the hostname and port number, + * and that is intentional. + */ +static int +parse_hostname_port (const char *str, char **hostname_r, + char **port_r, const char *default_port) +{ + char *p = strrchr (str, ':'); + if (p) { + *hostname_r = strndup (str, p-str); + *port_r = strdup (p+1); + } else { + // No :port, so just copy the hostname. + *hostname_r = strdup (str); + *port_r = strdup (default_port); + } + return 0; +} + +static int +remote_close (virConnectPtr conn) +{ + GET_PRIVATE (conn, -1); + + int *retcode = remote_rpc_close_1 (NULL, private->cl); + if (retcode == 0) { + error (conn, VIR_ERR_RPC, + clnt_sperror (private->cl, "remote_rpc_open")); + return -1; + } + if (*retcode == -1) return -1; + + // XXX freeres + + // NB. clnt_destroy should close the socket (private->sock) too. + clnt_destroy (private->cl); + // Force errors if anyone tries to reuse the closed connection. + private->magic = DEAD; + + return *retcode; +} + +/* Remote_open and remote_close functions above are the complex ones. The + * rest just shuffle arguments and pass them along to the remote libvirtd. + */ + +// Should we return our local type (ie. "remote"), or the type +// of the remote HV, or the type of the remote HV + some flag? +// I took the view that we should just transparently shuffle. +static const char * +remote_type (virConnectPtr conn) +{ + GET_PRIVATE (conn, NULL); + + char **type = remote_rpc_type_1 (NULL, private->cl); + if (type == 0) { + error (conn, VIR_ERR_RPC, + clnt_sperror (private->cl, "remote_rpc_open")); + return NULL; + } + + // XXX freeres + + return *type; +} + +static int +remote_version (virConnectPtr conn, unsigned long *hvVer) +{ + GET_PRIVATE (conn, -1); + + struct version_ret *ret = remote_rpc_version_1 (NULL, private->cl); + if (ret == 0) { + error (conn, VIR_ERR_RPC, + clnt_sperror (private->cl, "remote_rpc_open")); + return -1; + } + + // XXX freeres + + if (ret->retcode == -1) return -1; + *hvVer = ret->hvVer; + return ret->retcode; +} + +/* + .nodeGetInfo = remote_nodeGetInfo, + .listDomains = remote_listDomains, + .numOfDomains = remote_numOfDomains, + .domainCreateLinux = remote_domainCreateLinux, + .domainLookupByID = remote_domainLookupByID, + .domainLookupByUUID = remote_domainLookupByUUID, + .domainLookupByName = remote_domainLookupByName, + .domainSuspend = remote_domainSuspend, + .domainResume = remote_domainResume, + .domainShutdown = remote_domainShutdown, + .domainReboot = remote_domainReboot, + .domainDestroy = remote_domainDestroy, + .domainGetOSType = remote_domainGetOSType, + .domainGetMaxMemory = remote_domainGetMaxMemory, + .domainSetMaxMemory = remote_domainSetMaxMemory, + .domainSetMemory = remote_domainSetMemory, + .domainGetInfo = remote_domainGetInfo, + .domainSave = remote_domainSave, + .domainRestore = remote_domainRestore, + .domainCoreDump = remote_domainCoreDump, + .domainSetVcpus = remote_domainSetVcpus, + .domainPinVcpu = remote_domainPinVcpu, + .domainGetVcpus = remote_domainGetVcpus, + .domainDumpXML = remote_domainDumpXML, + .listDefinedDomains = remote_listDefinedDomains, + .numOfDefinedDomains = remote_numOfDefinedDomains, + .domainCreate = remote_domainCreate, + .domainDefineXML = remote_domainDefineXML, + .domainUndefine = remote_domainUndefine, + .domainAttachDevice = remote_domainAttachDevice, + .domainDetachDevice = remote_domainDetachDevice, +*/ + +/* Error handling. This error handling is on crack. */ +static void +error (virConnectPtr conn, virErrorNumber code, const char *info) +{ + const char *errmsg; + + errmsg = __virErrorMsg (code, info); + __virRaiseError (conn, NULL, VIR_FROM_REMOTE, + code, VIR_ERR_ERROR, errmsg, info, NULL, 0, 0, + errmsg, info); +} + +static virDriver driver = { + .no = VIR_DRV_REMOTE, + .name = "remote", + .ver = VERSION, + //.init = remote_init, + .open = remote_open, + .close = remote_close, + .type = remote_type, + .version = remote_version, +#if 0 + .nodeGetInfo = remote_nodeGetInfo, + .listDomains = remote_listDomains, + .numOfDomains = remote_numOfDomains, + .domainCreateLinux = remote_domainCreateLinux, + .domainLookupByID = remote_domainLookupByID, + .domainLookupByUUID = remote_domainLookupByUUID, + .domainLookupByName = remote_domainLookupByName, + .domainSuspend = remote_domainSuspend, + .domainResume = remote_domainResume, + .domainShutdown = remote_domainShutdown, + .domainReboot = remote_domainReboot, + .domainDestroy = remote_domainDestroy, + .domainGetOSType = remote_domainGetOSType, + .domainGetMaxMemory = remote_domainGetMaxMemory, + .domainSetMaxMemory = remote_domainSetMaxMemory, + .domainSetMemory = remote_domainSetMemory, + .domainGetInfo = remote_domainGetInfo, + .domainSave = remote_domainSave, + .domainRestore = remote_domainRestore, + .domainCoreDump = remote_domainCoreDump, + .domainSetVcpus = remote_domainSetVcpus, + .domainPinVcpu = remote_domainPinVcpu, + .domainGetVcpus = remote_domainGetVcpus, + .domainDumpXML = remote_domainDumpXML, + .listDefinedDomains = remote_listDefinedDomains, + .numOfDefinedDomains = remote_numOfDefinedDomains, + .domainCreate = remote_domainCreate, + .domainDefineXML = remote_domainDefineXML, + .domainUndefine = remote_domainUndefine, + .domainAttachDevice = remote_domainAttachDevice, + .domainDetachDevice = remote_domainDetachDevice, +#endif +}; + +/* Register driver. */ +void +remoteRegister (void) +{ + virRegisterDriver (&driver); +} --- /tmp/newfile 2007-01-30 17:23:46.000000000 +0000 +++ src/remote_internal.h 2007-01-30 16:21:19.000000000 +0000 @@ -0,0 +1,34 @@ +/* + * remote_internal.h: driver to provide access to libvirtd running + * on a remote machine. + * + * Copyright (C) 2007 Red Hat, Inc. + * + * See COPYING.LIB for the license of this software. + * + * Richard Jones <rjones@redhat.com> + */ + +#ifndef __VIR_REMOTE_INTERNAL_H__ +#define __VIR_REMOTE_INTERNAL_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +void remoteRegister (void); + +/* The port numbers are strings because you can also use + * service names here. + */ +#define LIBVIRTD_GNUTLS_PORT "16514" +#define LIBVIRTD_TCP_PORT "16509" + //#define LIBVIRTD_UNIX_SOCKET "/var/run/libvirtd/socket" +#define LIBVIRTD_UNIX_SOCKET "/tmp/socket" // Just for testing + +#ifdef __cplusplus +} +#endif + + +#endif /* __VIR_REMOTE_INTERNAL_H__ */ --- /tmp/newfile 2007-01-30 17:23:46.000000000 +0000 +++ src/remote_rpc.x 2007-01-30 13:32:56.000000000 +0000 @@ -0,0 +1,38 @@ +/* -*- C -*- + * remote_rpc.x: Remote procedure call interface for the remote driver. + * Process this file with rpcgen to generate client and server stubs. + * + * Copyright (C) 2007 Red Hat, Inc. + * + * See COPYING.LIB for the license of this software. + * + * Richard Jones <rjones@redhat.com> + */ + +/* Structure returned from remote_rpc_version. */ +struct version_ret { + int retcode; + long hvVer; +}; + +program LIBVIRTREMOTE { + version LIBVIRTREMOTE_VERS1 { + /* XXX The open interface should return a cookie to represent + * the virConnectPtr on the server side. Note also that SunRPC + * is stateless so it's unclear when cookies can be garbage + * collected. + */ + int remote_rpc_open (string) = 1; + int remote_rpc_close (void) = 2; + string remote_rpc_type (void) = 3; + version_ret remote_rpc_version (void) = 4; + + /* etc */ + + } = 1; + /* It doesn't really matter what program number we choose here because + * there will only ever be one "program" listening on the assigned + * TCP port number. Nevertheless, choose one from the Sun private + * space. + */ +} = 0x20008080; --- /tmp/newfile 2007-01-30 17:23:46.000000000 +0000 +++ src/sunrpc/README 2007-01-30 16:15:43.000000000 +0000 @@ -0,0 +1,25 @@ +This directory contains modified SunRPC transports. They are based on +the standard transports from glibc 2.5 (in the sunrpc/ directory +there). + +clnt_ext.c + - Fork an external program, eg. ssh. + (Original: clnt_unix.c) + +clnt_gnutls.c + - Modified for IPv6 and GnuTLS support. + (Original: clnt_tcp.c) + +clnt_tcp2.c + - Modified for IPv6 support. + (Original: clnt_tcp.c) + +svc_gnutls.c + - Modified for IPv6 and GnuTLS support. + (Original: svc_tcp.c) + +svc_tcp2.c + - Modified for IPv6 support. + (Original: svc_tcp.c) + +$Id$ \ No newline at end of file --- /tmp/newfile 2007-01-30 17:23:46.000000000 +0000 +++ src/sunrpc/clnt_ext.c 2007-01-30 11:48:09.000000000 +0000 @@ -0,0 +1,645 @@ +/* + * clnt_ext.c: SunRPC over an external program. This is a modified version + * of clnt_unix.c from glibc which is written to run over a forked + * external program. + * + * Note that there is no corresponding svc_ext.c. It is expected that + * this client will talk to a remote Unix domain socket (ie. svc_unix.c). + * + * Modifications from glibc-2.5 base by Richard Jones <rjones@redhat.com>. + */ + +/* + * Sun RPC is a product of Sun Microsystems, Inc. and is provided for + * unrestricted use provided that this legend is included on all tape + * media and as a part of the software program in whole or part. Users + * may copy or modify Sun RPC without charge, but are not authorized + * to license or distribute it to anyone else except as part of a product or + * program developed by the user. + * + * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE + * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR + * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. + * + * Sun RPC is provided with no support and without any obligation on the + * part of Sun Microsystems, Inc. to assist in its use, correction, + * modification or enhancement. + * + * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE + * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC + * OR ANY PART THEREOF. + * + * In no event will Sun Microsystems, Inc. be liable for any lost revenue + * or profits or other special, indirect and consequential damages, even if + * Sun has been advised of the possibility of such damages. + * + * Sun Microsystems, Inc. + * 2550 Garcia Avenue + * Mountain View, California 94043 + */ + +/* + * clnt_unix.c, Implements a TCP/IP based, client side RPC. + * + * Copyright (C) 1984, Sun Microsystems, Inc. + * + * TCP based RPC supports 'batched calls'. + * A sequence of calls may be batched-up in a send buffer. The rpc call + * return immediately to the client even though the call was not necessarily + * sent. The batching occurs if the results' xdr routine is NULL (0) AND + * the rpc timeout value is zero (see clnt.h, rpc). + * + * Clients should NOT casually batch calls that in fact return results; that is, + * the server side should be aware that a call is batched and not produce any + * return message. Batched calls that produce many result messages can + * deadlock (netlock) the client and the server.... + * + * Now go hang yourself. + */ + +#include <netdb.h> +#include <errno.h> +#include <stdio.h> +#include <unistd.h> +#include <libintl.h> +#include <rpc/rpc.h> +#include <sys/uio.h> +#include <sys/poll.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <rpc/pmap_clnt.h> +#ifdef USE_IN_LIBIO +# include <wchar.h> +#endif + +#include "clnt_ext.h" + +extern u_long _create_xid (void); + +#define MCALL_MSG_SIZE 24 + +struct ct_data + { + int ct_sock; + bool_t ct_closeit; + struct timeval ct_wait; + bool_t ct_waitset; /* wait set by clnt_control? */ + struct rpc_err ct_error; + char ct_mcall[MCALL_MSG_SIZE]; /* marshalled callmsg */ + u_int ct_mpos; /* pos after marshal */ + XDR ct_xdrs; + int ct_pid; /* Child PID. */ + }; + +static int readunix (char *, char *, int); +static int writeunix (char *, char *, int); + +static enum clnt_stat clntunix_call (CLIENT *, u_long, xdrproc_t, caddr_t, + xdrproc_t, caddr_t, struct timeval); +static void clntunix_abort (void); +static void clntunix_geterr (CLIENT *, struct rpc_err *); +static bool_t clntunix_freeres (CLIENT *, xdrproc_t, caddr_t); +static bool_t clntunix_control (CLIENT *, int, char *); +static void clntunix_destroy (CLIENT *); + +static const struct clnt_ops unix_ops = +{ + clntunix_call, + clntunix_abort, + clntunix_geterr, + clntunix_freeres, + clntunix_destroy, + clntunix_control +}; + +CLIENT * +clntext_create (char *filename, char *argv[], + u_long prog, u_long vers, + int *sockp, u_int sendsz, u_int recvsz) +{ + CLIENT *h; + struct ct_data *ct = (struct ct_data *) mem_alloc (sizeof (*ct)); + struct rpc_msg call_msg; + int len, sv[2]; + + h = (CLIENT *) mem_alloc (sizeof (*h)); + if (h == NULL || ct == NULL) + { + struct rpc_createerr *ce = &get_rpc_createerr (); + (void) fprintf (stderr, "clntext_create: out of memory\n"); + ce->cf_stat = RPC_SYSTEMERROR; + ce->cf_error.re_errno = ENOMEM; + goto fooy; + } + + /* Fork off the external process. Use socketpair to create a private + * (unnamed) Unix domain socket to the child process so we don't have + * to faff around with two file descriptors (a la 'pipe(2)'). + */ + if (*sockp < 0) + { + if (socketpair (PF_UNIX, SOCK_STREAM, 0, sv) == -1) + { + struct rpc_createerr *ce = &get_rpc_createerr (); + (void) fprintf (stderr, "clntext_create: socketpair\n"); + ce->cf_stat = RPC_SYSTEMERROR; + ce->cf_error.re_errno = errno; + goto fooy; + } + + ct->ct_pid = fork (); + if (ct->ct_pid == -1) { + struct rpc_createerr *ce = &get_rpc_createerr (); + (void) fprintf (stderr, "clntext_create: fork\n"); + ce->cf_stat = RPC_SYSTEMERROR; + ce->cf_error.re_errno = errno; + goto fooy; + } else if (ct->ct_pid == 0) { /* Child. */ + close (sv[0]); + // Connect socket (sv[1]) to stdin/stdout. + close (0); + dup (sv[1]); + close (1); + dup (sv[1]); + close (sv[1]); + + // Run the external process. + + if (!argv) { + argv = malloc (2 * sizeof (char *)); + argv[0] = filename; + argv[1] = 0; + } + execvp (filename, argv); + perror (filename); + _exit (1); + } + + /* Parent continues here. */ + close (sv[1]); + *sockp = sv[0]; + ct->ct_closeit = TRUE; + } + else + { + ct->ct_closeit = FALSE; + } + + /* + * Set up private data struct + */ + ct->ct_sock = *sockp; + ct->ct_wait.tv_usec = 0; + ct->ct_waitset = FALSE; + + /* + * Initialize call message + */ + call_msg.rm_xid = _create_xid (); + call_msg.rm_direction = CALL; + call_msg.rm_call.cb_rpcvers = RPC_MSG_VERSION; + call_msg.rm_call.cb_prog = prog; + call_msg.rm_call.cb_vers = vers; + + /* + * pre-serialize the static part of the call msg and stash it away + */ + xdrmem_create (&(ct->ct_xdrs), ct->ct_mcall, MCALL_MSG_SIZE, + XDR_ENCODE); + if (!xdr_callhdr (&(ct->ct_xdrs), &call_msg)) + { + if (ct->ct_closeit) + close (*sockp); + goto fooy; + } + ct->ct_mpos = XDR_GETPOS (&(ct->ct_xdrs)); + XDR_DESTROY (&(ct->ct_xdrs)); + + /* + * Create a client handle which uses xdrrec for serialization + * and authnone for authentication. + */ + xdrrec_create (&(ct->ct_xdrs), sendsz, recvsz, + (caddr_t) ct, readunix, writeunix); + h->cl_ops = (struct clnt_ops *) &unix_ops; + h->cl_private = (caddr_t) ct; + h->cl_auth = authnone_create (); + return h; + +fooy: + /* + * Something goofed, free stuff and barf + */ + mem_free ((caddr_t) ct, sizeof (struct ct_data)); + mem_free ((caddr_t) h, sizeof (CLIENT)); + return (CLIENT *) NULL; +} + +static enum clnt_stat +clntunix_call (h, proc, xdr_args, args_ptr, xdr_results, results_ptr, timeout) + CLIENT *h; + u_long proc; + xdrproc_t xdr_args; + caddr_t args_ptr; + xdrproc_t xdr_results; + caddr_t results_ptr; + struct timeval timeout; +{ + struct ct_data *ct = (struct ct_data *) h->cl_private; + XDR *xdrs = &(ct->ct_xdrs); + struct rpc_msg reply_msg; + u_long x_id; + u_int32_t *msg_x_id = (u_int32_t *) (ct->ct_mcall); /* yuk */ + bool_t shipnow; + int refreshes = 2; + + if (!ct->ct_waitset) + { + ct->ct_wait = timeout; + } + + shipnow = + (xdr_results == (xdrproc_t) 0 && ct->ct_wait.tv_sec == 0 + && ct->ct_wait.tv_usec == 0) ? FALSE : TRUE; + +call_again: + xdrs->x_op = XDR_ENCODE; + ct->ct_error.re_status = RPC_SUCCESS; + x_id = ntohl (--(*msg_x_id)); + if ((!XDR_PUTBYTES (xdrs, ct->ct_mcall, ct->ct_mpos)) || + (!XDR_PUTLONG (xdrs, (long *) &proc)) || + (!AUTH_MARSHALL (h->cl_auth, xdrs)) || + (!(*xdr_args) (xdrs, args_ptr))) + { + if (ct->ct_error.re_status == RPC_SUCCESS) + ct->ct_error.re_status = RPC_CANTENCODEARGS; + (void) xdrrec_endofrecord (xdrs, TRUE); + return ct->ct_error.re_status; + } + if (!xdrrec_endofrecord (xdrs, shipnow)) + return ct->ct_error.re_status = RPC_CANTSEND; + if (!shipnow) + return RPC_SUCCESS; + /* + * Hack to provide rpc-based message passing + */ + if (ct->ct_wait.tv_sec == 0 && ct->ct_wait.tv_usec == 0) + return ct->ct_error.re_status = RPC_TIMEDOUT; + + + /* + * Keep receiving until we get a valid transaction id + */ + xdrs->x_op = XDR_DECODE; + while (TRUE) + { + reply_msg.acpted_rply.ar_verf = _null_auth; + reply_msg.acpted_rply.ar_results.where = NULL; + reply_msg.acpted_rply.ar_results.proc = (xdrproc_t)xdr_void; + if (!xdrrec_skiprecord (xdrs)) + return ct->ct_error.re_status; + /* now decode and validate the response header */ + if (!xdr_replymsg (xdrs, &reply_msg)) + { + if (ct->ct_error.re_status == RPC_SUCCESS) + continue; + return ct->ct_error.re_status; + } + if (reply_msg.rm_xid == x_id) + break; + } + + /* + * process header + */ + _seterr_reply (&reply_msg, &(ct->ct_error)); + if (ct->ct_error.re_status == RPC_SUCCESS) + { + if (!AUTH_VALIDATE (h->cl_auth, &reply_msg.acpted_rply.ar_verf)) + { + ct->ct_error.re_status = RPC_AUTHERROR; + ct->ct_error.re_why = AUTH_INVALIDRESP; + } + else if (!(*xdr_results) (xdrs, results_ptr)) + { + if (ct->ct_error.re_status == RPC_SUCCESS) + ct->ct_error.re_status = RPC_CANTDECODERES; + } + /* free verifier ... */ + if (reply_msg.acpted_rply.ar_verf.oa_base != NULL) + { + xdrs->x_op = XDR_FREE; + (void) xdr_opaque_auth (xdrs, + &(reply_msg.acpted_rply.ar_verf)); + } + } /* end successful completion */ + else + { + /* maybe our credentials need to be refreshed ... */ + if (refreshes-- && AUTH_REFRESH (h->cl_auth)) + goto call_again; + } /* end of unsuccessful completion */ + return ct->ct_error.re_status; +} + +static void +clntunix_geterr (CLIENT *h, struct rpc_err *errp) +{ + struct ct_data *ct = (struct ct_data *) h->cl_private; + + *errp = ct->ct_error; +} + +static bool_t +clntunix_freeres (cl, xdr_res, res_ptr) + CLIENT *cl; + xdrproc_t xdr_res; + caddr_t res_ptr; +{ + struct ct_data *ct = (struct ct_data *) cl->cl_private; + XDR *xdrs = &(ct->ct_xdrs); + + xdrs->x_op = XDR_FREE; + return (*xdr_res) (xdrs, res_ptr); +} + +static void +clntunix_abort () +{ +} + +static bool_t +clntunix_control (CLIENT *cl, int request, char *info) +{ + struct ct_data *ct = (struct ct_data *) cl->cl_private; + + + switch (request) + { + case CLSET_FD_CLOSE: + ct->ct_closeit = TRUE; + break; + case CLSET_FD_NCLOSE: + ct->ct_closeit = FALSE; + break; + case CLSET_TIMEOUT: + ct->ct_wait = *(struct timeval *) info; + break; + case CLGET_TIMEOUT: + *(struct timeval *) info = ct->ct_wait; + break; + case CLGET_FD: + *(int *)info = ct->ct_sock; + break; + case CLGET_XID: + /* + * use the knowledge that xid is the + * first element in the call structure *. + * This will get the xid of the PREVIOUS call + */ + *(u_long *) info = ntohl (*(u_long *)ct->ct_mcall); + break; + case CLSET_XID: + /* This will set the xid of the NEXT call */ + *(u_long *) ct->ct_mcall = htonl (*(u_long *)info - 1); + /* decrement by 1 as clntunix_call() increments once */ + case CLGET_VERS: + /* + * This RELIES on the information that, in the call body, + * the version number field is the fifth field from the + * begining of the RPC header. MUST be changed if the + * call_struct is changed + */ + *(u_long *) info = ntohl (*(u_long *) (ct->ct_mcall + + 4 * BYTES_PER_XDR_UNIT)); + break; + case CLSET_VERS: + *(u_long *) (ct->ct_mcall + 4 * BYTES_PER_XDR_UNIT) + = htonl (*(u_long *) info); + break; + case CLGET_PROG: + /* + * This RELIES on the information that, in the call body, + * the program number field is the field from the + * begining of the RPC header. MUST be changed if the + * call_struct is changed + */ + *(u_long *) info = ntohl (*(u_long *) (ct->ct_mcall + + 3 * BYTES_PER_XDR_UNIT)); + break; + case CLSET_PROG: + *(u_long *) (ct->ct_mcall + 3 * BYTES_PER_XDR_UNIT) + = htonl(*(u_long *) info); + break; + /* The following are only possible with TI-RPC */ + case CLGET_RETRY_TIMEOUT: + case CLSET_RETRY_TIMEOUT: + case CLGET_SVC_ADDR: + case CLSET_SVC_ADDR: + case CLSET_PUSH_TIMOD: + case CLSET_POP_TIMOD: + default: + return FALSE; + } + return TRUE; +} + + +static void +clntunix_destroy (CLIENT *h) +{ + struct ct_data *ct = + (struct ct_data *) h->cl_private; + + if (ct->ct_closeit) + { + (void) close (ct->ct_sock); + } + XDR_DESTROY (&(ct->ct_xdrs)); + mem_free ((caddr_t) ct, sizeof (struct ct_data)); + mem_free ((caddr_t) h, sizeof (CLIENT)); + + // Wait for child to finish. Print any errors but don't stop. + int status; + if (waitpid (ct->ct_pid, &status, 0) == -1) { + perror ("waitpid"); + return; + } + if (WIFEXITED (status)) { + int s = WEXITSTATUS (status); + if (s != 0) + fprintf (stderr, "clntext_destroy: warning: external command exited with non-zero exit status %d\n", s); + } else if (WIFSIGNALED (status)) { + int s = WTERMSIG (status); + fprintf (stderr, "clntext_destroy: warning: external command died on signal %d\n", s); + } else if (WIFSTOPPED (status)) { + int s = WSTOPSIG (status); + fprintf (stderr, "clntext_destroy: warning: external command stopped on signal %d\n", s); + } +} + +static int +__msgread (int sock, void *data, size_t cnt) +{ + struct iovec iov; + struct msghdr msg; +#ifdef SCM_CREDENTIALS + static char cm[CMSG_SPACE(sizeof (struct ucred))]; +#endif + int len; + + iov.iov_base = data; + iov.iov_len = cnt; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_name = NULL; + msg.msg_namelen = 0; +#ifdef SCM_CREDENTIALS + msg.msg_control = (caddr_t) &cm; + msg.msg_controllen = CMSG_SPACE(sizeof (struct ucred)); +#endif + msg.msg_flags = 0; + +#ifdef SO_PASSCRED + { + int on = 1; + if (setsockopt (sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof (on))) + return -1; + } +#endif + + restart: + len = recvmsg (sock, &msg, 0); + if (len >= 0) + { + if (msg.msg_flags & MSG_CTRUNC || len == 0) + return 0; + else + return len; + } + if (errno == EINTR) + goto restart; + return -1; +} + +static int +__msgwrite (int sock, void *data, size_t cnt) +{ +#ifndef SCM_CREDENTIALS + /* We cannot implement this reliably. */ + __set_errno (ENOSYS); + return -1; +#else + struct iovec iov; + struct msghdr msg; + struct cmsghdr *cmsg = alloca (CMSG_SPACE(sizeof (struct ucred))); + struct ucred cred; + int len; + + /* XXX I'm not sure, if gete?id() is always correct, or if we should use + get?id(). But since keyserv needs geteuid(), we have no other chance. + It would be much better, if the kernel could pass both to the server. */ + cred.pid = getpid (); + cred.uid = geteuid (); + cred.gid = getegid (); + + memcpy (CMSG_DATA(cmsg), &cred, sizeof (struct ucred)); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_CREDENTIALS; + cmsg->cmsg_len = sizeof(*cmsg) + sizeof(struct ucred); + + iov.iov_base = data; + iov.iov_len = cnt; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_control = cmsg; + msg.msg_controllen = CMSG_ALIGN(cmsg->cmsg_len); + msg.msg_flags = 0; + + restart: + len = sendmsg (sock, &msg, 0); + if (len >= 0) + return len; + if (errno == EINTR) + goto restart; + return -1; + +#endif +} + + +/* + * Interface between xdr serializer and unix connection. + * Behaves like the system calls, read & write, but keeps some error state + * around for the rpc level. + */ +static int +readunix (char *ctptr, char *buf, int len) +{ + struct ct_data *ct = (struct ct_data *) ctptr; + struct pollfd fd; + int milliseconds = ((ct->ct_wait.tv_sec * 1000) + + (ct->ct_wait.tv_usec / 1000)); + + if (len == 0) + return 0; + + fd.fd = ct->ct_sock; + fd.events = POLLIN; + while (TRUE) + { + switch (poll (&fd, 1, milliseconds)) + { + case 0: + ct->ct_error.re_status = RPC_TIMEDOUT; + return -1; + + case -1: + if (errno == EINTR) + continue; + ct->ct_error.re_status = RPC_CANTRECV; + ct->ct_error.re_errno = errno; + return -1; + } + break; + } + switch (len = __msgread (ct->ct_sock, buf, len)) + { + + case 0: + /* premature eof */ + ct->ct_error.re_errno = ECONNRESET; + ct->ct_error.re_status = RPC_CANTRECV; + len = -1; /* it's really an error */ + break; + + case -1: + ct->ct_error.re_errno = errno; + ct->ct_error.re_status = RPC_CANTRECV; + break; + } + return len; +} + +static int +writeunix (char *ctptr, char *buf, int len) +{ + int i, cnt; + struct ct_data *ct = (struct ct_data *) ctptr; + + for (cnt = len; cnt > 0; cnt -= i, buf += i) + { + if ((i = __msgwrite (ct->ct_sock, buf, cnt)) == -1) + { + ct->ct_error.re_errno = errno; + ct->ct_error.re_status = RPC_CANTSEND; + return -1; + } + } + return len; +} --- /tmp/newfile 2007-01-30 17:23:46.000000000 +0000 +++ src/sunrpc/clnt_ext.h 2007-01-30 11:47:24.000000000 +0000 @@ -0,0 +1,18 @@ +/* + * clnt_ext.h: Interface to the SunRPC over external program. + * + * Copyright (C) 2007 Red Hat, Inc. + * + * See COPYING.LIB for the license of this software. + * + * Richard Jones <rjones@redhat.com> + */ + +#ifndef __CLNT_EXT_H__ +#define __CLNT_EXT_H__ + +extern CLIENT *clntext_create (char *filename, char *argv[], + u_long prog, u_long vers, + int *sockp, u_int sendsz, u_int recvsz); + +#endif /* __CLNT_EXT_H__ */ --- /tmp/newfile 2007-01-30 17:23:46.000000000 +0000 +++ src/sunrpc/clnt_gnutls.c 2007-01-30 17:03:31.000000000 +0000 @@ -0,0 +1,603 @@ +/* + * clnt_gnutls.c: SunRPC over GnuTLS client. This is a modified version + * of clnt_tcp.c from glibc which is written to run over GnuTLS. + * + * Modifications from glibc-2.5 base by Richard Jones <rjones@redhat.com>. + */ + +/* @(#)clnt_tcp.c 2.2 88/08/01 4.0 RPCSRC */ +/* + * Sun RPC is a product of Sun Microsystems, Inc. and is provided for + * unrestricted use provided that this legend is included on all tape + * media and as a part of the software program in whole or part. Users + * may copy or modify Sun RPC without charge, but are not authorized + * to license or distribute it to anyone else except as part of a product or + * program developed by the user. + * + * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE + * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR + * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. + * + * Sun RPC is provided with no support and without any obligation on the + * part of Sun Microsystems, Inc. to assist in its use, correction, + * modification or enhancement. + * + * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE + * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC + * OR ANY PART THEREOF. + * + * In no event will Sun Microsystems, Inc. be liable for any lost revenue + * or profits or other special, indirect and consequential damages, even if + * Sun has been advised of the possibility of such damages. + * + * Sun Microsystems, Inc. + * 2550 Garcia Avenue + * Mountain View, California 94043 + */ +#if !defined(lint) && defined(SCCSIDS) +static char sccsid[] = "@(#)clnt_tcp.c 1.37 87/10/05 Copyr 1984 Sun Micro"; +#endif + +/* + * clnt_tcp.c, Implements a TCP/IP based, client side RPC. + * + * Copyright (C) 1984, Sun Microsystems, Inc. + * + * TCP based RPC supports 'batched calls'. + * A sequence of calls may be batched-up in a send buffer. The rpc call + * return immediately to the client even though the call was not necessarily + * sent. The batching occurs if the results' xdr routine is NULL (0) AND + * the rpc timeout value is zero (see clnt.h, rpc). + * + * Clients should NOT casually batch calls that in fact return results; that is, + * the server side should be aware that a call is batched and not produce any + * return message. Batched calls that produce many result messages can + * deadlock (netlock) the client and the server.... + * + * Now go hang yourself. + */ + +#include <netdb.h> +#include <errno.h> +#include <stdio.h> +#include <unistd.h> +#include <libintl.h> +#include <rpc/rpc.h> +#include <sys/poll.h> +#include <sys/socket.h> +#include <rpc/pmap_clnt.h> +#ifdef USE_IN_LIBIO +# include <wchar.h> +#endif +#include <gnutls/gnutls.h> + +#include "clnt_gnutls.h" + +extern u_long _create_xid (void); + +#define MCALL_MSG_SIZE 24 + +struct ct_data + { + int ct_sock; + bool_t ct_closeit; + struct timeval ct_wait; + bool_t ct_waitset; /* wait set by clnt_control? */ + struct rpc_err ct_error; + char ct_mcall[MCALL_MSG_SIZE]; /* marshalled callmsg */ + u_int ct_mpos; /* pos after marshal */ + XDR ct_xdrs; + gnutls_session_t ct_session; /* GnuTLS session. */ + }; + +static int readtcp (char *, char *, int); +static int writetcp (char *, char *, int); + +static enum clnt_stat clnttcp_call (CLIENT *, u_long, xdrproc_t, caddr_t, + xdrproc_t, caddr_t, struct timeval); +static void clnttcp_abort (void); +static void clnttcp_geterr (CLIENT *, struct rpc_err *); +static bool_t clnttcp_freeres (CLIENT *, xdrproc_t, caddr_t); +static bool_t clnttcp_control (CLIENT *, int, char *); +static void clnttcp_destroy (CLIENT *); + +static const struct clnt_ops tcp_ops = +{ + clnttcp_call, + clnttcp_abort, + clnttcp_geterr, + clnttcp_freeres, + clnttcp_destroy, + clnttcp_control +}; + +static int start_gnutls_session (gnutls_certificate_credentials_t xcred, + struct ct_data *ct, int sock); +/* + * Create a client handle for a tcp/ip connection. + * If *sockp<0, *sockp is set to a newly created TCP socket and it is + * connected to raddr. If *sockp non-negative then + * raddr is ignored. The rpc/tcp package does buffering + * similar to stdio, so the client must pick send and receive buffer sizes,]; + * 0 => use the default. + * If raddr->sin_port is 0, then a binder on the remote machine is + * consulted for the right port number. + * NB: *sockp is copied into a private area. + * NB: It is the clients responsibility to close *sockp. + * NB: The rpch->cl_auth is set null authentication. Caller may wish to set this + * something more useful. + */ +CLIENT * +clntgnutls_create (int pf, struct sockaddr *raddr, int raddr_size, + gnutls_certificate_credentials_t xcred, + u_long prog, u_long vers, + int *sockp, u_int sendsz, u_int recvsz) +{ + CLIENT *h; + struct ct_data *ct; + struct rpc_msg call_msg; + + h = (CLIENT *) mem_alloc (sizeof (*h)); + ct = (struct ct_data *) mem_alloc (sizeof (*ct)); + if (h == NULL || ct == NULL) + { + struct rpc_createerr *ce = &get_rpc_createerr (); + (void) fprintf (stderr, "clnttcp_create: out of memory\n"); + ce->cf_stat = RPC_SYSTEMERROR; + ce->cf_error.re_errno = ENOMEM; + goto fooy; + } + +#if 0 + // RWMJ: Note this will never be supported for portmap because the + // portmap protocol depends pretty fundamentally on IPv4. Don't + /// use portmapper anyway -- it's silly. + /* + * If no port number given ask the pmap for one + */ + if (port == 0) + { + u_short port; + if ((port = pmap_getport (raddr, prog, vers, IPPROTO_TCP)) == 0) + { + mem_free ((caddr_t) ct, sizeof (struct ct_data)); + mem_free ((caddr_t) h, sizeof (CLIENT)); + return ((CLIENT *) NULL); + } + raddr->sin_port = htons (port); + } +#endif + + /* + * If no socket given, open one + */ + if (*sockp < 0) + { + *sockp = socket (pf, SOCK_STREAM, 0); + // RWMJ: Why? + //(void) bindresvport (*sockp, (struct sockaddr_in *) 0); + if ((*sockp < 0) + || (connect (*sockp, raddr, raddr_size) < 0)) + { + struct rpc_createerr *ce = &get_rpc_createerr (); + ce->cf_stat = RPC_SYSTEMERROR; + ce->cf_error.re_errno = errno; + if (*sockp >= 0) + (void) close (*sockp); + goto fooy; + } + ct->ct_closeit = TRUE; + } + else + { + ct->ct_closeit = FALSE; + } + + /* + * Set up private data struct + */ + ct->ct_sock = *sockp; + ct->ct_wait.tv_usec = 0; + ct->ct_waitset = FALSE; + + /* + * Initialize call message + */ + call_msg.rm_xid = _create_xid (); + call_msg.rm_direction = CALL; + call_msg.rm_call.cb_rpcvers = RPC_MSG_VERSION; + call_msg.rm_call.cb_prog = prog; + call_msg.rm_call.cb_vers = vers; + + /* + * pre-serialize the static part of the call msg and stash it away + */ + xdrmem_create (&(ct->ct_xdrs), ct->ct_mcall, MCALL_MSG_SIZE, + XDR_ENCODE); + if (!xdr_callhdr (&(ct->ct_xdrs), &call_msg)) + { + if (ct->ct_closeit) + { + (void) close (*sockp); + } + goto fooy; + } + ct->ct_mpos = XDR_GETPOS (&(ct->ct_xdrs)); + XDR_DESTROY (&(ct->ct_xdrs)); + + /* + * Create a client handle which uses xdrrec for serialization + * and authnone for authentication. + */ + xdrrec_create (&(ct->ct_xdrs), sendsz, recvsz, + (caddr_t) ct, readtcp, writetcp); + h->cl_ops = (struct clnt_ops *) &tcp_ops; + h->cl_private = (caddr_t) ct; + h->cl_auth = authnone_create (); + + /* Start GnuTLS on this socket. */ + if (start_gnutls_session (xcred, ct, *sockp) == -1) { + if (ct->ct_closeit) + close (*sockp); + goto fooy; + } + + return h; + +fooy: + /* + * Something goofed, free stuff and barf + */ + mem_free ((caddr_t) ct, sizeof (struct ct_data)); + mem_free ((caddr_t) h, sizeof (CLIENT)); + return ((CLIENT *) NULL); +} + +static int +start_gnutls_session (gnutls_certificate_credentials_t xcred, + struct ct_data *ct, int sock) +{ + const int cert_type_priority[3] = { + GNUTLS_CRT_X509, + GNUTLS_CRT_OPENPGP, + 0 + }; + + /* Initialize TLS session + */ + gnutls_init (&ct->ct_session, GNUTLS_CLIENT); + + /* Use default priorities */ + gnutls_set_default_priority (ct->ct_session); + gnutls_certificate_type_set_priority (ct->ct_session, cert_type_priority); + + /* put the x509 credentials to the current session + */ + gnutls_credentials_set (ct->ct_session, GNUTLS_CRD_CERTIFICATE, xcred); + + gnutls_transport_set_ptr (ct->ct_session, + (gnutls_transport_ptr_t) (long) sock); + + /* Perform the TLS handshake + */ + int ret = gnutls_handshake (ct->ct_session); + + if (ret < 0) + { + fprintf (stderr, "clnt_gnutls: TLS handshake failed\n"); + gnutls_perror (ret); + exit (1); + } + + /* XXX You need to verify the peer's certificate matches its name. */ + printf ("XXX need to verify peer's certificate matches its name.\n"); + +#if 0 + /* Print session info. */ + print_info (session); +#endif + + return 0; +} + +static enum clnt_stat +clnttcp_call (h, proc, xdr_args, args_ptr, xdr_results, results_ptr, timeout) + CLIENT *h; + u_long proc; + xdrproc_t xdr_args; + caddr_t args_ptr; + xdrproc_t xdr_results; + caddr_t results_ptr; + struct timeval timeout; +{ + struct ct_data *ct = (struct ct_data *) h->cl_private; + XDR *xdrs = &(ct->ct_xdrs); + struct rpc_msg reply_msg; + u_long x_id; + u_int32_t *msg_x_id = (u_int32_t *) (ct->ct_mcall); /* yuk */ + bool_t shipnow; + int refreshes = 2; + + if (!ct->ct_waitset) + { + ct->ct_wait = timeout; + } + + shipnow = + (xdr_results == (xdrproc_t) 0 && ct->ct_wait.tv_sec == 0 + && ct->ct_wait.tv_usec == 0) ? FALSE : TRUE; + +call_again: + xdrs->x_op = XDR_ENCODE; + ct->ct_error.re_status = RPC_SUCCESS; + x_id = ntohl (--(*msg_x_id)); + if ((!XDR_PUTBYTES (xdrs, ct->ct_mcall, ct->ct_mpos)) || + (!XDR_PUTLONG (xdrs, (long *) &proc)) || + (!AUTH_MARSHALL (h->cl_auth, xdrs)) || + (!(*xdr_args) (xdrs, args_ptr))) + { + if (ct->ct_error.re_status == RPC_SUCCESS) + ct->ct_error.re_status = RPC_CANTENCODEARGS; + (void) xdrrec_endofrecord (xdrs, TRUE); + return (ct->ct_error.re_status); + } + if (!xdrrec_endofrecord (xdrs, shipnow)) + return ct->ct_error.re_status = RPC_CANTSEND; + if (!shipnow) + return RPC_SUCCESS; + /* + * Hack to provide rpc-based message passing + */ + if (ct->ct_wait.tv_sec == 0 && ct->ct_wait.tv_usec == 0) + { + return ct->ct_error.re_status = RPC_TIMEDOUT; + } + + + /* + * Keep receiving until we get a valid transaction id + */ + xdrs->x_op = XDR_DECODE; + while (TRUE) + { + reply_msg.acpted_rply.ar_verf = _null_auth; + reply_msg.acpted_rply.ar_results.where = NULL; + reply_msg.acpted_rply.ar_results.proc = (xdrproc_t)xdr_void; + if (!xdrrec_skiprecord (xdrs)) + return (ct->ct_error.re_status); + /* now decode and validate the response header */ + if (!xdr_replymsg (xdrs, &reply_msg)) + { + if (ct->ct_error.re_status == RPC_SUCCESS) + continue; + return ct->ct_error.re_status; + } + if ((u_int32_t) reply_msg.rm_xid == (u_int32_t) x_id) + break; + } + + /* + * process header + */ + _seterr_reply (&reply_msg, &(ct->ct_error)); + if (ct->ct_error.re_status == RPC_SUCCESS) + { + if (!AUTH_VALIDATE (h->cl_auth, &reply_msg.acpted_rply.ar_verf)) + { + ct->ct_error.re_status = RPC_AUTHERROR; + ct->ct_error.re_why = AUTH_INVALIDRESP; + } + else if (!(*xdr_results) (xdrs, results_ptr)) + { + if (ct->ct_error.re_status == RPC_SUCCESS) + ct->ct_error.re_status = RPC_CANTDECODERES; + } + /* free verifier ... */ + if (reply_msg.acpted_rply.ar_verf.oa_base != NULL) + { + xdrs->x_op = XDR_FREE; + (void) xdr_opaque_auth (xdrs, + &(reply_msg.acpted_rply.ar_verf)); + } + } /* end successful completion */ + else + { + /* maybe our credentials need to be refreshed ... */ + if (refreshes-- && AUTH_REFRESH (h->cl_auth)) + goto call_again; + } /* end of unsuccessful completion */ + return ct->ct_error.re_status; +} + +static void +clnttcp_geterr (h, errp) + CLIENT *h; + struct rpc_err *errp; +{ + struct ct_data *ct = + (struct ct_data *) h->cl_private; + + *errp = ct->ct_error; +} + +static bool_t +clnttcp_freeres (cl, xdr_res, res_ptr) + CLIENT *cl; + xdrproc_t xdr_res; + caddr_t res_ptr; +{ + struct ct_data *ct = (struct ct_data *) cl->cl_private; + XDR *xdrs = &(ct->ct_xdrs); + + xdrs->x_op = XDR_FREE; + return (*xdr_res) (xdrs, res_ptr); +} + +static void +clnttcp_abort () +{ +} + +static bool_t +clnttcp_control (CLIENT *cl, int request, char *info) +{ + struct ct_data *ct = (struct ct_data *) cl->cl_private; + + + switch (request) + { + case CLSET_FD_CLOSE: + ct->ct_closeit = TRUE; + break; + case CLSET_FD_NCLOSE: + ct->ct_closeit = FALSE; + break; + case CLSET_TIMEOUT: + ct->ct_wait = *(struct timeval *) info; + ct->ct_waitset = TRUE; + break; + case CLGET_TIMEOUT: + *(struct timeval *) info = ct->ct_wait; + break; + case CLGET_FD: + *(int *)info = ct->ct_sock; + break; + case CLGET_XID: + /* + * use the knowledge that xid is the + * first element in the call structure *. + * This will get the xid of the PREVIOUS call + */ + *(u_long *)info = ntohl (*(u_long *)ct->ct_mcall); + break; + case CLSET_XID: + /* This will set the xid of the NEXT call */ + *(u_long *)ct->ct_mcall = htonl (*(u_long *)info - 1); + /* decrement by 1 as clnttcp_call() increments once */ + case CLGET_VERS: + /* + * This RELIES on the information that, in the call body, + * the version number field is the fifth field from the + * begining of the RPC header. MUST be changed if the + * call_struct is changed + */ + *(u_long *)info = ntohl (*(u_long *)(ct->ct_mcall + + 4 * BYTES_PER_XDR_UNIT)); + break; + case CLSET_VERS: + *(u_long *)(ct->ct_mcall + 4 * BYTES_PER_XDR_UNIT) + = htonl (*(u_long *)info); + break; + case CLGET_PROG: + /* + * This RELIES on the information that, in the call body, + * the program number field is the field from the + * begining of the RPC header. MUST be changed if the + * call_struct is changed + */ + *(u_long *)info = ntohl(*(u_long *)(ct->ct_mcall + + 3 * BYTES_PER_XDR_UNIT)); + break; + case CLSET_PROG: + *(u_long *)(ct->ct_mcall + 3 * BYTES_PER_XDR_UNIT) + = htonl(*(u_long *)info); + break; + /* The following are only possible with TI-RPC */ + case CLGET_RETRY_TIMEOUT: + case CLSET_RETRY_TIMEOUT: + case CLGET_SVC_ADDR: + case CLSET_SVC_ADDR: + case CLSET_PUSH_TIMOD: + case CLSET_POP_TIMOD: + default: + return FALSE; + } + return TRUE; +} + + +static void +clnttcp_destroy (CLIENT *h) +{ + struct ct_data *ct = + (struct ct_data *) h->cl_private; + + gnutls_bye (ct->ct_session, GNUTLS_SHUT_RDWR); + gnutls_deinit (ct->ct_session); + + if (ct->ct_closeit) + { + (void) close (ct->ct_sock); + } + XDR_DESTROY (&(ct->ct_xdrs)); + mem_free ((caddr_t) ct, sizeof (struct ct_data)); + mem_free ((caddr_t) h, sizeof (CLIENT)); +} + +/* + * Interface between xdr serializer and tcp connection. + * Behaves like the system calls, read & write, but keeps some error state + * around for the rpc level. + */ +static int +readtcp (char *ctptr, char *buf, int len) +{ + struct ct_data *ct = (struct ct_data *)ctptr; + struct pollfd fd; + int milliseconds = (ct->ct_wait.tv_sec * 1000) + + (ct->ct_wait.tv_usec / 1000); + + if (len == 0) + return 0; + + fd.fd = ct->ct_sock; + fd.events = POLLIN; + while (TRUE) + { + switch (poll(&fd, 1, milliseconds)) + { + case 0: + ct->ct_error.re_status = RPC_TIMEDOUT; + return -1; + + case -1: + if (errno == EINTR) + continue; + ct->ct_error.re_status = RPC_CANTRECV; + ct->ct_error.re_errno = errno; + return -1; + } + break; + } + + switch (len = gnutls_record_recv (ct->ct_session, buf, len)) + { + case 0: + /* premature eof */ + ct->ct_error.re_errno = ECONNRESET; + ct->ct_error.re_status = RPC_CANTRECV; + len = -1; /* it's really an error */ + break; + + case -1: + ct->ct_error.re_errno = errno; + ct->ct_error.re_status = RPC_CANTRECV; + break; + } + return len; +} + +static int +writetcp (char *ctptr, char *buf, int len) +{ + struct ct_data *ct = (struct ct_data*)ctptr; + + if (gnutls_record_send (ct->ct_session, buf, len) < 0) + { + ct->ct_error.re_errno = errno; + ct->ct_error.re_status = RPC_CANTSEND; + return -1; + } + + return len; +} --- /tmp/newfile 2007-01-30 17:23:46.000000000 +0000 +++ src/sunrpc/clnt_gnutls.h 2007-01-30 11:59:17.000000000 +0000 @@ -0,0 +1,20 @@ +/* + * clnt_gnutls.h: Interface to the SunRPC over GnuTLS. + * + * Copyright (C) 2007 Red Hat, Inc. + * + * See COPYING.LIB for the license of this software. + * + * Richard Jones <rjones@redhat.com> + */ + +#ifndef __CLNT_GNUTLS_H__ +#define __CLNT_GNUTLS_H__ + +extern CLIENT *clntgnutls_create (int pf, + struct sockaddr *raddr, int raddr_size, + gnutls_certificate_credentials_t xcred, + u_long prog, u_long vers, + int *sockp, u_int sendsz, u_int recvsz); + +#endif /* __CLNT_GNUTLS_H__ */ --- /tmp/newfile 2007-01-30 17:23:46.000000000 +0000 +++ src/sunrpc/clnt_tcp2.c 2007-01-30 16:08:20.000000000 +0000 @@ -0,0 +1,544 @@ +/* + * clnt_tcp2.c: SunRPC TCP client. This is a very slightly modified + * version of clnt_tcp.c from glibc which removes some of the IPv4 + * assumptions, so this version can be used over IPv4 or IPv6 sockets. + * + * Modifications from glibc-2.5 base by Richard Jones <rjones@redhat.com>. + */ + +/* @(#)clnt_tcp.c 2.2 88/08/01 4.0 RPCSRC */ +/* + * Sun RPC is a product of Sun Microsystems, Inc. and is provided for + * unrestricted use provided that this legend is included on all tape + * media and as a part of the software program in whole or part. Users + * may copy or modify Sun RPC without charge, but are not authorized + * to license or distribute it to anyone else except as part of a product or + * program developed by the user. + * + * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE + * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR + * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. + * + * Sun RPC is provided with no support and without any obligation on the + * part of Sun Microsystems, Inc. to assist in its use, correction, + * modification or enhancement. + * + * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE + * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC + * OR ANY PART THEREOF. + * + * In no event will Sun Microsystems, Inc. be liable for any lost revenue + * or profits or other special, indirect and consequential damages, even if + * Sun has been advised of the possibility of such damages. + * + * Sun Microsystems, Inc. + * 2550 Garcia Avenue + * Mountain View, California 94043 + */ +#if !defined(lint) && defined(SCCSIDS) +static char sccsid[] = "@(#)clnt_tcp.c 1.37 87/10/05 Copyr 1984 Sun Micro"; +#endif + +/* + * clnt_tcp.c, Implements a TCP/IP based, client side RPC. + * + * Copyright (C) 1984, Sun Microsystems, Inc. + * + * TCP based RPC supports 'batched calls'. + * A sequence of calls may be batched-up in a send buffer. The rpc call + * return immediately to the client even though the call was not necessarily + * sent. The batching occurs if the results' xdr routine is NULL (0) AND + * the rpc timeout value is zero (see clnt.h, rpc). + * + * Clients should NOT casually batch calls that in fact return results; that is, + * the server side should be aware that a call is batched and not produce any + * return message. Batched calls that produce many result messages can + * deadlock (netlock) the client and the server.... + * + * Now go hang yourself. + */ + +#include <netdb.h> +#include <errno.h> +#include <stdio.h> +#include <unistd.h> +#include <libintl.h> +#include <rpc/rpc.h> +#include <sys/poll.h> +#include <sys/socket.h> +#include <rpc/pmap_clnt.h> +#ifdef USE_IN_LIBIO +# include <wchar.h> +#endif + +#include "clnt_tcp2.h" + +extern u_long _create_xid (void); + +#define MCALL_MSG_SIZE 24 + +struct ct_data + { + int ct_sock; + bool_t ct_closeit; + struct timeval ct_wait; + bool_t ct_waitset; /* wait set by clnt_control? */ + struct rpc_err ct_error; + char ct_mcall[MCALL_MSG_SIZE]; /* marshalled callmsg */ + u_int ct_mpos; /* pos after marshal */ + XDR ct_xdrs; + }; + +static int readtcp (char *, char *, int); +static int writetcp (char *, char *, int); + +static enum clnt_stat clnttcp_call (CLIENT *, u_long, xdrproc_t, caddr_t, + xdrproc_t, caddr_t, struct timeval); +static void clnttcp_abort (void); +static void clnttcp_geterr (CLIENT *, struct rpc_err *); +static bool_t clnttcp_freeres (CLIENT *, xdrproc_t, caddr_t); +static bool_t clnttcp_control (CLIENT *, int, char *); +static void clnttcp_destroy (CLIENT *); + +static const struct clnt_ops tcp_ops = +{ + clnttcp_call, + clnttcp_abort, + clnttcp_geterr, + clnttcp_freeres, + clnttcp_destroy, + clnttcp_control +}; + +/* + * Create a client handle for a tcp/ip connection. + * If *sockp<0, *sockp is set to a newly created TCP socket and it is + * connected to raddr. If *sockp non-negative then + * raddr is ignored. The rpc/tcp package does buffering + * similar to stdio, so the client must pick send and receive buffer sizes,]; + * 0 => use the default. + * If raddr->sin_port is 0, then a binder on the remote machine is + * consulted for the right port number. + * NB: *sockp is copied into a private area. + * NB: It is the clients responsibility to close *sockp. + * NB: The rpch->cl_auth is set null authentication. Caller may wish to set this + * something more useful. + */ +CLIENT * +clnttcp2_create (int af, struct sockaddr *raddr, int raddr_size, + u_long prog, u_long vers, + int *sockp, u_int sendsz, u_int recvsz) +{ + CLIENT *h; + struct ct_data *ct; + struct rpc_msg call_msg; + + h = (CLIENT *) mem_alloc (sizeof (*h)); + ct = (struct ct_data *) mem_alloc (sizeof (*ct)); + if (h == NULL || ct == NULL) + { + struct rpc_createerr *ce = &get_rpc_createerr (); + (void) fprintf (stderr, "clnttcp_create: out of memory\n"); + ce->cf_stat = RPC_SYSTEMERROR; + ce->cf_error.re_errno = ENOMEM; + goto fooy; + } + +#if 0 + // RWMJ: Note this will never be supported for portmap because the + // portmap protocol depends pretty fundamentally on IPv4. Don't + /// use portmapper anyway -- it's silly. + /* + * If no port number given ask the pmap for one + */ + if (port == 0) + { + u_short port; + if ((port = pmap_getport (raddr, prog, vers, IPPROTO_TCP)) == 0) + { + mem_free ((caddr_t) ct, sizeof (struct ct_data)); + mem_free ((caddr_t) h, sizeof (CLIENT)); + return ((CLIENT *) NULL); + } + raddr->sin_port = htons (port); + } +#endif + + /* + * If no socket given, open one + */ + if (*sockp < 0) + { + *sockp = socket (af, SOCK_STREAM, 0); + // RWMJ: Why? + //(void) bindresvport (*sockp, (struct sockaddr_in *) 0); + if ((*sockp < 0) + || (connect (*sockp, raddr, raddr_size) < 0)) + { + struct rpc_createerr *ce = &get_rpc_createerr (); + ce->cf_stat = RPC_SYSTEMERROR; + ce->cf_error.re_errno = errno; + if (*sockp >= 0) + (void) close (*sockp); + goto fooy; + } + ct->ct_closeit = TRUE; + } + else + { + ct->ct_closeit = FALSE; + } + + /* + * Set up private data struct + */ + ct->ct_sock = *sockp; + ct->ct_wait.tv_usec = 0; + ct->ct_waitset = FALSE; + + /* + * Initialize call message + */ + call_msg.rm_xid = _create_xid (); + call_msg.rm_direction = CALL; + call_msg.rm_call.cb_rpcvers = RPC_MSG_VERSION; + call_msg.rm_call.cb_prog = prog; + call_msg.rm_call.cb_vers = vers; + + /* + * pre-serialize the static part of the call msg and stash it away + */ + xdrmem_create (&(ct->ct_xdrs), ct->ct_mcall, MCALL_MSG_SIZE, + XDR_ENCODE); + if (!xdr_callhdr (&(ct->ct_xdrs), &call_msg)) + { + if (ct->ct_closeit) + { + (void) close (*sockp); + } + goto fooy; + } + ct->ct_mpos = XDR_GETPOS (&(ct->ct_xdrs)); + XDR_DESTROY (&(ct->ct_xdrs)); + + /* + * Create a client handle which uses xdrrec for serialization + * and authnone for authentication. + */ + xdrrec_create (&(ct->ct_xdrs), sendsz, recvsz, + (caddr_t) ct, readtcp, writetcp); + h->cl_ops = (struct clnt_ops *) &tcp_ops; + h->cl_private = (caddr_t) ct; + h->cl_auth = authnone_create (); + return h; + +fooy: + /* + * Something goofed, free stuff and barf + */ + mem_free ((caddr_t) ct, sizeof (struct ct_data)); + mem_free ((caddr_t) h, sizeof (CLIENT)); + return ((CLIENT *) NULL); +} + +static enum clnt_stat +clnttcp_call (h, proc, xdr_args, args_ptr, xdr_results, results_ptr, timeout) + CLIENT *h; + u_long proc; + xdrproc_t xdr_args; + caddr_t args_ptr; + xdrproc_t xdr_results; + caddr_t results_ptr; + struct timeval timeout; +{ + struct ct_data *ct = (struct ct_data *) h->cl_private; + XDR *xdrs = &(ct->ct_xdrs); + struct rpc_msg reply_msg; + u_long x_id; + u_int32_t *msg_x_id = (u_int32_t *) (ct->ct_mcall); /* yuk */ + bool_t shipnow; + int refreshes = 2; + + if (!ct->ct_waitset) + { + ct->ct_wait = timeout; + } + + shipnow = + (xdr_results == (xdrproc_t) 0 && ct->ct_wait.tv_sec == 0 + && ct->ct_wait.tv_usec == 0) ? FALSE : TRUE; + +call_again: + xdrs->x_op = XDR_ENCODE; + ct->ct_error.re_status = RPC_SUCCESS; + x_id = ntohl (--(*msg_x_id)); + if ((!XDR_PUTBYTES (xdrs, ct->ct_mcall, ct->ct_mpos)) || + (!XDR_PUTLONG (xdrs, (long *) &proc)) || + (!AUTH_MARSHALL (h->cl_auth, xdrs)) || + (!(*xdr_args) (xdrs, args_ptr))) + { + if (ct->ct_error.re_status == RPC_SUCCESS) + ct->ct_error.re_status = RPC_CANTENCODEARGS; + (void) xdrrec_endofrecord (xdrs, TRUE); + return (ct->ct_error.re_status); + } + if (!xdrrec_endofrecord (xdrs, shipnow)) + return ct->ct_error.re_status = RPC_CANTSEND; + if (!shipnow) + return RPC_SUCCESS; + /* + * Hack to provide rpc-based message passing + */ + if (ct->ct_wait.tv_sec == 0 && ct->ct_wait.tv_usec == 0) + { + return ct->ct_error.re_status = RPC_TIMEDOUT; + } + + + /* + * Keep receiving until we get a valid transaction id + */ + xdrs->x_op = XDR_DECODE; + while (TRUE) + { + reply_msg.acpted_rply.ar_verf = _null_auth; + reply_msg.acpted_rply.ar_results.where = NULL; + reply_msg.acpted_rply.ar_results.proc = (xdrproc_t)xdr_void; + if (!xdrrec_skiprecord (xdrs)) + return (ct->ct_error.re_status); + /* now decode and validate the response header */ + if (!xdr_replymsg (xdrs, &reply_msg)) + { + if (ct->ct_error.re_status == RPC_SUCCESS) + continue; + return ct->ct_error.re_status; + } + if ((u_int32_t) reply_msg.rm_xid == (u_int32_t) x_id) + break; + } + + /* + * process header + */ + _seterr_reply (&reply_msg, &(ct->ct_error)); + if (ct->ct_error.re_status == RPC_SUCCESS) + { + if (!AUTH_VALIDATE (h->cl_auth, &reply_msg.acpted_rply.ar_verf)) + { + ct->ct_error.re_status = RPC_AUTHERROR; + ct->ct_error.re_why = AUTH_INVALIDRESP; + } + else if (!(*xdr_results) (xdrs, results_ptr)) + { + if (ct->ct_error.re_status == RPC_SUCCESS) + ct->ct_error.re_status = RPC_CANTDECODERES; + } + /* free verifier ... */ + if (reply_msg.acpted_rply.ar_verf.oa_base != NULL) + { + xdrs->x_op = XDR_FREE; + (void) xdr_opaque_auth (xdrs, + &(reply_msg.acpted_rply.ar_verf)); + } + } /* end successful completion */ + else + { + /* maybe our credentials need to be refreshed ... */ + if (refreshes-- && AUTH_REFRESH (h->cl_auth)) + goto call_again; + } /* end of unsuccessful completion */ + return ct->ct_error.re_status; +} + +static void +clnttcp_geterr (h, errp) + CLIENT *h; + struct rpc_err *errp; +{ + struct ct_data *ct = + (struct ct_data *) h->cl_private; + + *errp = ct->ct_error; +} + +static bool_t +clnttcp_freeres (cl, xdr_res, res_ptr) + CLIENT *cl; + xdrproc_t xdr_res; + caddr_t res_ptr; +{ + struct ct_data *ct = (struct ct_data *) cl->cl_private; + XDR *xdrs = &(ct->ct_xdrs); + + xdrs->x_op = XDR_FREE; + return (*xdr_res) (xdrs, res_ptr); +} + +static void +clnttcp_abort () +{ +} + +static bool_t +clnttcp_control (CLIENT *cl, int request, char *info) +{ + struct ct_data *ct = (struct ct_data *) cl->cl_private; + + + switch (request) + { + case CLSET_FD_CLOSE: + ct->ct_closeit = TRUE; + break; + case CLSET_FD_NCLOSE: + ct->ct_closeit = FALSE; + break; + case CLSET_TIMEOUT: + ct->ct_wait = *(struct timeval *) info; + ct->ct_waitset = TRUE; + break; + case CLGET_TIMEOUT: + *(struct timeval *) info = ct->ct_wait; + break; + case CLGET_FD: + *(int *)info = ct->ct_sock; + break; + case CLGET_XID: + /* + * use the knowledge that xid is the + * first element in the call structure *. + * This will get the xid of the PREVIOUS call + */ + *(u_long *)info = ntohl (*(u_long *)ct->ct_mcall); + break; + case CLSET_XID: + /* This will set the xid of the NEXT call */ + *(u_long *)ct->ct_mcall = htonl (*(u_long *)info - 1); + /* decrement by 1 as clnttcp_call() increments once */ + case CLGET_VERS: + /* + * This RELIES on the information that, in the call body, + * the version number field is the fifth field from the + * begining of the RPC header. MUST be changed if the + * call_struct is changed + */ + *(u_long *)info = ntohl (*(u_long *)(ct->ct_mcall + + 4 * BYTES_PER_XDR_UNIT)); + break; + case CLSET_VERS: + *(u_long *)(ct->ct_mcall + 4 * BYTES_PER_XDR_UNIT) + = htonl (*(u_long *)info); + break; + case CLGET_PROG: + /* + * This RELIES on the information that, in the call body, + * the program number field is the field from the + * begining of the RPC header. MUST be changed if the + * call_struct is changed + */ + *(u_long *)info = ntohl(*(u_long *)(ct->ct_mcall + + 3 * BYTES_PER_XDR_UNIT)); + break; + case CLSET_PROG: + *(u_long *)(ct->ct_mcall + 3 * BYTES_PER_XDR_UNIT) + = htonl(*(u_long *)info); + break; + /* The following are only possible with TI-RPC */ + case CLGET_RETRY_TIMEOUT: + case CLSET_RETRY_TIMEOUT: + case CLGET_SVC_ADDR: + case CLSET_SVC_ADDR: + case CLSET_PUSH_TIMOD: + case CLSET_POP_TIMOD: + default: + return FALSE; + } + return TRUE; +} + + +static void +clnttcp_destroy (CLIENT *h) +{ + struct ct_data *ct = + (struct ct_data *) h->cl_private; + + if (ct->ct_closeit) + { + (void) close (ct->ct_sock); + } + XDR_DESTROY (&(ct->ct_xdrs)); + mem_free ((caddr_t) ct, sizeof (struct ct_data)); + mem_free ((caddr_t) h, sizeof (CLIENT)); +} + +/* + * Interface between xdr serializer and tcp connection. + * Behaves like the system calls, read & write, but keeps some error state + * around for the rpc level. + */ +static int +readtcp (char *ctptr, char *buf, int len) +{ + struct ct_data *ct = (struct ct_data *)ctptr; + struct pollfd fd; + int milliseconds = (ct->ct_wait.tv_sec * 1000) + + (ct->ct_wait.tv_usec / 1000); + + if (len == 0) + return 0; + + fd.fd = ct->ct_sock; + fd.events = POLLIN; + while (TRUE) + { + switch (poll(&fd, 1, milliseconds)) + { + case 0: + ct->ct_error.re_status = RPC_TIMEDOUT; + return -1; + + case -1: + if (errno == EINTR) + continue; + ct->ct_error.re_status = RPC_CANTRECV; + ct->ct_error.re_errno = errno; + return -1; + } + break; + } + switch (len = read (ct->ct_sock, buf, len)) + { + + case 0: + /* premature eof */ + ct->ct_error.re_errno = ECONNRESET; + ct->ct_error.re_status = RPC_CANTRECV; + len = -1; /* it's really an error */ + break; + + case -1: + ct->ct_error.re_errno = errno; + ct->ct_error.re_status = RPC_CANTRECV; + break; + } + return len; +} + +static int +writetcp (char *ctptr, char *buf, int len) +{ + int i, cnt; + struct ct_data *ct = (struct ct_data*)ctptr; + + for (cnt = len; cnt > 0; cnt -= i, buf += i) + { + if ((i = write (ct->ct_sock, buf, cnt)) == -1) + { + ct->ct_error.re_errno = errno; + ct->ct_error.re_status = RPC_CANTSEND; + return -1; + } + } + return len; +} --- /tmp/newfile 2007-01-30 17:23:46.000000000 +0000 +++ src/sunrpc/clnt_tcp2.h 2007-01-30 15:26:09.000000000 +0000 @@ -0,0 +1,18 @@ +/* + * clnt_tcp2.h: Interface to the SunRPC over TCP. + * + * Copyright (C) 2007 Red Hat, Inc. + * + * See COPYING.LIB for the license of this software. + * + * Richard Jones <rjones@redhat.com> + */ + +#ifndef __CLNT_TCP2_H__ +#define __CLNT_TCP2_H__ + +extern CLIENT *clnttcp2_create (int af, struct sockaddr *raddr, int raddr_size, + u_long prog, u_long vers, + int *sockp, u_int sendsz, u_int recvsz); + +#endif /* __CLNT_TCP2_H__ */ --- /tmp/newfile 2007-01-30 17:23:46.000000000 +0000 +++ src/sunrpc/create_xid.c 2007-01-30 13:21:07.000000000 +0000 @@ -0,0 +1,55 @@ +/* + * create_xid.c: Makes unique, unguessable identifiers. This is modified + * from the original source found in glibc-2.5. The original version + * is thread safe (but the rest of SunRPC isn't). This version is + * not thread safe. + * + * Modifications from glibc-2.5 base by Richard Jones <rjones@redhat.com>. + */ + +/* Copyright (c) 1998, 2000 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Thorsten Kukuk <kukuk@vt.uni-paderborn.de>, 1998. + + The GNU C Library 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; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +#include <unistd.h> +#include <stdlib.h> +#include <sys/time.h> +#include <bits/libc-lock.h> +#include <rpc/rpc.h> + +static int is_initialized; +static struct drand48_data __rpc_lrand48_data; + +unsigned long +_create_xid (void) +{ + long int res; + + if (!is_initialized) + { + struct timeval now; + + gettimeofday (&now, (struct timezone *) 0); + srand48_r (now.tv_sec ^ now.tv_usec, &__rpc_lrand48_data); + is_initialized = 1; + } + + lrand48_r (&__rpc_lrand48_data, &res); + + return res; +} --- /tmp/newfile 2007-01-30 17:23:46.000000000 +0000 +++ src/sunrpc/svc_gnutls.c 2007-01-30 17:03:35.000000000 +0000 @@ -0,0 +1,477 @@ +/* + * svc_gnutls.c: SunRPC over GnuTLS server. This is a modified version + * of svc_tcp.c from glibc which is written to run over GnuTLS. + * + * Modifications from glibc-2.5 base by Richard Jones <rjones@redhat.com>. + */ + +/* @(#)svc_tcp.c 2.2 88/08/01 4.0 RPCSRC */ +/* + * Sun RPC is a product of Sun Microsystems, Inc. and is provided for + * unrestricted use provided that this legend is included on all tape + * media and as a part of the software program in whole or part. Users + * may copy or modify Sun RPC without charge, but are not authorized + * to license or distribute it to anyone else except as part of a product or + * program developed by the user. + * + * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE + * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR + * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. + * + * Sun RPC is provided with no support and without any obligation on the + * part of Sun Microsystems, Inc. to assist in its use, correction, + * modification or enhancement. + * + * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE + * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC + * OR ANY PART THEREOF. + * + * In no event will Sun Microsystems, Inc. be liable for any lost revenue + * or profits or other special, indirect and consequential damages, even if + * Sun has been advised of the possibility of such damages. + * + * Sun Microsystems, Inc. + * 2550 Garcia Avenue + * Mountain View, California 94043 + */ +#if !defined(lint) && defined(SCCSIDS) +static char sccsid[] = "@(#)svc_tcp.c 1.21 87/08/11 Copyr 1984 Sun Micro"; +#endif + +/* + * svc_tcp.c, Server side for TCP/IP based RPC. + * + * Copyright (C) 1984, Sun Microsystems, Inc. + * + * Actually implements two flavors of transporter - + * a tcp rendezvouser (a listener and connection establisher) + * and a record/tcp stream. + */ + +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <libintl.h> +#include <rpc/rpc.h> +#include <sys/socket.h> +#include <sys/poll.h> +#include <errno.h> +#include <stdlib.h> + +#ifdef USE_IN_LIBIO +# include <wchar.h> +# include <libio/iolibio.h> +#endif + +#include <gnutls/gnutls.h> +#include "svc_gnutls.h" + +/* + * Ops vector for TCP/IP based rpc service handle + */ +static bool_t svctcp_recv (SVCXPRT *, struct rpc_msg *); +static enum xprt_stat svctcp_stat (SVCXPRT *); +static bool_t svctcp_getargs (SVCXPRT *, xdrproc_t, caddr_t); +static bool_t svctcp_reply (SVCXPRT *, struct rpc_msg *); +static bool_t svctcp_freeargs (SVCXPRT *, xdrproc_t, caddr_t); +static void svctcp_destroy (SVCXPRT *); + +static const struct xp_ops svctcp_op = +{ + svctcp_recv, + svctcp_stat, + svctcp_getargs, + svctcp_reply, + svctcp_freeargs, + svctcp_destroy +}; + +/* + * Ops vector for TCP/IP rendezvous handler + */ +static bool_t rendezvous_request (SVCXPRT *, struct rpc_msg *); +static enum xprt_stat rendezvous_stat (SVCXPRT *); +static void svctcp_rendezvous_abort (void) __attribute__ ((__noreturn__)); + +/* This function makes sure abort() relocation goes through PLT + and thus can be lazy bound. */ +static void +svctcp_rendezvous_abort (void) +{ + abort (); +}; + +static const struct xp_ops svctcp_rendezvous_op = +{ + rendezvous_request, + rendezvous_stat, + (bool_t (*) (SVCXPRT *, xdrproc_t, caddr_t)) svctcp_rendezvous_abort, + (bool_t (*) (SVCXPRT *, struct rpc_msg *)) svctcp_rendezvous_abort, + (bool_t (*) (SVCXPRT *, xdrproc_t, caddr_t)) svctcp_rendezvous_abort, + svctcp_destroy +}; + +static int readtcp (char*, char *, int); +static int writetcp (char *, char *, int); +static SVCXPRT *makefd_xprt (int, u_int, u_int, gnutls_session_t); + +struct tcp_rendezvous + { /* kept in xprt->xp_p1 */ + u_int sendsize; + u_int recvsize; + gnutls_certificate_credentials_t x509_cred; + }; + +struct tcp_conn + { /* kept in xprt->xp_p1 */ + enum xprt_stat strm_stat; + u_long x_id; + XDR xdrs; + char verf_body[MAX_AUTH_BYTES]; + }; + +/* + * Usage: + * xprt = svcgnutls_create(sock, send_buf_size, recv_buf_size, + * gnutls_certificate_credentials_t x509_cred); + * + * Creates, registers, and returns a (rpc) tcp based transporter. + * Once *xprt is initialized, it is registered as a transporter + * see (svc.h, xprt_register). This routine returns + * a NULL if a problem occurred. + * + * If sock<0 then a socket is created, else sock is used. + * If the socket, sock is not bound to a port then svctcp_create + * binds it to an arbitrary port. The routine then starts a tcp + * listener on the socket's associated port. In any (successful) case, + * xprt->xp_sock is the registered socket number and xprt->xp_port is the + * associated port number. + * + * Since tcp streams do buffered io similar to stdio, the caller can specify + * how big the send and receive buffers are via the second and third parms; + * 0 => use the system default. + */ +SVCXPRT * +svcgnutls_create (int sock, u_int sendsize, u_int recvsize, + gnutls_certificate_credentials_t x509_cred) +{ + bool_t madesock = FALSE; + SVCXPRT *xprt; + struct tcp_rendezvous *r; + struct sockaddr_in addr; + socklen_t len = sizeof (struct sockaddr_in); + + if (sock == RPC_ANYSOCK) + { + if ((sock = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) + { + perror ("svc_gnutls.c - tcp socket creation problem"); + return (SVCXPRT *) NULL; + } + madesock = TRUE; + } + bzero ((char *) &addr, sizeof (addr)); + addr.sin_family = AF_INET; + if (bindresvport (sock, &addr)) + { + addr.sin_port = 0; + (void) bind (sock, (struct sockaddr *) &addr, len); + } + if ((getsockname (sock, (struct sockaddr *) &addr, &len) != 0) || + (listen (sock, SOMAXCONN) != 0)) + { + perror ("svc_gnutls.c - cannot getsockname or listen"); + if (madesock) + (void) close (sock); + return (SVCXPRT *) NULL; + } + r = (struct tcp_rendezvous *) mem_alloc (sizeof (*r)); + xprt = (SVCXPRT *) mem_alloc (sizeof (SVCXPRT)); + if (r == NULL || xprt == NULL) + { + (void) fprintf (stderr, "svcgnutls_create: out of memory\n"); + mem_free (r, sizeof (*r)); + mem_free (xprt, sizeof (SVCXPRT)); + return NULL; + } + r->sendsize = sendsize; + r->recvsize = recvsize; + r->x509_cred = x509_cred; + xprt->xp_p2 = NULL; + xprt->xp_p1 = (caddr_t) r; + xprt->xp_verf = _null_auth; + xprt->xp_ops = &svctcp_rendezvous_op; + xprt->xp_port = ntohs (addr.sin_port); + xprt->xp_sock = sock; + xprt_register (xprt); + return xprt; +} + +#if 0 +/* + * Like svtcp_create(), except the routine takes any *open* UNIX file + * descriptor as its first input. + */ +SVCXPRT * +svcfd_create (int fd, u_int sendsize, u_int recvsize) +{ + return makefd_xprt (fd, sendsize, recvsize); +} +#endif + +static SVCXPRT * +makefd_xprt (int fd, u_int sendsize, u_int recvsize, + gnutls_session_t session) +{ + SVCXPRT *xprt; + struct tcp_conn *cd; + + xprt = (SVCXPRT *) mem_alloc (sizeof (SVCXPRT)); + cd = (struct tcp_conn *) mem_alloc (sizeof (struct tcp_conn)); + if (xprt == (SVCXPRT *) NULL || cd == NULL) + { + (void) fprintf (stderr, "svc_gnutls: makefd_xprt: out of memory\n"); + mem_free (xprt, sizeof (SVCXPRT)); + mem_free (cd, sizeof (struct tcp_conn)); + return NULL; + } + cd->strm_stat = XPRT_IDLE; + xdrrec_create (&(cd->xdrs), sendsize, recvsize, + (caddr_t) xprt, readtcp, writetcp); + xprt->xp_p2 = (caddr_t) session; + xprt->xp_p1 = (caddr_t) cd; + xprt->xp_verf.oa_base = cd->verf_body; + xprt->xp_addrlen = 0; + xprt->xp_ops = &svctcp_op; /* truly deals with calls */ + xprt->xp_port = 0; /* this is a connection, not a rendezvouser */ + xprt->xp_sock = fd; + xprt_register (xprt); + return xprt; +} + +// XXX DH_BITS must be defined the same in the server main loop too. +#define DH_BITS 1024 + +static gnutls_session_t +initialize_tls_session (gnutls_certificate_credentials_t x509_cred) +{ + gnutls_session_t session; + + gnutls_init (&session, GNUTLS_SERVER); + + /* avoid calling all the priority functions, since the defaults + * are adequate. + */ + gnutls_set_default_priority (session); + + gnutls_credentials_set (session, GNUTLS_CRD_CERTIFICATE, x509_cred); + + /* request client certificate if any. + */ + gnutls_certificate_server_set_request (session, GNUTLS_CERT_REQUEST); + + gnutls_dh_set_prime_bits (session, DH_BITS); + + return session; +} + +static bool_t +rendezvous_request (SVCXPRT *xprt, + struct rpc_msg *errmsg __attribute__((unused))) +{ + int sock; + struct tcp_rendezvous *r; + struct sockaddr_in addr; + socklen_t len; + + r = (struct tcp_rendezvous *) xprt->xp_p1; + gnutls_session_t session = initialize_tls_session (r->x509_cred); + +again: + len = sizeof (struct sockaddr_in); + if ((sock = accept (xprt->xp_sock, (struct sockaddr *) &addr, &len)) < 0) + { + if (errno == EINTR) + goto again; + return FALSE; + } + + gnutls_transport_set_ptr (session, (gnutls_transport_ptr_t) (long) sock); + int ret = gnutls_handshake (session); + if (ret < 0) + { + close (sock); + gnutls_deinit (session); + fprintf (stderr, "svc_gnutls: TLS handshake failed (%s)\n\n", + gnutls_strerror (ret)); + return FALSE; + } + + // XXX verify peer + fprintf (stderr, "XXX you must verify the peer\n"); + + /* + * make a new transporter (re-uses xprt) + */ + xprt = makefd_xprt (sock, r->sendsize, r->recvsize, session); +#if 0 + memcpy (&xprt->xp_raddr, &addr, sizeof (addr)); + xprt->xp_addrlen = len; +#endif + return FALSE; /* there is never an rpc msg to be processed */ +} + +static enum xprt_stat +rendezvous_stat (SVCXPRT *xprt __attribute__((unused))) +{ + return XPRT_IDLE; +} + +static void +svctcp_destroy (SVCXPRT *xprt) +{ + struct tcp_conn *cd = (struct tcp_conn *) xprt->xp_p1; + gnutls_session_t session = (gnutls_session_t) xprt->xp_p2; + + gnutls_bye (session, GNUTLS_SHUT_WR); + + xprt_unregister (xprt); + (void) close (xprt->xp_sock); + if (xprt->xp_port != 0) + { + /* a rendezvouser socket */ + xprt->xp_port = 0; + } + else + { + /* an actual connection socket */ + XDR_DESTROY (&(cd->xdrs)); + } + + gnutls_deinit (session); + + mem_free ((caddr_t) cd, sizeof (struct tcp_conn)); + mem_free ((caddr_t) xprt, sizeof (SVCXPRT)); +} + + +/* + * reads data from the tcp connection. + * any error is fatal and the connection is closed. + * (And a read of zero bytes is a half closed stream => error.) + */ +static int +readtcp (char *xprtptr, char *buf, int len) +{ + SVCXPRT *xprt = (SVCXPRT *)xprtptr; + gnutls_session_t session = (gnutls_session_t) xprt->xp_p2; + int sock = xprt->xp_sock; + int milliseconds = 35 * 1000; + struct pollfd pollfd; + + do + { + pollfd.fd = sock; + pollfd.events = POLLIN; + switch (poll (&pollfd, 1, milliseconds)) + { + case -1: + if (errno == EINTR) + continue; + /*FALLTHROUGH*/ + case 0: + goto fatal_err; + default: + if ((pollfd.revents & POLLERR) || (pollfd.revents & POLLHUP) + || (pollfd.revents & POLLNVAL)) + goto fatal_err; + break; + } + } + while ((pollfd.revents & POLLIN) == 0); + + if ((len = gnutls_record_recv (session, buf, len)) > 0) + return len; + + fatal_err: + ((struct tcp_conn *) (xprt->xp_p1))->strm_stat = XPRT_DIED; + return -1; +} + +/* + * writes data to the tcp connection. + * Any error is fatal and the connection is closed. + */ +static int +writetcp (char *xprtptr, char * buf, int len) +{ + SVCXPRT *xprt = (SVCXPRT *)xprtptr; + gnutls_session_t session = (gnutls_session_t) xprt->xp_p2; + + if (gnutls_record_send (session, buf, len) < 0) { + ((struct tcp_conn *) (xprt->xp_p1))->strm_stat = XPRT_DIED; + return -1; + } + + return len; +} + +static enum xprt_stat +svctcp_stat (SVCXPRT *xprt) +{ + struct tcp_conn *cd = + (struct tcp_conn *) (xprt->xp_p1); + + if (cd->strm_stat == XPRT_DIED) + return XPRT_DIED; + if (!xdrrec_eof (&(cd->xdrs))) + return XPRT_MOREREQS; + return XPRT_IDLE; +} + +static bool_t +svctcp_recv (SVCXPRT *xprt, struct rpc_msg *msg) +{ + struct tcp_conn *cd = (struct tcp_conn *) (xprt->xp_p1); + XDR *xdrs = &(cd->xdrs); + + xdrs->x_op = XDR_DECODE; + (void) xdrrec_skiprecord (xdrs); + if (xdr_callmsg (xdrs, msg)) + { + cd->x_id = msg->rm_xid; + return TRUE; + } + cd->strm_stat = XPRT_DIED; /* XXXX */ + return FALSE; +} + +static bool_t +svctcp_getargs (SVCXPRT *xprt, xdrproc_t xdr_args, caddr_t args_ptr) +{ + return ((*xdr_args) (&(((struct tcp_conn *) + (xprt->xp_p1))->xdrs), args_ptr)); +} + +static bool_t +svctcp_freeargs (SVCXPRT *xprt, xdrproc_t xdr_args, caddr_t args_ptr) +{ + XDR *xdrs = &(((struct tcp_conn *) (xprt->xp_p1))->xdrs); + + xdrs->x_op = XDR_FREE; + return ((*xdr_args) (xdrs, args_ptr)); +} + +static bool_t +svctcp_reply (SVCXPRT *xprt, struct rpc_msg *msg) +{ + struct tcp_conn *cd = (struct tcp_conn *) (xprt->xp_p1); + XDR *xdrs = &(cd->xdrs); + bool_t stat; + + xdrs->x_op = XDR_ENCODE; + msg->rm_xid = cd->x_id; + stat = xdr_replymsg (xdrs, msg); + (void) xdrrec_endofrecord (xdrs, TRUE); + return stat; +} --- /tmp/newfile 2007-01-30 17:23:46.000000000 +0000 +++ src/sunrpc/svc_gnutls.h 2007-01-29 17:03:24.000000000 +0000 @@ -0,0 +1,17 @@ +/* + * svc_gnutls.h: Interface to the SunRPC over GnuTLS server. + * + * Copyright (C) 2007 Red Hat, Inc. + * + * See COPYING.LIB for the license of this software. + * + * Richard Jones <rjones@redhat.com> + */ + +#ifndef __SVC_GNUTLS_H__ +#define __SVC_GNUTLS_H__ + +extern SVCXPRT *svcgnutls_create (int sock, u_int sendsize, u_int recvsize, + gnutls_certificate_credentials_t x509_cred); + +#endif /* __SVC_GNUTLS_H__ */ --- /tmp/newfile 2007-01-30 17:23:46.000000000 +0000 +++ src/sunrpc/svc_tcp2.c 2007-01-30 16:16:32.000000000 +0000 @@ -0,0 +1,426 @@ +/* + * svc_tcp2.c: SunRPC TCP server. This is a very slightly modified + * version of svc_tcp.c from glibc which removes some of the IPv4 + * assumptions, so this version can be used over IPv4 or IPv6 sockets. + * + * Modifications from glibc-2.5 base by Richard Jones <rjones@redhat.com>. + */ + +/* @(#)svc_tcp.c 2.2 88/08/01 4.0 RPCSRC */ +/* + * Sun RPC is a product of Sun Microsystems, Inc. and is provided for + * unrestricted use provided that this legend is included on all tape + * media and as a part of the software program in whole or part. Users + * may copy or modify Sun RPC without charge, but are not authorized + * to license or distribute it to anyone else except as part of a product or + * program developed by the user. + * + * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE + * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR + * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. + * + * Sun RPC is provided with no support and without any obligation on the + * part of Sun Microsystems, Inc. to assist in its use, correction, + * modification or enhancement. + * + * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE + * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC + * OR ANY PART THEREOF. + * + * In no event will Sun Microsystems, Inc. be liable for any lost revenue + * or profits or other special, indirect and consequential damages, even if + * Sun has been advised of the possibility of such damages. + * + * Sun Microsystems, Inc. + * 2550 Garcia Avenue + * Mountain View, California 94043 + */ +#if !defined(lint) && defined(SCCSIDS) +static char sccsid[] = "@(#)svc_tcp.c 1.21 87/08/11 Copyr 1984 Sun Micro"; +#endif + +/* + * svc_tcp.c, Server side for TCP/IP based RPC. + * + * Copyright (C) 1984, Sun Microsystems, Inc. + * + * Actually implements two flavors of transporter - + * a tcp rendezvouser (a listener and connection establisher) + * and a record/tcp stream. + */ + +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <libintl.h> +#include <rpc/rpc.h> +#include <sys/socket.h> +#include <sys/poll.h> +#include <errno.h> +#include <stdlib.h> + +#ifdef USE_IN_LIBIO +# include <wchar.h> +# include <libio/iolibio.h> +#endif + +#include "svc_tcp2.h" + +/* + * Ops vector for TCP/IP based rpc service handle + */ +static bool_t svctcp_recv (SVCXPRT *, struct rpc_msg *); +static enum xprt_stat svctcp_stat (SVCXPRT *); +static bool_t svctcp_getargs (SVCXPRT *, xdrproc_t, caddr_t); +static bool_t svctcp_reply (SVCXPRT *, struct rpc_msg *); +static bool_t svctcp_freeargs (SVCXPRT *, xdrproc_t, caddr_t); +static void svctcp_destroy (SVCXPRT *); + +static const struct xp_ops svctcp_op = +{ + svctcp_recv, + svctcp_stat, + svctcp_getargs, + svctcp_reply, + svctcp_freeargs, + svctcp_destroy +}; + +/* + * Ops vector for TCP/IP rendezvous handler + */ +static bool_t rendezvous_request (SVCXPRT *, struct rpc_msg *); +static enum xprt_stat rendezvous_stat (SVCXPRT *); +static void svctcp_rendezvous_abort (void) __attribute__ ((__noreturn__)); + +/* This function makes sure abort() relocation goes through PLT + and thus can be lazy bound. */ +static void +svctcp_rendezvous_abort (void) +{ + abort (); +}; + +static const struct xp_ops svctcp_rendezvous_op = +{ + rendezvous_request, + rendezvous_stat, + (bool_t (*) (SVCXPRT *, xdrproc_t, caddr_t)) svctcp_rendezvous_abort, + (bool_t (*) (SVCXPRT *, struct rpc_msg *)) svctcp_rendezvous_abort, + (bool_t (*) (SVCXPRT *, xdrproc_t, caddr_t)) svctcp_rendezvous_abort, + svctcp_destroy +}; + +static int readtcp (char*, char *, int); +static int writetcp (char *, char *, int); +static SVCXPRT *makefd_xprt (int, u_int, u_int); + +struct tcp_rendezvous + { /* kept in xprt->xp_p1 */ + u_int sendsize; + u_int recvsize; + }; + +struct tcp_conn + { /* kept in xprt->xp_p1 */ + enum xprt_stat strm_stat; + u_long x_id; + XDR xdrs; + char verf_body[MAX_AUTH_BYTES]; + }; + +/* + * Usage: + * xprt = svctcp_create(sock, send_buf_size, recv_buf_size); + * + * Creates, registers, and returns a (rpc) tcp based transporter. + * Once *xprt is initialized, it is registered as a transporter + * see (svc.h, xprt_register). This routine returns + * a NULL if a problem occurred. + * + * If sock<0 then a socket is created, else sock is used. + * If the socket, sock is not bound to a port then svctcp_create + * binds it to an arbitrary port. The routine then starts a tcp + * listener on the socket's associated port. In any (successful) case, + * xprt->xp_sock is the registered socket number and xprt->xp_port is the + * associated port number. + * + * Since tcp streams do buffered io similar to stdio, the caller can specify + * how big the send and receive buffers are via the second and third parms; + * 0 => use the system default. + */ +SVCXPRT * +svctcp2_create (int sock, u_int sendsize, u_int recvsize) +{ + bool_t madesock = FALSE; + SVCXPRT *xprt; + struct tcp_rendezvous *r; + struct sockaddr_in addr; + socklen_t len = sizeof (struct sockaddr_in); + + if (sock == RPC_ANYSOCK) + { + if ((sock = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) + { + perror ("svc_tcp.c - tcp socket creation problem"); + return (SVCXPRT *) NULL; + } + madesock = TRUE; + + bzero ((char *) &addr, sizeof (addr)); + addr.sin_family = AF_INET; + if (bindresvport (sock, &addr)) + { + addr.sin_port = 0; + (void) bind (sock, (struct sockaddr *) &addr, len); + } + if ((getsockname (sock, (struct sockaddr *) &addr, &len) != 0) || + (listen (sock, SOMAXCONN) != 0)) + { + perror ("svc_tcp.c - cannot getsockname or listen"); + if (madesock) + (void) close (sock); + return (SVCXPRT *) NULL; + } + } + r = (struct tcp_rendezvous *) mem_alloc (sizeof (*r)); + xprt = (SVCXPRT *) mem_alloc (sizeof (SVCXPRT)); + if (r == NULL || xprt == NULL) + { + (void) fprintf (stderr, "svctcp_create: out of memory\n"); + mem_free (r, sizeof (*r)); + mem_free (xprt, sizeof (SVCXPRT)); + return NULL; + } + r->sendsize = sendsize; + r->recvsize = recvsize; + xprt->xp_p2 = NULL; + xprt->xp_p1 = (caddr_t) r; + xprt->xp_verf = _null_auth; + xprt->xp_ops = &svctcp_rendezvous_op; + xprt->xp_port = ntohs (addr.sin_port); + xprt->xp_sock = sock; + xprt_register (xprt); + return xprt; +} + +#if 0 +/* + * Like svtcp_create(), except the routine takes any *open* UNIX file + * descriptor as its first input. + */ +SVCXPRT * +svcfd_create (int fd, u_int sendsize, u_int recvsize) +{ + return makefd_xprt (fd, sendsize, recvsize); +} +#endif + +static SVCXPRT * +makefd_xprt (int fd, u_int sendsize, u_int recvsize) +{ + SVCXPRT *xprt; + struct tcp_conn *cd; + + xprt = (SVCXPRT *) mem_alloc (sizeof (SVCXPRT)); + cd = (struct tcp_conn *) mem_alloc (sizeof (struct tcp_conn)); + if (xprt == (SVCXPRT *) NULL || cd == NULL) + { + (void) fprintf (stderr, "svc_tcp: makefd_xprt: out of memory\n"); + mem_free (xprt, sizeof (SVCXPRT)); + mem_free (cd, sizeof (struct tcp_conn)); + return NULL; + } + cd->strm_stat = XPRT_IDLE; + xdrrec_create (&(cd->xdrs), sendsize, recvsize, + (caddr_t) xprt, readtcp, writetcp); + xprt->xp_p2 = NULL; + xprt->xp_p1 = (caddr_t) cd; + xprt->xp_verf.oa_base = cd->verf_body; + xprt->xp_addrlen = 0; + xprt->xp_ops = &svctcp_op; /* truly deals with calls */ + xprt->xp_port = 0; /* this is a connection, not a rendezvouser */ + xprt->xp_sock = fd; + xprt_register (xprt); + return xprt; +} + +static bool_t +rendezvous_request (SVCXPRT *xprt, + struct rpc_msg *errmsg __attribute__((unused))) +{ + int sock; + struct tcp_rendezvous *r; + struct sockaddr_in addr; + socklen_t len; + + r = (struct tcp_rendezvous *) xprt->xp_p1; +again: + len = sizeof (struct sockaddr_in); + if ((sock = accept (xprt->xp_sock, (struct sockaddr *) &addr, &len)) < 0) + { + if (errno == EINTR) + goto again; + return FALSE; + } + /* + * make a new transporter (re-uses xprt) + */ + xprt = makefd_xprt (sock, r->sendsize, r->recvsize); +#if 0 + memcpy (&xprt->xp_raddr, &addr, sizeof (addr)); + xprt->xp_addrlen = len; +#endif + return FALSE; /* there is never an rpc msg to be processed */ +} + +static enum xprt_stat +rendezvous_stat (SVCXPRT *xprt __attribute__((unused))) +{ + return XPRT_IDLE; +} + +static void +svctcp_destroy (SVCXPRT *xprt) +{ + struct tcp_conn *cd = (struct tcp_conn *) xprt->xp_p1; + + xprt_unregister (xprt); + (void) close (xprt->xp_sock); + if (xprt->xp_port != 0) + { + /* a rendezvouser socket */ + xprt->xp_port = 0; + } + else + { + /* an actual connection socket */ + XDR_DESTROY (&(cd->xdrs)); + } + mem_free ((caddr_t) cd, sizeof (struct tcp_conn)); + mem_free ((caddr_t) xprt, sizeof (SVCXPRT)); +} + + +/* + * reads data from the tcp connection. + * any error is fatal and the connection is closed. + * (And a read of zero bytes is a half closed stream => error.) + */ +static int +readtcp (char *xprtptr, char *buf, int len) +{ + SVCXPRT *xprt = (SVCXPRT *)xprtptr; + int sock = xprt->xp_sock; + int milliseconds = 35 * 1000; + struct pollfd pollfd; + + do + { + pollfd.fd = sock; + pollfd.events = POLLIN; + switch (poll (&pollfd, 1, milliseconds)) + { + case -1: + if (errno == EINTR) + continue; + /*FALLTHROUGH*/ + case 0: + goto fatal_err; + default: + if ((pollfd.revents & POLLERR) || (pollfd.revents & POLLHUP) + || (pollfd.revents & POLLNVAL)) + goto fatal_err; + break; + } + } + while ((pollfd.revents & POLLIN) == 0); + + if ((len = read (sock, buf, len)) > 0) + return len; + + fatal_err: + ((struct tcp_conn *) (xprt->xp_p1))->strm_stat = XPRT_DIED; + return -1; +} + +/* + * writes data to the tcp connection. + * Any error is fatal and the connection is closed. + */ +static int +writetcp (char *xprtptr, char * buf, int len) +{ + SVCXPRT *xprt = (SVCXPRT *)xprtptr; + int i, cnt; + + for (cnt = len; cnt > 0; cnt -= i, buf += i) + { + if ((i = write (xprt->xp_sock, buf, cnt)) < 0) + { + ((struct tcp_conn *) (xprt->xp_p1))->strm_stat = XPRT_DIED; + return -1; + } + } + return len; +} + +static enum xprt_stat +svctcp_stat (SVCXPRT *xprt) +{ + struct tcp_conn *cd = + (struct tcp_conn *) (xprt->xp_p1); + + if (cd->strm_stat == XPRT_DIED) + return XPRT_DIED; + if (!xdrrec_eof (&(cd->xdrs))) + return XPRT_MOREREQS; + return XPRT_IDLE; +} + +static bool_t +svctcp_recv (SVCXPRT *xprt, struct rpc_msg *msg) +{ + struct tcp_conn *cd = (struct tcp_conn *) (xprt->xp_p1); + XDR *xdrs = &(cd->xdrs); + + xdrs->x_op = XDR_DECODE; + (void) xdrrec_skiprecord (xdrs); + if (xdr_callmsg (xdrs, msg)) + { + cd->x_id = msg->rm_xid; + return TRUE; + } + cd->strm_stat = XPRT_DIED; /* XXXX */ + return FALSE; +} + +static bool_t +svctcp_getargs (SVCXPRT *xprt, xdrproc_t xdr_args, caddr_t args_ptr) +{ + return ((*xdr_args) (&(((struct tcp_conn *) + (xprt->xp_p1))->xdrs), args_ptr)); +} + +static bool_t +svctcp_freeargs (SVCXPRT *xprt, xdrproc_t xdr_args, caddr_t args_ptr) +{ + XDR *xdrs = &(((struct tcp_conn *) (xprt->xp_p1))->xdrs); + + xdrs->x_op = XDR_FREE; + return ((*xdr_args) (xdrs, args_ptr)); +} + +static bool_t +svctcp_reply (SVCXPRT *xprt, struct rpc_msg *msg) +{ + struct tcp_conn *cd = (struct tcp_conn *) (xprt->xp_p1); + XDR *xdrs = &(cd->xdrs); + bool_t stat; + + xdrs->x_op = XDR_ENCODE; + msg->rm_xid = cd->x_id; + stat = xdr_replymsg (xdrs, msg); + (void) xdrrec_endofrecord (xdrs, TRUE); + return stat; +} --- /tmp/newfile 2007-01-30 17:23:46.000000000 +0000 +++ src/sunrpc/svc_tcp2.h 2007-01-30 16:14:22.000000000 +0000 @@ -0,0 +1,16 @@ +/* + * svc_tcp2.h: Interface to the SunRPC over TCP server. + * + * Copyright (C) 2007 Red Hat, Inc. + * + * See COPYING.LIB for the license of this software. + * + * Richard Jones <rjones@redhat.com> + */ + +#ifndef __SVC_TCP2_H__ +#define __SVC_TCP2_H__ + +extern SVCXPRT *svctcp2_create (int sock, u_int sendsize, u_int recvsize); + +#endif /* __SVC_TCP2_H__ */

On Tue, Jan 30, 2007 at 05:42:21PM +0000, Richard W.M. Jones wrote:
This patch is just for discussion. It's not in a state to be applied, even if it were accepted (which is a long-shot at present anyway).
When looking at the patch, a good starting point is to search for "Architecture and notes" and read from there.
Supports:
* remote driver (just does the "open", "close", "type" and "version" calls at present as a proof of concept) * TLS transport (built using GnuTLS) * SSH transport (forks external ssh process) * TCP transport (unencrypted - just for testing) * Unix domain socket transport * arbitrary external program / shell script transport * IPv6-ready on client & server
I've tested all the transports, and in limited tests they all seem to work. ie. You really can do:
virsh -c remote:tls:server version
I don't think that's valid URI syntax - although we've got the historical cruft of a simple 'xen' connect string, we're trying to get the rest of the backends to use sane URIs. We need to be able to express the actual backend driver in this URI, so that its possible to pass it into the virConnectOpen method on the server side. Current drivers have prefixes like qemud:/// test:///default We could make use of the host/port number component to signify that the driver should use the libvirtd. So 'test:///default' would be procssed by the driver locally, while 'test://example.com/default' would get picked up by the 'remote' driver which would strip the host/port bit and pass 'test:///default' to the virConnectOpen method on the remote end.
Shortcomings in this version:
* in "open" call, name must be non-NULL (this is just a bug) * doesn't actually invoke libvirt on the server side; just prints out messages and returns dummy values * "ssh" not recognised as a service name by getaddrinfo, so you must always give a port number, ie. remote:ssh:server:22
That's a little odd - do other named services get recognised corectly ?
* /tmp/socket should be cleaned up when the server exits
Two scenarios I imagine: 1. A privileged daemon started from the init scripts. The init script would do the PID file based locking preventing 2 instances being started. Thus could simply unlink() the socket upon startup before bind()ing to it 2. Or just use sockets in the abstract namespace which don't hit the filesystem at all & thus are auto-cleaned up. I prefer the latter really.
* SunRPC is stateless so we need to hand out a cookie to represent the virConnectPtr handle on the server side. However if the client dies without explicitly calling close, we have no way to know, and so the cookie/handle on the server side lives forever.
I think you can get a handle to the socket representing the client connection from without the remote impl of the methods, by using the svc_getcaller() API, and thus associated state with the client socket. Since we have to replace svc_run() with a hand crafted mainloop we can then detect when a client socket is removed from the server and trigger cleanup of any resources.
* There's some confusion about the level of abstraction. At the moment I'm abstracting at the driver level, but that may be wrong and possibly I should be abstracting at the level of vir* calls. On the other hand, there's not a huge amount of difference.
The only interesting difference is probably in the virConnectOpen method where we iterate over the drivers figuring out which one to activate. Once you have an activated driver, then it doesn't matter really whether you call the public, or driver APIs. I think I'd probably start off at the vir* call level.
* Security: Is it safe for libvirt to be connecting to arbitrary TCP sockets?
Well the app always has to pass an explicit URI into virConnectOpen before we'll do any of the remote connection stuff, so I'd assume that if they're giving us an explicit request to connect to port XXX then they know what they're doing.
Is it safe for libvirt to be able to run arbitrary programs?
I'm not such a fan of libraries which spawn arbitrary programs on my behalf. But as long as it didn't actually spawn stuff unless ther was some explicit request based on the URI it should be ok - that way we can document that specific URI formats will result in the spawning of /usr/bin/ssh.
+ if (strncasecmp (name, "remote:", 7) != 0) + return -1; /* Not for me. */ + + /* Split the name at whitespace and parse it. */ + const char *p; + for (p = name; *p;) { + // NB. All versioning is done by SunRPC so we don't need to worry + // that we are connected to an incompatible daemon. + break; + }
We can use libxml's existing routines for parsing URIs #include <libxml/uri.h> xmlURIPtr uri = xmlParseURI(connectstr)
+ + case proto_unix: { + // 108 is hard-coded into the header files as well. +#define UNIX_PATH_MAX 108
Can't we use sizeof(addr.sun_path) because this may be hardcoded to a different value on non-Linux systems.
+program LIBVIRTREMOTE { + version LIBVIRTREMOTE_VERS1 { + /* XXX The open interface should return a cookie to represent + * the virConnectPtr on the server side. Note also that SunRPC + * is stateless so it's unclear when cookies can be garbage + * collected. + */ + int remote_rpc_open (string) = 1;
Do we actually need any to have any cookie for this - just assume 1 socket connetion == virConnectPtr object and thus only allow remote_rpc_open to be called a single time per connection. Regards, Dan. -- |=- Red Hat, Engineering, Emerging Technologies, Boston. +1 978 392 2496 -=| |=- Perl modules: http://search.cpan.org/~danberr/ -=| |=- Projects: http://freshmeat.net/~danielpb/ -=| |=- GnuPG: 7D3B9505 F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 -=|

On Tue, Jan 30, 2007 at 08:04:54PM +0000, Daniel P. Berrange wrote:
On Tue, Jan 30, 2007 at 05:42:21PM +0000, Richard W.M. Jones wrote:
This patch is just for discussion. It's not in a state to be applied, even if it were accepted (which is a long-shot at present anyway).
I'm in Paris this week at the expo, sorry it may be a bit difficult now to do a full review, will do as soon as possible :-)
When looking at the patch, a good starting point is to search for "Architecture and notes" and read from there.
Supports:
* remote driver (just does the "open", "close", "type" and "version" calls at present as a proof of concept) * TLS transport (built using GnuTLS) * SSH transport (forks external ssh process) * TCP transport (unencrypted - just for testing) * Unix domain socket transport * arbitrary external program / shell script transport * IPv6-ready on client & server
I've tested all the transports, and in limited tests they all seem to work. ie. You really can do:
virsh -c remote:tls:server version
I don't think that's valid URI syntax - although we've got the historical cruft of a simple 'xen' connect string, we're trying to get the rest of the backends to use sane URIs. We need to be able to express the actual backend driver in this URI, so that its possible to pass it into the virConnectOpen method on the server side.
Let's stick to the URI syntax. basically the first part must indicate the protocol, I suggest using something like libvirt+tls://hostname/ And if you just want to do xen monitoring or qemu monitoring libvirt+tls://hostname/#xen libvirt+tls://hostname/#qemu It should be possible at the connection level to indicate which subset one is interested in and pass it down at the protocol level when establishing the connection. I'm still wondering about read-only connections I could see how one client want to garantee no side effect, maybe libvirtro+tls://hostname/ ...
Current drivers have prefixes like
qemud:/// test:///default
We could make use of the host/port number component to signify that the driver should use the libvirtd. So 'test:///default' would be procssed by the driver locally, while 'test://example.com/default' would get picked up by the 'remote' driver which would strip the host/port bit and pass 'test:///default' to the virConnectOpen method on the remote end.
yes, libxml2 has all the parsing routines necessary already if needed, http://xmlsoft.org/html/libxml-uri.html
* There's some confusion about the level of abstraction. At the moment I'm abstracting at the driver level, but that may be wrong and possibly I should be abstracting at the level of vir* calls. On the other hand, there's not a huge amount of difference.
The only interesting difference is probably in the virConnectOpen method where we iterate over the drivers figuring out which one to activate. Once you have an activated driver, then it doesn't matter really whether you call the public, or driver APIs. I think I'd probably start off at the vir* call level.
I would say at the vir call level unless a specific 'driver' is asked for say with #qemu. The only trick is that #xen is implemented by 3 drivers, the existing entry point already have that logic, it may have to be replicated there ...
Is it safe for libvirt to be able to run arbitrary programs?
I would say no.
I'm not such a fan of libraries which spawn arbitrary programs on my behalf. But as long as it didn't actually spawn stuff unless ther was some explicit request based on the URI it should be ok - that way we can document that specific URI formats will result in the spawning of /usr/bin/ssh.
I would just hardcode the supported protocols and the associated programs, but not having seen the code yet I may be off track...
+ if (strncasecmp (name, "remote:", 7) != 0) + return -1; /* Not for me. */ + + /* Split the name at whitespace and parse it. */ + const char *p; + for (p = name; *p;) { + // NB. All versioning is done by SunRPC so we don't need to worry + // that we are connected to an incompatible daemon. + break; + }
We can use libxml's existing routines for parsing URIs
#include <libxml/uri.h>
xmlURIPtr uri = xmlParseURI(connectstr)
right, it will ensure it's a proper URI (c.f. RFC 2396 (I need to update to latest IETF spec but should not affect us)), the fields are then accessible from the record returned, which should be freed by xmlFreeURI(uri) when done. thanks ! Daniel -- Red Hat Virtualization group http://redhat.com/virtualization/ Daniel Veillard | virtualization library http://libvirt.org/ veillard@redhat.com | libxml GNOME XML XSLT toolkit http://xmlsoft.org/ http://veillard.com/ | Rpmfind RPM search engine http://rpmfind.net/

Before getting into more discussion on the URI issue, I'll copy the "Architecture & Notes" section here so that everyone can actually read it first. /* Architecture and notes: * * virConnectOpen ("remote:....") invokes this driver. Depending * on the exact contents of the ellipsis "...." in the name string * we will try some method to connect to a libvirtd daemon, running * on a remote machine (or sometimes running on the local machine). * * All other vir* calls made on this connection are forwarded * to the libvirtd daemon which carries out the requested action. * So for example if you call virDomainCreateLinux, then the * domain gets created on the remote machine, and virConnectListDomains * lists domains running on the remote machine. * * Connections can be authenticated and encrypted -- it depends * on the transport selected by the name string. * * The current implementation uses SunRPC layered over one of: * - GnuTLS (an SSL/TLS library providing enterprise-level * authentication and encryption) * - a local Unix domain socket * - ssh or another external program such as rsh * - a plain TCP socket (unencrypted, not recommended for production) * * See http://et.redhat.com/~rjones/secure_rpc for an insight into * the thinking that went into the selection of SunRPC. In * the future we may use a different RPC system - for example * XML-RPC would be a logical choice - so for now you should regard * the protocol used as private and subject to change in future * versions of libvirt without notice. * * The name string selects the transport to use and the type of * virtualisation at the remote end. The general format is: * * "remote:<protocol>:<path> var=value var=value ..." * * Some examples: * * "remote:unix:/var/run/libvirtd/socket" * "remote:tls:myxenserver" * "remote:ssh:myserver name=qemud" * "remote:ssh:myserver command=/opt/openssh/bin/ssh" * * The <protocol> is one of: tls, unix, ssh, ext or tcp. * The <path> is protocol specific: * * Protocol Path-format * ----------------------------------------- * tls hostname[:port] * unix Path to local socket * ssh hostname[:port] * ext Name or path of external program * tcp hostname[:port] * * For tls, the default port is 16514. For tcp, the default port is * 16509 (but note that tcp is almost never enabled because it is * insecure - it's only there for testing). * * For ssh: The default port for ssh is 22. You should configure ssh * so that it doesn't ask for a password (eg. using ssh-agent). The * remote server should have a recent version of the the netcat program * installed as 'nc', and the remote libvirtd must be configured to * listen on a Unix domain socket. The following full command is run: * ssh -p $port $hostname nc -U /var/run/libvirtd/socket * * For ext: Only the command you specify is run. It is up to you to * write this command so that it somehow makes a connection to a * remote libvirtd, and passes input and output over its stdin/stdout. * * The var=value pairs provide optional extra information: * * Variable Protocols Meaning * ----------------------------------------- * name (all) Name used in remote virConnectOpen * (default is NULL). * command ssh Name or path of external program (instead * of "ssh"). * * The value is %-escaped (just like URL encoding), so if you want it * to contain a literal space use "%20" or "+", if you want it to have * a literal + character use "%2b", and for a literal % character use "%25". * * To provide some forwards compatibility, variables which are not * understood are ignored (but a warning is printed on stderr). * * Several shorthand syntaxes are available: * * "remote:/var/run/libvirtd/socket" connect to Unix domain socket * "remote://server" connect to TLS socket on server * "remote://server:9000" connect to TLS server port 9000 * * For the details of the implementation of SunRPC over GnuTLS, etc. * please see http://et.redhat.com/~rjones/secure_rpc which contains * simple code samples which will allow you to understand what's * going on here. */ -- Emerging Technologies, Red Hat http://et.redhat.com/~rjones/ 64 Baker Street, London, W1U 7DF Mobile: +44 7866 314 421 "[Negative numbers] darken the very whole doctrines of the equations and make dark of the things which are in their nature excessively obvious and simple" (Francis Maseres FRS, mathematician, 1759)

On Wed, Jan 31, 2007 at 09:44:04AM +0000, Richard W.M. Jones wrote:
Before getting into more discussion on the URI issue, I'll copy the "Architecture & Notes" section here so that everyone can actually read it first. * The name string selects the transport to use and the type of * virtualisation at the remote end. The general format is: * * "remote:<protocol>:<path> var=value var=value ..."
nahh, let's stick to URI, really ! See section 3. URI Syntactic Components http://www.ietf.org/rfc/rfc2396.txt Daniel -- Red Hat Virtualization group http://redhat.com/virtualization/ Daniel Veillard | virtualization library http://libvirt.org/ veillard@redhat.com | libxml GNOME XML XSLT toolkit http://xmlsoft.org/ http://veillard.com/ | Rpmfind RPM search engine http://rpmfind.net/

This is the updated proposal for URIs. Code to follow ... /* Architecture and notes: * * virConnectOpen in this driver looks for remote URLs and will * try some method to connect to a libvirtd running on a remote * machine. * * Local URLs are the standard URLs used by other drivers - * for example "qemud:///system". * * Remote URLs contain either a server name or a remote transport * name. For example: "qemud://server.example.com/system" contains * a server name (server.example.com), and "qemud+unix:///system" * contains a transport (unix). Commonly remote URLs contain * both, for example: "qemud+tls://server.example.com/system" * (transport tls, server name server.example.com). * * All other vir* calls made on this connection are forwarded * to the libvirtd daemon which carries out the requested action. * So for example if you call virDomainCreateLinux, then the * domain gets created on the remote machine, and virConnectListDomains * lists domains running on the remote machine. * * Connections can be authenticated and encrypted -- it depends * on the transport selected. * * The current implementation uses SunRPC layered over one of: * - GnuTLS (an SSL/TLS library providing enterprise-level * authentication and encryption) * - a local Unix domain socket * - ssh or another external program such as rsh * - a plain TCP socket (unencrypted, not recommended for production) * * See http://et.redhat.com/~rjones/secure_rpc for an insight into * the thinking that went into the selection of SunRPC. In * the future we may use a different RPC system - for example * XML-RPC would be a logical choice - so for now you should regard * the protocol used as private and subject to change in future * versions of libvirt without notice. * * The URL (name parameter to virConnectOpen) is a URL of the * following general form: * * "driver+transport://server:port/path?var=value&var=value&..." * * Transport, server and port are all optional (except that you must * have either transport or server as explained above). You may * have zero or more variables (or omit the query string entirely). * Driver is the usual libvirt driver, as used on the remote * machine, and path is specific to driver. * * Some examples: * * "xend://server/" Remote xend, using TLS, port 16514 * "qemud+unix:///session" Communicate over Unix domain socket * to a local libvirtd. * "qemud+ssh://server/session?command=/opt/openssh/bin/ssh" * Communicate over ssh to a remote * libvirtd on server. Control qemu * on remote. ssh command is located * in a non-standard place. * "qemud+tcp://server:5000/session" * Unencrypted TCP, port 5000. * "xend+ext:///?command=my_shell_script" * Run my_shell_script which uses its own * method (eg. rsh, ssh, ...) to talk to * the remote libvirtd, controlling a * remote xend. * * [To emphasise - in ALL instances communication is with a remote * libvirtd, even if that remote libvirtd itself talks to another * daemon such as xend or qemud]. * * The transport is one of: tls, unix, ssh, ext or tcp. If no * transport is given, the default is to use tls. * * For tls, the default port is 16514. For tcp, the default port is * 16509 (but note that tcp is almost never enabled because it is * insecure - it's only there for testing). * * For ssh: The default port for ssh is 22. You should configure ssh * so that it doesn't ask for a password (eg. using ssh-agent). The * remote server should have a recent version of the the netcat program * installed as 'nc', and the remote libvirtd must be configured to * listen on a Unix domain socket. The following full command is run: * ssh -p $port $hostname nc -U /var/run/libvirtd/socket * * For ext: Only the command you specify is run. It is up to you to * write this command so that it somehow makes a connection to a * remote libvirtd, and passes input and output over its stdin/stdout. * * The var=value pairs provide optional extra information: * * Variable Transport Meaning * ----------------------------------------- * command ssh,ext Name or path of external program. * For ssh this defaults to "ssh". * For ext you must supply it. * name (all) Optionally the name used in remote * virConnectOpen. The default is to * construct the name by removing * transport, server name, port and * variables from the remote URL to * form a local URL. But if this * doesn't give the desired result you * may specify the exact name here. * socket unix,ssh,ext Name of the Unix domain socket. The * default is in <remote_internal.h>. * netcat ssh Name of the netcat program on the * remote server. Default is "nc". * * The value is %-escaped (just like URL encoding), so if you want it * to contain a literal space use "%20" or "+", if you want it to have * a literal + character use "%2b", and for a literal % character use "%25". * * To provide some forwards compatibility, variables which are not * understood are ignored (but a warning is printed on stderr). * * For the details of the implementation of SunRPC over GnuTLS, etc. * please see http://et.redhat.com/~rjones/secure_rpc which contains * simple code samples which will allow you to understand what's * going on here. */ -- Emerging Technologies, Red Hat http://et.redhat.com/~rjones/ 64 Baker Street, London, W1U 7DF Mobile: +44 7866 314 421 "[Negative numbers] darken the very whole doctrines of the equations and make dark of the things which are in their nature excessively obvious and simple" (Francis Maseres FRS, mathematician, 1759)

On Wed, 2007-01-31 at 15:25 +0000, Richard W.M. Jones wrote:
* Remote URLs contain either a server name or a remote transport * name. For example: "qemud://server.example.com/system" contains * a server name (server.example.com), and "qemud+unix:///system" * contains a transport (unix). Commonly remote URLs contain * both, for example: "qemud+tls://server.example.com/system" * (transport tls, server name server.example.com).
This makes my head hurt a bit, I think I prefer the libvirt:// even if it means encoding URIs in URIs. i.e. I would expect to connect to e.g. qemud:// and xend:// URIs to using the same protocol whether the URI is local or remote. I don't know, it's not something I really care about ... but I'd find it strange if qemud+tls://foo.bar.com/system means "access qemud:///system via a libvirt proxy" ... I'm pretty sure I've seen old man DV flame people to kingdom come and back about abusing URIs so, I guess if DV is happy, it can't be too bad :-)) Cheers, Mark.

On Thu, Feb 01, 2007 at 05:02:08PM +0000, Mark McLoughlin wrote:
On Wed, 2007-01-31 at 15:25 +0000, Richard W.M. Jones wrote:
* Remote URLs contain either a server name or a remote transport * name. For example: "qemud://server.example.com/system" contains * a server name (server.example.com), and "qemud+unix:///system" * contains a transport (unix). Commonly remote URLs contain * both, for example: "qemud+tls://server.example.com/system" * (transport tls, server name server.example.com).
This makes my head hurt a bit, I think I prefer the libvirt:// even if it means encoding URIs in URIs.
i.e. I would expect to connect to e.g. qemud:// and xend:// URIs to using the same protocol whether the URI is local or remote.
That's already not the case with Xen. I think of the leading foo:// bit of the URI to mean the type of libvirt driver being requested. For xen this can be hypercalls, xend or libvirt_proxy. We now merely adding in libvirtd for delaing with case of asking for a remote connection. Not to mention that we reserve the right to change the underlying comms protocol associated with a URI. eg changing from xend SEXPR to xend's XML-RPC/
I don't know, it's not something I really care about ... but I'd find it strange if qemud+tls://foo.bar.com/system means "access qemud:///system via a libvirt proxy" ...
I all really comes down to what we want to define the URIs to mean in the context of libvirt. With my virt-manager hat on, it makes life much nicer to have qemud:///system and qemud://example.com/system when dealing with URIs internally. Dan. -- |=- Red Hat, Engineering, Emerging Technologies, Boston. +1 978 392 2496 -=| |=- Perl modules: http://search.cpan.org/~danberr/ -=| |=- Projects: http://freshmeat.net/~danielpb/ -=| |=- GnuPG: 7D3B9505 F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 -=|

Hey, On Tue, 2007-01-30 at 17:42 +0000, Richard W.M. Jones wrote:
This patch is just for discussion. It's not in a state to be applied, even if it were accepted (which is a long-shot at present anyway).
Cool stuff on getting this far. Okay, some comments to start with: - From past dealings with SunRPC and CORBA and the like, the choice of SunRPC makes me pretty nervous. The amount of code we'd be adding to libvirt to support SunRPC over the different transports makes me even more nervous. I wouldn't fancy having to debug problems there, or add another transport, for example. I'm pretty jaded of RPC systems in general, though. So, I'd suggest either a homegrown binary protocol like we're using now or, if it turns out to be fairly simple, XML RPC. - Wrt. the TLS support - as recommended by the RFC AFAIR, new protocols are supposed to negotiate TLS at connection time rather than having a separate port for TLS (as Dan does with qemud) - There doesn't seem to be any authentication with TLS support. That's definitely necessary, even if it's just cert based. - If libvirtd is going to be a pure proxy, I don't think the UNIX transport is going useful. - Also, if it's just a proxy, couldn't this be launched by xinetd? - I'd advocate merging qemud into libvirtd, but you could have a long discussion either way on that one. - The ssh and ext transports just seem like hacks, especially if the ssh transport won't support authentication (both ways). If people want to use SSH, they can just set up their own tunnel. - Wrt. coding style - libvirt has a *fairly* consistent coding style throughout and I think that helps with the maintainability of any project. Deviations are already starting to creep in (e.g. not using studlyCaps in places) ... but we should try and stem that tide IMHO (even though the current coding style wouldn't be my favourite)
* SunRPC is stateless so we need to hand out a cookie to represent the virConnectPtr handle on the server side. However if the client dies without explicitly calling close, we have no way to know, and so the cookie/handle on the server side lives forever.
The server side context should be the file descriptor - i.e. there should be only a single open() call per fd, and so only a single virConnectPtr per fd ... so if socket is closed by the remote side, we know we can call close() on the virConnectPtr. There shouldn't be a need for cookies, IMHO. (Perhaps this "single open() call per connection" implies that there shouldn't be an open() RPC, but rather that happens as part of the initial negotiation stage)
+ /* driver private data + * (Ought to replace the above ad-hoc Xen data, IMHO anyway. + * Currently only the 'remote' driver uses this. + * - RWMJ). + */ + void *private; +
Yep, that'd be a nice cleanup - e.g. I have this patch to make qemud and xen not share the "handle" member: http://www.gnome.org/~markmc/code/libvirt-networking/libvirt-qemu-dont-share...
+// See: http://people.redhat.com/drepper/userapi-ipv6.html +static int +make_sockets (int *fds, int max_fds, int *nfds_r, const char *service) +{ + struct addrinfo *ai; + struct addrinfo hints; + memset (&hints, 0, sizeof hints); + hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; + hints.ai_socktype = SOCK_STREAM; + + int e = getaddrinfo (NULL, service, &hints, &ai); + if (e != 0) { + fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (e)); + return -1; + } + + struct addrinfo *runp = ai; + while (runp && *nfds_r < max_fds) { + fds[*nfds_r] = socket (runp->ai_family, runp->ai_socktype, + runp->ai_protocol); + if (fds[*nfds_r] == -1) { + perror ("socket"); + return -1; + } + + int opt = 1; + setsockopt (fds[*nfds_r], SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt); + + if (bind (fds[*nfds_r], runp->ai_addr, runp->ai_addrlen) == -1) { + if (errno != EADDRINUSE) { + perror ("bind"); + return -1; + } + close (fds[*nfds_r]); + } + else { + if (listen (fds[*nfds_r], SOMAXCONN) == -1) { + perror ("listen"); + return -1; + } + ++*nfds_r; + } + runp = runp->ai_next; + } + + freeaddrinfo (ai);
I'm really not a big fan of this way of doing IPv6 support because: - getaddrinfo() is a pretty complicated function, so the code above is pretty obtuse without a thorough read of the getaddrinfo() man page - you really want to end up with *either* a single IPv6 socket or and IPv4 socket ... the above code can end up with multiple sockets. Here's what I prefer to do: --- struct sockaddr_in addr_in; struct sockaddr *addr; socklen_t addrlen; #ifdef ENABLE_IPV6 struct sockaddr_in6 addr_in6; memset(&addr_in6, 0, sizeof(addr_in6)); addr_in6.sin6_family = AF_INET6; addr_in6.sin6_port = htons(port); addr_in6.sin6_addr = localOnly ? in6addr_loopback : in6addr_any; addr = (struct sockaddr *)&addr_in6; addrlen = sizeof(addr_in6); sock = socket(AF_INET6, SOCK_STREAM, 0); #endif if (sock < 0) { if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) return -1; memset(&addr_in, 0, sizeof(addr_in)); addr_in.sin_family = AF_INET; addr_in.sin_port = htons(port); addr_in.sin_addr.s_addr = localOnly ? htonl(INADDR_LOOPBACK) : htonl(INADDR_ANY); addr = (struct sockaddr *)&addr_in; addrlen = sizeof(addr_in); } if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one)) < 0) { close(sock); return -1; } if (bind(sock, addr, addrlen) < 0) { close(sock); return -1; } ----
+static int +remote_open (virConnectPtr conn, const char *name, + int flags __attribute__((unused)))
This function is huge, and extremely hard to verify. Recommend splitting it up a lot. Perhaps when you use libxml's URI parsing etc. it won't be so bad, though. Also, libvirt has ATTRIBUTE_UNUSED.
+ switch (proto) { + case proto_unknown: + abort (); /* Internal error in this function. */ + + case proto_tls: + case proto_tcp: { .... + proto == proto_tls + ? clntgnutls_create (r->ai_family, r->ai_addr, r->ai_addrlen, xcred, + LIBVIRTREMOTE, LIBVIRTREMOTE_VERS1, + &private.sock, 0, 0) + : clnttcp2_create (r->ai_family, r->ai_addr, r->ai_addrlen, + LIBVIRTREMOTE, LIBVIRTREMOTE_VERS1, + &private.sock, 0, 0); + if (private.cl) + goto tcp_connected; + } + + freeaddrinfo (res); + error (conn, VIR_ERR_RPC, + clnt_spcreateerror ("could not create SunRPC client")); + goto failed; + + tcp_connected: + freeaddrinfo (res);
A goto in a switch() statement? ... that's a whole litter of cute little puppies you've killed, right there! :-) HTH, Mark.

On Wed, Jan 31, 2007 at 07:07:13PM +0000, Mark McLoughlin wrote:
Hey,
On Tue, 2007-01-30 at 17:42 +0000, Richard W.M. Jones wrote:
This patch is just for discussion. It's not in a state to be applied, even if it were accepted (which is a long-shot at present anyway).
- From past dealings with SunRPC and CORBA and the like, the choice of SunRPC makes me pretty nervous. The amount of code we'd be adding to libvirt to support SunRPC over the different transports makes me even more nervous. I wouldn't fancy having to debug problems there, or add another transport, for example.
I'm pretty jaded of RPC systems in general, though. So, I'd suggest either a homegrown binary protocol like we're using now or, if it turns out to be fairly simple, XML RPC.
My home grown protocol was becoming decidely less simple as I made it more portable. It had a huge flaw in that although it used fixed size types, it still assumes that struct padding was the same across all hosts - not the case on 64-bit uint64_t is aligned on 8-bytes boundaris while 32-bit aligns it on 4-byte boundaries. The XDR encoding that is part of SunRPC guarentees this kind of issue is dealt with reliably
- Wrt. the TLS support - as recommended by the RFC AFAIR, new protocols are supposed to negotiate TLS at connection time rather than having a separate port for TLS (as Dan does with qemud)
We could probably add a RPC call to the SunRPC protocol to enable switching to the TLS mode on the fly. In many ways, however, I regretted adapting my original code to do up-negotiation to TLS. It made it seriously more complicated and I'm still not entirely sure I got it correct. So if anything I'd be infavour of refusing to support plain TCP at all - except that it could be valid to use plain TCP if your tunnelling over SSH.
- There doesn't seem to be any authentication with TLS support. That's definitely necessary, even if it's just cert based.
Yes, I say stick to just client cert auth to start with.
- If libvirtd is going to be a pure proxy, I don't think the UNIX transport is going useful.
It will be useful for the local Xen case - letting us have a full read-write connection to Xen even when unprivileged - so we would no longre have to run virt-manager as root.
- Also, if it's just a proxy, couldn't this be launched by xinetd?
That sounds like a reasonable option to allow - at the same time its nice to have a persistently running daemon to avoid having to re-init all the one-time TLS stuff for every connection because that can take a non-negligable time & deplete /dev/random unneccessarily
- I'd advocate merging qemud into libvirtd, but you could have a long discussion either way on that one.
Yes, I definitely want to do that - makes no sense to have 2 daemons if one will do. I'm waiting till libvirtd is further developed before seriously attacking it.
- Wrt. coding style - libvirt has a *fairly* consistent coding style throughout and I think that helps with the maintainability of any project. Deviations are already starting to creep in (e.g. not using studlyCaps in places) ... but we should try and stem that tide IMHO (even though the current coding style wouldn't be my favourite)
For indentation we can append these following to source files to do the right right thing automagically. I've been adding this to source files as I do patches & fixing up indentation. /* * vim: set tabstop=4: * vim: set shiftwidth=4: * vim: set expandtab: */ /* * Local variables: * indent-tabs-mode: nil * c-indent-level: 4 * c-basic-offset: 4 * tab-width: 4 * End: */
+// See: http://people.redhat.com/drepper/userapi-ipv6.html +static int +make_sockets (int *fds, int max_fds, int *nfds_r, const char *service) +{ + struct addrinfo *ai; + struct addrinfo hints; + memset (&hints, 0, sizeof hints); + hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; + hints.ai_socktype = SOCK_STREAM; + + int e = getaddrinfo (NULL, service, &hints, &ai); + if (e != 0) { + fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (e)); + return -1; + } + struct addrinfo *runp = ai; + while (runp && *nfds_r < max_fds) { + fds[*nfds_r] = socket (runp->ai_family, runp->ai_socktype, + runp->ai_protocol); + if (fds[*nfds_r] == -1) { + perror ("socket"); + return -1; + } + + int opt = 1; + setsockopt (fds[*nfds_r], SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt); + + if (bind (fds[*nfds_r], runp->ai_addr, runp->ai_addrlen) == -1) { + if (errno != EADDRINUSE) { + perror ("bind"); + return -1; + } + close (fds[*nfds_r]); + } + else { + if (listen (fds[*nfds_r], SOMAXCONN) == -1) { + perror ("listen"); + return -1; + } + ++*nfds_r; + } + runp = runp->ai_next; + } + + freeaddrinfo (ai);
I'm really not a big fan of this way of doing IPv6 support because:
- getaddrinfo() is a pretty complicated function, so the code above is pretty obtuse without a thorough read of the getaddrinfo() man page
It may be complicated - but this is only because doing the correct thing for IPv6 is complicated - the traditional getaddr/host related functions all have a number of flaws which make them unsuitable for modern day usage.
- you really want to end up with *either* a single IPv6 socket or and IPv4 socket ... the above code can end up with multiple sockets.
It can end up with multiple sockets because if a machine has multiple NICs, it is perfectly possible for a single address to be associated with multiple NICs. Thus to bind to all interfaces associated with an address, it may be neccessary to have multiple sockets.
Here's what I prefer to do:
[snip] IMHO, this code isn't simpler /shorter by any significant amount, and it is far less capable than the code already written above to deal with getaddrinfo. The semantics of the getaddrinfo code are also well documented by Uli if any reference is needed http://people.redhat.com/drepper/userapi-ipv6.html Regards, Dan. -- |=- Red Hat, Engineering, Emerging Technologies, Boston. +1 978 392 2496 -=| |=- Perl modules: http://search.cpan.org/~danberr/ -=| |=- Projects: http://freshmeat.net/~danielpb/ -=| |=- GnuPG: 7D3B9505 F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 -=|

On Wed, 2007-01-31 at 20:06 +0000, Daniel P. Berrange wrote:
On Wed, Jan 31, 2007 at 07:07:13PM +0000, Mark McLoughlin wrote:
- If libvirtd is going to be a pure proxy, I don't think the UNIX transport is going useful.
It will be useful for the local Xen case - letting us have a full read-write connection to Xen even when unprivileged - so we would no longre have to run virt-manager as root.
Isn't that what the existing proxy does? Are we talking about dumping that? (Which reminds me, we can and should auth by uid with the UNIX transport)
- Also, if it's just a proxy, couldn't this be launched by xinetd?
That sounds like a reasonable option to allow - at the same time its nice to have a persistently running daemon to avoid having to re-init all the one-time TLS stuff for every connection because that can take a non-negligable time & deplete /dev/random unneccessarily
Yep, agreed - you can mitigate the problem by continuing to handle new requests once started and hang around for a time when the last client disconnects ... but bleh. (Total tangent - since xinetd is supposed to be a "resource conserving super-server", I wonder if you could do something where xinetd keeps a cache of TLS params and passes them to servers when they start ... that might be cool)
- getaddrinfo() is a pretty complicated function, so the code above is pretty obtuse without a thorough read of the getaddrinfo() man page
It may be complicated - but this is only because doing the correct thing for IPv6 is complicated - the traditional getaddr/host related functions all have a number of flaws which make them unsuitable for modern day usage.
Seriously, if you see that the code I posted *isn't* correct, then I'd like to hear it ... but I don't think you are saying that ... (I'm not being a smartass ... this code is recent and could well be buggy)
- you really want to end up with *either* a single IPv6 socket or and IPv4 socket ... the above code can end up with multiple sockets.
It can end up with multiple sockets because if a machine has multiple NICs, it is perfectly possible for a single address to be associated with multiple NICs. Thus to bind to all interfaces associated with an address, it may be neccessary to have multiple sockets.
Absolutely. But we're not binding to a specific address. Hence, we do only want a single socket. Or to put it another way, if you just want to bind to inaddr_any, then getaddrinfo() just complicates things. For example, on a host with IPv4 and IPv6 address configured, does make_sockets() attempt to bind an ipv6 socket to the port, or an ipv4 one, or both? I suspected it would first try ipv6, then fail at bind() with the ipv4 socket, but I had to write some test code to be sure. There's one caveat to all this - if the code is ported to a system which *requires* both an IPv4 and IPv6 socket because it doesn't allow IPv4 connections to be accepted on an IPv6 socket, then you would need two sockets. Reading: http://httpd.apache.org/docs/trunk/bind.html it suggests that's true on the various BSDs.
Here's what I prefer to do:
[snip]
IMHO, this code isn't simpler /shorter by any significant amount, and it is far less capable than the code already written above to deal with getaddrinfo.
I'd agree that if you intended to allow libvirtd to bind to specific addresses, then you'd need getaddrinfo(). But I do firmly believe that using getaddrinfo() for the inaddr_any case just makes the code obtuse and re-affirms this notion that sockets coding is somehow a black art. If you had to explain the two different versions to someone, I think the getaddrinfo() version would be harder, without being any more correct.
The semantics of the getaddrinfo code are also well documented by Uli if any reference is needed http://people.redhat.com/drepper/userapi-ipv6.html
When I was implementing IPv6 support for Vino, Uli's writeup was my starting point (along with dear old R. Stevens, RIP) ... but I preferred to seriously consider alternatives rather than just copying and pasting it. Anyhow, I never intended to get into such a rant about getaddrinfo() ... it was just a friendly suggestion :) Cheers, Mark.

On Thu, Feb 01, 2007 at 11:45:27AM +0000, Mark McLoughlin wrote:
On Wed, 2007-01-31 at 20:06 +0000, Daniel P. Berrange wrote:
On Wed, Jan 31, 2007 at 07:07:13PM +0000, Mark McLoughlin wrote:
- If libvirtd is going to be a pure proxy, I don't think the UNIX transport is going useful.
It will be useful for the local Xen case - letting us have a full read-write connection to Xen even when unprivileged - so we would no longre have to run virt-manager as root.
Isn't that what the existing proxy does? Are we talking about dumping that?
The proxy only allows read-only access. If we have a daemon giving full read-write access I see little need for the proxy anymore
There's one caveat to all this - if the code is ported to a system which *requires* both an IPv4 and IPv6 socket because it doesn't allow IPv4 connections to be accepted on an IPv6 socket, then you would need two sockets. Reading:
http://httpd.apache.org/docs/trunk/bind.html
it suggests that's true on the various BSDs.
If a system required binding separately to IPv4 & 6 sockets, then the getaddrinfo() function on that platform would return as many entries as was neccessary to do it correctly. This is one of the benefits of using getaddrinfo() instead of the legacy inet function.
But I do firmly believe that using getaddrinfo() for the inaddr_any case just makes the code obtuse and re-affirms this notion that sockets coding is somehow a black art.
I don't think we want to restrict ourselves to inaddr_any - there are certainly times I'd want to only bind on 127.0.0.1 / ::1, or a specific network interface for a multi-homed server
Anyhow, I never intended to get into such a rant about getaddrinfo() ... it was just a friendly suggestion :)
Its useful to have this rants sometimes, in case we missed something dumb :-) Regards, Dan. -- |=- Red Hat, Engineering, Emerging Technologies, Boston. +1 978 392 2496 -=| |=- Perl modules: http://search.cpan.org/~danberr/ -=| |=- Projects: http://freshmeat.net/~danielpb/ -=| |=- GnuPG: 7D3B9505 F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 -=|

Daniel P. Berrange wrote:
/* * vim: set tabstop=4: * vim: set shiftwidth=4: * vim: set expandtab: */ /* * Local variables: * indent-tabs-mode: nil * c-indent-level: 4 * c-basic-offset: 4 * tab-width: 4 * End: */
Done. -- Emerging Technologies, Red Hat http://et.redhat.com/~rjones/ 64 Baker Street, London, W1U 7DF Mobile: +44 7866 314 421 "[Negative numbers] darken the very whole doctrines of the equations and make dark of the things which are in their nature excessively obvious and simple" (Francis Maseres FRS, mathematician, 1759)

Mark McLoughlin wrote:
Hey,
On Tue, 2007-01-30 at 17:42 +0000, Richard W.M. Jones wrote:
This patch is just for discussion. It's not in a state to be applied, even if it were accepted (which is a long-shot at present anyway).
Cool stuff on getting this far.
Okay, some comments to start with:
- From past dealings with SunRPC and CORBA and the like, the choice of SunRPC makes me pretty nervous. The amount of code we'd be adding to libvirt to support SunRPC over the different transports makes me even more nervous. I wouldn't fancy having to debug problems there, or add another transport, for example.
I'm pretty jaded of RPC systems in general, though. So, I'd suggest either a homegrown binary protocol like we're using now or, if it turns out to be fairly simple, XML RPC.
I think there are a lot of reasons to not like SunRPC but the fact remains that it's a lot simpler to implement and has a much smaller overhead than XML-RPC and I definitely wouldn't want to implement a home-grown protocol. The reasons not to like SunRPC are that it's completely thread unsafe and that it is (or tries to be) stateless. It's very flexible however: see: http://et.redhat.com/~rjones/secure_rpc/
- Wrt. the TLS support - as recommended by the RFC AFAIR, new protocols are supposed to negotiate TLS at connection time rather than having a separate port for TLS (as Dan does with qemud)
I think Dan addressed this. I don't even want to support TCP at all. It's only there for testing/debugging. The default will be only TLS for remote connections.
- There doesn't seem to be any authentication with TLS support. That's definitely necessary, even if it's just cert based.
It should be doing authentication. At the moment the code doesn't do it, but that's a bug (it prints out a big warning message instead). I'm not sure what you mean by "just cert based" though so maybe I didn't understand this point ...
- The ssh and ext transports just seem like hacks, especially if the ssh transport won't support authentication (both ways). If people want to use SSH, they can just set up their own tunnel.
I really think that ssh support is going to be important for casual sysadmins. ssh is much much more common than certificate infrastructures. Tunnels are possible, but not well understood by sysadmins either.
+static int +remote_open (virConnectPtr conn, const char *name, + int flags __attribute__((unused)))
This function is huge, and extremely hard to verify. Recommend splitting it up a lot. Perhaps when you use libxml's URI parsing etc. it won't be so bad, though.
This function is essentially completely rewritten now, so ... And it uses the libxml URI parser (not quite working at the moment, but that's what I'm debugging ...) Rich. -- Emerging Technologies, Red Hat http://et.redhat.com/~rjones/ 64 Baker Street, London, W1U 7DF Mobile: +44 7866 314 421 "[Negative numbers] darken the very whole doctrines of the equations and make dark of the things which are in their nature excessively obvious and simple" (Francis Maseres FRS, mathematician, 1759)

Attached is the latest rev of this patch to support remote drivers. It is still not in a state where it can or should be applied. In particular it still "does nothing" except supporting the version and type calls. Changes: * The URL syntax has changed. Please see the first attachment for complete details of the new syntax. * Attempts to verify client IP address using TLS certificate, but this code is probably not correct. * libvirtd has a comprehensive configuration file. * libvirtd forks into the background, and has many other improvements. * All previous comments that I received should have been taken into account and where possible fixed. Please let me know if there's anything I've missed. Some things to discuss ---------------------- Should libvirt clients automatically reconnect when the server (libvirtd) restarts? SunRPC supports this, reasonably seamlessly. The problem is that we will be issuing a "cookie" to the client to represent various structures held on the server (mainly virConnectPtr, but also virDomainPtr and a few others). The mapping of cookie -> structure is lost when libvirtd restarts unless we keep it somewhere persistent. Now if we keep track of cookie -> URLs in a persistent place, then when a client represents a cookie that we don't know about because it was created in a previous libvirtd session, we can look it up and reopen the connection to the backend (using the URL). In the current implementation of libvirt this looks safe. Should libvirtd run as a preforked server? SunRPC is basically single threaded. The server handles one RPC at a time and processes it to completion before handling the next. The simple way around this is to use a preforked server. This has implications with cookies -- either they need to be stored in shared memory (similar to Apache's scoreboard stuff) or else in persistent storage. - - - The second attachment is the patch. You can also download the patch from http://www.annexia.org/tmp/libvirt-tls-20070213.patch
participants (5)
-
Daniel P. Berrange
-
Daniel Veillard
-
Mark McLoughlin
-
Richard Jones
-
Richard W.M. Jones