From djm@web.us.uu.net Fri Sep 29 15:49:39 2000 Received: from MIT.EDU (SOUTH-STATION-ANNEX.MIT.EDU [18.72.1.2]) by rt-11.mit.edu (8.9.3/8.9.3) with SMTP id PAA10479 for ; Fri, 29 Sep 2000 15:49:35 -0400 (EDT) Received: from catapult.web.us.uu.net by MIT.EDU with SMTP id AA23663; Fri, 29 Sep 00 15:49:32 EDT Received: by catapult.web.us.uu.net (Postfix, from userid 515) id 603653E52; Fri, 29 Sep 2000 15:49:29 -0400 (EDT) Message-Id: <20000929194929.603653E52@catapult.web.us.uu.net> Date: Fri, 29 Sep 2000 15:49:29 -0400 (EDT) From: djm@web.us.uu.net Reply-To: djm@web.us.uu.net To: krb5-bugs@MIT.EDU Cc: web-unix-dev@web.us.uu.net, engprodstaff@eng.us.uu.net Subject: rsh timeouts patch X-Send-Pr-Version: 3.99 >Number: 892 >Category: krb5-appl >Synopsis: patch to add timeouts to rsh >Confidential: no >Severity: serious >Priority: medium >Responsible: krb5-unassigned >State: open >Class: change-request >Submitter-Id: unknown >Arrival-Date: Fri Sep 29 15:50:00 EDT 2000 >Last-Modified: Mon Oct 02 10:58:01 EDT 2000 >Originator: David MacKenzie >Organization: UUNET Technologies >Release: krb5-1.2.1 >Environment: System: BSD/OS catapult.web.us.uu.net 4.0.1 BSDI BSD/OS 4.0.1 Kernel #1: Mon May 8 23:23:57 EDT 2000 root@robby.web.us.uu.net:/usr/src/bsdi/sys/compile/SERVER+NFS i386 >Description: We use the krb5 rsh for many automated and semi-automated system administration tasks, to run commands and rdists from cron jobs and CGI programs. However, it's not robust; if a host is down or there are network problems, rsh will hang forever, and the cron or CGI program will not complete until the rsh is manually sent a SIGKILL. We could make a wrapper program that forks rsh, waits for it and times it out, but it seems more efficient and appropriate to add the missing functionality to rsh itself. Does anyone see a problem with this approach to solving the problem? >How-To-Repeat: rsh a-host-that-is-down hostname >Fix: This patch adds two timeout options to rsh. The default behavior is unchanged. Index: rsh.M =================================================================== RCS file: /export/src/CVS/usr.local/krb5-1.2/src/appl/bsd/rsh.M,v retrieving revision 1.1 diff -u -r1.1 rsh.M --- rsh.M 2000/06/30 21:57:13 1.1 +++ rsh.M 2000/09/29 19:26:21 @@ -23,9 +23,9 @@ .SH SYNOPSIS .B rsh .I host [\fB\-l\fP \fIusername\fP] [\fB\-n\fP] [\fB\-d\fP] [\fB\-k\fP \fIrealm\fP] [\fB\-f\fP | \fB\-F\fP] [\fB\-x\fP] -[\fB\-PN | \-PO\fP] +[\fB\-PN | \-PO\fP] [\fB\-C\fP \fIseconds\fP] [\fB\-T\fP \fIseconds\fP] .I command .SH DESCRIPTION .B Rsh @@ -103,6 +103,23 @@ .I /dev/null (see the BUGS section below). .TP +\fB\-C\fP \fIseconds\fP +Time out if unable to connect to the remote host within +.IR seconds . +By default, the connection does not time out. +This timeout helps prevent hanging from network problems and remote hosts that +are down. +.TP +\fB\-T\fP \fIseconds\fP +Time out if a read or write to the remote host does not complete within +.IR seconds . +The read and write buffer size is 5KB. +By default, reads and writes do not time out. +In some applications, you may know that the command you are running should +complete within a certain amount of time or something is wrong. +This timeout helps prevent hanging from network problems and remote hosts that +are down. +.TP \fB-PN\fP .TP \fB-PO\fP Index: krsh.c =================================================================== RCS file: /export/src/CVS/usr.local/krb5-1.2/src/appl/bsd/krsh.c,v retrieving revision 1.1 diff -u -r1.1 krsh.c --- krsh.c 2000/06/30 21:57:12 1.1 +++ krsh.c 2000/09/29 19:26:21 @@ -75,6 +75,10 @@ Key_schedule v4_schedule; #endif +#ifndef max +#define max(a,b) ((a) > (b) ? (a) : (b)) +#endif + /* * rsh - remote shell */ @@ -85,7 +89,7 @@ int options; int rfd2; int nflag; -krb5_sigtype sendsig(); +krb5_sigtype sendsig(), connect_alarm(), read_alarm(), write_alarm(); #ifdef KERBEROS @@ -126,7 +130,7 @@ int argc; char **argv0; { - int rem, pid; + int rem, highfd, pid; char *host=0, *cp, **ap, buf[RCMD_BUFSIZ], *args, **argv = argv0, *user = 0; register int cc; struct passwd *pwd; @@ -136,6 +140,7 @@ struct servent defaultservent; struct sockaddr_in local, foreign; int suppress = 0; + time_t connect_timeout = 0, data_timeout = 0; #ifdef POSIX_SIGNALS sigset_t omask, igmask; @@ -153,6 +158,7 @@ MSG_DAT v4_msg_data; #endif #endif /* KERBEROS */ int debug_port = 0; enum kcmd_proto kcmd_proto = KCMD_PROTOCOL_COMPAT_HACK; @@ -250,6 +256,24 @@ goto another; } #endif /* KERBEROS */ + if (argc > 0 && !strcmp(*argv, "-C")) { + argv++; argc--; + connect_timeout = atoi(*argv); + argv++; argc--; + goto another; + } + if (argc > 0 && !strcmp(*argv, "-T")) { + argv++; argc--; + data_timeout = atoi(*argv); + argv++; argc--; + goto another; + } + /* * Ignore the -L, -w, -e and -8 flags to allow aliases with rlogin * to work @@ -315,6 +339,8 @@ fprintf(stderr, "who are you?\n"); exit(1); } + + /* Flatten the command line into `args' to pass to kcmd. */ cc = 0; for (ap = argv; *ap; ap++) cc += strlen(*ap) + 1; @@ -352,6 +378,15 @@ debug_port = sp->s_port; } +#ifdef POSIX_SIGNALS + (void)sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = connect_alarm; + (void)sigaction(SIGALRM, &sa, (struct sigaction *)0); +#else /* !POSIX_SIGNALS */ + signal(SIGALRM, connect_alarm); +#endif /* POSIX_SIGNALS */ + #ifdef KERBEROS status = krb5_init_context(&bsd_context); if (status) { @@ -369,10 +404,12 @@ #ifdef HAVE_ISATTY suppress = !isatty(fileno(stderr)); #endif + if (connect_timeout) + alarm(connect_timeout); status = kcmd(&rem, &host, debug_port, pwd->pw_name, user ? user : pwd->pw_name, args, &rfd2, "host", krb_realm, &cred, 0, /* No need for sequence number */ 0, /* No need for server seq # */ @@ -381,6 +418,8 @@ 1, /* Always set anyport, there is no need not to. --proven */ suppress, &kcmd_proto); + if (connect_timeout) + alarm(0); if (status) { /* If new protocol requested, don't fall back to less secure ones. */ @@ -394,12 +433,16 @@ if (isatty(fileno(stderr))) fprintf(stderr, "Trying krb4 rsh...\n"); #endif + if (connect_timeout) + alarm(connect_timeout); status = k4cmd(&rem, &host, debug_port, pwd->pw_name, user ? user : pwd->pw_name, args, &rfd2, &v4_ticket, "rcmd", krb_realm, &v4_cred, v4_schedule, &v4_msg_data, &local, &foreign, 0L, 0); + if (connect_timeout) + alarm(0); if (status) try_normal(argv0); rcmd_stream_init_krb4(v4_cred.session, encrypt_flag, 0, 1); @@ -432,24 +475,33 @@ #endif #else /* !KERBEROS */ + if (connect_timeout) + alarm(connect_timeout); rem = rcmd(&host, debug_port, pwd->pw_name, user ? user : pwd->pw_name, args, &rfd2); + if (connect_timeout) + alarm(0); if (rem < 0) exit(1); #endif /* KERBEROS */ if (rfd2 < 0) { fprintf(stderr, "rsh: can't establish stderr\n"); exit(2); } if (options & SO_DEBUG) { if (setsockopt(rem, SOL_SOCKET, SO_DEBUG, (const char *) &one, sizeof (one)) < 0) perror("setsockopt (stdin)"); if (setsockopt(rfd2, SOL_SOCKET, SO_DEBUG, (const char *) &one, sizeof (one)) < 0) perror("setsockopt (stderr)"); } (void) setuid(getuid()); + #ifdef POSIX_SIGNALS sigemptyset(&igmask); sigaddset(&igmask, SIGINT); @@ -472,7 +524,7 @@ (void)sigaction(SIGTERM, (struct sigaction *)0, &osa); if (osa.sa_handler != SIG_IGN) (void)sigaction(SIGTERM, &sa, (struct sigaction *)0); -#else +#else /* !POSIX_SIGNALS */ #ifdef sgi omask = sigignore(mask(SIGINT)|mask(SIGQUIT)|mask(SIGTERM)); #else @@ -485,6 +537,7 @@ if (signal(SIGTERM, SIG_IGN) != SIG_IGN) signal(SIGTERM, sendsig); #endif /* POSIX_SIGNALS */ + if (nflag == 0) { pid = fork(); if (pid < 0) { @@ -493,24 +546,46 @@ } } if (!encrypt_flag) { ioctl(rfd2, FIONBIO, &one); ioctl(rem, FIONBIO, &one); } + if (nflag == 0 && pid == 0) { + /* Child. Reads from stdin, writes to remote host. */ char *bp; int wc; +#if 0 fd_set rembits; +#endif (void) close(rfd2); + +#ifdef POSIX_SIGNALS + (void)sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = write_alarm; + (void)sigaction(SIGALRM, &sa, (struct sigaction *)0); +#else /* !POSIX_SIGNALS */ + signal(SIGALRM, connect_alarm); +#endif /* POSIX_SIGNALS */ + reread: errno = 0; cc = read(0, buf, sizeof buf); if (cc <= 0) goto done; bp = buf; + if (data_timeout) + alarm(data_timeout); rewrite: +#if 0 FD_ZERO(&rembits); FD_SET(rem, &rembits); + /* Why 8*sizeof(rembits), and not just rem+1? + For that matter, why not just do a blocking write, + and omit the select? -djm */ if (select(8*sizeof(rembits), 0, &rembits, 0, 0) < 0) { if (errno != EINTR) { perror("select"); @@ -520,12 +595,15 @@ } if (FD_ISSET(rem, &rembits) == 0) goto rewrite; +#endif wc = rcmd_stream_write(rem, bp, cc, 0); if (wc < 0) { if ((errno == EWOULDBLOCK) || (errno == EAGAIN)) goto rewrite; goto done; } + if (data_timeout) + alarm(0); cc -= wc; bp += wc; if (cc == 0) goto reread; @@ -534,69 +612,120 @@ (void) shutdown(rem, 1); exit(0); } + + /* Parent. Reads from remote host, writes to stdout and stderr. */ #ifdef POSIX_SIGNALS sigprocmask(SIG_SETMASK, &omask, (sigset_t*)0); -#else + (void)sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = read_alarm; + (void)sigaction(SIGALRM, &sa, (struct sigaction *)0); +#else /* !POSIX_SIGNALS */ #ifndef sgi sigsetmask(omask); #endif + signal(SIGALRM, read_alarm); #endif /* POSIX_SIGNALS */ + FD_ZERO(&readfrom); FD_SET(rfd2, &readfrom); FD_SET(rem, &readfrom); + highfd = max(rfd2, rem) + 1; + do { - ready = readfrom; - if (select(8*sizeof(ready), &ready, 0, 0, 0) < 0) { + if (data_timeout) + alarm(data_timeout); + while (1) { + ready = readfrom; + if (select(highfd, &ready, 0, 0, 0) >= 0) + break; if (errno != EINTR) { perror("select"); exit(1); } - continue; } +#if 0 /* This code is probably unnecessary. -djm */ + if (data_timeout) + alarm(0); +#endif if (FD_ISSET(rfd2, &ready)) { + if (data_timeout) + alarm(data_timeout); errno = 0; cc = rcmd_stream_read(rfd2, buf, sizeof buf, 1); if (cc <= 0) { if ((errno != EWOULDBLOCK) && (errno != EAGAIN)) FD_CLR(rfd2, &readfrom); - } else + } else { + if (data_timeout) + alarm(0); (void) write(2, buf, cc); + } } if (FD_ISSET(rem, &ready)) { + if (data_timeout) + alarm(data_timeout); errno = 0; cc = rcmd_stream_read(rem, buf, sizeof buf, 0); if (cc <= 0) { if ((errno != EWOULDBLOCK) && (errno != EAGAIN)) FD_CLR(rem, &readfrom); - } else + } else { + if (data_timeout) + alarm(0); (void) write(1, buf, cc); + } } } while (FD_ISSET(rem, &readfrom) || FD_ISSET(rfd2, &readfrom)); if (nflag == 0) (void) kill(pid, SIGKILL); exit(0); + usage: fprintf(stderr, - "usage: \trsh host [ -PN / -PO ] [ -l login ] [ -n ] [ -x ] [ -f / -F] command\n"); + "usage: \trsh host [-PN / -PO] [-l login] [-n] [-x] [-f / -F] [-C seconds] [-T seconds] command\n"); fprintf(stderr, - "OR \trsh [ -PN / -PO ] [ -l login ] [-n ] [ -x ] [ -f / -F ] host command\n"); + "OR \trsh [-PN / -PO] [-l login] [-n] [-x] [-f / -F] [-C seconds] [-T seconds] host command\n"); exit(1); } - krb5_sigtype sendsig(signo) char signo; { (void) rcmd_stream_write(rfd2, &signo, 1, 1); } +krb5_sigtype connect_alarm(signo) + char signo; +{ + fprintf(stderr, "krsh: remote connection timed out\n"); + exit(1); +} + +krb5_sigtype read_alarm(signo) + char signo; +{ + fprintf(stderr, "krsh: remote read timed out\n"); + exit(1); +} + +krb5_sigtype write_alarm(signo) + char signo; +{ + fprintf(stderr, "krsh: remote write timed out\n"); + exit(1); +} #ifdef KERBEROS void try_normal(argv) char **argv; { char *host; #ifndef KRB5_ATHENA_COMPAT @@ -623,6 +752,7 @@ fflush(stderr); execv(UCB_RSH, argv); perror("exec"); exit(1); } #endif /* KERBEROS */ >Audit-Trail: From: "David J. MacKenzie" To: krb5-bugs@MIT.EDU, krb5-unassigned@rt-11.mit.edu Cc: Subject: Re: krb5-appl/892: rsh timeouts patch Date: Fri, 29 Sep 2000 18:00:59 -0400 I just noticed a copy and paste error in a non-POSIX signals block of my patch. Also, I had done some hand-editing to remove some other local changes that made the patch program choke. Here's a clean, corrected patch. Index: rsh.M =================================================================== RCS file: /export/src/CVS/usr.local/krb5-1.2/src/appl/bsd/rsh.M,v retrieving revision 1.1 diff -u -r1.1 rsh.M --- rsh.M 2000/06/30 21:57:13 1.1 +++ rsh.M 2000/09/29 21:55:36 @@ -25,7 +25,7 @@ .I host [\fB\-l\fP \fIusername\fP] [\fB\-n\fP] [\fB\-d\fP] [\fB\-k\fP \fIrealm\fP] [\fB\-f\fP | \fB\-F\fP] [\fB\-x\fP] -[\fB\-PN | \-PO\fP] +[\fB\-PN | \-PO\fP] [\fB\-C\fP \fIseconds\fP] [\fB\-T\fP \fIseconds\fP] .I command .SH DESCRIPTION .B Rsh @@ -102,6 +102,23 @@ redirects input from the special device .I /dev/null (see the BUGS section below). +.TP +\fB\-C\fP \fIseconds\fP +Time out if unable to connect to the remote host within +.IR seconds . +By default, the connection does not time out. +This timeout helps prevent hanging from network problems and remote hosts that +are down. +.TP +\fB\-T\fP \fIseconds\fP +Time out if a read or write to the remote host does not complete within +.IR seconds . +The read and write buffer size is 5KB. +By default, reads and writes do not time out. +In some applications, you may know that the command you are running should +complete within a certain amount of time or something is wrong. +This timeout helps prevent hanging from network problems and remote hosts that +are down. .TP \fB-PN\fP .TP Index: krsh.c =================================================================== RCS file: /export/src/CVS/usr.local/krb5-1.2/src/appl/bsd/krsh.c,v retrieving revision 1.1 diff -u -r1.1 krsh.c --- krsh.c 2000/06/30 21:57:12 1.1 +++ krsh.c 2000/09/29 21:55:36 @@ -75,6 +75,10 @@ Key_schedule v4_schedule; #endif +#ifndef max +#define max(a,b) ((a) > (b) ? (a) : (b)) +#endif + /* * rsh - remote shell */ @@ -85,7 +89,7 @@ int options; int rfd2; int nflag; -krb5_sigtype sendsig(); +krb5_sigtype sendsig(), connect_alarm(), read_alarm(), write_alarm(); #ifdef KERBEROS @@ -126,7 +130,7 @@ int argc; char **argv0; { - int rem, pid; + int rem, highfd, pid; char *host=0, *cp, **ap, buf[RCMD_BUFSIZ], *args, **argv = argv0, *user = 0; register int cc; struct passwd *pwd; @@ -136,6 +140,7 @@ struct servent defaultservent; struct sockaddr_in local, foreign; int suppress = 0; + time_t connect_timeout = 0, data_timeout = 0; #ifdef POSIX_SIGNALS sigset_t omask, igmask; @@ -250,6 +255,19 @@ goto another; } #endif /* KERBEROS */ + if (argc > 0 && !strcmp(*argv, "-C")) { + argv++; argc--; + connect_timeout = atoi(*argv); + argv++; argc--; + goto another; + } + if (argc > 0 && !strcmp(*argv, "-T")) { + argv++; argc--; + data_timeout = atoi(*argv); + argv++; argc--; + goto another; + } + /* * Ignore the -L, -w, -e and -8 flags to allow aliases with rlogin * to work @@ -315,6 +333,8 @@ fprintf(stderr, "who are you?\n"); exit(1); } + + /* Flatten the command line into `args' to pass to kcmd. */ cc = 0; for (ap = argv; *ap; ap++) cc += strlen(*ap) + 1; @@ -352,6 +372,15 @@ debug_port = sp->s_port; } +#ifdef POSIX_SIGNALS + (void)sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = connect_alarm; + (void)sigaction(SIGALRM, &sa, (struct sigaction *)0); +#else /* !POSIX_SIGNALS */ + signal(SIGALRM, connect_alarm); +#endif /* POSIX_SIGNALS */ + #ifdef KERBEROS status = krb5_init_context(&bsd_context); if (status) { @@ -369,6 +398,8 @@ #ifdef HAVE_ISATTY suppress = !isatty(fileno(stderr)); #endif + if (connect_timeout) + alarm(connect_timeout); status = kcmd(&rem, &host, debug_port, pwd->pw_name, user ? user : pwd->pw_name, @@ -381,6 +412,8 @@ 1, /* Always set anyport, there is no need not to. --proven */ suppress, &kcmd_proto); + if (connect_timeout) + alarm(0); if (status) { /* If new protocol requested, don't fall back to less secure ones. */ @@ -394,12 +427,16 @@ if (isatty(fileno(stderr))) fprintf(stderr, "Trying krb4 rsh...\n"); #endif + if (connect_timeout) + alarm(connect_timeout); status = k4cmd(&rem, &host, debug_port, pwd->pw_name, user ? user : pwd->pw_name, args, &rfd2, &v4_ticket, "rcmd", krb_realm, &v4_cred, v4_schedule, &v4_msg_data, &local, &foreign, 0L, 0); + if (connect_timeout) + alarm(0); if (status) try_normal(argv0); rcmd_stream_init_krb4(v4_cred.session, encrypt_flag, 0, 1); @@ -432,8 +469,12 @@ #endif #else /* !KERBEROS */ + if (connect_timeout) + alarm(connect_timeout); rem = rcmd(&host, debug_port, pwd->pw_name, user ? user : pwd->pw_name, args, &rfd2); + if (connect_timeout) + alarm(0); if (rem < 0) exit(1); #endif /* KERBEROS */ @@ -450,6 +491,7 @@ perror("setsockopt (stderr)"); } (void) setuid(getuid()); + #ifdef POSIX_SIGNALS sigemptyset(&igmask); sigaddset(&igmask, SIGINT); @@ -472,7 +514,7 @@ (void)sigaction(SIGTERM, (struct sigaction *)0, &osa); if (osa.sa_handler != SIG_IGN) (void)sigaction(SIGTERM, &sa, (struct sigaction *)0); -#else +#else /* !POSIX_SIGNALS */ #ifdef sgi omask = sigignore(mask(SIGINT)|mask(SIGQUIT)|mask(SIGTERM)); #else @@ -485,6 +527,7 @@ if (signal(SIGTERM, SIG_IGN) != SIG_IGN) signal(SIGTERM, sendsig); #endif /* POSIX_SIGNALS */ + if (nflag == 0) { pid = fork(); if (pid < 0) { @@ -496,21 +539,41 @@ ioctl(rfd2, FIONBIO, &one); ioctl(rem, FIONBIO, &one); } + if (nflag == 0 && pid == 0) { + /* Child. Reads from stdin, writes to remote host. */ char *bp; int wc; +#if 0 fd_set rembits; +#endif (void) close(rfd2); + +#ifdef POSIX_SIGNALS + (void)sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = write_alarm; + (void)sigaction(SIGALRM, &sa, (struct sigaction *)0); +#else /* !POSIX_SIGNALS */ + signal(SIGALRM, write_alarm); +#endif /* POSIX_SIGNALS */ + reread: errno = 0; cc = read(0, buf, sizeof buf); if (cc <= 0) goto done; bp = buf; + if (data_timeout) + alarm(data_timeout); rewrite: +#if 0 FD_ZERO(&rembits); FD_SET(rem, &rembits); + /* Why 8*sizeof(rembits), and not just rem+1? + For that matter, why not just do a blocking write, + and omit the select? -djm */ if (select(8*sizeof(rembits), 0, &rembits, 0, 0) < 0) { if (errno != EINTR) { perror("select"); @@ -520,12 +583,15 @@ } if (FD_ISSET(rem, &rembits) == 0) goto rewrite; +#endif wc = rcmd_stream_write(rem, bp, cc, 0); if (wc < 0) { if ((errno == EWOULDBLOCK) || (errno == EAGAIN)) goto rewrite; goto done; } + if (data_timeout) + alarm(0); cc -= wc; bp += wc; if (cc == 0) goto reread; @@ -534,63 +600,108 @@ (void) shutdown(rem, 1); exit(0); } + + /* Parent. Reads from remote host, writes to stdout and stderr. */ #ifdef POSIX_SIGNALS sigprocmask(SIG_SETMASK, &omask, (sigset_t*)0); -#else + (void)sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = read_alarm; + (void)sigaction(SIGALRM, &sa, (struct sigaction *)0); +#else /* !POSIX_SIGNALS */ #ifndef sgi sigsetmask(omask); #endif + signal(SIGALRM, read_alarm); #endif /* POSIX_SIGNALS */ + FD_ZERO(&readfrom); FD_SET(rfd2, &readfrom); FD_SET(rem, &readfrom); + highfd = max(rfd2, rem) + 1; + do { - ready = readfrom; - if (select(8*sizeof(ready), &ready, 0, 0, 0) < 0) { + if (data_timeout) + alarm(data_timeout); + while (1) { + ready = readfrom; + if (select(highfd, &ready, 0, 0, 0) >= 0) + break; if (errno != EINTR) { perror("select"); exit(1); } - continue; } + if (data_timeout) + alarm(0); if (FD_ISSET(rfd2, &ready)) { + if (data_timeout) + alarm(data_timeout); errno = 0; cc = rcmd_stream_read(rfd2, buf, sizeof buf, 1); if (cc <= 0) { if ((errno != EWOULDBLOCK) && (errno != EAGAIN)) FD_CLR(rfd2, &readfrom); - } else + } else { + if (data_timeout) + alarm(0); (void) write(2, buf, cc); + } } if (FD_ISSET(rem, &ready)) { + if (data_timeout) + alarm(data_timeout); errno = 0; cc = rcmd_stream_read(rem, buf, sizeof buf, 0); if (cc <= 0) { if ((errno != EWOULDBLOCK) && (errno != EAGAIN)) FD_CLR(rem, &readfrom); - } else + } else { + if (data_timeout) + alarm(0); (void) write(1, buf, cc); + } } } while (FD_ISSET(rem, &readfrom) || FD_ISSET(rfd2, &readfrom)); if (nflag == 0) (void) kill(pid, SIGKILL); exit(0); + usage: fprintf(stderr, - "usage: \trsh host [ -PN / -PO ] [ -l login ] [ -n ] [ -x ] [ -f / -F] command\n"); + "usage: \trsh host [-PN / -PO] [-l login] [-n] [-x] [-f / -F] [-C seconds] [-T seconds] command\n"); fprintf(stderr, - "OR \trsh [ -PN / -PO ] [ -l login ] [-n ] [ -x ] [ -f / -F ] host command\n"); + "OR \trsh [-PN / -PO] [-l login] [-n] [-x] [-f / -F] [-C seconds] [-T seconds] host command\n"); exit(1); } - krb5_sigtype sendsig(signo) char signo; { (void) rcmd_stream_write(rfd2, &signo, 1, 1); } +krb5_sigtype connect_alarm(signo) + char signo; +{ + fprintf(stderr, "krsh: remote connection timed out\n"); + exit(1); +} + +krb5_sigtype read_alarm(signo) + char signo; +{ + fprintf(stderr, "krsh: remote read timed out\n"); + exit(1); +} + +krb5_sigtype write_alarm(signo) + char signo; +{ + fprintf(stderr, "krsh: remote write timed out\n"); + exit(1); +} #ifdef KERBEROS From: "David J. MacKenzie" To: krb5-bugs@MIT.EDU, krb5-unassigned@rt-11.mit.edu Cc: Subject: Re: krb5-appl/892: rsh timeouts patch Date: Mon, 02 Oct 2000 10:57:07 -0400 Here's another improved version of the patch, which adds the host name to the timeout messages. Index: rsh.M =================================================================== RCS file: /export/src/CVS/usr.local/krb5-1.2/src/appl/bsd/rsh.M,v retrieving revision 1.1 diff -u -r1.1 rsh.M --- rsh.M 2000/06/30 21:57:13 1.1 +++ rsh.M 2000/10/02 14:44:41 @@ -25,7 +25,7 @@ .I host [\fB\-l\fP \fIusername\fP] [\fB\-n\fP] [\fB\-d\fP] [\fB\-k\fP \fIrealm\fP] [\fB\-f\fP | \fB\-F\fP] [\fB\-x\fP] -[\fB\-PN | \-PO\fP] +[\fB\-PN | \-PO\fP] [\fB\-C\fP \fIseconds\fP] [\fB\-T\fP \fIseconds\fP] .I command .SH DESCRIPTION .B Rsh @@ -102,6 +102,23 @@ redirects input from the special device .I /dev/null (see the BUGS section below). +.TP +\fB\-C\fP \fIseconds\fP +Time out if unable to connect to the remote host within +.IR seconds . +By default, the connection does not time out. +This timeout helps prevent hanging from network problems and remote hosts that +are down. +.TP +\fB\-T\fP \fIseconds\fP +Time out if a read or write to the remote host does not complete within +.IR seconds . +The read and write buffer size is 5KB. +By default, reads and writes do not time out. +In some applications, you may know that the command you are running should +complete within a certain amount of time or something is wrong. +This timeout helps prevent hanging from network problems and remote hosts that +are down. .TP \fB-PN\fP .TP Index: krsh.c =================================================================== RCS file: /export/src/CVS/usr.local/krb5-1.2/src/appl/bsd/krsh.c,v retrieving revision 1.1 diff -u -r1.1 krsh.c --- krsh.c 2000/06/30 21:57:12 1.1 +++ krsh.c 2000/10/02 14:44:41 @@ -75,6 +75,10 @@ Key_schedule v4_schedule; #endif +#ifndef max +#define max(a,b) ((a) > (b) ? (a) : (b)) +#endif + /* * rsh - remote shell */ @@ -85,7 +89,7 @@ int options; int rfd2; int nflag; -krb5_sigtype sendsig(); +krb5_sigtype sendsig(), connect_alarm(), read_alarm(), write_alarm(); #ifdef KERBEROS @@ -103,6 +107,7 @@ int encrypt_flag = 0; char *krb_realm = (char *)0; +char *host = 0; void try_normal(); #endif /* KERBEROS */ @@ -126,8 +131,8 @@ int argc; char **argv0; { - int rem, pid; - char *host=0, *cp, **ap, buf[RCMD_BUFSIZ], *args, **argv = argv0, *user = 0; + int rem, highfd, pid; + char *cp, **ap, buf[RCMD_BUFSIZ], *args, **argv = argv0, *user = 0; register int cc; struct passwd *pwd; fd_set readfrom, ready; @@ -136,6 +141,7 @@ struct servent defaultservent; struct sockaddr_in local, foreign; int suppress = 0; + time_t connect_timeout = 0, data_timeout = 0; #ifdef POSIX_SIGNALS sigset_t omask, igmask; @@ -250,6 +256,19 @@ goto another; } #endif /* KERBEROS */ + if (argc > 0 && !strcmp(*argv, "-C")) { + argv++; argc--; + connect_timeout = atoi(*argv); + argv++; argc--; + goto another; + } + if (argc > 0 && !strcmp(*argv, "-T")) { + argv++; argc--; + data_timeout = atoi(*argv); + argv++; argc--; + goto another; + } + /* * Ignore the -L, -w, -e and -8 flags to allow aliases with rlogin * to work @@ -315,6 +334,8 @@ fprintf(stderr, "who are you?\n"); exit(1); } + + /* Flatten the command line into `args' to pass to kcmd. */ cc = 0; for (ap = argv; *ap; ap++) cc += strlen(*ap) + 1; @@ -352,6 +373,15 @@ debug_port = sp->s_port; } +#ifdef POSIX_SIGNALS + (void)sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = connect_alarm; + (void)sigaction(SIGALRM, &sa, (struct sigaction *)0); +#else /* !POSIX_SIGNALS */ + signal(SIGALRM, connect_alarm); +#endif /* POSIX_SIGNALS */ + #ifdef KERBEROS status = krb5_init_context(&bsd_context); if (status) { @@ -369,6 +399,8 @@ #ifdef HAVE_ISATTY suppress = !isatty(fileno(stderr)); #endif + if (connect_timeout) + alarm(connect_timeout); status = kcmd(&rem, &host, debug_port, pwd->pw_name, user ? user : pwd->pw_name, @@ -381,6 +413,8 @@ 1, /* Always set anyport, there is no need not to. --proven */ suppress, &kcmd_proto); + if (connect_timeout) + alarm(0); if (status) { /* If new protocol requested, don't fall back to less secure ones. */ @@ -394,12 +428,16 @@ if (isatty(fileno(stderr))) fprintf(stderr, "Trying krb4 rsh...\n"); #endif + if (connect_timeout) + alarm(connect_timeout); status = k4cmd(&rem, &host, debug_port, pwd->pw_name, user ? user : pwd->pw_name, args, &rfd2, &v4_ticket, "rcmd", krb_realm, &v4_cred, v4_schedule, &v4_msg_data, &local, &foreign, 0L, 0); + if (connect_timeout) + alarm(0); if (status) try_normal(argv0); rcmd_stream_init_krb4(v4_cred.session, encrypt_flag, 0, 1); @@ -432,8 +470,12 @@ #endif #else /* !KERBEROS */ + if (connect_timeout) + alarm(connect_timeout); rem = rcmd(&host, debug_port, pwd->pw_name, user ? user : pwd->pw_name, args, &rfd2); + if (connect_timeout) + alarm(0); if (rem < 0) exit(1); #endif /* KERBEROS */ @@ -450,6 +492,7 @@ perror("setsockopt (stderr)"); } (void) setuid(getuid()); + #ifdef POSIX_SIGNALS sigemptyset(&igmask); sigaddset(&igmask, SIGINT); @@ -472,7 +515,7 @@ (void)sigaction(SIGTERM, (struct sigaction *)0, &osa); if (osa.sa_handler != SIG_IGN) (void)sigaction(SIGTERM, &sa, (struct sigaction *)0); -#else +#else /* !POSIX_SIGNALS */ #ifdef sgi omask = sigignore(mask(SIGINT)|mask(SIGQUIT)|mask(SIGTERM)); #else @@ -485,6 +528,7 @@ if (signal(SIGTERM, SIG_IGN) != SIG_IGN) signal(SIGTERM, sendsig); #endif /* POSIX_SIGNALS */ + if (nflag == 0) { pid = fork(); if (pid < 0) { @@ -496,21 +540,41 @@ ioctl(rfd2, FIONBIO, &one); ioctl(rem, FIONBIO, &one); } + if (nflag == 0 && pid == 0) { + /* Child. Reads from stdin, writes to remote host. */ char *bp; int wc; +#if 0 fd_set rembits; +#endif (void) close(rfd2); + +#ifdef POSIX_SIGNALS + (void)sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = write_alarm; + (void)sigaction(SIGALRM, &sa, (struct sigaction *)0); +#else /* !POSIX_SIGNALS */ + signal(SIGALRM, write_alarm); +#endif /* POSIX_SIGNALS */ + reread: errno = 0; cc = read(0, buf, sizeof buf); if (cc <= 0) goto done; bp = buf; + if (data_timeout) + alarm(data_timeout); rewrite: +#if 0 FD_ZERO(&rembits); FD_SET(rem, &rembits); + /* Why 8*sizeof(rembits), and not just rem+1? + For that matter, why not just do a blocking write, + and omit the select? -djm */ if (select(8*sizeof(rembits), 0, &rembits, 0, 0) < 0) { if (errno != EINTR) { perror("select"); @@ -520,12 +584,15 @@ } if (FD_ISSET(rem, &rembits) == 0) goto rewrite; +#endif wc = rcmd_stream_write(rem, bp, cc, 0); if (wc < 0) { if ((errno == EWOULDBLOCK) || (errno == EAGAIN)) goto rewrite; goto done; } + if (data_timeout) + alarm(0); cc -= wc; bp += wc; if (cc == 0) goto reread; @@ -534,63 +601,108 @@ (void) shutdown(rem, 1); exit(0); } + + /* Parent. Reads from remote host, writes to stdout and stderr. */ #ifdef POSIX_SIGNALS sigprocmask(SIG_SETMASK, &omask, (sigset_t*)0); -#else + (void)sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = read_alarm; + (void)sigaction(SIGALRM, &sa, (struct sigaction *)0); +#else /* !POSIX_SIGNALS */ #ifndef sgi sigsetmask(omask); #endif + signal(SIGALRM, read_alarm); #endif /* POSIX_SIGNALS */ + FD_ZERO(&readfrom); FD_SET(rfd2, &readfrom); FD_SET(rem, &readfrom); + highfd = max(rfd2, rem) + 1; + do { - ready = readfrom; - if (select(8*sizeof(ready), &ready, 0, 0, 0) < 0) { + if (data_timeout) + alarm(data_timeout); + while (1) { + ready = readfrom; + if (select(highfd, &ready, 0, 0, 0) >= 0) + break; if (errno != EINTR) { perror("select"); exit(1); } - continue; } + if (data_timeout) + alarm(0); if (FD_ISSET(rfd2, &ready)) { + if (data_timeout) + alarm(data_timeout); errno = 0; cc = rcmd_stream_read(rfd2, buf, sizeof buf, 1); if (cc <= 0) { if ((errno != EWOULDBLOCK) && (errno != EAGAIN)) FD_CLR(rfd2, &readfrom); - } else + } else { + if (data_timeout) + alarm(0); (void) write(2, buf, cc); + } } if (FD_ISSET(rem, &ready)) { + if (data_timeout) + alarm(data_timeout); errno = 0; cc = rcmd_stream_read(rem, buf, sizeof buf, 0); if (cc <= 0) { if ((errno != EWOULDBLOCK) && (errno != EAGAIN)) FD_CLR(rem, &readfrom); - } else + } else { + if (data_timeout) + alarm(0); (void) write(1, buf, cc); + } } } while (FD_ISSET(rem, &readfrom) || FD_ISSET(rfd2, &readfrom)); if (nflag == 0) (void) kill(pid, SIGKILL); exit(0); + usage: fprintf(stderr, - "usage: \trsh host [ -PN / -PO ] [ -l login ] [ -n ] [ -x ] [ -f / -F] command\n"); + "usage: \trsh host [-PN / -PO] [-l login] [-n] [-x] [-f / -F] [-C seconds] [-T seconds] command\n"); fprintf(stderr, - "OR \trsh [ -PN / -PO ] [ -l login ] [-n ] [ -x ] [ -f / -F ] host command\n"); + "OR \trsh [-PN / -PO] [-l login] [-n] [-x] [-f / -F] [-C seconds] [-T seconds] host command\n"); exit(1); } - krb5_sigtype sendsig(signo) char signo; { (void) rcmd_stream_write(rfd2, &signo, 1, 1); } +krb5_sigtype connect_alarm(signo) + char signo; +{ + fprintf(stderr, "rsh: remote connection timed out for %s\n", host); + exit(1); +} + +krb5_sigtype read_alarm(signo) + char signo; +{ + fprintf(stderr, "rsh: remote read timed out for %s\n", host); + exit(1); +} + +krb5_sigtype write_alarm(signo) + char signo; +{ + fprintf(stderr, "rsh: remote write timed out for %s\n", host); + exit(1); +} #ifdef KERBEROS >Unformatted: