blob: c038fc5d9a3deb083a5f42df44c839687315cd4d [file] [log] [blame]
Jiri Simsad7616c92015-03-24 23:44:30 -07001// 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 Wang8c4e5cc2015-04-09 11:30:52 -07005// Command vsh is a Tunnel client that can be used to start a shell on the
6// server.
Jiri Simsad5aae832014-05-10 09:56:38 -07007package main
8
9import (
10 "errors"
11 "flag"
12 "fmt"
13 "net"
14 "os"
15 "path"
16 "strings"
Jiri Simsad5aae832014-05-10 09:56:38 -070017
Jiri Simsa386ce3e2015-02-23 16:14:20 -080018 "v.io/v23"
19 "v.io/v23/context"
Jiri Simsa1d948572015-02-27 14:36:25 -080020 "v.io/x/lib/vlog"
Todd Wangaffd04d2015-04-08 10:25:35 -070021 "v.io/x/ref/examples/tunnel"
22 "v.io/x/ref/examples/tunnel/internal"
23 "v.io/x/ref/lib/signals"
Cosmos Nicolaoue7f30912014-10-16 22:24:59 -070024
Jiri Simsaffceefa2015-02-28 11:03:34 -080025 _ "v.io/x/ref/profiles"
Jiri Simsad5aae832014-05-10 09:56:38 -070026)
27
28var (
29 disablePty = flag.Bool("T", false, "Disable pseudo-terminal allocation.")
30 forcePty = flag.Bool("t", false, "Force allocation of pseudo-terminal.")
Jiri Simsad5aae832014-05-10 09:56:38 -070031
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
39func init() {
40 flag.Usage = func() {
41 bname := path.Base(os.Args[0])
Suharsh Sivakumard1cc6e02015-03-16 13:58:49 -070042 fmt.Fprintf(os.Stderr, `%s: Vanadium Shell.
Jiri Simsad5aae832014-05-10 09:56:38 -070043
44This tool is used to run shell commands or an interactive shell on a remote
45tunneld service.
46
47To open an interactive shell, use:
Asim Shankarf9a41682014-07-08 17:14:01 -070048 %s <object name>
Jiri Simsad5aae832014-05-10 09:56:38 -070049
50To run a shell command, use:
Asim Shankarf9a41682014-07-08 17:14:01 -070051 %s <object name> <command to run>
Jiri Simsad5aae832014-05-10 09:56:38 -070052
53The -L flag will forward connections from a local port to a remote address
54through the tunneld service. The flag value is localaddr,remoteaddr. E.g.
55 -L :14141,www.google.com:80
56
Robin Thellend320af422015-03-09 17:18:15 -070057%s can't be used directly with tools like rsync because vanadium object names
Asim Shankarf9a41682014-07-08 17:14:01 -070058don't look like traditional hostnames, which rsync doesn't understand. For
Jiri Simsad5aae832014-05-10 09:56:38 -070059compatibility with such tools, %s has a special feature that allows passing the
Robin Thellend320af422015-03-09 17:18:15 -070060vanadium object name via the VSH_NAME environment variable.
Jiri Simsad5aae832014-05-10 09:56:38 -070061
Robin Thellend320af422015-03-09 17:18:15 -070062 $ VSH_NAME=<object name> rsync -avh -e %s /foo/* v23:/foo/
Jiri Simsad5aae832014-05-10 09:56:38 -070063
Robin Thellend320af422015-03-09 17:18:15 -070064In this example, the "v23" host will be substituted with $VSH_NAME by %s and
65rsync will work as expected.
Jiri Simsad5aae832014-05-10 09:56:38 -070066
67Full flags:
68`, os.Args[0], bname, bname, bname, bname, os.Args[0], bname)
69 flag.PrintDefaults()
70 }
71}
72
73func main() {
74 // Work around the fact that os.Exit doesn't run deferred functions.
75 os.Exit(realMain())
76}
77
78func realMain() int {
Jiri Simsa386ce3e2015-02-23 16:14:20 -080079 ctx, shutdown := v23.Init()
Suharsh Sivakumar69f810d2015-01-15 00:00:07 -080080 defer shutdown()
Suharsh Sivakumar546e1292015-01-07 15:04:50 -080081
Asim Shankarf9a41682014-07-08 17:14:01 -070082 oname, cmd, err := objectNameAndCommandLine()
Jiri Simsad5aae832014-05-10 09:56:38 -070083 if err != nil {
84 flag.Usage()
85 fmt.Fprintf(os.Stderr, "\n%v\n", err)
86 return 1
87 }
88
Todd Wangd8cb55b2014-11-07 00:58:32 -080089 t := tunnel.TunnelClient(oname)
Matt Rosencrantz7a923782014-08-18 09:56:15 -070090
Jiri Simsad5aae832014-05-10 09:56:38 -070091 if len(*portforward) > 0 {
Matt Rosencrantz7a923782014-08-18 09:56:15 -070092 go runPortForwarding(ctx, t, oname)
Jiri Simsad5aae832014-05-10 09:56:38 -070093 }
94
95 if *noshell {
Suharsh Sivakumar546e1292015-01-07 15:04:50 -080096 <-signals.ShutdownOnSignals(ctx)
Jiri Simsad5aae832014-05-10 09:56:38 -070097 return 0
98 }
99
100 opts := shellOptions(cmd)
101
Matt Rosencrantz7a923782014-08-18 09:56:15 -0700102 stream, err := t.Shell(ctx, cmd, opts)
Jiri Simsad5aae832014-05-10 09:56:38 -0700103 if err != nil {
104 fmt.Fprintf(os.Stderr, "Error: %v\n", err)
105 return 1
106 }
Robin Thellend9faaef32014-08-01 19:45:40 -0700107 if opts.UsePty {
Todd Wangaffd04d2015-04-08 10:25:35 -0700108 saved := internal.EnterRawTerminalMode()
109 defer internal.RestoreTerminalSettings(saved)
Robin Thellend9faaef32014-08-01 19:45:40 -0700110 }
Jiri Simsad5aae832014-05-10 09:56:38 -0700111 runIOManager(os.Stdin, os.Stdout, os.Stderr, stream)
112
Asim Shankarf9a41682014-07-08 17:14:01 -0700113 exitMsg := fmt.Sprintf("Connection to %s closed.", oname)
Jiri Simsad5aae832014-05-10 09:56:38 -0700114 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
130func shellOptions(cmd string) (opts tunnel.ShellOpts) {
131 opts.UsePty = (len(cmd) == 0 || *forcePty) && !*disablePty
132 opts.Environment = environment()
Todd Wangaffd04d2015-04-08 10:25:35 -0700133 ws, err := internal.GetWindowSize()
Jiri Simsad5aae832014-05-10 09:56:38 -0700134 if err != nil {
135 vlog.VI(1).Infof("GetWindowSize failed: %v", err)
136 } else {
Robin Thellendc14adc92015-02-11 13:02:19 -0800137 opts.WinSize.Rows = ws.Row
138 opts.WinSize.Cols = ws.Col
Jiri Simsad5aae832014-05-10 09:56:38 -0700139 }
140 return
141}
142
143func 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 Shankarf9a41682014-07-08 17:14:01 -0700153// 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.
157func objectNameAndCommandLine() (string, string, error) {
Jiri Simsad5aae832014-05-10 09:56:38 -0700158 args := flag.Args()
Asim Shankarf9a41682014-07-08 17:14:01 -0700159 if len(args) < 1 {
Bogdan Caprita87851242014-07-02 14:40:39 -0700160 return "", "", errors.New("object name missing")
Jiri Simsad5aae832014-05-10 09:56:38 -0700161 }
Asim Shankarf9a41682014-07-08 17:14:01 -0700162 name := args[0]
163 args = args[1:]
164 // For compatibility with tools like rsync. Because object names
Jiri Simsad5aae832014-05-10 09:56:38 -0700165 // 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 Thellend320af422015-03-09 17:18:15 -0700168 // $ 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 Simsad5aae832014-05-10 09:56:38 -0700171 name = envName
172 }
173 cmd := strings.Join(args, " ")
174 return name, cmd, nil
175}
176
Matt Rosencrantz77f4b932014-12-29 11:28:49 -0800177func runPortForwarding(ctx *context.T, t tunnel.TunnelClientMethods, oname string) {
Jiri Simsad5aae832014-05-10 09:56:38 -0700178 // *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 Rosencrantz7a923782014-08-18 09:56:15 -0700199 stream, err := t.Forward(ctx, *rprotocol, raddr)
Jiri Simsad5aae832014-05-10 09:56:38 -0700200 if err != nil {
201 vlog.Infof("Tunnel(%q, %q) failed: %v", *rprotocol, raddr, err)
202 conn.Close()
203 continue
204 }
Asim Shankarf9a41682014-07-08 17:14:01 -0700205 name := fmt.Sprintf("%v-->%v-->(%v)-->%v", conn.RemoteAddr(), conn.LocalAddr(), oname, raddr)
Jiri Simsad5aae832014-05-10 09:56:38 -0700206 go func() {
207 vlog.VI(1).Infof("TUNNEL START: %v", name)
Todd Wangaffd04d2015-04-08 10:25:35 -0700208 errf := internal.Forward(conn, stream.SendStream(), stream.RecvStream())
Jiri Simsad5aae832014-05-10 09:56:38 -0700209 err := stream.Finish()
210 vlog.VI(1).Infof("TUNNEL END : %v (%v, %v)", name, errf, err)
211 }()
212 }
213}