github.com/devops-filetransfer/sshego@v7.0.4+incompatible/server.go (about)

     1  package sshego
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"image/png"
     9  	"io/ioutil"
    10  	"log"
    11  	"net"
    12  	"os"
    13  	"sync"
    14  	"time"
    15  
    16  	"github.com/glycerine/greenpack/msgp"
    17  	"github.com/pquerna/otp"
    18  	"github.com/pquerna/otp/totp"
    19  
    20  	ssh "github.com/glycerine/sshego/xendor/github.com/glycerine/xcryptossh"
    21  )
    22  
    23  // Esshd is our embedded sshd server,
    24  // running from inside this libary.
    25  type Esshd struct {
    26  	cfg                  *SshegoConfig
    27  	Halt                 ssh.Halter
    28  	addUserToDatabase    chan *User
    29  	replyWithCreatedUser chan *User
    30  
    31  	delUserReq           chan *User
    32  	replyWithDeletedDone chan bool
    33  
    34  	updateHostKey chan ssh.Signer
    35  
    36  	mut sync.Mutex
    37  
    38  	cr *CommandRecv
    39  }
    40  
    41  func (e *Esshd) Stop() error {
    42  	e.Halt.RequestStop()
    43  	<-e.Halt.DoneChan()
    44  
    45  	if -1 == WaitUntilAddrAvailable(e.cfg.EmbeddedSSHd.Addr, 100*time.Millisecond, 100) {
    46  		return fmt.Errorf("esshd never stopped; after 10 seconds of waits")
    47  	}
    48  
    49  	// gotta wait for xport to unbind as well...
    50  	xport := fmt.Sprintf("127.0.0.1:%v",
    51  		e.cfg.SshegoSystemMutexPort)
    52  	if -1 == WaitUntilAddrAvailable(xport, 100*time.Millisecond, 100) {
    53  		return fmt.Errorf("xport bound never stopped from old esshd; after 10 seconds of waits")
    54  	}
    55  
    56  	return nil
    57  }
    58  
    59  // NewEsshd sets cfg.Esshd with a newly
    60  // constructed Esshd. does NewHostDb()
    61  // internally.
    62  func (cfg *SshegoConfig) NewEsshd() *Esshd {
    63  	p("top of SshegoConfig.NewEsshd()...")
    64  	srv := &Esshd{
    65  		cfg:                  cfg,
    66  		Halt:                 *ssh.NewHalter(),
    67  		addUserToDatabase:    make(chan *User),
    68  		replyWithCreatedUser: make(chan *User),
    69  		delUserReq:           make(chan *User),
    70  		replyWithDeletedDone: make(chan bool),
    71  		updateHostKey:        make(chan ssh.Signer),
    72  	}
    73  	if srv.cfg.HostDb == nil {
    74  		err := srv.cfg.NewHostDb()
    75  		panicOn(err)
    76  	}
    77  	cfg.Esshd = srv
    78  	return srv
    79  }
    80  
    81  // CustomChannelHandlerCB is a callback that
    82  // is configured in the cfg.CustomChannelHandlers map.
    83  // Each will be called on its own goroutine already.
    84  // For example, "custom-inproc-stream" might
    85  // serve in-process streaming.
    86  type CustomChannelHandlerCB func(nc ssh.NewChannel, sshconn ssh.Conn, ca *ConnectionAlert)
    87  
    88  // PerAttempt holds the auth state
    89  // that should be reset anew on each
    90  // login attempt; plus a pointer to
    91  // the invariant State.
    92  type PerAttempt struct {
    93  	PublicKeyOK bool
    94  	OneTimeOK   bool
    95  
    96  	User   *User
    97  	State  *AuthState
    98  	Config *ssh.ServerConfig
    99  
   100  	cfg *SshegoConfig
   101  }
   102  
   103  func NewPerAttempt(s *AuthState, cfg *SshegoConfig) *PerAttempt {
   104  	pa := &PerAttempt{State: s}
   105  	pa.cfg = cfg
   106  	return pa
   107  }
   108  
   109  // AuthState holds the authorization information
   110  // that doesn't change after startup; each fresh
   111  // PerAttempt gets a pointer to one of these.
   112  // Currently assumes only one user.
   113  type AuthState struct {
   114  	HostKey ssh.Signer
   115  	OneTime *TOTP
   116  
   117  	AuthorizedKeysMap map[string]bool
   118  
   119  	PrivateKeys map[string]interface{}
   120  	Signers     map[string]ssh.Signer
   121  	PublicKeys  map[string]ssh.PublicKey
   122  
   123  	Cert *ssh.Certificate
   124  }
   125  
   126  func NewAuthState(w *TOTP) *AuthState {
   127  	if w == nil {
   128  		w = &TOTP{}
   129  	}
   130  	return &AuthState{
   131  		OneTime:           w,
   132  		AuthorizedKeysMap: map[string]bool{},
   133  	}
   134  }
   135  
   136  type CommandRecv struct {
   137  	userTcp TcpPort
   138  	esshd   *Esshd
   139  	cfg     *SshegoConfig
   140  
   141  	addUserReq           chan *User
   142  	replyWithCreatedUser chan *User
   143  
   144  	delUserReq           chan *User
   145  	replyWithDeletedDone chan bool
   146  
   147  	reqStop chan bool
   148  	Done    chan bool
   149  }
   150  
   151  var NewUserCmd = []byte("00NEWUSER___")
   152  var NewUserCmdStr = string(NewUserCmd)
   153  var NewUserReply = []byte("00REPLY_____")
   154  
   155  var DelUserCmd = []byte("01DELUSER___")
   156  var DelUserCmdStr = string(DelUserCmd)
   157  var DelUserReplyOK = []byte("01REPLY_OK__")
   158  var DelUserReplyFailed = []byte("01REPLY_FAIL")
   159  
   160  func (e *Esshd) NewCommandRecv() *CommandRecv {
   161  	return &CommandRecv{
   162  		userTcp:              TcpPort{Port: e.cfg.SshegoSystemMutexPort},
   163  		esshd:                e,
   164  		cfg:                  e.cfg,
   165  		addUserReq:           e.addUserToDatabase,
   166  		reqStop:              make(chan bool),
   167  		Done:                 make(chan bool),
   168  		replyWithCreatedUser: e.replyWithCreatedUser,
   169  		delUserReq:           e.delUserReq,
   170  		replyWithDeletedDone: e.replyWithDeletedDone,
   171  	}
   172  }
   173  
   174  func (cr *CommandRecv) Start(ctx context.Context) error {
   175  
   176  	msecLimit := 100
   177  	err := cr.userTcp.Lock(msecLimit)
   178  	if err != nil {
   179  		return err
   180  	}
   181  	go func() {
   182  		// basically, always hold the lock while we are up
   183  		defer cr.userTcp.Unlock()
   184  		tcpLsn := cr.userTcp.Lsn.(*net.TCPListener)
   185  		var nConn net.Conn
   186  
   187  		defer func() {
   188  			close(cr.Done)
   189  		}()
   190  
   191  	mainloop:
   192  		for {
   193  			timeoutMillisec := 500
   194  			err = tcpLsn.SetDeadline(time.Now().Add(time.Duration(timeoutMillisec) * time.Millisecond))
   195  			panicOn(err)
   196  			nConn, err = tcpLsn.Accept()
   197  			if err != nil {
   198  				// simple timeout, check if stop requested
   199  				// 'accept tcp 127.0.0.1:54796: i/o timeout'
   200  				// p("simple timeout err: '%v'", err)
   201  				select {
   202  				case <-ctx.Done():
   203  					return
   204  				case <-cr.reqStop:
   205  					return
   206  				default:
   207  					// no stop request, keep looping
   208  				}
   209  				continue
   210  			} else {
   211  				// not error, but connection
   212  
   213  				// read from it
   214  				err = nConn.SetReadDeadline(time.Now().Add(time.Second))
   215  				if err != nil {
   216  					log.Printf("warning: CommandRecv: nConn.Read ignoring "+
   217  						"SetReadDeadline error %v", err)
   218  					nConn.Close()
   219  					continue mainloop
   220  				}
   221  
   222  				by := make([]byte, len(NewUserCmd))
   223  				_, err := nConn.Read(by)
   224  				if err != nil {
   225  					log.Printf("warning: CommandRecv: nConn.Read ignoring "+
   226  						"Read error '%v'; could be timeout.", err)
   227  					nConn.Close()
   228  					continue mainloop
   229  				}
   230  				cmd := string(by)
   231  				switch cmd {
   232  				case NewUserCmdStr:
   233  					log.Printf("CommandRecv: we got a NEWUSER command")
   234  				case DelUserCmdStr:
   235  					log.Printf("CommandRecv: we got a DELUSER command")
   236  				default:
   237  					log.Printf("warning: CommandRecv: nConn.Read ignoring "+
   238  						"unrecognized command '%v'", cmd)
   239  					nConn.Close()
   240  					continue mainloop
   241  				}
   242  
   243  				// unmarshal into a User structure
   244  				newUser := NewUser()
   245  				reader := msgp.NewReader(nConn)
   246  				err = newUser.DecodeMsg(reader)
   247  				if err != nil {
   248  					log.Printf("warning: saw NEWUSER/DELUSER preamble but got"+
   249  						" error reading the User data: %v", err)
   250  					nConn.Close()
   251  					continue mainloop
   252  				}
   253  				log.Printf("CommandRecv: %s '%v' with email '%v'", cmd, newUser.MyLogin, newUser.MyEmail)
   254  
   255  				if cmd == DelUserCmdStr {
   256  					// make the delete request
   257  					select {
   258  					case cr.delUserReq <- newUser:
   259  					case <-time.After(10 * time.Second):
   260  						log.Printf("warning: unable to deliver delUser request " +
   261  							"after 10 seconds")
   262  					case <-cr.reqStop:
   263  						return
   264  					case <-ctx.Done():
   265  						return
   266  					}
   267  					// ack back
   268  					select {
   269  					case ok := <-cr.replyWithDeletedDone:
   270  						err := nConn.SetWriteDeadline(time.Now().Add(time.Second * 5))
   271  						panicOn(err)
   272  						if ok {
   273  							_, err = nConn.Write(DelUserReplyOK)
   274  						} else {
   275  							_, err = nConn.Write(DelUserReplyFailed)
   276  						}
   277  						panicOn(err)
   278  						nConn.Close()
   279  
   280  					case <-cr.reqStop:
   281  						return
   282  					case <-ctx.Done():
   283  						return
   284  					}
   285  				}
   286  
   287  				if cmd == NewUserCmdStr {
   288  					// make the add request
   289  					select {
   290  					case cr.addUserReq <- newUser:
   291  					case <-time.After(10 * time.Second):
   292  						log.Printf("warning: unable to deliver newUser request" +
   293  							"after 10 seconds")
   294  					case <-cr.reqStop:
   295  						return
   296  					case <-ctx.Done():
   297  						return
   298  					}
   299  					// send remote client a reply, also a User
   300  					// but now with fields filled in.
   301  					select {
   302  					case goback := <-cr.replyWithCreatedUser:
   303  						//p("goback received!")
   304  						writeBackHelper(goback, nConn)
   305  					case <-cr.reqStop:
   306  						return
   307  					case <-ctx.Done():
   308  						return
   309  					}
   310  				}
   311  			}
   312  		}
   313  	}()
   314  	return nil
   315  }
   316  
   317  func (e *Esshd) Start(ctx context.Context) {
   318  	p("Start for Esshd called.")
   319  
   320  	if !e.cfg.SkipCommandRecv {
   321  		e.cr = e.NewCommandRecv()
   322  		err := e.cr.Start(ctx)
   323  		if err != nil {
   324  			panic(err) // -xport error: could not acquire our -xport before the deadline, for -xport 127.0.0.1:55418
   325  		}
   326  	}
   327  
   328  	go func() {
   329  		p("%s Esshd.Start() called, for binding '%s'. %s",
   330  			e.cfg.Nickname, e.cfg.EmbeddedSSHd.Addr, SourceVersion())
   331  
   332  		// most of the auth state is per user, so it has
   333  		// to wait until we have a login and a
   334  		// username at hand.
   335  		a := NewAuthState(nil)
   336  
   337  		// we copy the host key here to avoid a data race later.
   338  		e.cfg.Mut.Lock()
   339  		e.cfg.HostDb.saveMut.Lock()
   340  		a.HostKey = e.cfg.HostDb.HostSshSigner // race unless we lock saveMut too.
   341  		e.cfg.HostDb.saveMut.Unlock()
   342  		e.cfg.Mut.Unlock()
   343  
   344  		p("about to listen on %v", e.cfg.EmbeddedSSHd.Addr)
   345  		// Once a ServerConfig has been configured, connections can be
   346  		// accepted.
   347  		domain := "tcp"
   348  		if e.cfg.EmbeddedSSHd.UnixDomainPath != "" {
   349  			domain = "unix"
   350  		}
   351  		listener, err := net.Listen(domain, e.cfg.EmbeddedSSHd.Addr)
   352  		if err != nil {
   353  			msg := fmt.Sprintf("failed to listen for connection on %v: %v",
   354  				e.cfg.EmbeddedSSHd.Addr, err)
   355  			log.Printf(msg)
   356  			//panic(msg)
   357  			return
   358  		}
   359  
   360  		// cleanup, any which way we return
   361  		defer func() {
   362  			if e.cr != nil {
   363  				close(e.cr.reqStop)
   364  			}
   365  			if listener != nil {
   366  				listener.Close()
   367  			}
   368  			e.Halt.MarkDone()
   369  		}()
   370  
   371  		p("info: Essh.Start() in server.go: listening on "+
   372  			"domain '%s', addr: '%s'", domain, e.cfg.EmbeddedSSHd.Addr)
   373  		for {
   374  			// TODO: fail2ban: notice bad login IPs and if too many, block the IP.
   375  
   376  			timeoutMillisec := 1000
   377  			err = listener.(*net.TCPListener).SetDeadline(time.Now().Add(time.Duration(timeoutMillisec) * time.Millisecond))
   378  			panicOn(err)
   379  			nConn, err := listener.Accept()
   380  			if err != nil {
   381  				// simple timeout, check if stop requested
   382  				// 'accept tcp 127.0.0.1:54796: i/o timeout'
   383  				// p("simple timeout err: '%v'", err)
   384  				select {
   385  				case <-ctx.Done():
   386  					return
   387  				case <-e.Halt.ReqStopChan():
   388  					return
   389  				case u := <-e.addUserToDatabase:
   390  					p("received on e.addUserToDatabase, calling finishUserBuildout with supplied *User u: '%#v'", u)
   391  					_, _, _, err = e.cfg.HostDb.finishUserBuildout(u)
   392  					panicOn(err)
   393  					select {
   394  					case e.replyWithCreatedUser <- u:
   395  						//p("sent: e.replyWithCreatedUser <- u")
   396  					case <-e.Halt.ReqStopChan():
   397  						return
   398  					}
   399  
   400  				case u := <-e.delUserReq:
   401  					//p("received on e.delUserReq: '%v'", u.MyLogin)
   402  					err = e.cfg.HostDb.DelUser(u.MyLogin)
   403  					ok := (err == nil)
   404  
   405  					select {
   406  					case e.replyWithDeletedDone <- ok:
   407  					case <-e.Halt.ReqStopChan():
   408  						e.Halt.MarkDone()
   409  						return
   410  					}
   411  
   412  				case newSigner := <-e.updateHostKey:
   413  					//p("we got newSigner")
   414  					a.HostKey = newSigner
   415  
   416  				default:
   417  					// no stop request, keep looping
   418  				}
   419  				continue
   420  			}
   421  			p("info: Essh.Start() in server.go: accepted new connection on "+
   422  				"domain '%s', addr: '%s'", domain, e.cfg.EmbeddedSSHd.Addr)
   423  
   424  			attempt := NewPerAttempt(a, e.cfg)
   425  			attempt.SetupAuthRequirements()
   426  
   427  			// We explicitly do not use a go routine here.
   428  			// We *want* and require serializing all authentication
   429  			// attempts, so that we don't get our user database
   430  			// into an inconsistent state by having multiple
   431  			// writers at once. This library is intended
   432  			// for light use (one user is the common case) anyway, so
   433  			// correctness and lack of corruption is much more
   434  			// important than concurrency of login processing.
   435  			// After login we let connections proceed freely
   436  			// and in parallel.
   437  			p("PRE attempt.PerConnection, server %v", e.cfg.EmbeddedSSHd.Addr)
   438  			attempt.PerConnection(ctx, nConn, nil)
   439  			p("POST attempt.PerConnection, server %v", e.cfg.EmbeddedSSHd.Addr)
   440  		}
   441  	}()
   442  }
   443  
   444  func (a *PerAttempt) PerConnection(ctx context.Context, nConn net.Conn, ca *ConnectionAlert) error {
   445  
   446  	loc := a.cfg.EmbeddedSSHd.Addr
   447  	p("%v Accept has returned an nConn... sshego PerConnection(). doing handshake. This is where the server handshake transport and kexLoop are started: ssh.NewServerConn().", loc)
   448  
   449  	// Before use, a handshake must be performed on the incoming
   450  	// net.Conn.
   451  
   452  	sshConn, chans, reqs, err := ssh.NewServerConn(ctx, nConn, a.Config)
   453  	if err != nil {
   454  		msg := fmt.Errorf("%v sshego PerAttempt.PerConnection() did not handshake: %v", loc, err)
   455  		p(msg.Error())
   456  		return msg
   457  	}
   458  
   459  	p("%s done with handshake. handlers in force: '%s'", loc, a.cfg.ChannelHandlerSummary())
   460  
   461  	p("server %s sees new SSH connection from %s (%s)", sshConn.LocalAddr(), sshConn.RemoteAddr(), sshConn.ClientVersion())
   462  
   463  	// The incoming Request channel must be serviced.
   464  	// Discard all global out-of-band Requests, except for keepalives.
   465  	go DiscardRequestsExceptKeepalives(ctx, reqs, a.cfg.Esshd.Halt.ReqStopChan())
   466  	// Accept all channels
   467  	go a.cfg.handleChannels(ctx, chans, sshConn, ca)
   468  
   469  	return nil
   470  }
   471  
   472  // DiscardRequestsExceptKeepalives accepts and responds
   473  // to requests of type "keepalive@sshego.glycerine.github.com"
   474  // that want reply; these are used as ping/pong messages
   475  // to detect ssh connection failure.
   476  func DiscardRequestsExceptKeepalives(ctx context.Context, in <-chan *ssh.Request, reqStop chan struct{}) {
   477  
   478  	for {
   479  		select {
   480  		case req, stillOpen := <-in:
   481  			if !stillOpen {
   482  				return
   483  			}
   484  			if req != nil && req.WantReply {
   485  				if req.Type != "keepalive@sshego.glycerine.github.com" || len(req.Payload) == 0 {
   486  					req.Reply(false, nil)
   487  					continue
   488  				}
   489  				// respond to keepalive pings
   490  				var ping KeepAlivePing
   491  				_, err := ping.UnmarshalMsg(req.Payload)
   492  				if err != nil {
   493  					req.Reply(false, nil)
   494  					continue
   495  				}
   496  
   497  				now := time.Now()
   498  				//p("sshego server.go: discardRequestsExceptKeepalives sees keepalive %v! ping.Sent: '%v'. setting replied to now='%v'", ping.Serial, ping.Sent, now)
   499  
   500  				ping.Replied = now
   501  				pingReplyBy, err := ping.MarshalMsg(nil)
   502  				panicOn(err)
   503  				req.Reply(true, pingReplyBy)
   504  			}
   505  		case <-reqStop:
   506  			return
   507  		case <-ctx.Done():
   508  			return
   509  		}
   510  	}
   511  }
   512  
   513  type TOTP struct {
   514  	UserEmail string
   515  	Issuer    string
   516  	Key       *otp.Key
   517  	QRcodePng []byte
   518  }
   519  
   520  func (w *TOTP) String() string {
   521  	return w.Key.String()
   522  }
   523  
   524  func (w *TOTP) SaveToFile(path string) (secretPath, qrPath string, err error) {
   525  	secretPath = path
   526  	var fd *os.File
   527  	fd, err = os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
   528  	if err != nil {
   529  		return
   530  	}
   531  	defer fd.Close()
   532  	_, err = fmt.Fprintf(fd, "%v\n", w.Key.String())
   533  	if err != nil {
   534  		return
   535  	}
   536  
   537  	// serialize qr-code too
   538  	if len(w.QRcodePng) > 0 {
   539  		qrPath = path + "-qrcode.png"
   540  		var qr *os.File
   541  		qr, err = os.OpenFile(qrPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
   542  		if err != nil {
   543  			return
   544  		}
   545  		defer qr.Close()
   546  		_, err = qr.Write(w.QRcodePng)
   547  		if err != nil {
   548  			return
   549  		}
   550  	}
   551  	return
   552  }
   553  
   554  func (w *TOTP) LoadFromFile(path string) error {
   555  	fd, err := os.Open(path)
   556  	if err != nil {
   557  		return err
   558  	}
   559  	defer fd.Close()
   560  	var orig string
   561  	_, err = fmt.Fscanf(fd, "%s", &orig)
   562  	if err != nil {
   563  		return err
   564  	}
   565  	w.Key, err = otp.NewKeyFromURL(orig)
   566  	return err
   567  }
   568  
   569  func (w *TOTP) IsValid(passcode string, mylogin string) bool {
   570  	valid := totp.Validate(passcode, w.Key.Secret())
   571  
   572  	if valid {
   573  		p("Login '%s' successfully used their "+
   574  			"Time-based-One-Time-Password!",
   575  			mylogin)
   576  	} else {
   577  		p("Login '%s' failed at Time-based-One-"+
   578  			"Time-Password attempt",
   579  			mylogin)
   580  	}
   581  	return valid
   582  }
   583  
   584  func NewTOTP(userEmail, issuer string) (w *TOTP, err error) {
   585  
   586  	key, err := totp.Generate(totp.GenerateOpts{
   587  		Issuer:      issuer,
   588  		AccountName: userEmail,
   589  	})
   590  	if err != nil {
   591  		return nil, err
   592  	}
   593  
   594  	w = &TOTP{
   595  		UserEmail: userEmail,
   596  		Issuer:    issuer,
   597  		Key:       key,
   598  	}
   599  
   600  	// Convert TOTP key into a QR code encoded as a PNG image.
   601  	var buf bytes.Buffer
   602  	img, err := key.Image(200, 200)
   603  	png.Encode(&buf, img)
   604  	w.QRcodePng = buf.Bytes()
   605  	return w, err
   606  }
   607  
   608  var keyFail = errors.New("keyboard-interactive failed")
   609  
   610  const passwordChallenge = "password: "
   611  const gauthChallenge = "google-authenticator-code: "
   612  
   613  func (a *PerAttempt) KeyboardInteractiveCallback(ctx context.Context, conn ssh.ConnMetadata, challenge ssh.KeyboardInteractiveChallenge) (*ssh.Permissions, error) {
   614  	//p("KeyboardInteractiveCallback top: a.PublicKeyOK=%v, a.OneTimeOK=%v", a.PublicKeyOK, a.OneTimeOK)
   615  
   616  	// no matter what happens, temper DDOS/many fast login attemps by
   617  	// waiting 1-2 seconds before replying.
   618  	defer wait()
   619  
   620  	mylogin := conn.User()
   621  	now := time.Now().UTC()
   622  	remoteAddr := conn.RemoteAddr()
   623  
   624  	user, knownUser := a.cfg.HostDb.Persist.Users.Get2(mylogin)
   625  
   626  	// don't reveal that the user is unknown by
   627  	// failing early without a challenge.
   628  
   629  	// Unless, of course, we have no call for
   630  	// interactive challenge at all... in which
   631  	// case, why are we in this routine? We
   632  	// should not be!
   633  	if a.cfg.SkipPassphrase && a.cfg.SkipTOTP {
   634  		panic("should not be in the KeyboardInteractiveCallback at all!")
   635  	}
   636  
   637  	firstPassOK := false
   638  	timeOK := false
   639  
   640  	var totpIdx int // where in the arrays the totp info is located
   641  	var chal []string
   642  	var echoAnswers []bool
   643  	if !a.cfg.SkipPassphrase {
   644  		chal = append(chal, passwordChallenge)
   645  		echoAnswers = append(echoAnswers, false)
   646  		totpIdx++
   647  	}
   648  	if !a.cfg.SkipTOTP {
   649  		chal = append(chal, gauthChallenge)
   650  		echoAnswers = append(echoAnswers, true)
   651  	}
   652  
   653  	ans, err := challenge(ctx, mylogin,
   654  		fmt.Sprintf("login for %s:", mylogin),
   655  		chal,
   656  		echoAnswers)
   657  	if err != nil {
   658  		p("actuall err is '%s', but we always return keyFail", err)
   659  		return nil, keyFail
   660  	}
   661  
   662  	if !knownUser {
   663  		log.Printf("unrecognized login '%s' from remoteAddr '%s' at %v",
   664  			mylogin, remoteAddr, now)
   665  		return nil, keyFail
   666  	}
   667  
   668  	p("KeyboardInteractiveCallback sees login "+
   669  		"attempt for recognized user '%v'", user.MyLogin)
   670  
   671  	if a.cfg.SkipPassphrase || user.MatchingHashAndPw(ans[0]) {
   672  		firstPassOK = true
   673  	}
   674  	p("KeyboardInteractiveCallback, first pass-phrase accepted: %v; ans[0] was user-attempting-login provided this cleartext: '%s'; our stored scrypted pw is: '%s'", firstPassOK, ans[0], user.ScryptedPassword)
   675  	user.RestoreTotp()
   676  
   677  	if a.cfg.SkipTOTP || (len(ans[totpIdx]) > 0 && user.oneTime.IsValid(ans[totpIdx], mylogin)) {
   678  		timeOK = true
   679  	}
   680  
   681  	ok := firstPassOK && timeOK
   682  	if ok {
   683  		a.OneTimeOK = true
   684  		if !a.PublicKeyOK {
   685  			p("keyboard interactive succeeded however public-key did not!, and we want to enforce *both*. Note that earlier we will have told the client that the public-key failed so that it will also do the keyboard-interactive which lets us do the 2FA/TOTP one-time-password/google-authenticator here.")
   686  			// must also be true
   687  			return nil, keyFail
   688  		}
   689  		prev := fmt.Sprintf("last login was at %v, from '%s'",
   690  			user.LastLoginTime.UTC(), user.LastLoginAddr)
   691  		challenge(ctx, fmt.Sprintf("user '%s' succesfully logged in", mylogin),
   692  			prev, nil, nil)
   693  		a.NoteLogin(user, now, conn)
   694  		return nil, nil
   695  	}
   696  	return nil, keyFail
   697  }
   698  
   699  func (a *PerAttempt) NoteLogin(user *User, now time.Time, conn ssh.ConnMetadata) {
   700  	user.LastLoginTime = now
   701  	user.LastLoginAddr = conn.RemoteAddr().String()
   702  	a.cfg.HostDb.save(lockit)
   703  }
   704  
   705  func (a *PerAttempt) AuthLogCallback(conn ssh.ConnMetadata, method string, err error) {
   706  	p("AuthLogCallback top: a.PublicKeyOK=%v, a.OneTimeOK=%v", a.PublicKeyOK, a.OneTimeOK)
   707  
   708  	if err == nil {
   709  		p("login success! auth-log-callback: user %q, method %q: %v",
   710  			conn.User(), method, err)
   711  		switch method {
   712  		case "keyboard-interactive":
   713  			a.OneTimeOK = true
   714  		case "publickey":
   715  			a.PublicKeyOK = true
   716  		}
   717  	} else {
   718  		p("login failure! auth-log-callback: user %q, method %q: %v",
   719  			conn.User(), method, err)
   720  	}
   721  }
   722  
   723  func (a *PerAttempt) PublicKeyCallback(c ssh.ConnMetadata, providedPubKey ssh.PublicKey) (perm *ssh.Permissions, rerr error) {
   724  	p("PublicKeyCallback top: a.PublicKeyOK=%v, a.OneTimeOK=%v", a.PublicKeyOK, a.OneTimeOK)
   725  
   726  	unknown := fmt.Errorf("unknown public key for %q", c.User())
   727  
   728  	//	if a.PublicKeyOK && !a.OneTimeOK {
   729  	//		p("already validated public key, skipping on 2nd round")
   730  	//		return nil, unknown
   731  	//	}
   732  
   733  	mylogin := c.User()
   734  
   735  	valid, err := a.cfg.HostDb.ValidLogin(mylogin)
   736  	if !valid {
   737  		return nil, err
   738  	}
   739  
   740  	remoteAddr := c.RemoteAddr()
   741  	now := time.Now().UTC()
   742  
   743  	user, foundUser := a.cfg.HostDb.Persist.Users.Get2(mylogin)
   744  	if !foundUser {
   745  		log.Printf("unrecognized user '%s' from remoteAddr '%s' at %v",
   746  			mylogin, remoteAddr, now)
   747  		log.Printf("debug: my userdb is = '%s'\n", a.cfg.HostDb)
   748  		return nil, unknown
   749  	}
   750  	p("PublicKeyCallback sees login attempt for recognized user '%v'", user.MyLogin)
   751  
   752  	// update user.FirstLoginTm / LastLoginTm
   753  
   754  	providedPubKeyStr := string(providedPubKey.Marshal())
   755  	providedPubKeyFinger := Fingerprint(providedPubKey)
   756  
   757  	// save the public key and when we saw it
   758  	loginRecord, already := user.SeenPubKey[providedPubKeyStr]
   759  	p("PublicKeyCallback: checking providedPubKey with fingerprint '%s'... already: %v, loginRecord: %s",
   760  		providedPubKeyFinger, already, loginRecord)
   761  	updated := loginRecord
   762  	updated.LastTm = now
   763  	if loginRecord.FirstTm.IsZero() {
   764  		updated.FirstTm = now
   765  	}
   766  	updated.SeenCount++
   767  	// defer so we can set updated.AcceptedCount below before saving...
   768  	defer func() {
   769  		if foundUser && user != nil {
   770  			if user.SeenPubKey == nil {
   771  				user.SeenPubKey = make(map[string]LoginRecord)
   772  			}
   773  			user.SeenPubKey[providedPubKeyStr] = updated
   774  			// TODO: save() re-saves the whole database. Could be
   775  			// slow if the db gets big, but for one-two users,
   776  			// this won't take up more than a page anyway.
   777  			a.cfg.HostDb.save(lockit) // save the SeenPubKey update.
   778  		}
   779  
   780  		// check if we are actually okay now, because we saw
   781  		// the right key in the past; hence we have to reply
   782  		// okay now to actually accept the login when
   783  
   784  		if a.PublicKeyOK && a.OneTimeOK {
   785  			perm = nil
   786  			rerr = nil
   787  			p("PublicKeyCallback: defer sees pub-key and one-time okay, authorizing login")
   788  		}
   789  	}()
   790  
   791  	// load up the public key
   792  	p("loading public key from '%s'", user.PublicKeyPath)
   793  	onfilePubKey, err := LoadRSAPublicKey(user.PublicKeyPath)
   794  	if err != nil {
   795  		return nil, unknown
   796  	}
   797  	onfilePubKeyFinger := Fingerprint(onfilePubKey)
   798  	p("ok: successful load of public key from '%s'... pub fingerprint = '%s'",
   799  		user.PublicKeyPath, onfilePubKeyFinger)
   800  
   801  	//	if a.State.AuthorizedKeysMap[string(providedPubKey.Marshal())] {
   802  	onfilePubKeyStr := string(onfilePubKey.Marshal())
   803  	if onfilePubKeyStr == providedPubKeyStr {
   804  		p("we have a public key match for user '%s', key fingerprint = '%s'", mylogin, onfilePubKeyFinger)
   805  		updated.AcceptedCount++
   806  		a.PublicKeyOK = true
   807  		// although we note this, we don't reveal this to the client.
   808  		if !a.OneTimeOK {
   809  			p("public-key succeeded however keyboard interactive did not (yet).")
   810  			return nil, unknown
   811  		}
   812  		return nil, nil
   813  	} else {
   814  		p("public key mismatch; onfilePubKey (%s) did not match providedPubKey (%s)",
   815  			onfilePubKeyFinger, Fingerprint(providedPubKey))
   816  	}
   817  	return nil, unknown
   818  }
   819  
   820  func (a *AuthState) LoadPublicKeys(authorizedKeysPath string) error {
   821  	// Public key authentication is done by comparing
   822  	// the public key of a received connection
   823  	// with the entries in the authorized_keys file.
   824  	authorizedKeysBytes, err := ioutil.ReadFile(authorizedKeysPath)
   825  	if err != nil {
   826  		return fmt.Errorf("Failed to load authorized_keys, err: %v", err)
   827  	}
   828  
   829  	for len(authorizedKeysBytes) > 0 {
   830  		pubKey, _, _, rest, err := ssh.ParseAuthorizedKey(authorizedKeysBytes)
   831  		if err != nil {
   832  			return fmt.Errorf("failed Parsing public keys:  %v", err)
   833  		}
   834  
   835  		a.AuthorizedKeysMap[string(pubKey.Marshal())] = true
   836  		authorizedKeysBytes = rest
   837  	}
   838  	return nil
   839  }
   840  
   841  func (a *PerAttempt) SetupAuthRequirements() {
   842  	a.cfg.Mut.Lock()
   843  	defer a.cfg.Mut.Unlock()
   844  	a.SetTripleConfig()
   845  	if a.cfg.SkipRSA {
   846  		a.Config.PublicKeyCallback = nil
   847  		a.PublicKeyOK = true
   848  	}
   849  	if a.cfg.SkipPassphrase && a.cfg.SkipTOTP {
   850  		a.Config.KeyboardInteractiveCallback = nil
   851  		a.OneTimeOK = true
   852  	}
   853  }
   854  
   855  // see vendor/github.com/glycerine/xcryptossh/kex.go
   856  const (
   857  	kexAlgoCurve25519SHA256 = "curve25519-sha256@libssh.org"
   858  )
   859  
   860  // SetTripleConfig establishes an a.State.Config that requires
   861  // *both* public key and one-time password validation.
   862  func (a *PerAttempt) SetTripleConfig() {
   863  	a.Config = &ssh.ServerConfig{
   864  		PublicKeyCallback:           a.PublicKeyCallback,
   865  		KeyboardInteractiveCallback: a.KeyboardInteractiveCallback,
   866  		AuthLogCallback:             a.AuthLogCallback,
   867  		Config: ssh.Config{
   868  			Ciphers:      getCiphers(),
   869  			KeyExchanges: []string{kexAlgoCurve25519SHA256},
   870  			Halt:         a.cfg.Halt,
   871  		},
   872  		ServerVersion: "SSH-2.0-OpenSSH_6.9",
   873  	}
   874  	a.Config.AddHostKey(a.State.HostKey)
   875  }
   876  
   877  //func StartServer() {
   878  //    //go newServer(c1, serverConfig)
   879  //}
   880  
   881  func (a *AuthState) LoadHostKey(path string) error {
   882  
   883  	//a.Config.AddHostKey(a.Signers["rsa"])
   884  
   885  	privateBytes, err := ioutil.ReadFile(path)
   886  	if err != nil {
   887  		return fmt.Errorf("Failed to load private key from path '%s': %s",
   888  			path, err)
   889  	}
   890  
   891  	private, err := ssh.ParsePrivateKey(privateBytes)
   892  	if err != nil {
   893  		return fmt.Errorf("Failed to parse private key '%s': %s",
   894  			path, err)
   895  	}
   896  
   897  	a.HostKey = private
   898  	return nil
   899  }
   900  
   901  // wait between 1-2 seconds
   902  func wait() {
   903  	// 1000 - 2000 millisecond
   904  	n := 1000 + CryptoRandNonNegInt(1000)
   905  	time.Sleep(time.Millisecond * time.Duration(n))
   906  }
   907  
   908  // write NewUserReply + MarshalMsg(goback) back to our remote client
   909  func writeBackHelper(goback *User, nConn net.Conn) error {
   910  	//p("top of writeBackHelper")
   911  	err := nConn.SetWriteDeadline(time.Now().Add(time.Second * 5))
   912  	panicOn(err)
   913  
   914  	_, err = nConn.Write(NewUserReply)
   915  	panicOn(err)
   916  
   917  	err = nConn.SetWriteDeadline(time.Now().Add(time.Second * 5))
   918  	panicOn(err)
   919  
   920  	wri := msgp.NewWriter(nConn)
   921  	err = goback.EncodeMsg(wri)
   922  	panicOn(err)
   923  
   924  	p("end of writeBackHelper")
   925  	wri.Flush()
   926  	nConn.Close()
   927  	return nil
   928  }