/* $NetBSD: transp_sockets.c,v 1.2 2015/01/17 17:46:31 christos Exp $ */ /* * Copyright (c) 1997-2014 Erez Zadok * Copyright (c) 1990 Jan-Simon Pendry * Copyright (c) 1990 Imperial College of Science, Technology & Medicine * Copyright (c) 1990 The Regents of the University of California. * All rights reserved. * * This code is derived from software contributed to Berkeley by * Jan-Simon Pendry at Imperial College, London. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * * File: am-utils/conf/transp/transp_sockets.c * * Socket specific utilities. * -Erez Zadok */ #ifdef HAVE_CONFIG_H # include #endif /* HAVE_CONFIG_H */ #include #include /* * find the IP address that can be used to connect to the local host */ void amu_get_myaddress(struct in_addr *iap, const char *preferred_localhost) { struct hostent *hp; char dq[20]; #ifdef DEBUG_off #error this code is old and probably not useful any longer. #error Erez, Jan 21, 2004. struct sockaddr_in sin; /* * Most modern systems should use 127.0.0.1 as the localhost address over * which you can do NFS mounts. In the past we found that some NFS * clients may not allow mounts from localhost. So we used * get_myaddress() and that seemed to work. Alas, on some other systems, * get_myaddress() may return one of the interface addresses at random, * and thus use a less efficient IP address than 127.0.0.1. The solution * is to hard-code 127.0.0.1, but still check if get_myaddress() returns a * different value and warn about it. */ memset((char *) &sin, 0, sizeof(sin)); get_myaddress(&sin); if (sin.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) dlog("amu_get_myaddress: myaddress conflict (0x%x vs. 0x%lx)", sin.sin_addr.s_addr, (u_long) htonl(INADDR_LOOPBACK)); #endif /* DEBUG_off */ if (preferred_localhost == NULL) goto out; /* if specified preferred locahost, then try to use it */ hp = gethostbyname(preferred_localhost); if (hp == NULL) { /* XXX: if hstrerror()/h_errno aren't portable, then need to port the next statement */ plog(XLOG_ERROR, "Unable to resolve localhost_address \"%s\" (%s): using default", preferred_localhost, hstrerror(h_errno)); goto out; } if (hp->h_addr_list == NULL) { plog(XLOG_ERROR, "localhost_address \"%s\" has no IP addresses: using default", preferred_localhost); goto out; } if (hp->h_addr_list[1] != NULL) { plog(XLOG_ERROR, "localhost_address \"%s\" has more than one IP addresses: using first", preferred_localhost); goto out; } memmove((voidp) &iap->s_addr, (voidp) hp->h_addr_list[0], sizeof(iap->s_addr)); plog(XLOG_INFO, "localhost_address \"%s\" requested, using %s", preferred_localhost, inet_dquad(dq, sizeof(dq), iap->s_addr)); return; out: iap->s_addr = htonl(INADDR_LOOPBACK); } /* * How to bind to reserved ports. * Note: if *pp is non-null and is greater than 0, then *pp will not be modified. */ int bind_resv_port(int so, u_short *pp) { struct sockaddr_in sin; int rc; u_short port; memset((voidp) &sin, 0, sizeof(sin)); sin.sin_family = AF_INET; if (pp && *pp > 0) { sin.sin_port = htons(*pp); rc = bind(so, (struct sockaddr *) &sin, sizeof(sin)); } else { port = IPPORT_RESERVED; do { --port; sin.sin_port = htons(port); rc = bind(so, (struct sockaddr *) &sin, sizeof(sin)); } while (rc < 0 && (int) port > IPPORT_RESERVED / 2); if (pp && rc == 0) *pp = port; } return rc; } /* * close a descriptor, Sockets style */ int amu_close(int fd) { return close(fd); } /* * Create an rpc client attached to the mount daemon. */ CLIENT * get_mount_client(char *unused_host, struct sockaddr_in *sin, struct timeval *tv, int *sock, u_long mnt_version) { CLIENT *client; /* * First try a TCP socket */ if ((*sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) > 0) { /* * Bind to a privileged port */ if (bind_resv_port(*sock, (u_short *) NULL) < 0) plog(XLOG_ERROR, "can't bind privileged port (socket)"); /* * Find mountd port to connect to. * Connect to mountd. * Create a tcp client. */ if ((sin->sin_port = htons(pmap_getport(sin, MOUNTPROG, mnt_version, IPPROTO_TCP))) != 0) { if (connect(*sock, (struct sockaddr *) sin, sizeof(*sin)) >= 0 && ((client = clnttcp_create(sin, MOUNTPROG, mnt_version, sock, 0, 0)) != NULL)) return client; } /* * Failed so close socket */ (void) close(*sock); } /* tcp socket opened */ /* TCP failed so try UDP */ if ((*sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { plog(XLOG_ERROR, "Can't create socket to connect to mountd: %m"); *sock = RPC_ANYSOCK; return NULL; } /* * Bind to a privileged port */ if (bind_resv_port(*sock, (u_short *) NULL) < 0) plog(XLOG_ERROR, "can't bind privileged port"); /* * Zero out the port - make sure we recompute */ sin->sin_port = 0; /* * Make a UDP client */ if ((client = clntudp_create(sin, MOUNTPROG, mnt_version, *tv, sock)) == NULL) { (void) close(*sock); *sock = RPC_ANYSOCK; return NULL; } dlog("get_mount_client: Using udp, port %d", sin->sin_port); return client; } /* * find the address of the caller of an RPC procedure. */ struct sockaddr_in * amu_svc_getcaller(SVCXPRT *xprt) { /* glibc 2.2 returns a sockaddr_storage ??? */ return (struct sockaddr_in *) svc_getcaller(xprt); } /* * Register an RPC server: * return 1 on success, 0 otherwise. */ int amu_svc_register(SVCXPRT *xprt, u_long prognum, u_long versnum, void (*dispatch)(struct svc_req *rqstp, SVCXPRT *transp), u_long protocol, struct netconfig *dummy) { /* on Sockets: svc_register returns 1 on success, 0 otherwise */ return svc_register(xprt, prognum, versnum, dispatch, protocol); } /* * Create the nfs service for amd */ int create_nfs_service(int *soNFSp, u_short *nfs_portp, SVCXPRT **nfs_xprtp, void (*dispatch_fxn)(struct svc_req *rqstp, SVCXPRT *transp), u_long nfs_version) { *soNFSp = socket(AF_INET, SOCK_DGRAM, 0); if (*soNFSp < 0 || bind_resv_port(*soNFSp, nfs_portp) < 0) { plog(XLOG_FATAL, "Can't create privileged nfs port (socket)"); if (*soNFSp >= 0) close(*soNFSp); return 1; } if ((*nfs_xprtp = svcudp_create(*soNFSp)) == NULL) { plog(XLOG_FATAL, "cannot create rpc/udp service"); close(*soNFSp); return 2; } if ((*nfs_portp = (*nfs_xprtp)->xp_port) >= IPPORT_RESERVED) { plog(XLOG_FATAL, "Can't create privileged nfs port"); svc_destroy(*nfs_xprtp); close(*soNFSp); return 1; } if (!svc_register(*nfs_xprtp, NFS_PROGRAM, nfs_version, dispatch_fxn, 0)) { plog(XLOG_FATAL, "unable to register (%lu, %lu, 0)", (u_long) NFS_PROGRAM, nfs_version); svc_destroy(*nfs_xprtp); close(*soNFSp); return 3; } return 0; /* all is well */ } /* * Create the amq service for amd (both TCP and UDP) */ int create_amq_service(int *udp_soAMQp, SVCXPRT **udp_amqpp, struct netconfig **dummy1, int *tcp_soAMQp, SVCXPRT **tcp_amqpp, struct netconfig **dummy2, u_short preferred_amq_port) { /* first create TCP service */ if (tcp_soAMQp) { *tcp_soAMQp = socket(AF_INET, SOCK_STREAM, 0); if (*tcp_soAMQp < 0) { plog(XLOG_FATAL, "cannot create tcp socket for amq service: %m"); return 1; } /* next, bind to a specific (TCP) port if asked for */ if (preferred_amq_port > 0) { /* * Note: if &preferred_amq_port is non-null and is greater than 0, * then the pointer will not be modified. We don't want it to be * modified because it was passed down to create_amq_service as a * non-pointer (a variable on the stack, not to be modified!) */ if (bind_resv_port(*tcp_soAMQp, &preferred_amq_port) < 0) { plog(XLOG_FATAL, "can't bind amq service to requested TCP port %d: %m)", preferred_amq_port); return 1; } } /* now create RPC service handle for amq */ if (tcp_amqpp && (*tcp_amqpp = svctcp_create(*tcp_soAMQp, AMQ_SIZE, AMQ_SIZE)) == NULL) { plog(XLOG_FATAL, "cannot create tcp service for amq: soAMQp=%d", *tcp_soAMQp); return 1; } #ifdef SVCSET_CONNMAXREC /* * This is *BSD at its best. * They just had to do things differently than everyone else * so they fixed a library DoS issue by forcing client-side changes... */ # ifndef RPC_MAXDATASIZE # define RPC_MAXDATASIZE 9000 # endif /* not RPC_MAXDATASIZE */ if (tcp_amqpp) { int maxrec = RPC_MAXDATASIZE; SVC_CONTROL(*tcp_amqpp, SVCSET_CONNMAXREC, &maxrec); } #endif /* not SVCSET_CONNMAXREC */ } /* next create UDP service */ if (udp_soAMQp) { *udp_soAMQp = socket(AF_INET, SOCK_DGRAM, 0); if (*udp_soAMQp < 0) { plog(XLOG_FATAL, "cannot create udp socket for amq service: %m"); return 1; } /* next, bind to a specific (UDP) port if asked for */ if (preferred_amq_port > 0) { /* * Note: see comment about using &preferred_amq_port above in this * function. */ if (bind_resv_port(*udp_soAMQp, &preferred_amq_port) < 0) { plog(XLOG_FATAL, "can't bind amq service to requested UDP port %d: %m)", preferred_amq_port); return 1; } } /* now create RPC service handle for amq */ if (udp_amqpp && (*udp_amqpp = svcudp_bufcreate(*udp_soAMQp, AMQ_SIZE, AMQ_SIZE)) == NULL) { plog(XLOG_FATAL, "cannot create udp service for amq: soAMQp=%d", *udp_soAMQp); return 1; } } return 0; /* all is well */ } /* * Check if the portmapper is running and reachable: 0==down, 1==up */ int check_pmap_up(char *host, struct sockaddr_in* sin) { CLIENT *client; enum clnt_stat clnt_stat = RPC_TIMEDOUT; /* assume failure */ int socket = RPC_ANYSOCK; struct timeval timeout; timeout.tv_sec = 2; timeout.tv_usec = 0; sin->sin_port = htons(PMAPPORT); client = clntudp_create(sin, PMAPPROG, PMAPVERS, timeout, &socket); if (client == (CLIENT *) NULL) { plog(XLOG_ERROR, "%s: cannot create connection to contact portmapper on host \"%s\"%s", __func__, host, clnt_spcreateerror("")); return 0; } timeout.tv_sec = 6; /* Ping the portmapper on a remote system by calling the nullproc */ clnt_stat = clnt_call(client, PMAPPROC_NULL, (XDRPROC_T_TYPE) xdr_void, NULL, (XDRPROC_T_TYPE) xdr_void, NULL, timeout); clnt_destroy(client); close(socket); sin->sin_port = 0; if (clnt_stat == RPC_TIMEDOUT) { plog(XLOG_ERROR, "%s: failed to contact portmapper on host \"%s\": %s", __func__, host, clnt_sperrno(clnt_stat)); return 0; } return 1; } /* * Find the best NFS version for a host and protocol. */ u_long get_nfs_version(char *host, struct sockaddr_in *sin, u_long nfs_version, const char *proto, u_long def) { CLIENT *clnt; enum clnt_stat clnt_stat; struct timeval tv; int sock; char *errstr; /* * If not set or set wrong, then try from NFS_VERS_MAX on down. If * set, then try from nfs_version on down. */ if (!nfs_valid_version(nfs_version)) { if (nfs_valid_version(def)) nfs_version = def; else nfs_version = NFS_VERS_MAX; } tv.tv_sec = 2; /* retry every 2 seconds, but also timeout */ tv.tv_usec = 0; for (; nfs_version >= NFS_VERS_MIN; nfs_version--) { sock = RPC_ANYSOCK; errstr = NULL; if (STREQ(proto, "tcp")) clnt = clnttcp_create(sin, NFS_PROGRAM, nfs_version, &sock, 0, 0); else if (STREQ(proto, "udp")) clnt = clntudp_create(sin, NFS_PROGRAM, nfs_version, tv, &sock); else clnt = NULL; if (clnt != NULL) { /* Try three times (6/2=3) to verify the CLIENT handle. */ tv.tv_sec = 6; clnt_stat = clnt_call(clnt, NFSPROC_NULL, (XDRPROC_T_TYPE) xdr_void, 0, (XDRPROC_T_TYPE) xdr_void, 0, tv); if (clnt_stat != RPC_SUCCESS) errstr = clnt_sperrno(clnt_stat); close(sock); clnt_destroy(clnt); if (clnt_stat == RPC_SUCCESS) break; } else { #ifdef HAVE_CLNT_SPCREATEERROR errstr = clnt_spcreateerror(""); #else /* not HAVE_CLNT_SPCREATEERROR */ errstr = ""; #endif /* not HAVE_CLNT_SPCREATEERROR */ } if (errstr) { plog(XLOG_INFO, "%s: NFS(%lu,%s) failed for %s: %s", __func__, nfs_version, proto, host, errstr); } } if (nfs_version < NFS_VERS_MIN) nfs_version = 0; plog(XLOG_INFO, "%s: returning NFS(%lu,%s) on host %s", __func__, nfs_version, proto, host); return nfs_version; } #if defined(HAVE_FS_AUTOFS) && defined(AUTOFS_PROG) /* * Register the autofs service for amd */ int register_autofs_service(char *autofs_conftype, void (*autofs_dispatch)(struct svc_req *rqstp, SVCXPRT *transp)) { int autofs_socket; SVCXPRT *autofs_xprt = NULL; autofs_socket = socket(AF_INET, SOCK_DGRAM, 0); if (autofs_socket < 0 || bind_resv_port(autofs_socket, NULL) < 0) { plog(XLOG_FATAL, "Can't create privileged autofs port (socket)"); return 1; } if ((autofs_xprt = svcudp_create(autofs_socket)) == NULL) { plog(XLOG_FATAL, "Can't create autofs rpc/udp service"); return 2; } if (autofs_xprt->xp_port >= IPPORT_RESERVED) { plog(XLOG_FATAL, "Can't create privileged autofs port"); return 1; } if (!svc_register(autofs_xprt, AUTOFS_PROG, AUTOFS_VERS, autofs_dispatch, 0)) { plog(XLOG_FATAL, "unable to register (%ld, %ld, 0)", (u_long) AUTOFS_PROG, (u_long) AUTOFS_VERS); return 3; } return 0; /* all is well */ } int unregister_autofs_service(char *autofs_conftype) { svc_unregister(AUTOFS_PROG, AUTOFS_VERS); return 0; } #endif /* HAVE_FS_AUTOFS && AUTOFS_PROG */