github.com/omoiti/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  }