webclient.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626
  1. package httpd
  2. import (
  3. "errors"
  4. "fmt"
  5. "html/template"
  6. "io"
  7. "mime"
  8. "net/http"
  9. "net/url"
  10. "os"
  11. "path"
  12. "path/filepath"
  13. "strconv"
  14. "strings"
  15. "time"
  16. "github.com/rs/xid"
  17. "github.com/drakkan/sftpgo/common"
  18. "github.com/drakkan/sftpgo/dataprovider"
  19. "github.com/drakkan/sftpgo/logger"
  20. "github.com/drakkan/sftpgo/metrics"
  21. "github.com/drakkan/sftpgo/utils"
  22. "github.com/drakkan/sftpgo/version"
  23. "github.com/drakkan/sftpgo/vfs"
  24. )
  25. const (
  26. templateClientDir = "webclient"
  27. templateClientBase = "base.html"
  28. templateClientLogin = "login.html"
  29. templateClientFiles = "files.html"
  30. templateClientMessage = "message.html"
  31. templateClientCredentials = "credentials.html"
  32. pageClientFilesTitle = "My Files"
  33. pageClientCredentialsTitle = "Credentials"
  34. )
  35. // condResult is the result of an HTTP request precondition check.
  36. // See https://tools.ietf.org/html/rfc7232 section 3.
  37. type condResult int
  38. const (
  39. condNone condResult = iota
  40. condTrue
  41. condFalse
  42. )
  43. var (
  44. clientTemplates = make(map[string]*template.Template)
  45. unixEpochTime = time.Unix(0, 0)
  46. )
  47. // isZeroTime reports whether t is obviously unspecified (either zero or Unix()=0).
  48. func isZeroTime(t time.Time) bool {
  49. return t.IsZero() || t.Equal(unixEpochTime)
  50. }
  51. type baseClientPage struct {
  52. Title string
  53. CurrentURL string
  54. FilesURL string
  55. CredentialsURL string
  56. StaticURL string
  57. LogoutURL string
  58. FilesTitle string
  59. CredentialsTitle string
  60. Version string
  61. CSRFToken string
  62. LoggedUser *dataprovider.User
  63. }
  64. type dirMapping struct {
  65. DirName string
  66. Href string
  67. }
  68. type filesPage struct {
  69. baseClientPage
  70. CurrentDir string
  71. Files []os.FileInfo
  72. Error string
  73. Paths []dirMapping
  74. FormatTime func(time.Time) string
  75. GetObjectURL func(string, string) string
  76. GetSize func(int64) string
  77. IsLink func(os.FileInfo) bool
  78. }
  79. type clientMessagePage struct {
  80. baseClientPage
  81. Error string
  82. Success string
  83. }
  84. type credentialsPage struct {
  85. baseClientPage
  86. PublicKeys []string
  87. ChangePwdURL string
  88. ManageKeysURL string
  89. PwdError string
  90. KeyError string
  91. }
  92. func getFileObjectURL(baseDir, name string) string {
  93. return fmt.Sprintf("%v?path=%v", webClientFilesPath, url.QueryEscape(path.Join(baseDir, name)))
  94. }
  95. func getFileObjectModTime(t time.Time) string {
  96. if isZeroTime(t) {
  97. return ""
  98. }
  99. return t.Format("2006-01-02 15:04")
  100. }
  101. func isFileObjectLink(info os.FileInfo) bool {
  102. return info.Mode()&os.ModeSymlink != 0
  103. }
  104. func loadClientTemplates(templatesPath string) {
  105. filesPaths := []string{
  106. filepath.Join(templatesPath, templateClientDir, templateClientBase),
  107. filepath.Join(templatesPath, templateClientDir, templateClientFiles),
  108. }
  109. credentialsPaths := []string{
  110. filepath.Join(templatesPath, templateClientDir, templateClientBase),
  111. filepath.Join(templatesPath, templateClientDir, templateClientCredentials),
  112. }
  113. loginPath := []string{
  114. filepath.Join(templatesPath, templateClientDir, templateClientLogin),
  115. }
  116. messagePath := []string{
  117. filepath.Join(templatesPath, templateClientDir, templateClientBase),
  118. filepath.Join(templatesPath, templateClientDir, templateClientMessage),
  119. }
  120. filesTmpl := utils.LoadTemplate(template.ParseFiles(filesPaths...))
  121. credentialsTmpl := utils.LoadTemplate(template.ParseFiles(credentialsPaths...))
  122. loginTmpl := utils.LoadTemplate(template.ParseFiles(loginPath...))
  123. messageTmpl := utils.LoadTemplate(template.ParseFiles(messagePath...))
  124. clientTemplates[templateClientFiles] = filesTmpl
  125. clientTemplates[templateClientCredentials] = credentialsTmpl
  126. clientTemplates[templateClientLogin] = loginTmpl
  127. clientTemplates[templateClientMessage] = messageTmpl
  128. }
  129. func getBaseClientPageData(title, currentURL string, r *http.Request) baseClientPage {
  130. var csrfToken string
  131. if currentURL != "" {
  132. csrfToken = createCSRFToken()
  133. }
  134. v := version.Get()
  135. return baseClientPage{
  136. Title: title,
  137. CurrentURL: currentURL,
  138. FilesURL: webClientFilesPath,
  139. CredentialsURL: webClientCredentialsPath,
  140. StaticURL: webStaticFilesPath,
  141. LogoutURL: webClientLogoutPath,
  142. FilesTitle: pageClientFilesTitle,
  143. CredentialsTitle: pageClientCredentialsTitle,
  144. Version: fmt.Sprintf("%v-%v", v.Version, v.CommitHash),
  145. CSRFToken: csrfToken,
  146. LoggedUser: getUserFromToken(r),
  147. }
  148. }
  149. func renderClientTemplate(w http.ResponseWriter, tmplName string, data interface{}) {
  150. err := clientTemplates[tmplName].ExecuteTemplate(w, tmplName, data)
  151. if err != nil {
  152. http.Error(w, err.Error(), http.StatusInternalServerError)
  153. }
  154. }
  155. func renderClientLoginPage(w http.ResponseWriter, error string) {
  156. data := loginPage{
  157. CurrentURL: webClientLoginPath,
  158. Version: version.Get().Version,
  159. Error: error,
  160. CSRFToken: createCSRFToken(),
  161. StaticURL: webStaticFilesPath,
  162. }
  163. renderClientTemplate(w, templateClientLogin, data)
  164. }
  165. func renderClientMessagePage(w http.ResponseWriter, r *http.Request, title, body string, statusCode int, err error, message string) {
  166. var errorString string
  167. if body != "" {
  168. errorString = body + " "
  169. }
  170. if err != nil {
  171. errorString += err.Error()
  172. }
  173. data := clientMessagePage{
  174. baseClientPage: getBaseClientPageData(title, "", r),
  175. Error: errorString,
  176. Success: message,
  177. }
  178. w.WriteHeader(statusCode)
  179. renderClientTemplate(w, templateClientMessage, data)
  180. }
  181. func renderClientInternalServerErrorPage(w http.ResponseWriter, r *http.Request, err error) {
  182. renderClientMessagePage(w, r, page500Title, page500Body, http.StatusInternalServerError, err, "")
  183. }
  184. func renderClientBadRequestPage(w http.ResponseWriter, r *http.Request, err error) {
  185. renderClientMessagePage(w, r, page400Title, "", http.StatusBadRequest, err, "")
  186. }
  187. func renderClientForbiddenPage(w http.ResponseWriter, r *http.Request, body string) {
  188. renderClientMessagePage(w, r, page403Title, "", http.StatusForbidden, nil, body)
  189. }
  190. func renderClientNotFoundPage(w http.ResponseWriter, r *http.Request, err error) {
  191. renderClientMessagePage(w, r, page404Title, page404Body, http.StatusNotFound, err, "")
  192. }
  193. func renderFilesPage(w http.ResponseWriter, r *http.Request, files []os.FileInfo, dirName, error string) {
  194. data := filesPage{
  195. baseClientPage: getBaseClientPageData(pageClientFilesTitle, webClientFilesPath, r),
  196. Files: files,
  197. Error: error,
  198. CurrentDir: dirName,
  199. FormatTime: getFileObjectModTime,
  200. GetObjectURL: getFileObjectURL,
  201. GetSize: utils.ByteCountIEC,
  202. IsLink: isFileObjectLink,
  203. }
  204. paths := []dirMapping{}
  205. if dirName != "/" {
  206. paths = append(paths, dirMapping{
  207. DirName: path.Base(dirName),
  208. Href: "",
  209. })
  210. for {
  211. dirName = path.Dir(dirName)
  212. if dirName == "/" || dirName == "." {
  213. break
  214. }
  215. paths = append([]dirMapping{{
  216. DirName: path.Base(dirName),
  217. Href: getFileObjectURL("/", dirName)},
  218. }, paths...)
  219. }
  220. }
  221. data.Paths = paths
  222. renderClientTemplate(w, templateClientFiles, data)
  223. }
  224. func renderCredentialsPage(w http.ResponseWriter, r *http.Request, pwdError string, keyError string) {
  225. data := credentialsPage{
  226. baseClientPage: getBaseClientPageData(pageClientCredentialsTitle, webClientCredentialsPath, r),
  227. ChangePwdURL: webChangeClientPwdPath,
  228. ManageKeysURL: webChangeClientKeysPath,
  229. PwdError: pwdError,
  230. KeyError: keyError,
  231. }
  232. user, err := dataprovider.UserExists(data.LoggedUser.Username)
  233. if err != nil {
  234. renderClientInternalServerErrorPage(w, r, err)
  235. }
  236. data.PublicKeys = user.PublicKeys
  237. renderClientTemplate(w, templateClientCredentials, data)
  238. }
  239. func handleClientWebLogin(w http.ResponseWriter, r *http.Request) {
  240. renderClientLoginPage(w, "")
  241. }
  242. func handleWebClientLogout(w http.ResponseWriter, r *http.Request) {
  243. c := jwtTokenClaims{}
  244. c.removeCookie(w, r)
  245. http.Redirect(w, r, webClientLoginPath, http.StatusFound)
  246. }
  247. func handleClientGetFiles(w http.ResponseWriter, r *http.Request) {
  248. claims, err := getTokenClaims(r)
  249. if err != nil || claims.Username == "" {
  250. renderClientForbiddenPage(w, r, "Invalid token claims")
  251. return
  252. }
  253. user, err := dataprovider.UserExists(claims.Username)
  254. if err != nil {
  255. renderClientInternalServerErrorPage(w, r, err)
  256. return
  257. }
  258. connID := xid.New().String()
  259. connectionID := fmt.Sprintf("%v_%v", common.ProtocolHTTP, connID)
  260. if err := checkWebClientUser(&user, r, connectionID); err != nil {
  261. renderClientForbiddenPage(w, r, err.Error())
  262. return
  263. }
  264. connection := &Connection{
  265. BaseConnection: common.NewBaseConnection(connID, common.ProtocolHTTP, user),
  266. request: r,
  267. }
  268. common.Connections.Add(connection)
  269. defer common.Connections.Remove(connection.GetID())
  270. name := "/"
  271. if _, ok := r.URL.Query()["path"]; ok {
  272. name = utils.CleanPath(r.URL.Query().Get("path"))
  273. }
  274. var info os.FileInfo
  275. if name == "/" {
  276. info = vfs.NewFileInfo(name, true, 0, time.Now(), false)
  277. } else {
  278. info, err = connection.Stat(name, 0)
  279. }
  280. if err != nil {
  281. renderFilesPage(w, r, nil, name, fmt.Sprintf("unable to stat file %#v: %v", name, err))
  282. return
  283. }
  284. if info.IsDir() {
  285. renderDirContents(w, r, connection, name)
  286. return
  287. }
  288. downloadFile(w, r, connection, name, info)
  289. }
  290. func handleClientGetCredentials(w http.ResponseWriter, r *http.Request) {
  291. renderCredentialsPage(w, r, "", "")
  292. }
  293. func handleWebClientChangePwdPost(w http.ResponseWriter, r *http.Request) {
  294. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  295. err := r.ParseForm()
  296. if err != nil {
  297. renderCredentialsPage(w, r, err.Error(), "")
  298. return
  299. }
  300. if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
  301. renderClientForbiddenPage(w, r, err.Error())
  302. return
  303. }
  304. err = doChangeUserPassword(r, r.Form.Get("current_password"), r.Form.Get("new_password1"),
  305. r.Form.Get("new_password2"))
  306. if err != nil {
  307. renderCredentialsPage(w, r, err.Error(), "")
  308. return
  309. }
  310. handleWebClientLogout(w, r)
  311. }
  312. func handleWebClientManageKeysPost(w http.ResponseWriter, r *http.Request) {
  313. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  314. err := r.ParseForm()
  315. if err != nil {
  316. renderCredentialsPage(w, r, "", err.Error())
  317. return
  318. }
  319. if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
  320. renderClientForbiddenPage(w, r, err.Error())
  321. return
  322. }
  323. claims, err := getTokenClaims(r)
  324. if err != nil || claims.Username == "" {
  325. renderCredentialsPage(w, r, "", "Invalid token claims")
  326. return
  327. }
  328. user, err := dataprovider.UserExists(claims.Username)
  329. if err != nil {
  330. renderCredentialsPage(w, r, "", err.Error())
  331. return
  332. }
  333. publicKeysFormValue := r.Form.Get("public_keys")
  334. publicKeys := getSliceFromDelimitedValues(publicKeysFormValue, "\n")
  335. user.PublicKeys = publicKeys
  336. err = dataprovider.UpdateUser(&user)
  337. if err != nil {
  338. renderCredentialsPage(w, r, "", err.Error())
  339. return
  340. }
  341. renderClientMessagePage(w, r, "Public keys updated", "", http.StatusOK, nil, "Your public keys has been successfully updated")
  342. }
  343. func doChangeUserPassword(r *http.Request, currentPassword, newPassword, confirmNewPassword string) error {
  344. if currentPassword == "" || newPassword == "" || confirmNewPassword == "" {
  345. return dataprovider.NewValidationError("please provide the current password and the new one two times")
  346. }
  347. if newPassword != confirmNewPassword {
  348. return dataprovider.NewValidationError("the two password fields do not match")
  349. }
  350. if currentPassword == newPassword {
  351. return dataprovider.NewValidationError("the new password must be different from the current one")
  352. }
  353. claims, err := getTokenClaims(r)
  354. if err != nil || claims.Username == "" {
  355. return errors.New("invalid token claims")
  356. }
  357. user, err := dataprovider.CheckUserAndPass(claims.Username, currentPassword, utils.GetIPFromRemoteAddress(r.RemoteAddr),
  358. common.ProtocolHTTP)
  359. if err != nil {
  360. return dataprovider.NewValidationError("current password does not match")
  361. }
  362. user.Password = newPassword
  363. return dataprovider.UpdateUser(&user)
  364. }
  365. func renderDirContents(w http.ResponseWriter, r *http.Request, connection *Connection, name string) {
  366. contents, err := connection.ReadDir(name)
  367. if err != nil {
  368. renderFilesPage(w, r, nil, name, fmt.Sprintf("unable to get contents for directory %#v: %v", name, err))
  369. return
  370. }
  371. renderFilesPage(w, r, contents, name, "")
  372. }
  373. func downloadFile(w http.ResponseWriter, r *http.Request, connection *Connection, name string, info os.FileInfo) {
  374. var err error
  375. rangeHeader := r.Header.Get("Range")
  376. if rangeHeader != "" && checkIfRange(r, info.ModTime()) == condFalse {
  377. rangeHeader = ""
  378. }
  379. offset := int64(0)
  380. size := info.Size()
  381. responseStatus := http.StatusOK
  382. if strings.HasPrefix(rangeHeader, "bytes=") {
  383. if strings.Contains(rangeHeader, ",") {
  384. http.Error(w, fmt.Sprintf("unsupported range %#v", rangeHeader), http.StatusRequestedRangeNotSatisfiable)
  385. return
  386. }
  387. offset, size, err = parseRangeRequest(rangeHeader[6:], size)
  388. if err != nil {
  389. http.Error(w, err.Error(), http.StatusRequestedRangeNotSatisfiable)
  390. return
  391. }
  392. responseStatus = http.StatusPartialContent
  393. }
  394. reader, err := connection.getFileReader(name, offset)
  395. if err != nil {
  396. renderFilesPage(w, r, nil, name, fmt.Sprintf("unable to read file %#v: %v", name, err))
  397. return
  398. }
  399. defer reader.Close()
  400. w.Header().Set("Last-Modified", info.ModTime().UTC().Format(http.TimeFormat))
  401. if checkPreconditions(w, r, info.ModTime()) {
  402. return
  403. }
  404. ctype := mime.TypeByExtension(path.Ext(name))
  405. if ctype == "" {
  406. ctype = "application/octet-stream"
  407. }
  408. if responseStatus == http.StatusPartialContent {
  409. w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", offset, offset+size-1, info.Size()))
  410. }
  411. w.Header().Set("Content-Length", strconv.FormatInt(size, 10))
  412. w.Header().Set("Content-Type", ctype)
  413. w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%#v", path.Base(name)))
  414. w.Header().Set("Accept-Ranges", "bytes")
  415. w.WriteHeader(responseStatus)
  416. if r.Method != http.MethodHead {
  417. io.CopyN(w, reader, size) //nolint:errcheck
  418. }
  419. }
  420. func checkPreconditions(w http.ResponseWriter, r *http.Request, modtime time.Time) bool {
  421. if checkIfUnmodifiedSince(r, modtime) == condFalse {
  422. w.WriteHeader(http.StatusPreconditionFailed)
  423. return true
  424. }
  425. if checkIfModifiedSince(r, modtime) == condFalse {
  426. w.WriteHeader(http.StatusNotModified)
  427. return true
  428. }
  429. return false
  430. }
  431. func checkIfUnmodifiedSince(r *http.Request, modtime time.Time) condResult {
  432. ius := r.Header.Get("If-Unmodified-Since")
  433. if ius == "" || isZeroTime(modtime) {
  434. return condNone
  435. }
  436. t, err := http.ParseTime(ius)
  437. if err != nil {
  438. return condNone
  439. }
  440. // The Last-Modified header truncates sub-second precision so
  441. // the modtime needs to be truncated too.
  442. modtime = modtime.Truncate(time.Second)
  443. if modtime.Before(t) || modtime.Equal(t) {
  444. return condTrue
  445. }
  446. return condFalse
  447. }
  448. func checkIfModifiedSince(r *http.Request, modtime time.Time) condResult {
  449. if r.Method != http.MethodGet && r.Method != http.MethodHead {
  450. return condNone
  451. }
  452. ims := r.Header.Get("If-Modified-Since")
  453. if ims == "" || isZeroTime(modtime) {
  454. return condNone
  455. }
  456. t, err := http.ParseTime(ims)
  457. if err != nil {
  458. return condNone
  459. }
  460. // The Last-Modified header truncates sub-second precision so
  461. // the modtime needs to be truncated too.
  462. modtime = modtime.Truncate(time.Second)
  463. if modtime.Before(t) || modtime.Equal(t) {
  464. return condFalse
  465. }
  466. return condTrue
  467. }
  468. func checkIfRange(r *http.Request, modtime time.Time) condResult {
  469. if r.Method != http.MethodGet && r.Method != http.MethodHead {
  470. return condNone
  471. }
  472. ir := r.Header.Get("If-Range")
  473. if ir == "" {
  474. return condNone
  475. }
  476. if modtime.IsZero() {
  477. return condFalse
  478. }
  479. t, err := http.ParseTime(ir)
  480. if err != nil {
  481. return condFalse
  482. }
  483. if modtime.Add(60 * time.Second).Before(t) {
  484. return condTrue
  485. }
  486. return condFalse
  487. }
  488. func parseRangeRequest(bytesRange string, size int64) (int64, int64, error) {
  489. var start, end int64
  490. var err error
  491. values := strings.Split(bytesRange, "-")
  492. if values[0] == "" {
  493. start = -1
  494. } else {
  495. start, err = strconv.ParseInt(values[0], 10, 64)
  496. if err != nil {
  497. return start, size, err
  498. }
  499. }
  500. if len(values) >= 2 {
  501. if values[1] != "" {
  502. end, err = strconv.ParseInt(values[1], 10, 64)
  503. if err != nil {
  504. return start, size, err
  505. }
  506. if end >= size {
  507. end = size - 1
  508. }
  509. }
  510. }
  511. if start == -1 && end == 0 {
  512. return 0, 0, fmt.Errorf("unsupported range %#v", bytesRange)
  513. }
  514. if end > 0 {
  515. if start == -1 {
  516. // we have something like -500
  517. start = size - end
  518. size = end
  519. // start cannit be < 0 here, we did end = size -1 above
  520. } else {
  521. // we have something like 500-600
  522. size = end - start + 1
  523. if size < 0 {
  524. return 0, 0, fmt.Errorf("unacceptable range %#v", bytesRange)
  525. }
  526. }
  527. return start, size, nil
  528. }
  529. // we have something like 500-
  530. size -= start
  531. if size < 0 {
  532. return 0, 0, fmt.Errorf("unacceptable range %#v", bytesRange)
  533. }
  534. return start, size, err
  535. }
  536. func updateLoginMetrics(user *dataprovider.User, ip string, err error) {
  537. metrics.AddLoginAttempt(dataprovider.LoginMethodPassword)
  538. if err != nil {
  539. logger.ConnectionFailedLog(user.Username, ip, dataprovider.LoginMethodPassword, common.ProtocolHTTP, err.Error())
  540. event := common.HostEventLoginFailed
  541. if _, ok := err.(*dataprovider.RecordNotFoundError); ok {
  542. event = common.HostEventUserNotFound
  543. }
  544. common.AddDefenderEvent(ip, event)
  545. }
  546. metrics.AddLoginResult(dataprovider.LoginMethodPassword, err)
  547. dataprovider.ExecutePostLoginHook(user, dataprovider.LoginMethodPassword, ip, common.ProtocolHTTP, err)
  548. }
  549. func checkWebClientUser(user *dataprovider.User, r *http.Request, connectionID string) error {
  550. if utils.IsStringInSlice(common.ProtocolHTTP, user.Filters.DeniedProtocols) {
  551. logger.Debug(logSender, connectionID, "cannot login user %#v, protocol HTTP is not allowed", user.Username)
  552. return fmt.Errorf("protocol HTTP is not allowed for user %#v", user.Username)
  553. }
  554. if !user.IsLoginMethodAllowed(dataprovider.LoginMethodPassword, nil) {
  555. logger.Debug(logSender, connectionID, "cannot login user %#v, password login method is not allowed", user.Username)
  556. return fmt.Errorf("login method password is not allowed for user %#v", user.Username)
  557. }
  558. if user.MaxSessions > 0 {
  559. activeSessions := common.Connections.GetActiveSessions(user.Username)
  560. if activeSessions >= user.MaxSessions {
  561. logger.Debug(logSender, connectionID, "authentication refused for user: %#v, too many open sessions: %v/%v", user.Username,
  562. activeSessions, user.MaxSessions)
  563. return fmt.Errorf("too many open sessions: %v", activeSessions)
  564. }
  565. }
  566. if !user.IsLoginFromAddrAllowed(r.RemoteAddr) {
  567. logger.Debug(logSender, connectionID, "cannot login user %#v, remote address is not allowed: %v", user.Username, r.RemoteAddr)
  568. return fmt.Errorf("login for user %#v is not allowed from this address: %v", user.Username, r.RemoteAddr)
  569. }
  570. return nil
  571. }