github.com/devops-filetransfer/sshego@v7.0.4+incompatible/user.go (about) 1 package sshego 2 3 import ( 4 "bytes" 5 "fmt" 6 "os" 7 "path/filepath" 8 "regexp" 9 "sync" 10 "time" 11 12 scrypt "github.com/elithrar/simple-scrypt" 13 "github.com/glycerine/greenpack/msgp" 14 "github.com/glycerine/sshego/xendor/github.com/glycerine/xcryptossh" 15 "github.com/pquerna/otp" 16 ) 17 18 //go:generate greenpack 19 20 // LoginRecord is per public key. 21 type LoginRecord struct { 22 FirstTm time.Time 23 LastTm time.Time 24 SeenCount int64 25 AcceptedCount int64 26 PubFinger string 27 } 28 29 func (r LoginRecord) String() string { 30 return fmt.Sprintf(`LoginRecord{ FirstTm:"%s", LastTm:"%s", SeenCount:%v, AcceptedCount: %v, PubFinger:"%s"}`, 31 r.FirstTm, r.LastTm, r.SeenCount, r.AcceptedCount, r.PubFinger) 32 } 33 34 // User represents a user authorized 35 // to login to the embedded sshd. 36 type User struct { 37 MyEmail string 38 MyFullname string 39 MyLogin string 40 41 PublicKeyPath string 42 PrivateKeyPath string 43 TOTPpath string 44 QrPath string 45 46 Issuer string 47 PublicKey ssh.PublicKey `msg:"-"` 48 SeenPubKey map[string]LoginRecord 49 50 ScryptedPassword []byte 51 ClearPw string // only on network, never on disk. 52 TOTPorig string 53 oneTime *TOTP 54 55 FirstLoginTime time.Time 56 LastLoginTime time.Time 57 LastLoginAddr string 58 IPwhitelist []string 59 DisabledAcct bool 60 61 mut sync.Mutex 62 } 63 64 func (u *User) String() string { 65 var buf bytes.Buffer 66 err := msgp.Encode(&buf, u) 67 panicOn(err) 68 var js bytes.Buffer 69 _, err = msgp.CopyToJSON(&js, &buf) 70 panicOn(err) 71 return js.String() 72 } 73 74 func NewUser() *User { 75 u := &User{ 76 SeenPubKey: make(map[string]LoginRecord), 77 } 78 return u 79 } 80 81 // only these fields are actually saved/restored. 82 type HostDbPersist struct { 83 // Users: key is MyLogin; value is *User. 84 Users *AtomicUserMap `zid:"0"` 85 HostPrivateKeyPath string `zid:"1"` 86 } 87 88 type HostDb struct { 89 UserHomePrefix string 90 91 HostSshSigner ssh.Signer `msg:"-"` 92 cfg *SshegoConfig 93 94 Persist HostDbPersist 95 96 loadedFromDisk bool 97 98 saveMut sync.Mutex 99 100 userTcp TcpPort 101 102 db Filedb 103 } 104 105 func (h *HostDb) String() string { 106 return h.Persist.Users.String() 107 } 108 109 func (cfg *SshegoConfig) NewHostDb() error { 110 p("SshegoConfig.NewHostDB() called...") 111 h := &HostDb{ 112 UserHomePrefix: "", 113 cfg: cfg, 114 Persist: HostDbPersist{ 115 Users: NewAtomicUserMap(), 116 }, 117 userTcp: TcpPort{Port: cfg.SshegoSystemMutexPort}, 118 } 119 cfg.HostDb = h 120 return h.init() 121 } 122 123 func (h *HostDb) privpath() string { 124 return h.cfg.EmbeddedSSHdHostDbPath + ".hostkey" 125 } 126 127 func (h *HostDb) init() error { 128 h.Persist.HostPrivateKeyPath = h.privpath() 129 p("HostDb.init(): h.Persist.HostPrivateKeyPath = '%v'", h.Persist.HostPrivateKeyPath) 130 err := h.loadOrCreate() 131 return err 132 } 133 134 func (h *HostDb) generateHostKey() error { 135 p("generateHostKey called.") 136 err := h.gendir() 137 if err != nil { 138 return err 139 } 140 path := h.privpath() 141 bits := h.cfg.BitLenRSAkeys // default 4096 142 143 p("\n bits = %v\n", bits) 144 host, _ := os.Hostname() 145 _, signer, err := GenRSAKeyPair(path, bits, host) 146 if err != nil { 147 return err 148 } 149 h.HostSshSigner = signer 150 h.Persist.HostPrivateKeyPath = path 151 return nil 152 } 153 154 func (h *HostDb) gendir() error { 155 path := h.cfg.EmbeddedSSHdHostDbPath 156 p("HostDb.gendir has h.cfg.EmbeddedSSHdHostDbPath='%s'", h.cfg.EmbeddedSSHdHostDbPath) 157 if dirExists(path) { 158 return nil 159 } 160 err := os.MkdirAll(path, 0777) 161 if err != nil { 162 return fmt.Errorf("HostDb: MkdirAll on '%s' failed: %v", 163 path, err) 164 } 165 return nil 166 } 167 168 func makeway(path string) error { 169 dir := filepath.Dir(path) 170 return os.MkdirAll(dir, 0777) 171 } 172 173 func (h *HostDb) msgpath() string { 174 return h.cfg.EmbeddedSSHdHostDbPath + "/msgp.db" 175 } 176 177 func (h *HostDb) userpath(username string) string { 178 return h.cfg.EmbeddedSSHdHostDbPath + "/users/" + username 179 } 180 181 func (h *HostDb) Rsapath(username string) string { 182 return h.cfg.EmbeddedSSHdHostDbPath + "/users/" + username + "/id_rsa" 183 } 184 185 func (h *HostDb) toptpath(username string) string { 186 return h.cfg.EmbeddedSSHdHostDbPath + "/users/" + username + "/topt" 187 } 188 189 const skiplock = false 190 const lockit = true 191 192 // always opens h.msgpath() 193 func (h *HostDb) opendb() error { 194 if h.cfg.EmbeddedSSHdHostDbPath == "" { 195 panic("opendb() called on empty h.cfg.EmbeddedSSHdHostDbPath") 196 } 197 p("HostDb.opendb() has h.cfg.EmbeddedSSHdHostDbPath='%s'", h.cfg.EmbeddedSSHdHostDbPath) 198 if h.db.HostDb == nil { 199 err := h.gendir() 200 if err != nil { 201 return err 202 } 203 204 filedb, err := NewFiledb(h.msgpath()) 205 if err != nil { 206 return fmt.Errorf("HostDb.opendb: create newFiledb at '%s' failed: %v", 207 h.msgpath(), err) 208 } 209 if filedb.HostDb != nil { 210 h.Persist = filedb.HostDb.Persist 211 filedb.HostDb = h 212 } 213 } 214 return nil 215 } 216 217 // There should only one writer to disk at a time... 218 // Let this be the main handshake/user auth goroutine 219 // that listens for sshd connections. 220 func (h *HostDb) save(lock bool) error { 221 if lock == lockit { 222 h.saveMut.Lock() 223 defer h.saveMut.Unlock() 224 } 225 226 h.db.filepath = h.msgpath() 227 err := h.db.storeHostDb(h) 228 if err != nil { 229 return fmt.Errorf("HostDb: h.db.storeHostDb(h) gave error = '%v'", err) 230 } 231 return nil 232 } 233 234 func (h *HostDb) loadOrCreate() error { 235 p("top of HostDb.loadOrCreate()...") 236 p("HostDb.loadOrCreate has h.cfg.EmbeddedSSHdHostDbPath='%s'", h.cfg.EmbeddedSSHdHostDbPath) 237 err := h.opendb() 238 if err != nil { 239 panic(err) 240 return fmt.Errorf("HostDb.loadOrCreate(): opendb() at path '%s' gave error '%v'", 241 h.msgpath(), err) 242 } 243 244 if h.Persist.HostPrivateKeyPath != "" && fileExists(h.Persist.HostPrivateKeyPath) { 245 p("h.Persist.HostPrivateKeyPath exists already... loaded HostDb from msgpath()='%s'. db = '%s'", h.msgpath(), h) 246 247 } else { 248 249 p("h.Persist.HostPrivateKeyPath = '%s' doesn't exist; make a host key...", h.msgpath()) 250 251 // no db, so make a host key 252 err := h.generateHostKey() 253 if err != nil { 254 return err 255 } 256 257 err = h.save(skiplock) 258 259 if err != nil { 260 return fmt.Errorf("HostDb.Save MarshalMsg failed: %v", err) 261 } 262 263 } 264 h.loadedFromDisk = true 265 266 if fileExists(h.Persist.HostPrivateKeyPath) { 267 _, err := h.adoptNewHostKeyFromPath(h.Persist.HostPrivateKeyPath) 268 if err != nil { 269 return err 270 } 271 } else { 272 panic(fmt.Sprintf("missing h.Persist.HostPrivateKeyPath='%s'", h.Persist.HostPrivateKeyPath)) 273 } 274 return nil 275 } 276 277 func (h *HostDb) adoptNewHostKeyFromPath(path string) (ssh.PublicKey, error) { 278 if !fileExists(path) { 279 return nil, fmt.Errorf("error in adoptNewHostKeyFromPath: path '%s' does not exist", path) 280 } 281 282 sshPrivKey, err := LoadRSAPrivateKey(path) 283 if err != nil { 284 return nil, fmt.Errorf("error in adoptNewHostKeyFromPath: loading"+ 285 " path '%s' with LoadRSAPrivateKey() resulted in error '%v'", path, err) 286 } 287 288 // avoid data race: 289 h.saveMut.Lock() 290 h.HostSshSigner = sshPrivKey 291 h.saveMut.Unlock() 292 293 h.Persist.HostPrivateKeyPath = path 294 return sshPrivKey.PublicKey(), nil 295 } 296 297 func ScryptHash(password string) []byte { 298 hash, err := scrypt.GenerateFromPassword([]byte(password), scrypt.DefaultParams) 299 panicOn(err) 300 return hash 301 } 302 303 func (user *User) MatchingHashAndPw(password string) bool { 304 return nil == scrypt.CompareHashAndPassword(user.ScryptedPassword, []byte(password)) 305 } 306 307 // emailAddressRE matches the mail addresses 308 // we admit. Since we are writing out 309 // to file system paths that include the email, 310 // we want to be restrictive. 311 // 312 var emailAddressREstring = `^([a-zA-Z0-9][\+-_.a-zA-Z0-9]{0,63})@([-_.a-zA-Z0-9]{1,255})$` 313 var emailAddressRE = regexp.MustCompile(emailAddressREstring) 314 315 // AddUser will use an existing extantRsaPath path to private key if provided, otherwise 316 // we make a new private/public key pair. 317 // 318 func (h *HostDb) AddUser(mylogin, myemail, pw, issuer, fullname, extantPrivateKeyPath string) (toptPath, qrPath, rsaPath string, err error) { 319 320 p("AddUser mylogin:'%v' pw:'%v' myemail:'%v'", mylogin, pw, myemail) 321 322 var valid bool 323 valid, err = h.ValidLogin(mylogin) 324 if !valid { 325 // err already set 326 return 327 } 328 329 p("h = %#v", h) 330 _, ok := h.Persist.Users.Get2(mylogin) 331 if ok { 332 err = fmt.Errorf("user already exists; manually -deluser '%s' first!", 333 mylogin) 334 return 335 } else { 336 p("brand new user '%s'", mylogin) 337 } 338 if extantPrivateKeyPath != "" { 339 rsaPath = extantPrivateKeyPath 340 } else { 341 rsaPath = h.Rsapath(mylogin) 342 } 343 344 // path := h.userpath(mylogin) 345 346 user := NewUser() 347 user.MyLogin = mylogin 348 user.MyEmail = myemail 349 user.ClearPw = pw 350 user.Issuer = issuer 351 user.MyFullname = fullname 352 if !h.cfg.SkipRSA { 353 user.PrivateKeyPath = rsaPath 354 user.PublicKeyPath = rsaPath + ".pub" 355 } 356 return h.finishUserBuildout(user) 357 } 358 359 func (h *HostDb) finishUserBuildout(user *User) (toptPath, qrPath, rsaPath string, err error) { 360 p("finishUserBuildout started: user.MyLogin:'%v' user.ClearPw:'%v' user.MyEmail:'%v' toptPath='%v'", 361 user.MyLogin, user.ClearPw, user.MyEmail, toptPath) 362 363 if !h.cfg.SkipPassphrase { 364 user.ScryptedPassword = ScryptHash(user.ClearPw) 365 } 366 367 if !h.cfg.SkipTOTP { 368 var w *TOTP 369 w, err = NewTOTP(user.MyEmail, fmt.Sprintf("%s/%s", user.MyLogin, user.Issuer)) 370 if err != nil { 371 panic(err) 372 } 373 toptPath = h.toptpath(user.MyLogin) 374 user.TOTPpath = toptPath 375 makeway(toptPath) 376 377 user.TOTPorig = w.Key.String() 378 _, qrPath, err = w.SaveToFile(toptPath) 379 panicOn(err) 380 user.oneTime = w 381 user.QrPath = qrPath 382 } 383 384 if !h.cfg.SkipRSA { 385 // rsa private key already exists and supplied above? 386 if user.PrivateKeyPath != "" && fileExists(user.PrivateKeyPath) { 387 rsaPath = user.PrivateKeyPath 388 } else { 389 390 // need to make a new 391 rsaPath = h.Rsapath(user.MyLogin) 392 user.PrivateKeyPath = rsaPath 393 user.PublicKeyPath = rsaPath + ".pub" 394 395 makeway(rsaPath) 396 bits := h.cfg.BitLenRSAkeys // default 4096 397 398 var signer ssh.Signer 399 _, signer, err = GenRSAKeyPair(rsaPath, bits, user.MyEmail) 400 if err != nil { 401 return 402 } 403 user.PublicKeyPath = rsaPath + ".pub" 404 user.PublicKey = signer.PublicKey() 405 } 406 } 407 408 // don't save ClearPw to disk, and no need 409 // to ship it back b/c they supplied it in 410 // the first place (and we can't change it 411 // after the fact). 412 user.ClearPw = "" 413 414 // p("user = %#v", user) 415 h.Persist.Users.Set(user.MyLogin, user) 416 417 err = h.save(lockit) 418 return 419 } 420 421 func (h *HostDb) DelUser(mylogin string) error { 422 423 ok, err := h.ValidLogin(mylogin) 424 if !ok { 425 return err 426 } 427 /* 428 if !emailAddressRE.MatchString(mylogin) { 429 return fmt.Errorf("We are restrictive about what we "+ 430 "accept as user email, and '%s' doesn't match "+ 431 "our permitted regex '%s'", myemail, emailAddressREstring) 432 } 433 */ 434 435 p("DelUser %v", mylogin) 436 _, ok = h.Persist.Users.Get2(mylogin) 437 438 if ok { 439 // cleanup old 440 path := h.userpath(mylogin) 441 err := os.RemoveAll(path) 442 h.Persist.Users.Del(mylogin) 443 if err != nil { 444 panicOn(err) 445 } 446 return h.save(lockit) 447 } 448 return fmt.Errorf("error in -userdel '%s': user not found.", mylogin) 449 } 450 451 func (user *User) RestoreTotp() { 452 if user.oneTime == nil && user.TOTPorig != "" { 453 user.oneTime = &TOTP{} 454 w, err := otp.NewKeyFromURL(user.TOTPorig) 455 panicOn(err) 456 user.oneTime.Key = w 457 } 458 } 459 460 // UserExists is used by sshego/cmd/gosshtun/main.go 461 func (h *HostDb) UserExists(mylogin string) bool { 462 _, ok := h.Persist.Users.Get2(mylogin) 463 return ok 464 } 465 466 func (h *HostDb) ValidEmail(myemail string) (bool, error) { 467 if !emailAddressRE.MatchString(myemail) { 468 return false, fmt.Errorf("bad email: '%s' did not "+ 469 "conform to '%s'. Please provide a conforming "+ 470 "email if you wish to opt-in to passphrase "+ 471 "backup to email.", myemail, emailAddressREstring) 472 } 473 return true, nil 474 } 475 476 var loginREstring = `^[a-z][-_a-z0-9]{0,31}$` 477 var loginRE = regexp.MustCompile(loginREstring) 478 479 func (h *HostDb) ValidLogin(login string) (bool, error) { 480 if !loginRE.MatchString(login) { 481 return false, fmt.Errorf("bad login: '%s' did not conform to '%s'", 482 login, loginREstring) 483 } 484 return true, nil 485 }