mirror of
				https://codeberg.org/forgejo/forgejo.git
				synced 2025-10-30 22:11:07 +00:00 
			
		
		
		
	* update github.com/alecthomas/chroma v0.8.0 -> v0.8.1 * github.com/blevesearch/bleve v1.0.10 -> v1.0.12 * editorconfig-core-go v2.1.1 -> v2.3.7 * github.com/gliderlabs/ssh v0.2.2 -> v0.3.1 * migrate editorconfig.ParseBytes to Parse * github.com/shurcooL/vfsgen to 0d455de96546 * github.com/go-git/go-git/v5 v5.1.0 -> v5.2.0 * github.com/google/uuid v1.1.1 -> v1.1.2 * github.com/huandu/xstrings v1.3.0 -> v1.3.2 * github.com/klauspost/compress v1.10.11 -> v1.11.1 * github.com/markbates/goth v1.61.2 -> v1.65.0 * github.com/mattn/go-sqlite3 v1.14.0 -> v1.14.4 * github.com/mholt/archiver v3.3.0 -> v3.3.2 * github.com/microcosm-cc/bluemonday 4f7140c49acb -> v1.0.4 * github.com/minio/minio-go v7.0.4 -> v7.0.5 * github.com/olivere/elastic v7.0.9 -> v7.0.20 * github.com/urfave/cli v1.20.0 -> v1.22.4 * github.com/prometheus/client_golang v1.1.0 -> v1.8.0 * github.com/xanzy/go-gitlab v0.37.0 -> v0.38.1 * mvdan.cc/xurls v2.1.0 -> v2.2.0 Co-authored-by: Lauris BH <lauris@nix.lv>
		
			
				
	
	
		
			374 lines
		
	
	
	
		
			9.3 KiB
		
	
	
	
		
			Go
		
	
	
	
		
			Vendored
		
	
	
	
			
		
		
	
	
			374 lines
		
	
	
	
		
			9.3 KiB
		
	
	
	
		
			Go
		
	
	
	
		
			Vendored
		
	
	
	
| package ssh
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"context"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"net"
 | |
| 	"sync"
 | |
| 
 | |
| 	"github.com/anmitsu/go-shlex"
 | |
| 	gossh "golang.org/x/crypto/ssh"
 | |
| )
 | |
| 
 | |
| // Session provides access to information about an SSH session and methods
 | |
| // to read and write to the SSH channel with an embedded Channel interface from
 | |
| // cypto/ssh.
 | |
| //
 | |
| // When Command() returns an empty slice, the user requested a shell. Otherwise
 | |
| // the user is performing an exec with those command arguments.
 | |
| //
 | |
| // TODO: Signals
 | |
| type Session interface {
 | |
| 	gossh.Channel
 | |
| 
 | |
| 	// User returns the username used when establishing the SSH connection.
 | |
| 	User() string
 | |
| 
 | |
| 	// RemoteAddr returns the net.Addr of the client side of the connection.
 | |
| 	RemoteAddr() net.Addr
 | |
| 
 | |
| 	// LocalAddr returns the net.Addr of the server side of the connection.
 | |
| 	LocalAddr() net.Addr
 | |
| 
 | |
| 	// Environ returns a copy of strings representing the environment set by the
 | |
| 	// user for this session, in the form "key=value".
 | |
| 	Environ() []string
 | |
| 
 | |
| 	// Exit sends an exit status and then closes the session.
 | |
| 	Exit(code int) error
 | |
| 
 | |
| 	// Command returns a shell parsed slice of arguments that were provided by the
 | |
| 	// user. Shell parsing splits the command string according to POSIX shell rules,
 | |
| 	// which considers quoting not just whitespace.
 | |
| 	Command() []string
 | |
| 
 | |
| 	// RawCommand returns the exact command that was provided by the user.
 | |
| 	RawCommand() string
 | |
| 
 | |
| 	// Subsystem returns the subsystem requested by the user.
 | |
| 	Subsystem() string
 | |
| 
 | |
| 	// PublicKey returns the PublicKey used to authenticate. If a public key was not
 | |
| 	// used it will return nil.
 | |
| 	PublicKey() PublicKey
 | |
| 
 | |
| 	// Context returns the connection's context. The returned context is always
 | |
| 	// non-nil and holds the same data as the Context passed into auth
 | |
| 	// handlers and callbacks.
 | |
| 	//
 | |
| 	// The context is canceled when the client's connection closes or I/O
 | |
| 	// operation fails.
 | |
| 	Context() context.Context
 | |
| 
 | |
| 	// Permissions returns a copy of the Permissions object that was available for
 | |
| 	// setup in the auth handlers via the Context.
 | |
| 	Permissions() Permissions
 | |
| 
 | |
| 	// Pty returns PTY information, a channel of window size changes, and a boolean
 | |
| 	// of whether or not a PTY was accepted for this session.
 | |
| 	Pty() (Pty, <-chan Window, bool)
 | |
| 
 | |
| 	// Signals registers a channel to receive signals sent from the client. The
 | |
| 	// channel must handle signal sends or it will block the SSH request loop.
 | |
| 	// Registering nil will unregister the channel from signal sends. During the
 | |
| 	// time no channel is registered signals are buffered up to a reasonable amount.
 | |
| 	// If there are buffered signals when a channel is registered, they will be
 | |
| 	// sent in order on the channel immediately after registering.
 | |
| 	Signals(c chan<- Signal)
 | |
| 
 | |
| 	// Break regisers a channel to receive notifications of break requests sent
 | |
| 	// from the client. The channel must handle break requests, or it will block
 | |
| 	// the request handling loop. Registering nil will unregister the channel.
 | |
| 	// During the time that no channel is registered, breaks are ignored.
 | |
| 	Break(c chan<- bool)
 | |
| }
 | |
| 
 | |
| // maxSigBufSize is how many signals will be buffered
 | |
| // when there is no signal channel specified
 | |
| const maxSigBufSize = 128
 | |
| 
 | |
| func DefaultSessionHandler(srv *Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx Context) {
 | |
| 	ch, reqs, err := newChan.Accept()
 | |
| 	if err != nil {
 | |
| 		// TODO: trigger event callback
 | |
| 		return
 | |
| 	}
 | |
| 	sess := &session{
 | |
| 		Channel:           ch,
 | |
| 		conn:              conn,
 | |
| 		handler:           srv.Handler,
 | |
| 		ptyCb:             srv.PtyCallback,
 | |
| 		sessReqCb:         srv.SessionRequestCallback,
 | |
| 		subsystemHandlers: srv.SubsystemHandlers,
 | |
| 		ctx:               ctx,
 | |
| 	}
 | |
| 	sess.handleRequests(reqs)
 | |
| }
 | |
| 
 | |
| type session struct {
 | |
| 	sync.Mutex
 | |
| 	gossh.Channel
 | |
| 	conn              *gossh.ServerConn
 | |
| 	handler           Handler
 | |
| 	subsystemHandlers map[string]SubsystemHandler
 | |
| 	handled           bool
 | |
| 	exited            bool
 | |
| 	pty               *Pty
 | |
| 	winch             chan Window
 | |
| 	env               []string
 | |
| 	ptyCb             PtyCallback
 | |
| 	sessReqCb         SessionRequestCallback
 | |
| 	rawCmd            string
 | |
| 	subsystem         string
 | |
| 	ctx               Context
 | |
| 	sigCh             chan<- Signal
 | |
| 	sigBuf            []Signal
 | |
| 	breakCh           chan<- bool
 | |
| }
 | |
| 
 | |
| func (sess *session) Write(p []byte) (n int, err error) {
 | |
| 	if sess.pty != nil {
 | |
| 		m := len(p)
 | |
| 		// normalize \n to \r\n when pty is accepted.
 | |
| 		// this is a hardcoded shortcut since we don't support terminal modes.
 | |
| 		p = bytes.Replace(p, []byte{'\n'}, []byte{'\r', '\n'}, -1)
 | |
| 		p = bytes.Replace(p, []byte{'\r', '\r', '\n'}, []byte{'\r', '\n'}, -1)
 | |
| 		n, err = sess.Channel.Write(p)
 | |
| 		if n > m {
 | |
| 			n = m
 | |
| 		}
 | |
| 		return
 | |
| 	}
 | |
| 	return sess.Channel.Write(p)
 | |
| }
 | |
| 
 | |
| func (sess *session) PublicKey() PublicKey {
 | |
| 	sessionkey := sess.ctx.Value(ContextKeyPublicKey)
 | |
| 	if sessionkey == nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 	return sessionkey.(PublicKey)
 | |
| }
 | |
| 
 | |
| func (sess *session) Permissions() Permissions {
 | |
| 	// use context permissions because its properly
 | |
| 	// wrapped and easier to dereference
 | |
| 	perms := sess.ctx.Value(ContextKeyPermissions).(*Permissions)
 | |
| 	return *perms
 | |
| }
 | |
| 
 | |
| func (sess *session) Context() context.Context {
 | |
| 	return sess.ctx
 | |
| }
 | |
| 
 | |
| func (sess *session) Exit(code int) error {
 | |
| 	sess.Lock()
 | |
| 	defer sess.Unlock()
 | |
| 	if sess.exited {
 | |
| 		return errors.New("Session.Exit called multiple times")
 | |
| 	}
 | |
| 	sess.exited = true
 | |
| 
 | |
| 	status := struct{ Status uint32 }{uint32(code)}
 | |
| 	_, err := sess.SendRequest("exit-status", false, gossh.Marshal(&status))
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return sess.Close()
 | |
| }
 | |
| 
 | |
| func (sess *session) User() string {
 | |
| 	return sess.conn.User()
 | |
| }
 | |
| 
 | |
| func (sess *session) RemoteAddr() net.Addr {
 | |
| 	return sess.conn.RemoteAddr()
 | |
| }
 | |
| 
 | |
| func (sess *session) LocalAddr() net.Addr {
 | |
| 	return sess.conn.LocalAddr()
 | |
| }
 | |
| 
 | |
| func (sess *session) Environ() []string {
 | |
| 	return append([]string(nil), sess.env...)
 | |
| }
 | |
| 
 | |
| func (sess *session) RawCommand() string {
 | |
| 	return sess.rawCmd
 | |
| }
 | |
| 
 | |
| func (sess *session) Command() []string {
 | |
| 	cmd, _ := shlex.Split(sess.rawCmd, true)
 | |
| 	return append([]string(nil), cmd...)
 | |
| }
 | |
| 
 | |
| func (sess *session) Subsystem() string {
 | |
| 	return sess.subsystem
 | |
| }
 | |
| 
 | |
| func (sess *session) Pty() (Pty, <-chan Window, bool) {
 | |
| 	if sess.pty != nil {
 | |
| 		return *sess.pty, sess.winch, true
 | |
| 	}
 | |
| 	return Pty{}, sess.winch, false
 | |
| }
 | |
| 
 | |
| func (sess *session) Signals(c chan<- Signal) {
 | |
| 	sess.Lock()
 | |
| 	defer sess.Unlock()
 | |
| 	sess.sigCh = c
 | |
| 	if len(sess.sigBuf) > 0 {
 | |
| 		go func() {
 | |
| 			for _, sig := range sess.sigBuf {
 | |
| 				sess.sigCh <- sig
 | |
| 			}
 | |
| 		}()
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (sess *session) Break(c chan<- bool) {
 | |
| 	sess.Lock()
 | |
| 	defer sess.Unlock()
 | |
| 	sess.breakCh = c
 | |
| }
 | |
| 
 | |
| func (sess *session) handleRequests(reqs <-chan *gossh.Request) {
 | |
| 	for req := range reqs {
 | |
| 		switch req.Type {
 | |
| 		case "shell", "exec":
 | |
| 			if sess.handled {
 | |
| 				req.Reply(false, nil)
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			var payload = struct{ Value string }{}
 | |
| 			gossh.Unmarshal(req.Payload, &payload)
 | |
| 			sess.rawCmd = payload.Value
 | |
| 
 | |
| 			// If there's a session policy callback, we need to confirm before
 | |
| 			// accepting the session.
 | |
| 			if sess.sessReqCb != nil && !sess.sessReqCb(sess, req.Type) {
 | |
| 				sess.rawCmd = ""
 | |
| 				req.Reply(false, nil)
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			sess.handled = true
 | |
| 			req.Reply(true, nil)
 | |
| 
 | |
| 			go func() {
 | |
| 				sess.handler(sess)
 | |
| 				sess.Exit(0)
 | |
| 			}()
 | |
| 		case "subsystem":
 | |
| 			if sess.handled {
 | |
| 				req.Reply(false, nil)
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			var payload = struct{ Value string }{}
 | |
| 			gossh.Unmarshal(req.Payload, &payload)
 | |
| 			sess.subsystem = payload.Value
 | |
| 
 | |
| 			// If there's a session policy callback, we need to confirm before
 | |
| 			// accepting the session.
 | |
| 			if sess.sessReqCb != nil && !sess.sessReqCb(sess, req.Type) {
 | |
| 				sess.rawCmd = ""
 | |
| 				req.Reply(false, nil)
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			handler := sess.subsystemHandlers[payload.Value]
 | |
| 			if handler == nil {
 | |
| 				handler = sess.subsystemHandlers["default"]
 | |
| 			}
 | |
| 			if handler == nil {
 | |
| 				req.Reply(false, nil)
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			sess.handled = true
 | |
| 			req.Reply(true, nil)
 | |
| 
 | |
| 			go func() {
 | |
| 				handler(sess)
 | |
| 				sess.Exit(0)
 | |
| 			}()
 | |
| 		case "env":
 | |
| 			if sess.handled {
 | |
| 				req.Reply(false, nil)
 | |
| 				continue
 | |
| 			}
 | |
| 			var kv struct{ Key, Value string }
 | |
| 			gossh.Unmarshal(req.Payload, &kv)
 | |
| 			sess.env = append(sess.env, fmt.Sprintf("%s=%s", kv.Key, kv.Value))
 | |
| 			req.Reply(true, nil)
 | |
| 		case "signal":
 | |
| 			var payload struct{ Signal string }
 | |
| 			gossh.Unmarshal(req.Payload, &payload)
 | |
| 			sess.Lock()
 | |
| 			if sess.sigCh != nil {
 | |
| 				sess.sigCh <- Signal(payload.Signal)
 | |
| 			} else {
 | |
| 				if len(sess.sigBuf) < maxSigBufSize {
 | |
| 					sess.sigBuf = append(sess.sigBuf, Signal(payload.Signal))
 | |
| 				}
 | |
| 			}
 | |
| 			sess.Unlock()
 | |
| 		case "pty-req":
 | |
| 			if sess.handled || sess.pty != nil {
 | |
| 				req.Reply(false, nil)
 | |
| 				continue
 | |
| 			}
 | |
| 			ptyReq, ok := parsePtyRequest(req.Payload)
 | |
| 			if !ok {
 | |
| 				req.Reply(false, nil)
 | |
| 				continue
 | |
| 			}
 | |
| 			if sess.ptyCb != nil {
 | |
| 				ok := sess.ptyCb(sess.ctx, ptyReq)
 | |
| 				if !ok {
 | |
| 					req.Reply(false, nil)
 | |
| 					continue
 | |
| 				}
 | |
| 			}
 | |
| 			sess.pty = &ptyReq
 | |
| 			sess.winch = make(chan Window, 1)
 | |
| 			sess.winch <- ptyReq.Window
 | |
| 			defer func() {
 | |
| 				// when reqs is closed
 | |
| 				close(sess.winch)
 | |
| 			}()
 | |
| 			req.Reply(ok, nil)
 | |
| 		case "window-change":
 | |
| 			if sess.pty == nil {
 | |
| 				req.Reply(false, nil)
 | |
| 				continue
 | |
| 			}
 | |
| 			win, ok := parseWinchRequest(req.Payload)
 | |
| 			if ok {
 | |
| 				sess.pty.Window = win
 | |
| 				sess.winch <- win
 | |
| 			}
 | |
| 			req.Reply(ok, nil)
 | |
| 		case agentRequestType:
 | |
| 			// TODO: option/callback to allow agent forwarding
 | |
| 			SetAgentRequested(sess.ctx)
 | |
| 			req.Reply(true, nil)
 | |
| 		case "break":
 | |
| 			ok := false
 | |
| 			sess.Lock()
 | |
| 			if sess.breakCh != nil {
 | |
| 				sess.breakCh <- true
 | |
| 				ok = true
 | |
| 			}
 | |
| 			req.Reply(ok, nil)
 | |
| 			sess.Unlock()
 | |
| 		default:
 | |
| 			// TODO: debug log
 | |
| 			req.Reply(false, nil)
 | |
| 		}
 | |
| 	}
 | |
| }
 |