diff options
Diffstat (limited to 'unix/os/zfioks.c')
-rw-r--r-- | unix/os/zfioks.c | 2101 |
1 files changed, 2101 insertions, 0 deletions
diff --git a/unix/os/zfioks.c b/unix/os/zfioks.c new file mode 100644 index 00000000..5302dff0 --- /dev/null +++ b/unix/os/zfioks.c @@ -0,0 +1,2101 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include <stdio.h> +#include <ctype.h> +#include <signal.h> +#include <setjmp.h> +#include <string.h> +#include <unistd.h> +#include <sys/errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <netinet/in.h> +#include <netdb.h> +#include <fcntl.h> +#include <time.h> +#include <pwd.h> + +#ifdef SYSV +#include <termios.h> +#else +#include <sgtty.h> +#endif + +#ifdef MACOSX +#define USE_RCMD 1 +#include <unistd.h> +#endif + +#define import_kernel +#define import_knames +#define import_zfstat +#define import_prtype +#define import_spp +#include <iraf.h> + + +/* ZFIOKS -- File i/o to a remote kernel server. This driver is the network + * interface for the kernel interface package (sys$ki). The KS driver is + * normally called directly by the KI routines, but is patterned after the + * regular FIO drivers hence may be connected to FIO to provide a network + * interface to the high level code. + * + * zopcks open kernel server on remote node + * zclcks close kernel server + * zardks read from the remote kernel server + * zawrks write to the remote kernel server + * zawtks wait for i/o + * zsttks get channel/device status + * + * In the Berkeley UNIX environment the network interface is TCP/IP. The + * kernel server process irafks.e, running on the remote node, communicates + * directly with the zfioks driver in the iraf client process via a socket. + * Two protocols are supported for connecting to the irafks.e server. The + * default protocol uses RSH (or REMSH) as a bootstrap to start a daemon + * irafks.e process (known as in.irafksd) on the remote node. There is one + * such daemon process per node per user. Login authentication is performed + * when the daemon process is started. Once the daemon is running, each + * instance of the irafks.e server is started by a simple request to the daemon + * without repeating full login authentication, although an authorization code + * is required. The second, or alternate protocol uses REXEC to start the + * irafks.e server directly, and requires that a password be supplied, either + * in the irafhosts file or by an interactive query. + * + * The advantage of the default protocol is efficiency: starting a new server + * is fast once the in.irafksd daemon is up and running on the server node. + * All that is required is a connnect, an irafks.e fork, and another connect. + * There are however some drawbacks (which result from the desire to make all + * this work by boostrapping off of RSH so that no suid root processes are + * needed). First, for things to work correctly it must be possible to assign + * each user a unique port number for the in.irafksd daemon. This is done via + * a uid based scheme. There is however no guarantee that the port will not + * already be in use, preventing the daemon from being bound to the port; if + * this happens, a port is dynamically assigned by the daemon for temporary + * use to set up the server. If this happens a rsh request will be required + * to spawn each server (this involves a total of half a dozen or so forks + * and execs) but it should still work. + * + * The other complication has to do with security. There is a potentially + * serious security problem if the in.irafksd daemon simply responds to + * every request to spawn a kernel server, because any user could issue such + * a request (there is no way to check the uid of the client process over a + * socket connection). In an "open" environment this might be acceptable + * since some effort is required to take advantage of this loophole, but to + * avoid the loophole a simple authentication scheme is used. This involves + * a unique authentication integer which is set by the client when the daemon + * is spawned - spawning the daemon via rsh is "secure" if rsh is secure. + * Subsequent client requests must supply the same authentication number or + * the request will be denied. The authentication number may be specified + * either in the user environment or in the .irafhosts file. The driver will + * automatically set protection 0600 (read-only) on the .irafhosts file when + * accessed. + * + * The driver has a fall-back mode wherein a separate rsh call is used for + * every server connection. This is enabled by setting the in.irafksd port + * number to zero in the irafhosts file. In this mode, all port numbers are + * dynamically assigned, eliminating the chance of collisions with reserved + * or active ports. + */ + +/* + * PORTING NOTE -- A "SysV" system may use rsh instead of remsh. This should + * be checked when doing a port. Also, on at least one system it was necessary + * to change MAXCONN to 1 (maybe that is what it should be anyway). + */ + +extern int errno; +extern int save_prtype; + +#define SZ_NAME 32 /* max size node, etc. name */ +#define SZ_CMD 256 /* max size rexec sh command */ +#define MAXCONN 1 /* for listen */ +#define MAX_UNAUTH 32 /* max unauthorized requests */ +#define ONEHOUR (60*60) /* number of seconds in one hour */ +#define DEF_TIMEOUT (1*ONEHOUR) /* default in.irafksd idle timo */ +#define MIN_TIMEOUT 60 /* minimum timeout (seconds) */ +#define PRO_TIMEOUT 45 /* protocol (ks_geti) timeout */ +#define DEF_HIPORT 47000 /* default in.irafksd port region */ +#define MAX_HIPORT 65535 /* top of port number range */ +#define MIN_HIPORT 15000 /* bottom of port number range */ +#define REXEC_PORT 512 /* port for rexecd daemon */ +#define FNNODE_CHAR '!' /* node name delimiter */ +#define IRAFHOSTS ".irafhosts" /* user host login file */ +#define HOSTLOGIN "dev/irafhosts" /* system host login file */ +#define USER "<user>" /* symbol for user account info */ +#define UNAUTH 99 /* means auth did not match */ + +#ifdef BSD +#define IPPORT_USERRESERVED 5000 +#endif + +#ifdef POSIX +#define SELWIDTH FD_SETSIZE /* number of bits for select */ +#else +#define SELWIDTH 32 /* number of bits for select */ +#endif + +#define KS_RETRY "KS_RETRY" /* number of connection attempts */ +#define KS_NO_RETRY "KS_NO_RETRY" /* env to override rexec retry */ + +#define KSRSH "KSRSH" /* set in env to override RSH cmd */ +#if (defined(BSD) | defined(LINUX)) +#define RSH "rsh" /* typical names are rsh, remsh */ +#else +#ifdef SYSV +#define RSH "remsh" /* typical names are rsh, remsh */ +#else +#define RSH "rsh" /* typical names are rsh, remsh */ +#endif +#endif + +#define IRAFKS_DIRECT 0 /* direct connection (via rexec) */ +#define IRAFKS_CALLBACK 1 /* callback to client socket */ +#define IRAFKS_DAEMON 2 /* in.irafksd daemon process */ +#define IRAFKS_SERVER 3 /* irafks server process */ +#define IRAFKS_CLIENT 4 /* zfioks client process */ + +#define C_RSH 1 /* rsh connect protocol */ +#define C_REXEC 2 /* rexec connect protocol */ +#define C_REXEC_CALLBACK 3 /* rexec-callback protocol */ + +struct ksparam { + int auth; /* user authorization code */ + int port; /* in.irafksd port */ + int hiport; /* in.irafksd port region */ + int timeout; /* in.irafksd idle timeout, sec */ + int protocol; /* connect protocol */ +}; + +int debug_ks = 0; /* print debug info on stderr */ +char debug_file[64] = ""; /* debug output file if nonnull */ +FILE *debug_fp = NULL; /* debugging output */ + +extern uid_t getuid(); +extern char *getenv(); +extern char *strerror(); +static jmp_buf jmpbuf; +static int jmpset = 0; +static int recursion = 0; +static int parent = -1; +static SIGFUNC old_sigcld; +static int ks_pchan[MAXOFILES]; +static int ks_achan[MAXOFILES]; +static int ks_getresvport(), ks_rexecport(); +static int ks_socket(), ks_geti(), ks_puti(), ks_getlogin(); +static void dbgsp(), dbgmsg(), dbgmsgs(); +static void dbgmsg1(), dbgmsg2(), dbgmsg3(), dbgmsg4(); +static char *ks_getpass(); +static void ks_onsig(), ks_reaper(); + +static int ks_getlogin (char *hostname, char *loginname, char *password, + struct ksparam *ks); +static char *ks_username (char *filename, char *pathname, char *username); +static char *ks_sysname (char *filename, char *pathname); +static struct irafhosts *ks_rhosts (char *filename); +static int ks_getword (char **ipp, char *obuf); +static void ks_whosts (struct irafhosts *hp, char *filename); +static char *ks_getpass (char *user, char *host); + +void pr_mask (char *str); + +/* ZOPNKS -- Open a connected subprocess on a remote node. Parse the "server" + * argument to obtain the node name and the command to be issued to connect the + * remote process. Set up a socket to be used for communications with the + * remote irafks kernel server. The "server" string is implementation + * dependent and normally comes from the file dev$hosts. This file is read + * by the high level VOS code before we are called. + */ +int +ZOPNKS ( + PKCHAR *x_server, /* type of connection */ + XINT *mode, /* access mode (not used) */ + XINT *chan /* receives channel code (socket) */ +) +{ + register char *ip, *op; + char *server = (char *)x_server; + char host[SZ_NAME+1], username[SZ_NAME+1], password[SZ_NAME+1]; + int proctype=0, port=0, auth=0, s_port=0, pid=0, s=0, i=0; + struct sockaddr_in from; + char *hostp=NULL, *cmd=NULL; + char obuf[SZ_LINE]; + struct ksparam ks; + + + + /* Initialize local arrays */ + host[0] = username[0] = password[0] = (char) 0; + + /* Parse the server specification. We can be called to set up either + * the irafks daemon or to open a client connection. + * + * (null) direct via rexec + * callback port@host callback client + * in.irafksd [port auth [timeout]] start daemon + * [-prot] [-log file] host!command client connection + * + * where -prot is -rsh, -rex, or -rcb denoting the client connect + * protocols rsh, rexec, and rexec-callback. + */ + + /* Eat any protocol specification strings. The default connect + * protocol is rsh. + */ + for (ip = server; isspace(*ip); ip++) + ; + ks.protocol = C_RSH; + if (strncmp (ip, "-rsh", 4) == 0) { + ks.protocol = C_RSH; + ip += 4; + } else if (strncmp (ip, "-rex", 4) == 0) { + ks.protocol = C_REXEC; + ip += 4; + } else if (strncmp (ip, "-rcb", 4) == 0) { + ks.protocol = C_REXEC_CALLBACK; + ip += 4; + } + + /* Check for the debug log flag. */ + for ( ; isspace(*ip); ip++) + ; + if (strncmp (ip, "-log", 4) == 0) { + debug_ks++; + for (ip += 4; isspace(*ip); ip++) + ; + for (op=debug_file; *ip && !isspace(*ip); ) + *op++ = *ip++; + *op = EOS; + } + + /* Determine connection type. */ + for ( ; isspace(*ip); ip++) + ; + if (!*ip) { + proctype = IRAFKS_DIRECT; + } else if (strncmp (ip, "callback ", 9) == 0) { + proctype = IRAFKS_CALLBACK; + ip += 9; + } else if (strncmp (ip, "in.irafksd", 10) == 0) { + proctype = IRAFKS_DAEMON; + ip += 10; + } else { + proctype = IRAFKS_CLIENT; + cmd = NULL; + for (op=host; *ip != EOS; ip++) + if (*ip == FNNODE_CHAR) { + *op = EOS; + cmd = ++ip; + break; + } else + *op++ = *ip; + if (cmd == NULL) { + *chan = ERR; + goto done; + } + } + + + /* Debug output. If debug_ks is set (e.g. with adb) but no filename + * is given, debug output goes to stderr. + */ + if (debug_ks && !debug_fp) { + if (debug_file[0] != EOS) { + if ((debug_fp = fopen (debug_file, "a")) == NULL) + debug_fp = stderr; + } else + debug_fp = stderr; + } + + + /* Begin debug message log. */ + dbgmsg ("---------------------------------------------------------\n"); + dbgmsg1 ("zopnks (`%s')\n", server); + dbgmsg4 ("kstype=%d, prot=%d, host=`%s', cmd=`%s')\n", + proctype, ks.protocol, host, ip); + parent = getpid(); + + /* + * SERVER side code. + * --------------------- + */ + + if (proctype == IRAFKS_DIRECT) { + /* Kernel server was called by rexec and is already set up to + * communicate on the standard streams. + */ + *chan = 0; + goto done; + + } else if (proctype == IRAFKS_CALLBACK) { + /* The kernel server was called by rexec using the rexec-callback + * protocol. Connect to the client specified socket. + */ + char *client_host; + int port, s; + + /* Parse "port@client_host". */ + for (port=0; isdigit(*ip); ip++) + port = port * 10 + (*ip - '0'); + client_host = ip + 1; + + dbgmsg2 ("S:callback client %s on port %d\n", client_host, port); + if ((s = ks_socket (client_host, NULL, port, "connect")) < 0) + *chan = ERR; + else + *chan = s; + goto done; + + } else if (proctype == IRAFKS_DAEMON) { + /* Handle the special case of spawning the in.irafksd daemon. This + * happens when the zfioks driver is called by the irafks.e process + * which is started up on a remote server node by the networking + * system. (via either rsh or rexec). To start the in.irafksd + * daemon we fork the irafks.e and exit the parent so that the + * rsh|remsh or rexec completes. The daemon will run indefinitely + * or until the specified timeout interval passes without receiving + * any requests. The daemon listens for connections on a global + * (per-user) socket; when a connection is made, the client passes + * in a socket address and authentication code, and provided the + * request is authenticated an irafks server is forked and + * connected to the client socket. The irafks server then runs + * indefinitely, receiving and processing iraf kernel RPCs from the + * client until commanded to shutdown, the connection is broken, or + * an i/o error occurs. + */ + struct timeval timeout; + int check, fromlen, req_port; + int nsec, fd, sig; +#if defined(POSIX) || defined(LINUX) || defined(MACOSX) + fd_set rfd; +#else + int rfd; +#endif + int once_only = 0; + int detached = 0; + int unauth = 0; + int status = 0; + + /* Get the server parameters. These may be passed either via + * the client in the datastream, or on the command line. The + * latter mode allows the daemon to be run standalone on a server + * node, without the need for a rsh call from a client to start + * the daemon. + */ + while (*ip && isspace (*ip)) + ip++; + if (isdigit (*ip)) { + /* Server parameters were passed on the command line. */ + char *np; + + detached++; + port = req_port = strtol (ip, &np, 10); + if (np == NULL) { + status = 1; + goto d_err; + } else + ip = np; + auth = strtol (ip, &np, 10); + if (np == NULL) { + status = 2; + goto d_err; + } else + ip = np; + nsec = strtol (ip, &np, 10); + if (np == NULL) { + nsec = 0; /* no timeout */ + } else + ip = np; + dbgmsg3 ("S:detached in.irafksd, port=%d, auth=%d, timeout=%d\n", + port, auth, nsec); + + } else { + /* Get client data from the client datastream. */ + if ((req_port = port = ks_geti(0)) < 0) + { status = 1; goto d_err; } + if ((auth = ks_geti(0)) < 0) + { status = 2; goto d_err; } + if ((nsec = ks_geti(0)) < 0) + { status = 3; goto d_err; } + dbgmsg2 ("S:client spawned in.irafksd, port=%d, timeout=%d\n", + port, nsec); + } + + /* Attempt to bind the in.irafksd server socket to the client + * specified port. If this fails a free socket is dynamically + * allocated. If no port is specified (e.g. port=0) the port + * will always be dynamically allocated and a rsh call will be + * employed for every server connection. + */ + if (port <= IPPORT_RESERVED) + port = IPPORT_USERRESERVED - 1; + s = ks_getresvport (&port); + if (s < 0 || listen(s,MAXCONN) < 0) { + status = 4; + goto d_err; + } else if (port != req_port) { + if (detached) { + status = 4; + goto d_err; + } + once_only++; + } + + /* Fork daemon process and return if parent, exiting rsh. */ + dbgmsg2 ("S:fork in.irafksd, port=%d, timeout=%d\n", port, nsec); + pid = fork(); + if (pid < 0) { + status = 4; + goto d_err; + } + + if (pid) { +d_err: dbgmsg1 ("S:in.irafksd parent exit, status=%d\n", status); + if (!detached) { + ks_puti (1, status); + ks_puti (1, port); + } + exit(0); + } + + /* + * What follows is the code for the daemon process. Close the + * stdio streams, which we won't need any more. Create a socket + * and bind it to the given port. Sit in a loop until timeout, + * listening for client connection requests and forking the irafks + * server in response to each such request. + */ + + dbgmsg3 ("S:in.irafksd started, pid=%d ppid=%d\n", + getpid(), getppid()); + old_sigcld = (SIGFUNC) signal (SIGCHLD, (SIGFUNC)ks_reaper); + + /* Reset standard streams to console to record error messages. */ + fd = open ("/dev/null", 0); close(0); dup(fd); close(fd); + fd = open ("/dev/console", 1); close(1); dup(fd); close(fd); + fd = open ("/dev/console", 2); close(2); dup(fd); close(fd); + + /* Loop forever or until the idle timeout expires, waiting for a + * client connection. + */ + for (;;) { + timeout.tv_sec = nsec; + timeout.tv_usec = 0; +#if defined(POSIX) || defined(LINUX) || defined(MACOSX) + FD_ZERO(&rfd); + FD_SET(s,&rfd); +#else + rfd = (1 << s); +#endif + status = 0; + + /* Wait until either we get a connection, or a previously + * started server exits. + */ + jmpset++; + if ((sig = setjmp(jmpbuf))) { + if (sig == SIGCHLD) { + dbgmsg ("S:in.irafksd sigchld return\n"); + while (waitpid ((pid_t)0, (int *)0, WNOHANG) > 0) + ; + } else + exit (0); + } + if (select (SELWIDTH,&rfd,NULL,NULL, nsec ? &timeout : 0) <= 0) + exit (0); + + /* Accept the connection. */ + if ((fd = accept (s, (struct sockaddr *)0, + (socklen_t *)0)) < 0) { + fprintf (stderr, + "S:in.irafksd: accept on port %d failed\n", port); + exit (2); + } else + dbgmsg ("S:in.irafksd: connection established\n"); + + /* Find out where the connection is coming from. */ + fromlen = sizeof (from); +#if defined(POSIX) || defined(LINUX) || defined(MACOSX) + if (getpeername (fd, (struct sockaddr *)&from, + (socklen_t *)&fromlen) < 0) { +#else + if (getpeername (fd, &from, (socklen_t *)&fromlen) < 0) { +#endif + fprintf (stderr, "in.irafksd: getpeername failed\n"); + exit (3); + } + + /* Connection established. Get client data. */ + if ((s_port = ks_geti(fd)) < 0 || (check = ks_geti(fd)) < 0) { + fprintf (stderr, "in.irafksd: protocol error\n"); + status = 1; + goto s_err; + } + + /* Verify authorization. Shutdown if repeated unauthorized + * requests occur. + */ + if (auth && check != auth) { + if (unauth++ > MAX_UNAUTH) { + fprintf (stderr, + "in.irafksd: unauthorized connection attempt\n"); + exit (4); + } + status = UNAUTH; + goto s_err; + } + + /* Connection authorized if this message is output. */ + dbgmsg1 ("S:in.irafksd: client port = %d\n", s_port); + + /* Fork the iraf kernel server. */ + pid = fork(); + if (pid < 0) { + fprintf (stderr, "in.irafksd: process creation failed\n"); + status = 3; + goto s_err; + } + + if (pid) { /** parent **/ +s_err: dbgmsg1 ("S:in.irafksd fork complete, status=%d\n", + status); + ks_puti (fd, status); + close (fd); + if (once_only) + exit (0); + /* otherwise loop indefinitely */ + + } else { /** child **/ + /* Set up iraf kernel server. */ + u_long n_addr, addr; + unsigned char *ap = (unsigned char *)&n_addr; + + dbgmsg2 ("S:irafks server started, pid=%d ppid=%d\n", + getpid(), getppid()); + signal (SIGCHLD, old_sigcld); + /* + old_sigcld = (SIGFUNC) signal (SIGCHLD, (SIGFUNC)ks_reaper); + */ + close (fd); close (s); + + n_addr = from.sin_addr.s_addr; + addr = ntohl(n_addr); + sprintf (obuf, "%d.%d.%d.%d", ap[0],ap[1],ap[2],ap[3]); + dbgmsg2 ("S:client address=%s port=%d\n", obuf, s_port); + + if ((s = ks_socket (NULL, addr, s_port, "connect")) < 0) { + dbgmsg1 ("S:irafks connect to port %d failed\n", s_port); + fprintf (stderr, "irafks: cannot connect to client\n"); + exit (1); + } else + dbgmsg1 ("S:irafks connected on port %d\n", s_port); + + *chan = s; + goto done; + } + } + } /* else fall through to DAEMON_CLIENT code */ + + /* + * CLIENT side code. + * --------------------- + */ + + /* Attempt to fire up the kernel server process. Get login name + * and password for the named host. If a password is given attempt + * to connect via the rexec protocol, otherwise attempt the connection + * via the rsh/in.irafksd protocol. + */ + if (ks_getlogin (host, username, password, &ks) == ERR) { + *chan = ERR; + + } else if (ks.protocol == C_REXEC) { + /* Use rexec protocol. We start the remote kernel server with + * rexec and communicate via the socket returned by rexec. + */ + hostp = host; + dbgmsg2 ("C:rexec for host=%s, user=%s\n", host, username); +#ifdef USE_RCMD + *chan = rcmd (&hostp, ks_rexecport(), + getlogin(), username, cmd, 0); +#else + *chan = rexec (&hostp, ks_rexecport(), username, password, cmd, 0); +#endif + + } else if (ks.protocol == C_REXEC_CALLBACK) { + /* Use rexec-callback protocol. In this case the remote kernel + * server is started with rexec, but we have the remote server + * call us back on a private socket. This guarantees a direct + * socket connection for use in cases where the standard i/o + * streams set up for rexec do not provide a direct connection. + */ + char localhost[SZ_FNAME]; + char callback_cmd[SZ_LINE]; + struct hostent *hp; + int tfd=0, fd=0, ss=0; + + /* Get reserved port for direct communications link. */ + s_port = IPPORT_USERRESERVED - 1; + s = ks_getresvport (&s_port); + if (s < 0) + goto r_err; + + /* Ready to receive callback from server. */ + if (listen (s, MAXCONN) < 0) + goto r_err; + + /* Compose rexec-callback command: "cmd port@client-host". */ + if (gethostname (localhost, SZ_FNAME) < 0) + goto r_err; + if ((hp = gethostbyname (localhost)) == NULL) + goto r_err; + sprintf (callback_cmd, "%s callback %d@%s", + cmd, s_port, hp->h_name); + dbgmsg2 ("rexec to host %s: %s\n", host, callback_cmd); + + hostp = host; + dbgmsg3 ("rexec for host=%s, user=%s, using client port %d\n", + host, username, s_port); +#ifdef USE_RCMD + ss = rcmd (&hostp, ks_rexecport(), + getlogin(), username, callback_cmd, 0); +#else + ss = rexec (&hostp, + ks_rexecport(), username, password, callback_cmd, 0); +#endif + + /* Wait for the server to call us back. */ + dbgmsg1 ("waiting for connection on port %d\n", s_port); + if ((tfd = accept (s, (struct sockaddr *)0, (socklen_t *)0)) < 0) { +r_err: dbgmsg ("rexec-callback connect failed\n"); + close(s); close(ss); + *chan = ERR; + } else { + close(s); fd = dup(tfd); close(tfd); + dbgmsg1 ("connected to irafks server on fd=%d\n", fd); + *chan = fd; + + /* Mark the rexec channel for deletion at close time when + * the i/o socket is closed. + */ + for (i=0; i < MAXOFILES; i++) + if (!ks_pchan[i]) { + ks_pchan[i] = fd; + ks_achan[i] = ss; + break; + } + } + + } else { + /* Use the default protocol, which avoids passwords. This uses + * rsh to start up (once) the iraf networking daemon in.irafksd + * on the remote node, and thereafter merely places requests to + * in.irafksd to spawn each instance of the irafks.e server. + */ + char command[SZ_LINE], *nretryp; + int pin[2], pout[2]; + int status = 0; + int ntries = 0, nretries = 0; + char *password; + int fd, tfd; + int t=0, s=0; + + /* Get reserved port for client. */ + s_port = IPPORT_USERRESERVED - 1; + s = ks_getresvport (&s_port); + if (s < 0) { + status |= 01; + goto c_err; + } + dbgmsg2 ("C:connect to in.irafksd host=%s client port=%d\n", + host, s_port); + + /* Ready to receive callback from server. */ + if (listen (s, MAXCONN) < 0) { + status |= 02; + goto c_err; + } + + /* Check for the number of connection attempts. */ + if ((nretryp = getenv(KS_RETRY))) + nretries = atoi(nretryp); + + /* in.irafkd port. */ + port = ks.port; +again: + /* Connect to in.irafksd daemon on server system and send request + * to start up a new irafks daemon on the port just created. If + * the connection fails, fork an rsh and start up the in.irafksd. + */ + if (!port || (t = ks_socket (host, NULL, port, "connect")) < 0) { + dbgmsg ("C:no server, fork rsh to start in.irafksd\n"); + + if (pipe(pin) < 0 || pipe(pout) < 0) { + status |= 04; + goto c_err; + } + pid = fork(); + if (pid < 0) { + status |= 010; + goto c_err; + } + + if (pid) { + /* Pass target port and authorization code to in.irafksd. + * Server returns the actual port assigned. + */ + close (pin[1]); + close (pout[0]); +retry: + dbgmsg2 ("C:send port=%d, timeout=%d to irafks.e\n", + ks.port, ks.timeout); + if (ks_puti (pout[1], port) <= 0) + status |= 0020; + if (ks_puti (pout[1], ks.auth) <= 0) + status |= 0040; + if (ks_puti (pout[1], ks.timeout) <= 0) + status |= 0100; + if (ks_geti(pin[0])) + status |= 0200; + + port = ks_geti (pin[0]); + dbgmsg1 ("C:irafks.e returns port=%d\n", port); + + /* Wait for the rsh connection to shut down. */ + while (read (pin[0], obuf, SZ_LINE) > 0) + ; + wait (NULL); + close (pin[0]); + if (pout[1] != pin[0]) + close (pout[1]); + + /* If the rsh succeeded the in.irafksd daemon should be + * running now. Attempt again to connect. If this fails, + * most likely the rsh failed. Try to use rexecd to start + * the daemon. + */ + if (status || + (t = ks_socket (host, NULL, port, "connect")) < 0) { + + /* The KS_RETRY environment variable may be set to + * the number of times we wish to try to reconnect. + * We'll sleep for 1-second between attempts before + * giving up. + */ + if (getenv (KS_RETRY) && nretries--) { + sleep (1); + goto again; + } + + /* If KS_NO_RETRY is set then we won't try at all + * with an rexec. These two variables give us a + * chance to retry using the rsh/KSRSH protocol some + * number of times before failing, and optionally + * trying with a different (rexec) before quitting + * entirely. On most recent systems the rexec port + * isn't enabled anyway. + */ + if (getenv (KS_NO_RETRY) || ntries++) { + status |= 0400; + goto c_err; + } + + dbgmsg ("C:rsh failed - try rexec\n"); + if (!(password = ks_getpass (username, host))) + { status |= 01000; goto c_err; } + + sprintf (command, "%s in.irafksd", cmd); + dbgmsg3 ("C:rexec %s@%s: %s\n", username, host, command); + + hostp = host; +#ifdef USE_RCMD + fd = rcmd (&hostp, ks_rexecport(), + getlogin(), username, command, 0); +#else + fd = rexec (&hostp, ks_rexecport(), + username, password, command, NULL); +#endif + + if (fd < 0) { + status |= 02000; + goto c_err; + } else { + status = 0; + port = ks.port; + pin[0] = pout[1] = fd; + goto retry; + } + } + + } else { + /* Call rsh to start up in.irafksd on server node. + */ + char *s, *rshcmd; + + close (pin[0]); close (pout[1]); + close (0); dup (pout[0]); close (pout[0]); + close (1); dup (pin[1]); close (pin[1]); + + rshcmd = (s = getenv(KSRSH)) ? s : RSH; + + dbgmsg3 ("C:exec rsh %s -l %s `%s' in.irafksd\n", + host, username, cmd); + execlp (rshcmd, rshcmd, + host, "-l", username, cmd, "in.irafksd", NULL); + exit (1); + } + } + + /* Send command to start up irafks server. This consists of the + * reserved port for the server connection followed by the + * authorization code. The in.irafksd daemon returns a status + * byte which will be zero if the operation is successful. + */ + dbgmsg1 ("C:request irafks server for client port %d\n", s_port); + + if (ks_puti (t, s_port) <= 0) + { status |= 004000; goto c_err; } + if (ks_puti (t, ks.auth) <= 0) + { status |= 010000; goto c_err; } + + /* Check for an authorization failure and connect on a dynamically + * allocated port if this happens (in.irafksd will allocate the + * port). An authorization failure does not necessarily indicate + * an unauthorized connection attempt; it may mean instead that + * the user has not set up the same authorization code on two + * different nodes and iraf clients on both nodes, with different + * authorization codes, are trying to access the same server. + * If this happens the first client will get the in.irafksd daemon + * and the other client will have to do an rsh connect each time. + */ + if ((status = ks_geti(t))) { + if (port && status == UNAUTH) { + close(t); + port = 0; + dbgmsg ("C:authorization failed, retry with port=0\n"); + status = 0; + goto again; + } else { + status |= 020000; + goto c_err; + } + } + + /* Wait for the server to call us back. */ + if ((tfd = accept (s, (struct sockaddr *)0, (socklen_t *)0)) < 0) { +c_err: dbgmsg1 ("C:zfioks client status=%o\n", status); + close(t); close(s); + kill (pid, SIGTERM); + *chan = ERR; + } else { + close(t); close(s); fd = dup(tfd); close(tfd); + dbgmsg1 ("C:connected to irafks server on fd=%d\n", fd); + *chan = fd; + } + } + +done: + jmpset = 0; + if (*chan > 0) { + if (*chan < MAXOFILES) + zfd[*chan].nbytes = 0; + else { + close (*chan); + *chan = ERR; + } + } + + dbgmsg1 ("zopnks returns status=%d\n", *chan); + + return (*chan); +} + + +/* ZCLSKS -- Close a kernel server connection. + */ +int +ZCLSKS ( + XINT *chan, /* socket to kernel server */ + XINT *status /* receives close status */ +) +{ + int i; + + /* Close the primary channel. */ + *status = close (*chan); + + /* Close any alternate channels associated with the primary. */ + for (i=0; i < MAXOFILES; i++) { + if (ks_pchan[i] == *chan) { + close (ks_achan[i]); + ks_pchan[i] = 0; + } + } + + dbgmsg2 ("server [%d] terminated, status = %d\n", *chan, *status); + + return (*status); +} + + +/* ZARDKS -- Read from the kernel server channel. No attempt is made to + * impose a record structure upon the channel, as is the case with IPC. + * In UNIX the channel is stream oriented and it is up to the caller to + * unblock records from the input stream. Data blocks are assumed to be + * preceded by headers telling how much data to read, hence we read from + * the channel until the specified number of bytes have been read or ERR + * or EOF is seen on the stream. + */ +int +ZARDKS ( + XINT *chan, /* kernel server channel (socket) */ + XCHAR *buf, /* output buffer */ + XINT *totbytes, /* total number of bytes to read */ + XLONG *loffset /* not used */ +) +{ +#ifdef ANSI + volatile char *op; + volatile int fd, nbytes; +#else + char *op; + int fd, nbytes; +#endif + SIGFUNC sigint, sigterm; + int status = ERR; + + fd = *chan; + op = (char *)buf; + zfd[fd].nbytes = nbytes = *totbytes; + if (debug_ks > 1) + dbgmsg2 ("initiate read of %d bytes from KS channel %d\n", + nbytes, fd); + + /* Now read exactly nbytes of data from channel into user buffer. + * Return actual byte count if EOF is seen. If ERR is seen return + * ERR. If necessary multiple read requests are issued to read the + * entire record. Reads are interruptable but the interrupt is caught + * and returned as a read error on the server channel. + */ + sigint = (SIGFUNC) signal (SIGINT, (SIGFUNC)ks_onsig); + sigterm = (SIGFUNC) signal (SIGTERM, (SIGFUNC)ks_onsig); + + while (nbytes > 0) { + jmpset++; + if (setjmp (jmpbuf) == 0) + status = read (fd, op, nbytes); + else + status = ERR; + + switch (status) { + case 0: + zfd[fd].nbytes -= nbytes; + return (XERR); + case ERR: + zfd[fd].nbytes = ERR; + return (XERR); + default: + nbytes -= status; + op += status; + break; + } + } + + jmpset = 0; + signal (SIGINT, sigint); + signal (SIGTERM, sigterm); + if (debug_ks > 1) + dbgmsg2 ("read %d bytes from KS channel %d:\n", op-(char *)buf, fd); + + return (status); +} + + +/* ZAWRKS -- Write to a kernel server channel. + */ +int +ZAWRKS ( + XINT *chan, /* kernel server channel (socket) */ + XCHAR *buf, /* output buffer */ + XINT *totbytes, /* number of bytes to write */ + XLONG *loffset /* not used */ +) +{ + SIGFUNC sigint, sigterm, sigpipe; +#ifdef ANSI + volatile int fd, nbytes; + volatile int ofd; +#else + int fd, nbytes; + int ofd; +#endif + + /* If chan=0 (the process standard input) then we really want to + * write to channel 1, the standard output. + */ + if ((ofd = fd = *chan) == 0) + ofd = 1; + + zfd[fd].nbytes = nbytes = *totbytes; + if (debug_ks > 1) + dbgmsg2 ("initiate write of %d bytes to KS channel %d\n", + nbytes, ofd); + + /* Write exactly nbytes of data to the channel from user buffer to + * the channel. Block interrupt during the write to avoid corrupting + * the data stream protocol if the user interrupts the client task. + * Trap SIGPIPE and return it as a write error on the channel instead. + * Likewise, turn an interrupt into a write error on the channel. + */ + sigint = (SIGFUNC) signal (SIGINT, (SIGFUNC)ks_onsig); + sigterm = (SIGFUNC) signal (SIGTERM, (SIGFUNC)ks_onsig); + sigpipe = (SIGFUNC) signal (SIGPIPE, (SIGFUNC)ks_onsig); + recursion = 0; + + jmpset++; + if (setjmp (jmpbuf) == 0) + zfd[fd].nbytes = write (ofd, (char *)buf, nbytes); + else + zfd[fd].nbytes = ERR; + + jmpset = 0; + signal (SIGINT, sigint); + signal (SIGTERM, sigterm); + signal (SIGPIPE, sigpipe); + if (debug_ks > 1) + dbgmsg2 ("wrote %d bytes to KS channel %d:\n", zfd[fd].nbytes, ofd); + + return (XOK); +} + + +/* KS_ONSIG -- Catch a signal. + */ +static void +ks_onsig ( + int sig, /* signal which was trapped */ + int *arg1, /* not used */ + int *arg2 /* not used */ +) +{ + /* If we get a SIGPIPE writing to a server the server has probably + * died. Make it look like there was an i/o error on the channel. + */ + if (sig == SIGPIPE && recursion++ == 0) + fputs ("kernel server process has died\n", stderr); + + if (jmpset) + longjmp (jmpbuf, sig); +} + + +/* KS_REAPER -- Catch a SIGCHLD signal and reap all children. + */ +static void +ks_reaper ( + int sig, /* signal which was trapped */ + int *arg1, /* not used */ + int *arg2 /* not used */ +) +{ + int status=0, pid=0; + + while ((pid = waitpid ((pid_t)0, (int *) &status, WNOHANG)) > 0) + dbgmsg2 ("ks_reaper -- pid=%d, status=%d\n", pid, status); + + if (jmpset) + longjmp (jmpbuf, sig); +} + + +/* ZAWTKS -- Wait for i/o to a KS channel. Since UNIX i/o is not asynchronous + * we do not really wait, rather we return the status value (byte count) from + * the last read or write to the channel. + */ +int +ZAWTKS (XINT *chan, XINT *status) +{ + if ((*status = zfd[*chan].nbytes) == ERR) + *status = XERR; + + return (*status); +} + + +/* ZSTTKS -- Get binary file status for an KS channel. A KS channel is a + * streaming binary file. + */ +int +ZSTTKS ( + XINT *chan, /* not used; all KS channels have same status */ + XINT *param, + XLONG *lvalue +) +{ + switch (*param) { + case FSTT_BLKSIZE: + case FSTT_FILSIZE: + *lvalue = 0; + break; + case FSTT_OPTBUFSIZE: + *lvalue = KS_OPTBUFSIZE; + break; + case FSTT_MAXBUFSIZE: + *lvalue = KS_MAXBUFSIZE; + break; + default: + *lvalue = XERR; + } + + return (XOK); +} + + +/* + * Internal routines. + * ------------------- + */ + +/* KS_SOCKET -- Get a socket configured for the given host and port. Either + * bind the socket to the port and make it ready for connections, or connect + * to the remote socket at the given address. + */ +static int +ks_socket (host, addr, port, mode) +char *host; +u_long addr; +int port; +char *mode; +{ + struct sockaddr_in sockaddr; + struct hostent *hp; + int s; + + /* Create socket. */ + if ((s = socket (AF_INET, SOCK_STREAM, 0)) < 0) + return (ERR); + + /* Set socket address. */ + bzero ((char *)&sockaddr, sizeof(sockaddr)); + sockaddr.sin_family = AF_INET; + sockaddr.sin_port = htons((short)port); + + /* Get address of server host. */ + if (addr) { + sockaddr.sin_addr.s_addr = htonl((long)addr); + } else if (*host) { + if ((hp = gethostbyname (host)) == NULL) + goto failed; + bcopy((char *)hp->h_addr,(char *)&sockaddr.sin_addr, hp->h_length); + } else + sockaddr.sin_addr.s_addr = INADDR_ANY; + + /* Either bind and listen for connnections, or connect to a remote + * socket. + */ + if (strncmp (mode, "listen", 1) == 0) { + if (bind (s, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) < 0) + goto failed; + if (listen (s, MAXCONN) < 0) + goto failed; + } else if (strncmp (mode, "connect", 1) == 0) { + if (connect(s,(struct sockaddr *)&sockaddr,sizeof(sockaddr)) < 0) + goto failed; + } else + goto failed; + + return (s); + +failed: + dbgmsg2 ("ks_socket: errno=%d (%s)\n", errno, strerror(errno)); + close (s); + return (ERR); +} + + +/* KS_GETRESVPORT -- Open a socket and attempt to bind it to the given port. + * Locate a usable port if this fails. The actual port is returned in the + * output argument. + */ +static int +ks_getresvport (alport) +int *alport; +{ + struct sockaddr_in sin; + int s; + + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = INADDR_ANY; + s = socket (AF_INET, SOCK_STREAM, 0); + if (s < 0) + return (-1); + + for (;;) { + sin.sin_port = htons((u_short)*alport); +#if defined(POSIX) || defined(LINUX) || defined(MACOSX) + if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) >= 0) { +#else + if (bind(s, (caddr_t)&sin, sizeof (sin)) >= 0) { +#endif + return (s); + } + if (errno != EADDRINUSE) { + (void) close(s); + return (-1); + } + dbgmsg4 ("ks_getresvport: decr errno=%d (%s) alport=%d -> %d\n", + errno, strerror(errno), *alport, *alport - 1); + (*alport)--; + if (*alport == IPPORT_RESERVED) { + (void) close(s); + errno = EAGAIN; /* close */ + return (-1); + } + } +} + + +/* KS_REXECPORT -- Return the port for the rexec system service. + */ +static int +ks_rexecport() +{ + register struct servent *sv; + static int port = 0; + + if (port) + return (port); + + if ((sv = getservbyname ("exec", "tcp"))) + return (port = sv->s_port); + else + return (port = REXEC_PORT); +} + + +/* KS_PUTI -- Write an integer value to the output stream as a null terminated + * ascii string. + */ +static int +ks_puti (fd, ival) +int fd; +int ival; +{ + char obuf[SZ_FNAME]; + + sprintf (obuf, "%d", ival); + return (write (fd, obuf, strlen(obuf)+1)); +} + + +/* KS_GETI -- Read a positive integer value, passed as a null terminated ascii + * string, base decimal, from the given stream. + */ +static int +ks_geti (fd) +int fd; +{ + register int value = 0; + struct timeval timeout; + int stat, sig; +#if defined(POSIX) || defined(LINUX) || defined(MACOSX) + fd_set rfd; +#else + int rfd; +#endif + char ch; + + jmpset++; + if ((sig = setjmp(jmpbuf))) + if (sig == SIGCHLD) + waitpid ((pid_t)0, (int *)0, WNOHANG); + + timeout.tv_sec = PRO_TIMEOUT; + timeout.tv_usec = 0; +#if defined(POSIX) || defined(LINUX) || defined(MACOSX) + FD_ZERO(&rfd); + FD_SET(fd,&rfd); +#else + rfd = (1 << fd); +#endif + + /* Read and accumulate a decimal integer. Timeout if the client + * does not respond within a reasonable period. + */ + do { + if (select (SELWIDTH, &rfd, NULL, NULL, &timeout) <= 0) { + dbgmsg ("ks_geti: timeout on read\n"); + jmpset = 0; + return (ERR); + } + + if ((stat = read (fd, &ch, 1)) <= 0) { + dbgmsg3 ("ks_geti: read status=%d, errno=%d (%s)\n", + stat, errno, strerror(errno)); + jmpset = 0; + return (ERR); + } + + if (ch) { + if (isdigit(ch)) + value = value * 10 + (ch - '0'); + else { + dbgmsg1 ("ks_geti: read char=%o\n", ch); + jmpset = 0; + return (ERR); + } + } + } while (ch); + + jmpset = 0; + return (value); +} + + +/* KS_GETS -- Read a null terminated ascii string. +static int +ks_gets (fd, outstr) +int fd; +char *outstr; +{ + register char *op = outstr; + int stat; + + do { + if ((stat = read (fd, op, 1)) <= 0) { + dbgmsg3 ("ks_gets: read status=%d, errno=%d (%s)\n", + stat, errno, strerror(errno)); + return (ERR); + } + } while (*op++); + + return (op - outstr - 1); +} + */ + + +/* KS_MSG -- Print debugging messages. + */ +static void dbgsp (pid) +int pid; +{ + int i, nsp = ((parent > 0) ? (pid - parent) : 0); + for (i=0; i < nsp; i++) + fprintf (debug_fp, " "); +} + +static void +dbgmsg (msg) +char *msg; +{ + int pid; + if (debug_ks) { + fprintf (debug_fp, "[%5d] ", (pid = getpid())); dbgsp(pid); + fprintf (debug_fp, "%s", msg); + } +} +static void +dbgmsgs (fmt, arg) +char *fmt; +char *arg; +{ + int pid; + if (debug_ks) { + fprintf (debug_fp, "[%5d] ", (pid = getpid())); dbgsp(pid); + fprintf (debug_fp, fmt, arg); + fflush (debug_fp); + } +} +static void +dbgmsg1 (fmt, arg) +char *fmt; +int arg; +{ + int pid; + if (debug_ks) { + fprintf (debug_fp, "[%5d] ", (pid = getpid())); dbgsp(pid); + fprintf (debug_fp, fmt, arg); + fflush (debug_fp); + } +} +static void +dbgmsg2 (fmt, arg1, arg2) +char *fmt; +int arg1, arg2; +{ + int pid; + if (debug_ks) { + fprintf (debug_fp, "[%5d] ", (pid = getpid())); dbgsp(pid); + fprintf (debug_fp, fmt, arg1, arg2); + fflush (debug_fp); + } +} +static void +dbgmsg3 (fmt, arg1, arg2, arg3) +char *fmt; +int arg1, arg2, arg3; +{ + int pid; + if (debug_ks) { + fprintf (debug_fp, "[%5d] ", (pid = getpid())); dbgsp(pid); + fprintf (debug_fp, fmt, arg1, arg2, arg3); + fflush (debug_fp); + } +} +static void +dbgmsg4 (fmt, arg1, arg2, arg3, arg4) +char *fmt; +int arg1, arg2, arg3, arg4; +{ + int pid; + if (debug_ks) { + fprintf (debug_fp, "[%5d] ", (pid = getpid())); dbgsp(pid); + fprintf (debug_fp, fmt, arg1, arg2, arg3, arg4); + fflush (debug_fp); + } +} + + +/* + * Stuff for processing the irafhosts file. + * ---------------------------------------- + */ + +#define MAX_HEADERLINES 128 +#define MAX_NODES 256 +#define SZ_SBUF 4096 +#define DEFAULT (-1) +#define KSAUTH "KSAUTH" + +struct irafhosts { + int port; + int auth; + int hiport; + int timeout; + int nheaderlines; + int nparams; + int mode; + char *header[MAX_HEADERLINES]; + int nnodes; + struct nodelist { + char *name; + char *login; + char *password; + int port; + int auth; + int hiport; + int timeout; + int protocol; + } node[MAX_NODES]; + int sbuflen; + char sbuf[SZ_SBUF]; +}; + + +/* KS_GETLOGIN -- Read the irafhosts file to determine how to connect to + * the indicated host. If the user has a .irafhosts file read that, otherwise + * read the system default irafhosts file in iraf$dev. Fill in or correct + * any networking parameters as necessary. If the rsh protocol is enabled + * and the user does not have a .irafhosts file, create one for them. If + * the user has a .irafhosts file but a unique authorization code has not + * yet been assigned, assign one and write a new file. Ensure that the + * file has read-only access privileges. + */ +static int +ks_getlogin ( + char *hostname, /* node we wish a login for */ + char *loginname, /* receives the login name */ + char *password, /* receives the login password */ + struct ksparam *ks /* networking parameters */ +) +{ + register struct irafhosts *hp; + register int i; + char userfile[SZ_PATHNAME]; + char sysfile[SZ_PATHNAME]; + char fname[SZ_PATHNAME]; + char username[SZ_NAME]; + char *namep, *authp; + struct nodelist *np; + int update = 0; + int auth; + + /* Get path to user irafhosts file. */ + if (ks_username (IRAFHOSTS, userfile, username) == NULL) + return (ERR); + + /* Read user irafhosts file if there is one. Check for an old-style + * irafhosts file, and read the system file instead if the user file + * is the old obsolete version. + */ + if ((hp = ks_rhosts (userfile))) { + /* Old style irafhosts file? */ + if (hp->nparams == 0) { + /* Attempt to preserve old file with .OLD extension. */ + strcpy (fname, username); + strcat (fname, ".OLD"); + unlink (fname); + rename (username, fname); + + /* Read system file instead. */ + free ((char *)hp); + if (ks_sysname (HOSTLOGIN, sysfile) == NULL) + return (ERR); + if ((hp = ks_rhosts (sysfile)) == NULL) + return (ERR); + update++; + } + } else { + /* Use system default irafhosts. */ + if (ks_sysname (HOSTLOGIN, sysfile) == NULL) + return (ERR); + if ((hp = ks_rhosts (sysfile)) == NULL) + return (ERR); + update++; + } + + /* Search the node list for an entry for the named host. + */ + for (i=0, np=NULL; i < hp->nnodes; i++) { + namep = hp->node[i].name; + if (strcmp (hostname, namep) == 0 || strcmp (namep, "*") == 0) { + np = &hp->node[i]; + break; + } + } + + /* Get the login name. If this is "disable" networking is disabled + * for the given node entry. + */ + if (np->login[0] && strcmp(np->login,"disable") == 0) { + free ((char *)hp); + return (ERR); + } else if (np->login[0] && strcmp(np->login,USER) != 0) { + strcpy (loginname, np->login); + } else + strcpy (loginname, username); + + /* Get the password. */ + if (np->password[0]) { + if (strcmp (np->password, USER) == 0) { + if (ks->protocol == C_RSH) + password[0] = EOS; + else + goto query; /* need a valid password for rexec */ + } else if (strcmp (np->password, "?") == 0) { +query: if ((namep = ks_getpass (loginname, hostname))) + strcpy (password, namep); + else + password[0] = EOS; + } else + strcpy (password, np->password); + } else + password[0] = EOS; + + /* + * Set up ksparam structure. Check to see if any of the irafhosts + * parameter values are out of range; if so, set the default values, + * and mark the file for updating. + * + * NOTE -- If possible, the user should have the same port number and + * authorization code (e.g., same .irafhosts file) on all local nodes. + * All we can do here is manage the file on the local node. It is up + * to the user to make all the files the same. + */ + + /* The port number for the in.irafksd daemon should be unique for + * every user, as well as unique in the sense that no other network + * service uses the port. This is impossible to guarantee, but we + * can come close by choosing port numbers in the high range for a + * short integer, using the user's UID to attempt to give each user + * a unique port. + */ + if (hp->hiport == DEFAULT) { + ks->hiport = DEF_HIPORT; + } else if (hp->hiport > MAX_HIPORT || hp->hiport < MIN_HIPORT) { + ks->hiport = DEF_HIPORT; + hp->hiport = DEFAULT; + update++; + } else + ks->hiport = hp->hiport; + + if (hp->port == 0) + ks->port = 0; + else if (hp->port > MAX_HIPORT || hp->port < IPPORT_RESERVED) + ks->port = ks->hiport - (getuid() % 10000); + else + ks->port = hp->port; + + /* Every user should also have a unique authorization code. It should + * be next to impossible to predict apriori, so that user A cannot + * predict user B's authorization code. The number is arbitary, + * and can be changed by the user by editing the irafhosts file. + * Any heuristic which produces a reasonable unique and unpredictable + * number will do. We use the system clock time and a snapshot of + * the machine registers as saved in a setjmp. Given that any iraf + * program can cause a network request which results in generation + * of an authorization code, and the point at which this request + * occurs during the execution of a task is arbtrary, the register + * set should be pretty unpredictable. + */ + if (hp->auth == DEFAULT) { + jmp_buf jmpbuf; + int value; + + setjmp (jmpbuf); + value = time(NULL); + for (i=0; i < sizeof(jmpbuf)/sizeof(int); i++) + value ^= ((int *)jmpbuf)[i]; + value = (value << 13) / 1000 * 1000; + if (value < 0) + value = -value; + value += (getuid() % 1000); + ks->auth = hp->auth = value; + update++; + } else + ks->auth = hp->auth; + + /* If KSAUTH is defined in the user environment this overrides the + * value given in the .irafhosts file. This allows authorization + * codes to be dynamically allocated at login time if someone doesn't + * want to take the risk of having their authorization code in the + * irafhosts file. + */ + if ((authp = getenv(KSAUTH)) && (auth = atoi(authp))) + ks->auth = auth; + + /* The timeout value is the time in seconds after which the in.irafksd + * daemon will shutdown if idle. + */ + if (hp->timeout == DEFAULT) { + ks->timeout = DEF_TIMEOUT; + } else if (hp->timeout < MIN_TIMEOUT) { + ks->timeout = DEF_TIMEOUT; + hp->timeout = DEFAULT; + update++; + } else + ks->timeout = hp->timeout; + + /* Check for any node specific KS parameter overrides. */ + if (np->port) + ks->port = np->port; + if (np->auth) + ks->auth = np->auth; + if (np->hiport) + ks->hiport = np->hiport; + if (np->timeout) + ks->timeout = np->timeout; + if (np->protocol) + ks->protocol = np->protocol; + + dbgmsg1 ("ks.port = %d\n", ks->port); + dbgmsg1 ("ks.hiport = %d\n", ks->hiport); + dbgmsg1 ("ks.timeout = %d\n", ks->timeout); + + /* Update irafhosts if necessary. */ + if (update || (hp->mode & 077)) + ks_whosts (hp, userfile); + + free ((char *)hp); + return (0); +} + + +/* KS_USERNAME -- Convert the given filename into a user home directory + * relative pathname. A pointer to a buffer containing the pathname is + * returned as the function value. If the pointer "username" is nonnull + * the user's name is returned as well. + */ +static char * +ks_username (char *filename, char *pathname, char *username) +{ + register struct passwd *pwd; + + pwd = getpwuid (getuid()); + if (pwd == NULL) + return (NULL); + + strcpy (pathname, pwd->pw_dir); + strcat (pathname, "/"); + strcat (pathname, IRAFHOSTS); + if (username) + strcpy (username, pwd->pw_name); + + endpwent(); + return (pathname); +} + + +/* KS_SYSNAME -- Convert the given filename into an iraf$dev pathname. + * A pointer to a buffer containing the pathname is returned as the + * function value. + */ +static char * +ks_sysname (char *filename, char *pathname) +{ + XCHAR irafdir[SZ_PATHNAME+1]; + XINT x_maxch=SZ_PATHNAME, x_nchars; + extern int ZGTENV(); + + + ZGTENV ("iraf", irafdir, &x_maxch, &x_nchars); + if (x_nchars <= 0) + return (NULL); + + strcpy (pathname, (char *)irafdir); + strcat (pathname, HOSTLOGIN); + + return (pathname); +} + + +/* KS_RHOSTS -- Read the named irafhosts file into a descriptor, returning + * the descriptor as the function value. + */ +static struct irafhosts * +ks_rhosts (char *filename) +{ + char lbuf[SZ_LINE]; + char word[SZ_LINE]; + struct irafhosts *hp; + struct nodelist *np; + struct stat st; + char *ip, *op; + int value; + FILE *fp; + + dbgmsgs ("read %s\n", filename); + + /* Open irafhosts file. */ + if ((fp = fopen (filename, "r")) == NULL) + return (NULL); + + /* Get descriptor. */ + hp = (struct irafhosts *) malloc (sizeof(struct irafhosts)); + if (hp == NULL) { + fclose (fp); + return (NULL); + } + + hp->port = DEFAULT; + hp->auth = DEFAULT; + hp->hiport = DEF_HIPORT; + hp->timeout = DEF_TIMEOUT; + hp->nheaderlines = 0; + hp->nparams = 0; + hp->nnodes = 0; + hp->mode = 0; + op = hp->sbuf; + + if (fstat (fileno(fp), &st) == 0) + hp->mode = st.st_mode; + + /* Get file header. */ + while (fgets (op, SZ_LINE, fp)) + if (op[0] == '#' || isspace(op[0])) { + hp->header[hp->nheaderlines++] = op; + op += strlen(op) + 1; + } else { + strcpy (lbuf, op); + break; + } + + /* Everything else is a parameter assignment (param =), a node + * entry (node :), or ignored. + */ + do { + ip = lbuf; + if (*ip == '#' || isspace(*ip)) + continue; + + ks_getword (&ip, word); + while (*ip && isspace(*ip)) + ip++; + + if (*ip == '=') { + for (ip++; *ip && isspace(*ip); ip++) + ; + if (strncmp (ip, "default", 7) == 0) + value = DEFAULT; + else if (isdigit (*ip)) + value = atoi (ip); + else + value = 0; + + if (strcmp (word, "port") == 0) + hp->port = value; + else if (strcmp (word, "auth") == 0) + hp->auth = value; + else if (strcmp (word, "hiport") == 0) + hp->hiport = value; + else if (strcmp (word, "timeout") == 0) + hp->timeout = value; + /* else disregard */ + + hp->nparams++; + + } else if (*ip == ':') { + /* Node entry. + */ + np = &hp->node[hp->nnodes++]; /* nodename */ + strcpy (op, word); + np->name = op; + op += strlen(op) + 1; + + ip++; + ks_getword (&ip, word); /* loginname */ + strcpy (op, word); + np->login = op; + op += strlen(op) + 1; + + ks_getword (&ip, word); /* password */ + strcpy (op, word); + np->password = op; + op += strlen(op) + 1; + + /* Process any optional networking paramaeter overrides. + * These are in the form param=value where param is port, + * auth, etc. + */ + np->port = 0; + np->auth = 0; + np->hiport = 0; + np->timeout = 0; + np->protocol = 0; + + while (ks_getword (&ip, word)) { + if (strncmp (word, "port=", 5) == 0) { + np->port = atoi (word + 5); + } else if (strncmp (word, "auth=", 5) == 0) { + np->auth = atoi (word + 5); + } else if (strncmp (word, "hiport=", 7) == 0) { + np->hiport = atoi (word + 7); + } else if (strncmp (word, "timeout=", 8) == 0) { + np->timeout = atoi (word + 8); + } else if (strncmp (word, "protocol=", 9) == 0) { + if (strcmp (word + 9, "rsh") == 0) + np->protocol = C_RSH; + else if (strcmp (word + 9, "rex") == 0) + np->protocol = C_REXEC; + else if (strcmp (word + 9, "rcb") == 0) + np->protocol = C_REXEC_CALLBACK; + } + } + } + + hp->sbuflen = op - hp->sbuf; + if (hp->sbuflen + SZ_LINE > SZ_SBUF) + break; + + } while (fgets (lbuf, SZ_LINE, fp)); + + fclose (fp); + return (hp); +} + + +/* KS_GETWORD -- Get a quoted or whitespace delimited word. + */ +static int +ks_getword (char **ipp, char *obuf) +{ + register char *ip = *ipp, *op = obuf; + + while (*ip && isspace(*ip)) + ip++; + + if (*ip == '"') { + for (ip++; *ip && *ip != '"'; ) + *op++ = *ip++; + } else { + while (*ip && !isspace(*ip)) + *op++ = *ip++; + } + + *op = EOS; + *ipp = ip; + return (op - obuf); +} + + +/* KS_WHOSTS -- Write out a hosts file from the internal descriptor to disk. + */ +static void +ks_whosts ( + struct irafhosts *hp, + char *filename +) +{ + register char *ip; + struct nodelist *np; + int fd, q, i; + FILE *fp; + + dbgmsgs ("update %s\n", filename); + + /* Open new irafhosts file. */ + unlink (filename); + if ((fd = creat (filename, 0600)) < 0) + return; + if ((fp = fdopen(fd,"w")) == NULL) { + close (fd); + unlink (filename); + return; + } + + /* Output any header comments. */ + for (i=0; i < hp->nheaderlines; i++) + fputs (hp->header[i], fp); + + /* Output the networking parameters. */ + if (hp->port == DEFAULT) + fprintf (fp, "port = default\n"); + else + fprintf (fp, "port = %d\n", hp->port); + fprintf (fp, "auth = %d\n", hp->auth); + if (hp->hiport == DEFAULT) + fprintf (fp, "hiport = default\n"); + else + fprintf (fp, "hiport = %d\n", hp->hiport); + if (hp->timeout == DEFAULT) + fprintf (fp, "timeout = default\n"); + else + fprintf (fp, "timeout = %d\n", hp->timeout); + fprintf (fp, "\n"); + + /* Output each "node : login password" sequence, quoting the login + * and password strings if they contain any whitespace. + */ + for (i=0; i < hp->nnodes; i++) { + np = &hp->node[i]; + + /* Output username. */ + fprintf (fp, "%s\t:", np->name); + for (q=0, ip=np->login; *ip && !(q = isspace(*ip)); ip++) + ; + fprintf (fp, q ? " \"%s\"" : " %s", np->login); + + /* Output password field. */ + for (q=0, ip=np->password; *ip && !(q = isspace(*ip)); ip++) + ; + fprintf (fp, q ? " \"%s\"" : " %s", np->password); + + /* Add any optional parameter overrides given in the file when + * originally read. + */ + if (np->port) + fprintf (fp, " port=%d", np->port); + if (np->auth) + fprintf (fp, " auth=%d", np->auth); + if (np->hiport) + fprintf (fp, " hiport=%d", np->hiport); + if (np->timeout) + fprintf (fp, " timeout=%d", np->timeout); + if (np->protocol) { + fprintf (fp, " protocol="); + switch (np->protocol) { + case C_REXEC: + fprintf (fp, "rex"); + break; + case C_REXEC_CALLBACK: + fprintf (fp, "rcb"); + break; + default: + fprintf (fp, "rsh"); + break; + } + } + + fprintf (fp, "\n"); + } + + fclose (fp); +} + + +/* KS_GETPASS -- Access the terminal in raw mode to get the user's + * password. + */ +static char *ks_getpass (char *user, char *host) +{ + static char password[SZ_NAME]; + char prompt[80]; + int tty, n; +#ifdef SYSV + struct termios tc, tc_save; +#else + struct sgttyb ttystat; + int sg_flags; +#endif + + if ((tty = open ("/dev/tty", 2)) == ERR) + return (NULL); + + sprintf (prompt, "Password (%s@%s): ", user, host); + write (tty, prompt, strlen(prompt)); + +#ifdef SYSV + tcgetattr (tty, &tc); + tc_save = tc; + + tc.c_lflag &= + ~(0 | ECHO | ECHOE | ECHOK | ECHONL); + tc.c_oflag |= + (0 | TAB3 | OPOST | ONLCR); + tc.c_oflag &= + ~(0 | OCRNL | ONOCR | ONLRET); + + tc.c_cc[VMIN] = 1; + tc.c_cc[VTIME] = 0; + tc.c_cc[VLNEXT] = 0; + + tcsetattr (tty, TCSADRAIN, &tc); +#else + ioctl (tty, TIOCGETP, &ttystat); + sg_flags = ttystat.sg_flags; + ttystat.sg_flags &= ~ECHO; + ioctl (tty, TIOCSETP, &ttystat); +#endif + + n = read (tty, password, SZ_NAME); + write (tty, "\n", 1); + +#ifdef SYSV + tcsetattr (tty, TCSADRAIN, &tc_save); +#else + ttystat.sg_flags = sg_flags; + ioctl (tty, TIOCSETP, &ttystat); +#endif + + close (tty); + + if (n <= 0) + return (NULL); + else + password[n-1] = EOS; + + return (password); +} + + +/* PR_MASK -- Debug routine to print the current SIGCHLD mask. + */ +void pr_mask (char *str) +{ + sigset_t sigset, pending; + + if (sigprocmask (0, NULL, &sigset) < 0) + dbgmsg ("sigprocmask error"); + + dbgmsg (str); + if (sigismember (&sigset, SIGCHLD)) + dbgmsg ("pr_mask: SIGCHLD set\n"); + + if (sigpending (&pending) < 0) + dbgmsg ("sigpending error"); + if (sigismember (&pending, SIGCHLD)) + dbgmsg ("\tpr_mask: SIGCHLD pending\n"); +} |