Files
cdesktop/cde/lib/tt/slib/mp_rpc_server.C
Jon Trulson a198d898e8 TT RPC server: Don't search 538 million transients trying to allocate one.
Currently, mp_rpc_server.C tries 538 million ports to acquire an
available transient rpcbind port number.  This is bad when rpcbind is
running in secure mode (and you are not using tirpc) - Xsession will
'hang' at the dthello (blue) screen filling up your error logs with
RPC errors.

Now, just try +- 50 (for a total of 100 ports) before bailing.  The
dthello 'blue screen of death' is the most common problem in starting
CDE when rpcbind isn't set up properly.  This should at least not
cause the appearance of a 'hang'.
2013-05-17 16:16:17 -06:00

489 lines
13 KiB
C

/*
* CDE - Common Desktop Environment
*
* Copyright (c) 1993-2012, The Open Group. All rights reserved.
*
* These libraries and programs are free software; you can
* redistribute them and/or modify them under the terms of the GNU
* Lesser General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* These libraries and programs are distributed in the hope that
* they 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 these librararies and programs; if not, write
* to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
* Floor, Boston, MA 02110-1301 USA
*/
//%% (c) Copyright 1993, 1994 Hewlett-Packard Company
//%% (c) Copyright 1993, 1994 International Business Machines Corp.
//%% (c) Copyright 1993, 1994 Sun Microsystems, Inc.
//%% (c) Copyright 1993, 1994 Novell, Inc.
//%% $TOG: mp_rpc_server.C /main/11 1999/08/30 11:03:00 mgreess $
/*
*
* @(#)mp_rpc_server.C 1.46 94/11/17
*
* Copyright (c) 1990 by Sun Microsystems, Inc.
*/
#include "tt_options.h"
#include <stdio.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <errno.h>
#include "mp_rpc_server.h"
#include "util/tt_port.h"
#include "util/tt_gettext.h"
#include "util/tt_global_env.h"
#include "mp/mp_mp.h"
#include "mp/mp_rpc.h"
#if defined(OPT_TLI)
#include <netdir.h>
static int gettransient(int, netconfig *, netbuf *);
#if defined(OPT_BUG_SUNOS_5) || defined(OPT_BUG_UXP)
extern "C" { char * nc_sperror(); }
#endif
# if defined(OPT_BUG_USL) || defined(OPT_BUG_UXP)
extern int t_errno;
extern char *t_strerror(int t_errno);
# endif
#else
#include <rpc/pmap_clnt.h>
#include <netinet/tcp.h>
static int gettransient(int,int,int *);
#endif /* OPT_TLI */
#if defined(OPT_BUG_USL)
typedef void (*SERVICE_FN_TYPE)(const struct svc_req *, const SVCXPRT*);
#elif defined(OPT_BUG_AIX)
typedef void (*SERVICE_FN_TYPE)();
#else
typedef void (*SERVICE_FN_TYPE)(struct svc_req *, SVCXPRT*);
#endif
/*
* Constructs an rpc server for the given program, version and socket.
*/
_Tt_rpc_server::
_Tt_rpc_server(int program, int version, int Rsocket, _Tt_auth &auth)
{
_version = version;
_socket = Rsocket;
_program = program;
_auth = auth;
}
/*
* Destroys an rpc server. Unsets the program,version mapping in the
* portmapper.
*/
_Tt_rpc_server::
~_Tt_rpc_server()
{
#ifndef OPT_TLI
/*
* pmap_unset(_program, _version);
*/
#else
for (int version = _version; version >= 1; version--) {
rpcb_unset(_program, version, (netconfig *)0);
}
#endif // OPT_TLI
}
/*
* Initializes an rpc server with a service function. If _program is set
* to -1 then an unused program number is obtained using the gettransient
* function. If _socket is anything other than RPC_ANYSOCK then it will
* be used to create the rpc transport using svfd_create and the rpc
* numbers will not be registered with the portmapper.
*/
int _Tt_rpc_server::
init(void (*service_fn)(struct svc_req *, SVCXPRT *))
{
char *bufopt = (char *)0;
#ifndef OPT_TLI
bufopt = getenv("TT_BUFSIZE");
unsigned int buffersize = (bufopt != (char *)0) ? atoi(bufopt) : 32000;
if (_socket != RPC_ANYSOCK) {
_transp = svcfd_create(_socket, buffersize, buffersize);
if (_transp == (SVCXPRT *)0) {
return(0);
}
if (!svc_register(_transp, _program, _version,
(SERVICE_FN_TYPE)service_fn, 0))
{
_tt_syslog(0, LOG_ERR, "svc_register(): %m");
return(0);
}
return(1);
}
if (_program == -1) {
if (! (_program =
gettransient(IPPROTO_TCP, _version, &_socket))) {
return(0);
}
} else {
_socket = socket(AF_INET, SOCK_STREAM, 0);
if (_socket < 0) {
_tt_syslog(0, LOG_ERR,
"_Tt_rpc_server::init(): socket(): %m");
return 0;
}
}
int optval = 1;
if (setsockopt(_socket, IPPROTO_TCP, TCP_NODELAY,
(char *)&optval, sizeof(int)) == -1) {
_tt_syslog(0, LOG_ERR, "setsockopt(TCP_NODELAY): %m");
}
if (setsockopt(_socket, SOL_SOCKET, SO_RCVBUF, (char *)&buffersize,
sizeof(int)) == -1) {
_tt_syslog(0, LOG_ERR, "setsockopt(SO_RCVBUF): %m");
}
if (setsockopt(_socket, SOL_SOCKET, SO_SNDBUF, (char *)&buffersize,
sizeof(int)) == -1) {
_tt_syslog(0, LOG_ERR, "setsockopt(SO_SNDBUF): %m");
}
_transp = svctcp_create(_socket, buffersize, buffersize);
if (_transp == (SVCXPRT *)0) {
return(0);
}
if ( !svc_register(_transp, _program, _version,
(SERVICE_FN_TYPE)service_fn, IPPROTO_TCP)
|| !svc_register(_transp, _program, 1,
(SERVICE_FN_TYPE)service_fn, 0))
{
_tt_syslog(0, LOG_ERR, "svc_register(): %m");
return(0);
}
#else
netconfig *nconf;
void *handlep;
t_info tinfo;
int fd;
if ((handlep = setnetconfig()) == (void *)0) {
_tt_syslog(0, LOG_ERR, "setnetconfig(): %s", nc_sperror());
return(0);
}
// Find a connection-oriented transport.
while (nconf = getnetconfig(handlep)) {
if ((nconf->nc_semantics == NC_TPI_COTS) ||
(nconf->nc_semantics == NC_TPI_COTS_ORD)) {
// Make sure this netconfig maps to an address
if (0 == strcmp(nconf->nc_protofmly, NC_INET))
break;
}
}
// If we failed to find a suitable transport, exit.
if (nconf == (netconfig *)0) {
endnetconfig(handlep);
_tt_syslog(0, LOG_ERR,
catgets(_ttcatd, 2, 3,
"No connection-oriented transport"));
return(0);
}
fd = t_open(nconf->nc_device, O_RDWR, &tinfo);
if (fd == -1) {
_tt_syslog(0, LOG_ERR,
"_Tt_rpc_server::init(): t_open(): %s",
t_strerror( t_errno ) );
endnetconfig(handlep);
return 0;
}
// No longer need to try to set NODELAY here as TIRPC does it for us
// tinfo.tsdu can be negative, but that's not a valid buf size.
u_int buf_size = 0;
if (tinfo.tsdu > 0) {
buf_size = (u_int)tinfo.tsdu;
}
_transp = svc_tli_create(fd, nconf, (struct t_bind *)0,
buf_size, buf_size);
if (_transp == (SVCXPRT *)0) {
_tt_syslog(0, LOG_ERR, "svc_tli_create(): 0");
(void)t_close(fd);
return(0);
}
if (_program == -1 &&
(! (_program = gettransient(_version, nconf,
&_transp->xp_ltaddr)))) {
_tt_syslog(0, LOG_ERR, "gettransient(): 0");
return(0);
}
for (int version = _version; version >= 1; version--) {
if (!svc_reg(_transp, _program, version,
(SERVICE_FN_TYPE)service_fn, nconf)) {
_tt_syslog(0, LOG_ERR, "svc_reg(,,%d): 0", version);
return(0);
}
}
// it is important to not call endnetconfig until one is done
// using nconf as endnetconfig frees the nconf storage.
(void)endnetconfig(handlep);
#endif /* OPT_TLI */
// now figure out what fd the rpc package is using
int maxfds = _tt_global->maxfds();
for (int i=0; i < maxfds; i++) {
if (FD_ISSET(i, &svc_fdset)) {
_rpc_fd = i;
}
}
return(1);
}
/*
* Runs an rpc server. If a non-negative timeout is given then this
* function will return if the timeout expired before any rpc requests
* came in. The values returned are: -1 for error, 0 for timeout, 1
* for when timeout is 0 and an rpc request was serviced.
*/
_Tt_rpcsrv_err _Tt_rpc_server::
run_until(int *stop, int timeout, _Tt_int_rec_list_ptr &efds)
{
fd_set readfds;
timeval tmout;
int fd;
int done = 0;
int select_stat;
_Tt_rpcsrv_err status = _TT_RPCSRV_OK;
tmout.tv_sec = timeout;
tmout.tv_usec = 0;
_Tt_int_rec_list_cursor efds_c(efds);
do {
// Add our fd's to a copy of the rpc fdset.
readfds = svc_fdset;
efds_c.reset();
while (efds_c.next()) {
fd = efds_c->val;
// NOTE that it is crucially important that the bit
// for fd 0 not be set. fd 0 (stdin) is always set
// to /dev/null, which is always active.
// The reason fd 0 is in efds at all is that
// _Tt_self_procid uses it as a dummy entry
// for ttsession itself, which doesn\'t need a
// signalling channel.
// I haven\'t verified this, but I bet it\'s possible
// for negative entries to be in the efds list too,
// representing signalling channels that were found
// active on a previous pass but are not yet cleared
// out.
if (fd > 0) {
FD_SET(fd, &readfds);
}
}
// Drop the global mutex around any polling or RPC calls.
_tt_global->drop_mutex();
select_stat =
select(FD_SETSIZE,&readfds, 0, 0,
(timeout >= 0) ? &tmout : (timeval *)0);
_tt_global->grab_mutex();
switch (select_stat) {
case -1:
return(_TT_RPCSRV_ERR);
case 0:
return(_TT_RPCSRV_TMOUT);
default:
// check for exception fds
efds_c.reset();
while (efds_c.next()) {
fd = efds_c->val;
if (fd < 0) continue; // -1 => not valid fd
if (FD_ISSET(fd, &readfds)) {
efds_c->val = (0 - fd);
status = _TT_RPCSRV_FDERR;
done = 1;
}
// Clear our fd from the fdset so
// svc_getreqset() won't get confused (bug
// 2000972).
FD_CLR(fd, &readfds);
}
svc_getreqset(&readfds);
}
} while ((! done) && ((stop == 0) || (! *stop)));
return status;
}
/*
* Returns an unused transient program number. Definition taken out of
* the RPC manual.
*/
#ifdef OPT_TLI
static int
gettransient(int vers, netconfig *nconf, netbuf *address)
#else
static int
gettransient(int proto, int vers, int *sockp)
#endif /* OPT_TLI */
{
int prognum;
#ifndef OPT_TLI
int found;
int s;
#if defined(linux) || defined(CSRG_BASED)
socklen_t len;
#else
int len;
#endif
int socktype;
sockaddr_in addr;
sockaddr_in tport;
sockaddr_in uport;
switch (proto) {
case IPPROTO_UDP:
socktype = SOCK_DGRAM;
break;
case IPPROTO_TCP:
socktype = SOCK_STREAM;
break;
default:
return(0);
}
if (*sockp == RPC_ANYSOCK) {
s = socket(AF_INET, socktype, 0);
if (s < 0) {
_tt_syslog(0, LOG_ERR, "gettransient(): socket(): %m");
return 0;
}
*sockp = s;
} else {
s = *sockp;
}
memset(&addr, 0, sizeof(addr));
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(0);
addr.sin_family = AF_INET;
len = sizeof(addr);
bind(s, (sockaddr *)&addr, len);
#if defined (_AIX) && (OSMAJORVERSION==4) && (OSMINORVERSION==2)
if (getsockname(s, (sockaddr *)&addr, (size_t *)&len) < 0) {
#else
if (getsockname(s, (sockaddr *)&addr, &len) < 0) {
#endif
_tt_syslog(0, LOG_ERR, "getsockname(): %m");
return(0);
}
int optval = 0;
#if !defined(linux)
if (setsockopt(s, SOL_SOCKET, SO_USELOOPBACK,
(char *)&optval, sizeof(optval)) == -1) {
}
#endif
#endif /* !OPT_TLI */
// Search for a transient rpc number in the range 0x40000000 -
// 0x5fffffff by starting in the middle of the range searching
// up and then searching down if that fails. The reason for
// this is to make it less likely for other programs to grab
// this transient number (since pmap_getport doesn't complain
// if you try to grab a number for udp and we have it grabbed
// for tcp).
// JET - this is way too many pnums to search, and causes what
// appears to be an infinite loop (though it isn't) if the
// user is running an rpcbind in secure mode and not using
// libtirpc - a common error. The end result is staring at
// the dthello welcome screen. So - rather than search this
// immense space, we will only search from start to +-50
// before bailing. If a hundred attmepts to get a transient
// fail, I don't see that doing approximately 537 million
// attempts are worth it :)
#define MAX_TRANS_RANGE 50
// search up in the range 0x4fffffff to 0x4fffffff + MAX_TRANS_RANGE
for (prognum = 0x4fffffff; prognum <= (0x4fffffff + MAX_TRANS_RANGE); prognum++) {
/* XXX: pmap_set allows the same prognum for different */
/* protocols so we hack around that by attemptint to */
/* set both tcp and udp. */
#ifndef OPT_TLI
found = (!pmap_getport(&uport, prognum, vers,
IPPROTO_UDP) &&
!pmap_getport(&tport, prognum, vers, proto));
if (found &&
(found = pmap_set(prognum,
vers,
proto,
ntohs(addr.sin_port))) &&
(vers==1 || (found = pmap_set(prognum,
1,
proto,
ntohs(addr.sin_port))))) {
return(prognum);
}
#else
if (rpcb_set(prognum, vers, nconf, address) &&
(vers==1 || rpcb_set(prognum, 1, nconf, address))) {
return(prognum);
}
#endif /* !OPT_TLI */
}
// search down in the range 0x4ffffffe - 0x40000000
for (prognum = 0x4ffffffe; prognum >= (0x4ffffffe - MAX_TRANS_RANGE); prognum--) {
/* XXX: pmap_set allows the same prognum for different */
/* protocols so we hack around that by attemptint to */
/* set both tcp and udp. */
#ifndef OPT_TLI
found = (!pmap_getport(&uport, prognum, vers,
IPPROTO_UDP) &&
!pmap_getport(&tport, prognum, vers, proto));
if (found &&
(found = pmap_set(prognum,
vers,
proto,
ntohs(addr.sin_port)))) {
return(prognum);
}
#else
if (rpcb_set(prognum, vers, nconf, address)) {
return(prognum);
}
#endif /* !OPT_TLI */
}
return(0);
}