gitlab.com/apertussolutions/u-root@v7.0.0+incompatible/cmds/core/sshd/sshd.go (about) 1 // Copyright 2018 the u-root 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 5 package main 6 7 import ( 8 "flag" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "log" 13 "net" 14 "os" 15 "os/exec" 16 17 "github.com/u-root/u-root/pkg/pty" 18 "golang.org/x/crypto/ssh" 19 ) 20 21 // The ssh package does not define these things so we will 22 type ( 23 ptyReq struct { 24 TERM string //TERM environment variable value (e.g., vt100) 25 Col uint32 26 Row uint32 27 Xpixel uint32 28 Ypixel uint32 29 Modes string //encoded terminal modes 30 } 31 execReq struct { 32 Command string 33 } 34 exitStatusReq struct { 35 ExitStatus uint32 36 } 37 ) 38 39 var ( 40 shells = [...]string{"bash", "zsh", "elvish"} 41 shell = "/bin/sh" 42 debug = flag.Bool("d", false, "Enable debug prints") 43 keys = flag.String("keys", "authorized_keys", "Path to the authorized_keys file") 44 privkey = flag.String("privatekey", "id_rsa", "Path of private key") 45 ip = flag.String("ip", "0.0.0.0", "ip address to listen on") 46 port = flag.String("port", "2022", "port to listen on") 47 dprintf = func(string, ...interface{}) {} 48 ) 49 50 // start a command 51 // TODO: use /etc/passwd, but the Go support for that is incomplete 52 func runCommand(c ssh.Channel, p *pty.Pty, cmd string, args ...string) error { 53 var ps *os.ProcessState 54 defer c.Close() 55 56 if p != nil { 57 log.Printf("Executing PTY command %s %v", cmd, args) 58 p.Command(cmd, args...) 59 if err := p.C.Start(); err != nil { 60 dprintf("Failed to execute: %v", err) 61 return err 62 } 63 defer p.C.Wait() 64 go io.Copy(p.Ptm, c) 65 go io.Copy(c, p.Ptm) 66 ps, _ = p.C.Process.Wait() 67 } else { 68 e := exec.Command(cmd, args...) 69 e.Stdin, e.Stdout, e.Stderr = c, c, c 70 log.Printf("Executing non-PTY command %s %v", cmd, args) 71 if err := e.Start(); err != nil { 72 dprintf("Failed to execute: %v", err) 73 return err 74 } 75 ps, _ = e.Process.Wait() 76 } 77 78 // TODO(bluecmd): If somebody wants we can send exit-signal to return 79 // information about signal termination, but leave it until somebody needs 80 // it. 81 // if ws.Signaled() { 82 // } 83 if ps.Exited() { 84 code := uint32(ps.ExitCode()) 85 dprintf("Exit status %v", code) 86 c.SendRequest("exit-status", false, ssh.Marshal(exitStatusReq{code})) 87 } 88 return nil 89 } 90 91 func newPTY(b []byte) (*pty.Pty, error) { 92 ptyReq := &ptyReq{} 93 err := ssh.Unmarshal(b, ptyReq) 94 dprintf("newPTY: %q", ptyReq) 95 if err != nil { 96 return nil, err 97 } 98 p, err := pty.New() 99 if err != nil { 100 return nil, err 101 } 102 ws, err := p.TTY.GetWinSize() 103 if err != nil { 104 return nil, err 105 } 106 ws.Row = uint16(ptyReq.Row) 107 ws.Ypixel = uint16(ptyReq.Ypixel) 108 ws.Col = uint16(ptyReq.Col) 109 ws.Xpixel = uint16(ptyReq.Xpixel) 110 dprintf("newPTY: Set winsizes to %v", ws) 111 if err := p.TTY.SetWinSize(ws); err != nil { 112 return nil, err 113 } 114 dprintf("newPTY: set TERM to %q", ptyReq.TERM) 115 if err := os.Setenv("TERM", ptyReq.TERM); err != nil { 116 return nil, err 117 } 118 return p, nil 119 } 120 121 func init() { 122 for _, s := range shells { 123 if _, err := exec.LookPath(s); err == nil { 124 shell = s 125 } 126 } 127 } 128 129 func session(chans <-chan ssh.NewChannel) { 130 var p *pty.Pty 131 // Service the incoming Channel channel. 132 for newChannel := range chans { 133 // Channels have a type, depending on the application level 134 // protocol intended. In the case of a shell, the type is 135 // "session" and ServerShell may be used to present a simple 136 // terminal interface. 137 if newChannel.ChannelType() != "session" { 138 newChannel.Reject(ssh.UnknownChannelType, "unknown channel type") 139 continue 140 } 141 channel, requests, err := newChannel.Accept() 142 if err != nil { 143 log.Printf("Could not accept channel: %v", err) 144 continue 145 } 146 147 // Sessions have out-of-band requests such as "shell", 148 // "pty-req" and "env". Here we handle only the 149 // "shell" request. 150 go func(in <-chan *ssh.Request) { 151 for req := range in { 152 dprintf("Request %v", req.Type) 153 switch req.Type { 154 case "shell": 155 err := runCommand(channel, p, shell) 156 req.Reply(true, []byte(fmt.Sprintf("%v", err))) 157 case "exec": 158 e := &execReq{} 159 if err := ssh.Unmarshal(req.Payload, e); err != nil { 160 log.Printf("sshd: %v", err) 161 break 162 } 163 // Execute command using user's shell. This is what OpenSSH does 164 // so it's the least surprising to the user. 165 err := runCommand(channel, p, shell, "-c", e.Command) 166 req.Reply(true, []byte(fmt.Sprintf("%v", err))) 167 case "pty-req": 168 p, err = newPTY(req.Payload) 169 req.Reply(err == nil, nil) 170 default: 171 log.Printf("Not handling req %v %q", req, string(req.Payload)) 172 req.Reply(false, nil) 173 } 174 } 175 }(requests) 176 177 } 178 } 179 180 func main() { 181 flag.Parse() 182 if *debug { 183 dprintf = log.Printf 184 } 185 // Public key authentication is done by comparing 186 // the public key of a received connection 187 // with the entries in the authorized_keys file. 188 authorizedKeysBytes, err := ioutil.ReadFile(*keys) 189 if err != nil { 190 log.Fatal(err) 191 } 192 193 authorizedKeysMap := map[string]bool{} 194 for len(authorizedKeysBytes) > 0 { 195 pubKey, _, _, rest, err := ssh.ParseAuthorizedKey(authorizedKeysBytes) 196 if err != nil { 197 log.Fatal(err) 198 } 199 200 authorizedKeysMap[string(pubKey.Marshal())] = true 201 authorizedKeysBytes = rest 202 } 203 204 // An SSH server is represented by a ServerConfig, which holds 205 // certificate details and handles authentication of ServerConns. 206 config := &ssh.ServerConfig{ 207 // Remove to disable public key auth. 208 PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) { 209 if authorizedKeysMap[string(pubKey.Marshal())] { 210 return &ssh.Permissions{ 211 // Record the public key used for authentication. 212 Extensions: map[string]string{ 213 "pubkey-fp": ssh.FingerprintSHA256(pubKey), 214 }, 215 }, nil 216 } 217 return nil, fmt.Errorf("unknown public key for %q", c.User()) 218 }, 219 } 220 221 privateBytes, err := ioutil.ReadFile(*privkey) 222 if err != nil { 223 log.Fatal(err) 224 } 225 226 private, err := ssh.ParsePrivateKey(privateBytes) 227 if err != nil { 228 log.Fatal(err) 229 } 230 231 config.AddHostKey(private) 232 233 // Once a ServerConfig has been configured, connections can be 234 // accepted. 235 listener, err := net.Listen("tcp", net.JoinHostPort(*ip, *port)) 236 if err != nil { 237 log.Fatal(err) 238 } 239 for { 240 nConn, err := listener.Accept() 241 if err != nil { 242 log.Printf("failed to accept incoming connection: %s", err) 243 continue 244 } 245 246 // Before use, a handshake must be performed on the incoming 247 // net.Conn. 248 conn, chans, reqs, err := ssh.NewServerConn(nConn, config) 249 if err != nil { 250 log.Printf("failed to handshake: %v", err) 251 continue 252 } 253 log.Printf("%v logged in with key %s", conn.RemoteAddr(), conn.Permissions.Extensions["pubkey-fp"]) 254 255 // The incoming Request channel must be serviced. 256 go ssh.DiscardRequests(reqs) 257 258 go session(chans) 259 } 260 }