├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile.debian ├── Makefile.linux ├── Makefile.openbsd ├── Makefile.osx ├── Readme.md ├── debian ├── changelog ├── compat ├── control ├── copyright ├── docs └── rules ├── sshdrill.1 ├── sshdrill.c └── strlcpy.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.swp 3 | sshdrill 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | script: make -f Makefile.linux && ./sshdrill -V 3 | compiler: 4 | - clang 5 | - gcc 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Marco Pfatschbacher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile.debian: -------------------------------------------------------------------------------- 1 | 2 | DPKG_EXPORT_BUILDFLAGS = 1 3 | include /usr/share/dpkg/buildflags.mk 4 | 5 | LD=$(CC) 6 | 7 | CFLAGS+= -Wall 8 | CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes 9 | CFLAGS+= -Wmissing-declarations 10 | CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual 11 | CFLAGS+= -Wsign-compare 12 | LDADD= -lutil 13 | PREFIX?= $(DESTDIR)/usr 14 | BINDIR?= $(PREFIX)/bin 15 | MANDIR?= $(DESTDIR)/usr/share/man/man1 16 | 17 | CPPFLAGS+=-DHAVE_PTY_H -DHAVE_UTMP_H 18 | 19 | .c.o: 20 | $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -c $< -o $@ 21 | 22 | sshdrill: sshdrill.o strlcpy.o 23 | $(LD) -o $@ sshdrill.o strlcpy.o $(LDFLAGS) $(LDADD) 24 | 25 | install: sshdrill 26 | install -d $(BINDIR) 27 | install -d $(MANDIR) 28 | install sshdrill $(BINDIR) 29 | install sshdrill.1 $(MANDIR) 30 | 31 | clean: 32 | rm -f *.o sshdrill 33 | -------------------------------------------------------------------------------- /Makefile.linux: -------------------------------------------------------------------------------- 1 | 2 | LD=$(CC) 3 | 4 | CFLAGS+= -Wall 5 | CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes 6 | CFLAGS+= -Wmissing-declarations 7 | CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual 8 | CFLAGS+= -Wsign-compare 9 | LDADD= -lutil 10 | PREFIX?= $(DESTDIR)/usr/local 11 | BINDIR?= $(PREFIX)/bin 12 | MANDIR?= $(DESTDIR)/usr/local/share/man/man1 13 | 14 | CPPFLAGS+=-DHAVE_PTY_H -DHAVE_UTMP_H 15 | 16 | .c.o: 17 | $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -c $< -o $@ 18 | 19 | sshdrill: sshdrill.o strlcpy.o 20 | $(LD) -o $@ sshdrill.o strlcpy.o $(LDFLAGS) $(LDADD) 21 | 22 | install: sshdrill 23 | install -d $(BINDIR) 24 | install -d $(MANDIR) 25 | install sshdrill $(BINDIR) 26 | install sshdrill.1 $(MANDIR) 27 | 28 | clean: 29 | rm -f *.o sshdrill 30 | -------------------------------------------------------------------------------- /Makefile.openbsd: -------------------------------------------------------------------------------- 1 | 2 | PROG= sshdrill 3 | CFLAGS+= -Wall 4 | CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes 5 | CFLAGS+= -Wmissing-declarations 6 | CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual 7 | CFLAGS+= -Wsign-compare 8 | LDADD= -lutil 9 | DPADD= ${LIBUTIL} 10 | NOMAN= true 11 | 12 | CPPFLAGS+=-DHAVE_UTIL_H 13 | .include 14 | -------------------------------------------------------------------------------- /Makefile.osx: -------------------------------------------------------------------------------- 1 | 2 | LD=$(CC) 3 | 4 | CFLAGS+= -Wall 5 | CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes 6 | CFLAGS+= -Wmissing-declarations 7 | CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual 8 | CFLAGS+= -Wsign-compare 9 | LDADD= -lutil 10 | PREFIX?= $(DESTDIR)/usr/local 11 | BINDIR?= $(PREFIX)/bin 12 | MANDIR?= $(DESTDIR)/usr/local/share/man/man1 13 | 14 | CPPFLAGS+=-DHAVE_UTMP_H -DHAVE_STRLCPY -DHAVE_UTIL_H 15 | 16 | .c.o: 17 | $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -c $< -o $@ 18 | 19 | sshdrill: sshdrill.o strlcpy.o 20 | $(LD) -o $@ sshdrill.o strlcpy.o $(LDFLAGS) $(LDADD) 21 | 22 | install: sshdrill 23 | install -d $(BINDIR) 24 | install -d $(MANDIR) 25 | install sshdrill $(BINDIR) 26 | install sshdrill.1 $(MANDIR) 27 | 28 | clean: 29 | rm -f *.o sshdrill 30 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | [![Build Status](https://travis-ci.org/mpfz0r/sshdrill.svg?branch=master)](https://travis-ci.org/mpfz0r/sshdrill) 3 | 4 | # sshdrill 5 | A ssh wrapper to automate tunnel creation over multiple jump hosts. 6 | 7 | 8 | ## Description 9 | 10 | Imagine you are logged into a customer's server. 11 | To get there, you had to *ssh* through one or more jump hosts, and now you need to set up a port forwarding to debug a service. 12 | Configuring the intermediate tunnels to get a port forwarded through all the jump hosts can be tedious. 13 | *sshdrill* automates this task for you. 14 | 15 | *sshdrill* runs as a wrapper around your interactive *ssh* session. It will listen for the *ssh* escape sequence '~C' to break into a command prompt which 16 | accepts *ssh's* forwarding syntax. 17 | *sshdrill* converts the requested forwarding into a series of forwardings for each hop on the way, scans how many nested *ssh* sessions are running, and applies the forwardings accordingly. 18 | 19 | 20 | ##Example Session 21 | 22 | ``` 23 | me@workstation:~$ alias ssh=sshdrill 24 | me@workstation:~$ ssh jumphost1 25 | user@jumphost1:~$ ssh jumphost2 26 | user@jumphost2:~$ ssh root@target 27 | root@target~$ 28 | << ENTER ~C >> 29 | 30 | sshdrill> -L8080:172.16.5.5:80 31 | Forwarding port through 3 ssh sessions. 32 | 33 | ssh> -L8080:172.16.5.5:80 34 | Forwarding port. 35 | 36 | ssh> -L8080:127.0.0.1:8080 37 | Forwarding port. 38 | 39 | ssh> -L8080:127.0.0.1:8080 40 | Forwarding port. 41 | 42 | root@target~$ 43 | 44 | ``` 45 | 46 | The requested listen port in the forwarding specification is used for the intermediate tunnels. (port 8080 in the example above.) 47 | 48 | ## Features 49 | *sshdrill* can tunnel local, remote and dynamic port forwardings through any number of *ssh* sessions. 50 | 51 | ``` 52 | sshdrill> h 53 | Commands: 54 | -L[bind_address:]port:host:hostport Request local forward 55 | -R[bind_address:]port:host:hostport Request remote forward 56 | -D[bind_address:]port Request dynamic forward 57 | ``` 58 | 59 | ## Installation 60 | ### OSX 61 | ``` 62 | brew tap mpfz0r/mpfz0r 63 | brew install sshdrill 64 | ``` 65 | ### Debian 66 | ``` 67 | # apt-get install devscripts 68 | $ git clone https://github.com/mpfz0r/sshdrill 69 | $ cd sshdrill 70 | $ debuild -uc -us -b 71 | # dpkg -i ../sshdrill_*.deb 72 | ``` 73 | ### Other 74 | ``` 75 | make -f Makefile. install 76 | ``` 77 | 78 | ## Todo 79 | * IPv6 forwardings are not supported yet. 80 | * The escape character cannot be configured. 81 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | sshdrill (1.0-1) stable; urgency=low 2 | 3 | * Initial release 4 | 5 | -- Marco Pfatschbacher Fri, 25 Sep 2015 15:06:03 +0200 6 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: sshdrill 2 | Section: net 3 | Priority: extra 4 | Maintainer: Marco Pfatschbacher 5 | Build-Depends: debhelper (>= 8.0.0) 6 | Standards-Version: 3.9.3 7 | 8 | Package: sshdrill 9 | Architecture: any 10 | Depends: ${shlibs:Depends}, ${misc:Depends} 11 | Description: a ssh wrapper to automate tunnel creation over multiple jump hosts. 12 | Imagine you are logged into a customer's server. To get there, you had 13 | to ssh through one or more jump hosts, and now you need to set up a 14 | port forwarding to debug a service. 15 | Configuring the intermediate tunnels to get a port forwarded through 16 | all the jump hosts can be tedious. sshdrill automates this task for 17 | you. 18 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Marco Pfatschbacher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /debian/docs: -------------------------------------------------------------------------------- 1 | Readme.md 2 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- makefile -*- 3 | 4 | # Uncomment this to turn on verbose mode. 5 | #export DH_VERBOSE=1 6 | 7 | %: 8 | dh $@ 9 | 10 | override_dh_auto_configure: 11 | cp -f Makefile.debian Makefile 12 | -------------------------------------------------------------------------------- /sshdrill.1: -------------------------------------------------------------------------------- 1 | .\" 2 | .\" (C) Copyright 2015 Marco Pfatschbacher , 3 | .\" 4 | .TH SSHDRILL 1 "September 25, 2015" 5 | .SH NAME 6 | sshdrill \- a ssh wrapper to automate tunnel creation over multiple jump hosts 7 | .SH SYNOPSIS 8 | .B sshdrill 9 | .RI [ arguments ] 10 | .br 11 | .B sshdrill 12 | .SH DESCRIPTION 13 | .B sshdrill 14 | runs as a wrapper around an interactive 15 | .BR ssh (1) 16 | session. 17 | It will 18 | listen for the ssh escape sequence '~C' to break into a command prompt 19 | which accepts ssh's forwarding syntax. 20 | sshdrill converts the requested 21 | forwarding into a series of forwardings for each hop on the way, scans 22 | how many nested ssh sessions are running, and applies the forwardings 23 | accordingly. 24 | .PP 25 | .SH OPTIONS 26 | If 27 | .B sshdrill 28 | is invoked with arguments, it will run 29 | .BR ssh (1) 30 | directly and pass all arguments unmodified. 31 | .br 32 | If run without arguments, it will execute a login shell. 33 | .SH SEE ALSO 34 | .BR ssh (1) 35 | -------------------------------------------------------------------------------- /sshdrill.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Marco Pfatschbacher 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | * tty handling code taken from script(1) 17 | * 18 | * Copyright (c) 1980, 1992, 1993 19 | * The Regents of the University of California. All rights reserved. 20 | * Copyright (c) 2001 Theo de Raadt 21 | * 22 | * Permission to use, copy, modify, and distribute this software for any 23 | * purpose with or without fee is hereby granted, provided that the above 24 | * copyright notice and this permission notice appear in all copies. 25 | * 26 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 27 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 28 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 29 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 30 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 31 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 32 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 33 | */ 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | 51 | #include 52 | #include 53 | #ifdef HAVE_PTY_H 54 | # include 55 | #endif 56 | #ifdef HAVE_UTIL_H 57 | # include 58 | #endif 59 | #ifdef HAVE_UTMP_H 60 | # include 61 | #endif 62 | #ifndef HAVE_STRLCPY 63 | size_t strlcpy(char *dst, const char *src, size_t siz); 64 | #endif 65 | 66 | #define ESCAPE_CHAR '~' 67 | #define ESCAPE_STR "~" 68 | #define MAX_SSH_DEPTH 6 69 | #define PROGNAME "sshdrill" 70 | #define PROMPT "\r\nsshdrill> " 71 | 72 | int master, slave; 73 | volatile sig_atomic_t child; 74 | struct termios tt; 75 | struct termios rtt; 76 | int runshell = 0; 77 | 78 | volatile sig_atomic_t dead; 79 | volatile sig_atomic_t sigdeadstatus; 80 | volatile sig_atomic_t flush; 81 | 82 | void done(int, int); 83 | void execcmd(char *[]); 84 | void dooutput(void); 85 | void fail(void); 86 | void finish(int); 87 | void handlesigwinch(int); 88 | int scan_for_escape(ssize_t, char*); 89 | void command_prompt(void); 90 | int setup_fwding(int, char *); 91 | ssize_t do_write(int, char *, ssize_t); 92 | int wait_for_str(char *); 93 | int prepare_fwds(char *, char *, char *, char *); 94 | void poke_through(char *, char *, char *, char *); 95 | 96 | int 97 | main(int argc, char *argv[]) 98 | { 99 | struct sigaction sa; 100 | struct winsize win; 101 | char ibuf[BUFSIZ]; 102 | char obuf[BUFSIZ]; 103 | ssize_t cc, off; 104 | fd_set rfdset; 105 | int ret; 106 | 107 | /* If not interactive, just exec ssh */ 108 | if (!isatty(STDIN_FILENO)) 109 | execvp("ssh", argv); 110 | 111 | if (argc == 1) 112 | runshell = 1; 113 | 114 | (void)tcgetattr(STDIN_FILENO, &tt); 115 | (void)ioctl(STDIN_FILENO, TIOCGWINSZ, &win); 116 | if (openpty(&master, &slave, NULL, &tt, &win) == -1) 117 | err(1, "openpty"); 118 | 119 | rtt = tt; 120 | cfmakeraw(&rtt); 121 | rtt.c_lflag &= ~ECHO; 122 | (void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &rtt); 123 | 124 | bzero(&sa, sizeof sa); 125 | sigemptyset(&sa.sa_mask); 126 | sa.sa_handler = finish; 127 | (void)sigaction(SIGCHLD, &sa, NULL); 128 | 129 | sa.sa_handler = handlesigwinch; 130 | sa.sa_flags = SA_RESTART; 131 | (void)sigaction(SIGWINCH, &sa, NULL); 132 | 133 | child = fork(); 134 | if (child < 0) { 135 | warn("fork"); 136 | fail(); 137 | } 138 | if (child == 0) { 139 | execcmd(argv); 140 | } 141 | 142 | for (;;) { 143 | FD_ZERO(&rfdset); 144 | FD_SET(STDIN_FILENO, &rfdset); 145 | FD_SET(master, &rfdset); 146 | if (dead) 147 | break; 148 | ret = select(master + 1, &rfdset, NULL, NULL, NULL); 149 | if (ret < 0) { 150 | if (errno == EINTR) 151 | continue; 152 | else 153 | err(1, "select"); 154 | } 155 | 156 | if (FD_ISSET(STDIN_FILENO, &rfdset)) { 157 | cc = read(STDIN_FILENO, ibuf, BUFSIZ); 158 | if (cc == -1 && errno == EINTR) 159 | continue; 160 | if (cc <= 0) 161 | break; 162 | if (scan_for_escape(cc, ibuf)) { 163 | off = 1; /* jump over ~ */ 164 | } else { 165 | off = 0; 166 | } 167 | do_write(master, ibuf + off, cc - off); 168 | } 169 | if (FD_ISSET(master, &rfdset)) { 170 | cc = read(master, obuf, sizeof (obuf)); 171 | if (cc == -1 && errno == EINTR) 172 | continue; 173 | if (cc <= 0) 174 | continue; 175 | do_write(STDOUT_FILENO, obuf, cc); 176 | } 177 | } 178 | done(sigdeadstatus, 0); 179 | 180 | return 1; /* NOTREACHED */ 181 | } 182 | 183 | void 184 | command_prompt(void) 185 | { 186 | char orig_fwd[BUFSIZ], first_fwd[BUFSIZ]; 187 | char tunnel_fwd[BUFSIZ], last_fwd[BUFSIZ]; 188 | ssize_t nr; 189 | char ch, *p, *fwd, buf[BUFSIZ]; 190 | struct termios ptt; 191 | 192 | ptt = tt; 193 | ptt.c_lflag &= ~ISIG; 194 | (void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &ptt); 195 | 196 | if (do_write(STDOUT_FILENO, PROMPT, strlen(PROMPT)) <= 0) 197 | goto abort; 198 | 199 | p = buf; 200 | while ((nr = read(STDIN_FILENO, &ch, 1)) == 1 && 201 | ch != '\n' && ch != '\r') { 202 | if (p < buf + (sizeof(buf) - 1)) { 203 | *p++ = ch; 204 | } 205 | } 206 | *p = '\0'; 207 | 208 | p = buf; 209 | 210 | while (isspace((u_char)*p)) 211 | p++; 212 | 213 | if (*p == '\0') 214 | goto abort; 215 | 216 | if (*p == 'h' || *p == 'H' || *p == '?') { 217 | char *help = 218 | "\rCommands:\n" 219 | " -L[bind_address:]port:host:hostport " 220 | "Request local forward\n" 221 | " -R[bind_address:]port:host:hostport " 222 | "Request remote forward\n" 223 | " -D[bind_address:]port " 224 | "Request dynamic forward\n"; 225 | do_write(STDOUT_FILENO, help, strlen(help)); 226 | goto abort; 227 | } 228 | fwd = p; 229 | snprintf(orig_fwd, BUFSIZ, "%s\r", fwd); 230 | 231 | 232 | if (prepare_fwds(fwd, first_fwd, tunnel_fwd, last_fwd) == 0) 233 | poke_through(orig_fwd, first_fwd, tunnel_fwd, last_fwd); 234 | 235 | abort: 236 | (void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &rtt); 237 | } 238 | 239 | int 240 | prepare_fwds(char *cmd, char *first_fwd, char *tunnel_fwd, char *last_fwd) 241 | { 242 | 243 | char type, colcount, i; 244 | char *s, *tok; 245 | char buf[BUFSIZ], source[BUFSIZ], dest[BUFSIZ]; 246 | unsigned int lport; 247 | 248 | s = cmd; 249 | 250 | if (*s == '-') 251 | s++; /* Skip cmdline '-', if any */ 252 | 253 | type = *s; 254 | if (type != 'L' && type != 'R' && type != 'D') 255 | goto parse_error; 256 | s++; 257 | 258 | if (type == 'D') { 259 | strlcpy(source, s, BUFSIZ); 260 | tok = s; 261 | if ((tok = strchr(tok, ':'))) 262 | s = tok + 1; 263 | if ((lport = strtoul(s, NULL, 10)) == 0) 264 | goto parse_error; 265 | snprintf(first_fwd, BUFSIZ, "-L%s:127.0.0.1:%u\r", source, lport); 266 | snprintf(tunnel_fwd, BUFSIZ, "-L%u:127.0.0.1:%u\r", lport, lport); 267 | snprintf(last_fwd, BUFSIZ, "-D:%u\r", lport); 268 | return 0; 269 | } 270 | 271 | tok = s; 272 | colcount = 0; 273 | while ((tok = strchr(tok, ':'))) { 274 | tok++; 275 | colcount++; 276 | } 277 | if (colcount < 2 || colcount > 3) 278 | goto parse_error; 279 | 280 | strlcpy(source, s, BUFSIZ); 281 | tok = s; 282 | for (i = 0; i < colcount - 2; i++) { 283 | tok = strchr(tok, ':'); 284 | tok++; 285 | } 286 | if (strlcpy(buf, tok, BUFSIZ) >= BUFSIZ) 287 | goto parse_error; 288 | tok = strchr(tok, ':'); 289 | *tok = '\0'; 290 | 291 | *(source + (tok - s)) = '\0'; 292 | 293 | if ((lport = strtoul(s, NULL, 10)) == 0) 294 | goto parse_error; 295 | strlcpy(dest, tok + 1, BUFSIZ); 296 | 297 | if (type == 'L') { 298 | snprintf(first_fwd, BUFSIZ, "-L%s:127.0.0.1:%u\r", source, lport); 299 | snprintf(tunnel_fwd, BUFSIZ, "-L%u:127.0.0.1:%u\r", lport, lport); 300 | snprintf(last_fwd, BUFSIZ, "-L%u:%s\r", lport, dest); 301 | } else if (type == 'R') { 302 | snprintf(first_fwd, BUFSIZ, "-R%u:%s\r", lport, dest); 303 | snprintf(tunnel_fwd, BUFSIZ, "-R%u:127.0.0.1:%u\r", lport, lport); 304 | snprintf(last_fwd, BUFSIZ, "-R%s:127.0.0.1:%u\r", source, lport); 305 | } 306 | 307 | return 0; 308 | 309 | parse_error: 310 | fprintf(stderr, "Invalid command."); 311 | return 1; 312 | } 313 | 314 | #define SEND_PROBE 1 315 | #define READ_PROBE 2 316 | #define SEND_CLEANUP 3 317 | #define PROBE_DONE 4 318 | 319 | void 320 | poke_through(char *orig_fwd, char *first_fwd, char *tunnel_fwd, char *last_fwd) 321 | { 322 | fd_set rfdset; 323 | int ret; 324 | char obuf[BUFSIZ]; 325 | struct timeval tout; 326 | int state = SEND_PROBE; 327 | int escapes_sent = 0; 328 | int backspaces_sent = 0; 329 | int escapes_rcvd = 0; 330 | int nested_sshs = 0; 331 | int current_hop; 332 | 333 | tout.tv_sec = 1; 334 | tout.tv_usec = 0; 335 | 336 | for (;;) { 337 | if (state == SEND_PROBE) { 338 | if (do_write(master, ESCAPE_STR, 1)) { 339 | if (++escapes_sent == MAX_SSH_DEPTH) 340 | state = READ_PROBE; 341 | } 342 | } 343 | 344 | if (state == READ_PROBE) { 345 | FD_ZERO(&rfdset); 346 | FD_SET(master, &rfdset); 347 | ret = select(master + 1, &rfdset, NULL, NULL, &tout); 348 | if (ret < 0 && errno != EINTR) 349 | err(1, "select"); 350 | if (ret == 0) { 351 | state = SEND_CLEANUP; 352 | } 353 | if (FD_ISSET(master, &rfdset)) { 354 | ssize_t cc; 355 | cc = read(master, obuf, sizeof (obuf)); 356 | if (cc == -1 && errno == EINTR) 357 | continue; 358 | if (cc <= 0) 359 | break; 360 | /* 361 | * XXX Check for actual ~ chars 362 | * This only works if there's no additional 363 | * console output 364 | */ 365 | escapes_rcvd += cc; 366 | } 367 | } 368 | if (state == SEND_CLEANUP) { 369 | ssize_t n = write(master, "", 1); 370 | if (n == -1 && errno != EAGAIN) 371 | break; 372 | if (n == 0) 373 | break; /* skip writing */ 374 | if (n > 0) { 375 | if (++backspaces_sent == MAX_SSH_DEPTH) { 376 | state = PROBE_DONE; 377 | break; 378 | } 379 | } 380 | } 381 | } 382 | 383 | if (state == PROBE_DONE) { 384 | nested_sshs = escapes_sent - escapes_rcvd; 385 | if (nested_sshs < 0) { 386 | fprintf(stderr, "Scan failure.\n"); 387 | return; 388 | } 389 | } 390 | 391 | if (nested_sshs) 392 | fprintf(stderr, 393 | "Forwarding port through %d ssh sessions.\n", nested_sshs); 394 | else 395 | fprintf(stderr, "No ssh sessions found.\n"); 396 | 397 | for (current_hop = nested_sshs; current_hop; current_hop--) { 398 | char *fwd; 399 | 400 | if (current_hop == nested_sshs) { 401 | if (do_write(master, "\r", 1) <= 0) 402 | fprintf(stderr, "do_write error"); 403 | fwd = nested_sshs == 1 ? orig_fwd : last_fwd; 404 | } else if (current_hop > 1) 405 | fwd = tunnel_fwd; 406 | else 407 | fwd = first_fwd; 408 | 409 | if (setup_fwding(current_hop, fwd)) 410 | fprintf(stderr, "setup_fwding error\n"); 411 | } 412 | } 413 | 414 | int 415 | setup_fwding(int current_hop, char *fwd) 416 | { 417 | 418 | while (current_hop) { 419 | if (do_write(master, "~", 1) > 0) 420 | current_hop--; 421 | else 422 | return -1; 423 | } 424 | do_write(master, "C", 1); 425 | if (wait_for_str("ssh>") != 0) { 426 | fprintf(stderr, "timeout waiting for ssh> prompt\n"); 427 | return -1; 428 | } 429 | if (do_write(master, fwd, strlen(fwd)) > 0) { 430 | wait_for_str(NULL); 431 | return 0; 432 | } else 433 | return -1; 434 | } 435 | 436 | int 437 | wait_for_str(char *searchstr) 438 | { 439 | fd_set rfdset; 440 | int ret; 441 | unsigned int cc = 0; 442 | ssize_t n; 443 | char obuf[BUFSIZ]; 444 | struct timeval tout; 445 | 446 | tout.tv_sec = 1; 447 | tout.tv_usec = 0; 448 | 449 | while (cc < sizeof(obuf)) { 450 | FD_ZERO(&rfdset); 451 | FD_SET(master, &rfdset); 452 | ret = select(master + 1, &rfdset, NULL, NULL, &tout); 453 | if (ret < 0 && errno != EINTR) 454 | err(1, "select"); 455 | if (ret == 0) { 456 | return 1; 457 | } 458 | if (FD_ISSET(master, &rfdset)) { 459 | n = read(master, obuf + cc, sizeof (obuf) - cc); 460 | if (n == -1 && errno == EINTR) 461 | continue; 462 | do_write(STDOUT_FILENO, obuf + cc, n); 463 | 464 | if (searchstr && strstr(obuf, searchstr)) 465 | return 0; 466 | cc += n; 467 | } 468 | } 469 | return 1; 470 | } 471 | 472 | ssize_t 473 | do_write(int fd, char *buf, ssize_t cc) { 474 | 475 | ssize_t off = 0; 476 | ssize_t n = 0; 477 | 478 | while (off < cc) { 479 | n = write(fd, buf + off, cc - off); 480 | if (n == -1 && errno != EAGAIN) 481 | return n; 482 | if (n == 0) 483 | return n; 484 | if (n > 0) { 485 | off += n; 486 | } 487 | } 488 | return off; 489 | } 490 | 491 | int 492 | scan_for_escape(ssize_t cc, char *ibuf) 493 | { 494 | static int got_newline = 0; 495 | static int got_escape = 0; 496 | int i; 497 | 498 | for (i = 0; i < cc; i++) { 499 | if (got_escape && ibuf[i] == '.') { 500 | done(255, 1); 501 | } else if (got_escape && ibuf[i] == 'C') { 502 | got_newline = got_escape = 0; 503 | command_prompt(); 504 | return 1; 505 | } else if (got_newline && ibuf[i] == ESCAPE_CHAR) { 506 | got_escape = 1; 507 | got_newline = 0; 508 | return 1; 509 | } else if (ibuf[i] == '\r') { 510 | got_newline = 1; 511 | got_escape = 0; 512 | } else { 513 | got_newline = got_escape = 0; 514 | } 515 | } 516 | return 0; 517 | } 518 | 519 | /* ARGSUSED */ 520 | void 521 | handlesigwinch(int signo) 522 | { 523 | int save_errno = errno; 524 | struct winsize win; 525 | pid_t pgrp; 526 | 527 | if (ioctl(STDIN_FILENO, TIOCGWINSZ, &win) != -1) { 528 | ioctl(slave, TIOCSWINSZ, &win); 529 | if (ioctl(slave, TIOCGPGRP, &pgrp) != -1) 530 | killpg(pgrp, SIGWINCH); 531 | } 532 | errno = save_errno; 533 | } 534 | 535 | /* ARGSUSED */ 536 | void 537 | finish(int signo) 538 | { 539 | int save_errno = errno; 540 | int status, e = 1; 541 | pid_t pid; 542 | 543 | while ((pid = wait3(&status, WNOHANG, 0)) > 0) { 544 | if (pid == (pid_t)child) { 545 | if (WIFEXITED(status)) 546 | e = WEXITSTATUS(status); 547 | } 548 | } 549 | dead = 1; 550 | sigdeadstatus = e; 551 | errno = save_errno; 552 | } 553 | 554 | void 555 | execcmd(char *argv[]) 556 | { 557 | char *shell; 558 | 559 | shell = getenv("SHELL"); 560 | if (shell == NULL) 561 | shell = _PATH_BSHELL; 562 | 563 | (void)close(master); 564 | login_tty(slave); 565 | if (runshell) { 566 | fprintf(stderr, "Sshdrill shell session started.\n"); 567 | execl(shell, shell, "-il", (char *)NULL); 568 | } else { 569 | execvp("ssh", argv); 570 | } 571 | warn("%s", shell); 572 | fail(); 573 | } 574 | 575 | void 576 | fail(void) 577 | { 578 | (void)kill(0, SIGTERM); 579 | done(1, 0); 580 | } 581 | 582 | void 583 | done(int eval, int msg) 584 | { 585 | (void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &tt); 586 | if (msg) 587 | fprintf(stderr, "Terminating %s.\n", PROGNAME); 588 | exit(eval); 589 | } 590 | -------------------------------------------------------------------------------- /strlcpy.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: strlcpy.c,v 1.11 2006/05/05 15:27:38 millert Exp $ */ 2 | 3 | /* 4 | * Copyright (c) 1998 Todd C. Miller 5 | * 6 | * Permission to use, copy, modify, and distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | /* OPENBSD ORIGINAL: lib/libc/string/strlcpy.c */ 20 | 21 | #ifndef HAVE_STRLCPY 22 | 23 | #include 24 | #include 25 | 26 | size_t strlcpy(char *dst, const char *src, size_t siz); 27 | 28 | /* 29 | * Copy src to string dst of size siz. At most siz-1 characters 30 | * will be copied. Always NUL terminates (unless siz == 0). 31 | * Returns strlen(src); if retval >= siz, truncation occurred. 32 | */ 33 | size_t 34 | strlcpy(char *dst, const char *src, size_t siz) 35 | { 36 | char *d = dst; 37 | const char *s = src; 38 | size_t n = siz; 39 | 40 | /* Copy as many bytes as will fit */ 41 | if (n != 0) { 42 | while (--n != 0) { 43 | if ((*d++ = *s++) == '\0') 44 | break; 45 | } 46 | } 47 | 48 | /* Not enough room in dst, add NUL and traverse rest of src */ 49 | if (n == 0) { 50 | if (siz != 0) 51 | *d = '\0'; /* NUL-terminate dst */ 52 | while (*s++) 53 | ; 54 | } 55 | 56 | return(s - src - 1); /* count does not include NUL */ 57 | } 58 | 59 | #endif /* !HAVE_STRLCPY */ 60 | --------------------------------------------------------------------------------