Jiri Simsa | d7616c9 | 2015-03-24 23:44:30 -0700 | [diff] [blame] | 1 | // Copyright 2015 The Vanadium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
Todd Wang | 8c4e5cc | 2015-04-09 11:30:52 -0700 | [diff] [blame] | 5 | // Command vsh is a Tunnel client that can be used to start a shell on the |
| 6 | // server. |
Jiri Simsa | d5aae83 | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 7 | package main |
| 8 | |
| 9 | import ( |
| 10 | "errors" |
| 11 | "flag" |
| 12 | "fmt" |
| 13 | "net" |
| 14 | "os" |
| 15 | "path" |
| 16 | "strings" |
Jiri Simsa | d5aae83 | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 17 | |
Jiri Simsa | 386ce3e | 2015-02-23 16:14:20 -0800 | [diff] [blame] | 18 | "v.io/v23" |
| 19 | "v.io/v23/context" |
Jiri Simsa | 1d94857 | 2015-02-27 14:36:25 -0800 | [diff] [blame] | 20 | "v.io/x/lib/vlog" |
Todd Wang | affd04d | 2015-04-08 10:25:35 -0700 | [diff] [blame] | 21 | "v.io/x/ref/examples/tunnel" |
| 22 | "v.io/x/ref/examples/tunnel/internal" |
| 23 | "v.io/x/ref/lib/signals" |
Cosmos Nicolaou | e7f3091 | 2014-10-16 22:24:59 -0700 | [diff] [blame] | 24 | |
Jiri Simsa | ffceefa | 2015-02-28 11:03:34 -0800 | [diff] [blame] | 25 | _ "v.io/x/ref/profiles" |
Jiri Simsa | d5aae83 | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 26 | ) |
| 27 | |
| 28 | var ( |
| 29 | disablePty = flag.Bool("T", false, "Disable pseudo-terminal allocation.") |
| 30 | forcePty = flag.Bool("t", false, "Force allocation of pseudo-terminal.") |
Jiri Simsa | d5aae83 | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 31 | |
| 32 | portforward = flag.String("L", "", "localaddr,remoteaddr Forward local 'localaddr' to 'remoteaddr'") |
| 33 | lprotocol = flag.String("local_protocol", "tcp", "Local network protocol for port forwarding") |
| 34 | rprotocol = flag.String("remote_protocol", "tcp", "Remote network protocol for port forwarding") |
| 35 | |
| 36 | noshell = flag.Bool("N", false, "Do not execute a shell. Only do port forwarding.") |
| 37 | ) |
| 38 | |
| 39 | func init() { |
| 40 | flag.Usage = func() { |
| 41 | bname := path.Base(os.Args[0]) |
Suharsh Sivakumar | d1cc6e0 | 2015-03-16 13:58:49 -0700 | [diff] [blame] | 42 | fmt.Fprintf(os.Stderr, `%s: Vanadium Shell. |
Jiri Simsa | d5aae83 | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 43 | |
| 44 | This tool is used to run shell commands or an interactive shell on a remote |
| 45 | tunneld service. |
| 46 | |
| 47 | To open an interactive shell, use: |
Asim Shankar | f9a4168 | 2014-07-08 17:14:01 -0700 | [diff] [blame] | 48 | %s <object name> |
Jiri Simsa | d5aae83 | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 49 | |
| 50 | To run a shell command, use: |
Asim Shankar | f9a4168 | 2014-07-08 17:14:01 -0700 | [diff] [blame] | 51 | %s <object name> <command to run> |
Jiri Simsa | d5aae83 | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 52 | |
| 53 | The -L flag will forward connections from a local port to a remote address |
| 54 | through the tunneld service. The flag value is localaddr,remoteaddr. E.g. |
| 55 | -L :14141,www.google.com:80 |
| 56 | |
Robin Thellend | 320af42 | 2015-03-09 17:18:15 -0700 | [diff] [blame] | 57 | %s can't be used directly with tools like rsync because vanadium object names |
Asim Shankar | f9a4168 | 2014-07-08 17:14:01 -0700 | [diff] [blame] | 58 | don't look like traditional hostnames, which rsync doesn't understand. For |
Jiri Simsa | d5aae83 | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 59 | compatibility with such tools, %s has a special feature that allows passing the |
Robin Thellend | 320af42 | 2015-03-09 17:18:15 -0700 | [diff] [blame] | 60 | vanadium object name via the VSH_NAME environment variable. |
Jiri Simsa | d5aae83 | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 61 | |
Robin Thellend | 320af42 | 2015-03-09 17:18:15 -0700 | [diff] [blame] | 62 | $ VSH_NAME=<object name> rsync -avh -e %s /foo/* v23:/foo/ |
Jiri Simsa | d5aae83 | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 63 | |
Robin Thellend | 320af42 | 2015-03-09 17:18:15 -0700 | [diff] [blame] | 64 | In this example, the "v23" host will be substituted with $VSH_NAME by %s and |
| 65 | rsync will work as expected. |
Jiri Simsa | d5aae83 | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 66 | |
| 67 | Full flags: |
| 68 | `, os.Args[0], bname, bname, bname, bname, os.Args[0], bname) |
| 69 | flag.PrintDefaults() |
| 70 | } |
| 71 | } |
| 72 | |
| 73 | func main() { |
| 74 | // Work around the fact that os.Exit doesn't run deferred functions. |
| 75 | os.Exit(realMain()) |
| 76 | } |
| 77 | |
| 78 | func realMain() int { |
Jiri Simsa | 386ce3e | 2015-02-23 16:14:20 -0800 | [diff] [blame] | 79 | ctx, shutdown := v23.Init() |
Suharsh Sivakumar | 69f810d | 2015-01-15 00:00:07 -0800 | [diff] [blame] | 80 | defer shutdown() |
Suharsh Sivakumar | 546e129 | 2015-01-07 15:04:50 -0800 | [diff] [blame] | 81 | |
Asim Shankar | f9a4168 | 2014-07-08 17:14:01 -0700 | [diff] [blame] | 82 | oname, cmd, err := objectNameAndCommandLine() |
Jiri Simsa | d5aae83 | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 83 | if err != nil { |
| 84 | flag.Usage() |
| 85 | fmt.Fprintf(os.Stderr, "\n%v\n", err) |
| 86 | return 1 |
| 87 | } |
| 88 | |
Todd Wang | d8cb55b | 2014-11-07 00:58:32 -0800 | [diff] [blame] | 89 | t := tunnel.TunnelClient(oname) |
Matt Rosencrantz | 7a92378 | 2014-08-18 09:56:15 -0700 | [diff] [blame] | 90 | |
Jiri Simsa | d5aae83 | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 91 | if len(*portforward) > 0 { |
Matt Rosencrantz | 7a92378 | 2014-08-18 09:56:15 -0700 | [diff] [blame] | 92 | go runPortForwarding(ctx, t, oname) |
Jiri Simsa | d5aae83 | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 93 | } |
| 94 | |
| 95 | if *noshell { |
Suharsh Sivakumar | 546e129 | 2015-01-07 15:04:50 -0800 | [diff] [blame] | 96 | <-signals.ShutdownOnSignals(ctx) |
Jiri Simsa | d5aae83 | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 97 | return 0 |
| 98 | } |
| 99 | |
| 100 | opts := shellOptions(cmd) |
| 101 | |
Matt Rosencrantz | 7a92378 | 2014-08-18 09:56:15 -0700 | [diff] [blame] | 102 | stream, err := t.Shell(ctx, cmd, opts) |
Jiri Simsa | d5aae83 | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 103 | if err != nil { |
| 104 | fmt.Fprintf(os.Stderr, "Error: %v\n", err) |
| 105 | return 1 |
| 106 | } |
Robin Thellend | 9faaef3 | 2014-08-01 19:45:40 -0700 | [diff] [blame] | 107 | if opts.UsePty { |
Todd Wang | affd04d | 2015-04-08 10:25:35 -0700 | [diff] [blame] | 108 | saved := internal.EnterRawTerminalMode() |
| 109 | defer internal.RestoreTerminalSettings(saved) |
Robin Thellend | 9faaef3 | 2014-08-01 19:45:40 -0700 | [diff] [blame] | 110 | } |
Jiri Simsa | d5aae83 | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 111 | runIOManager(os.Stdin, os.Stdout, os.Stderr, stream) |
| 112 | |
Asim Shankar | f9a4168 | 2014-07-08 17:14:01 -0700 | [diff] [blame] | 113 | exitMsg := fmt.Sprintf("Connection to %s closed.", oname) |
Jiri Simsa | d5aae83 | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 114 | exitStatus, err := stream.Finish() |
| 115 | if err != nil { |
| 116 | exitMsg += fmt.Sprintf(" (%v)", err) |
| 117 | } |
| 118 | vlog.VI(1).Info(exitMsg) |
| 119 | // Only show the exit message on stdout for interactive shells. |
| 120 | // Otherwise, the exit message might get confused with the output |
| 121 | // of the command that was run. |
| 122 | if err != nil { |
| 123 | fmt.Fprintln(os.Stderr, exitMsg) |
| 124 | } else if len(cmd) == 0 { |
| 125 | fmt.Println(exitMsg) |
| 126 | } |
| 127 | return int(exitStatus) |
| 128 | } |
| 129 | |
| 130 | func shellOptions(cmd string) (opts tunnel.ShellOpts) { |
| 131 | opts.UsePty = (len(cmd) == 0 || *forcePty) && !*disablePty |
| 132 | opts.Environment = environment() |
Todd Wang | affd04d | 2015-04-08 10:25:35 -0700 | [diff] [blame] | 133 | ws, err := internal.GetWindowSize() |
Jiri Simsa | d5aae83 | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 134 | if err != nil { |
| 135 | vlog.VI(1).Infof("GetWindowSize failed: %v", err) |
| 136 | } else { |
Robin Thellend | c14adc9 | 2015-02-11 13:02:19 -0800 | [diff] [blame] | 137 | opts.WinSize.Rows = ws.Row |
| 138 | opts.WinSize.Cols = ws.Col |
Jiri Simsa | d5aae83 | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 139 | } |
| 140 | return |
| 141 | } |
| 142 | |
| 143 | func environment() []string { |
| 144 | env := []string{} |
| 145 | for _, name := range []string{"TERM", "COLORTERM"} { |
| 146 | if value := os.Getenv(name); value != "" { |
| 147 | env = append(env, fmt.Sprintf("%s=%s", name, value)) |
| 148 | } |
| 149 | } |
| 150 | return env |
| 151 | } |
| 152 | |
Asim Shankar | f9a4168 | 2014-07-08 17:14:01 -0700 | [diff] [blame] | 153 | // objectNameAndCommandLine extracts the object name and the remote command to |
| 154 | // send to the server. The object name is the first non-flag argument. |
| 155 | // The command line is the concatenation of all non-flag arguments excluding |
| 156 | // the object name. |
| 157 | func objectNameAndCommandLine() (string, string, error) { |
Jiri Simsa | d5aae83 | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 158 | args := flag.Args() |
Asim Shankar | f9a4168 | 2014-07-08 17:14:01 -0700 | [diff] [blame] | 159 | if len(args) < 1 { |
Bogdan Caprita | 8785124 | 2014-07-02 14:40:39 -0700 | [diff] [blame] | 160 | return "", "", errors.New("object name missing") |
Jiri Simsa | d5aae83 | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 161 | } |
Asim Shankar | f9a4168 | 2014-07-08 17:14:01 -0700 | [diff] [blame] | 162 | name := args[0] |
| 163 | args = args[1:] |
| 164 | // For compatibility with tools like rsync. Because object names |
Jiri Simsa | d5aae83 | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 165 | // don't look like traditional hostnames, tools that work with rsh and |
| 166 | // ssh can't work directly with vsh. This trick makes the following |
| 167 | // possible: |
Robin Thellend | 320af42 | 2015-03-09 17:18:15 -0700 | [diff] [blame] | 168 | // $ VSH_NAME=<object name> rsync -avh -e vsh /foo/* v23:/foo/ |
| 169 | // The "v23" host will be substituted with <object name>. |
| 170 | if envName := os.Getenv("VSH_NAME"); len(envName) > 0 && name == "v23" { |
Jiri Simsa | d5aae83 | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 171 | name = envName |
| 172 | } |
| 173 | cmd := strings.Join(args, " ") |
| 174 | return name, cmd, nil |
| 175 | } |
| 176 | |
Matt Rosencrantz | 77f4b93 | 2014-12-29 11:28:49 -0800 | [diff] [blame] | 177 | func runPortForwarding(ctx *context.T, t tunnel.TunnelClientMethods, oname string) { |
Jiri Simsa | d5aae83 | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 178 | // *portforward is localaddr,remoteaddr |
| 179 | parts := strings.Split(*portforward, ",") |
| 180 | var laddr, raddr string |
| 181 | if len(parts) != 2 { |
| 182 | vlog.Fatalf("-L flag expects 2 values separated by a comma") |
| 183 | } |
| 184 | laddr = parts[0] |
| 185 | raddr = parts[1] |
| 186 | |
| 187 | ln, err := net.Listen(*lprotocol, laddr) |
| 188 | if err != nil { |
| 189 | vlog.Fatalf("net.Listen(%q, %q) failed: %v", *lprotocol, laddr, err) |
| 190 | } |
| 191 | defer ln.Close() |
| 192 | vlog.VI(1).Infof("Listening on %q", ln.Addr()) |
| 193 | for { |
| 194 | conn, err := ln.Accept() |
| 195 | if err != nil { |
| 196 | vlog.Infof("Accept failed: %v", err) |
| 197 | continue |
| 198 | } |
Matt Rosencrantz | 7a92378 | 2014-08-18 09:56:15 -0700 | [diff] [blame] | 199 | stream, err := t.Forward(ctx, *rprotocol, raddr) |
Jiri Simsa | d5aae83 | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 200 | if err != nil { |
| 201 | vlog.Infof("Tunnel(%q, %q) failed: %v", *rprotocol, raddr, err) |
| 202 | conn.Close() |
| 203 | continue |
| 204 | } |
Asim Shankar | f9a4168 | 2014-07-08 17:14:01 -0700 | [diff] [blame] | 205 | name := fmt.Sprintf("%v-->%v-->(%v)-->%v", conn.RemoteAddr(), conn.LocalAddr(), oname, raddr) |
Jiri Simsa | d5aae83 | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 206 | go func() { |
| 207 | vlog.VI(1).Infof("TUNNEL START: %v", name) |
Todd Wang | affd04d | 2015-04-08 10:25:35 -0700 | [diff] [blame] | 208 | errf := internal.Forward(conn, stream.SendStream(), stream.RecvStream()) |
Jiri Simsa | d5aae83 | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 209 | err := stream.Finish() |
| 210 | vlog.VI(1).Infof("TUNNEL END : %v (%v, %v)", name, errf, err) |
| 211 | }() |
| 212 | } |
| 213 | } |