authlogger.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. package authlogger
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "log"
  6. "net/http"
  7. "os"
  8. "strconv"
  9. "strings"
  10. "time"
  11. "imuslab.com/arozos/mod/database"
  12. "imuslab.com/arozos/mod/utils"
  13. )
  14. /*
  15. AuthLogger
  16. Author: tobychui
  17. This module help log the login request and help the user to trace who is trying
  18. to break into their system.
  19. */
  20. type Logger struct {
  21. database *database.Database
  22. }
  23. type LoginRecord struct {
  24. Timestamp int64
  25. TargetUsername string
  26. LoginSucceed bool
  27. IpAddr string
  28. AuthType string
  29. Port int
  30. }
  31. //New Logger create a new logger object
  32. func NewLogger() (*Logger, error) {
  33. os.MkdirAll("./system/auth/", 0775)
  34. db, err := database.NewDatabase("./system/auth/authlog.db", false)
  35. if err != nil {
  36. return nil, errors.New("*ERROR* Failed to create database for login tracking: " + err.Error())
  37. }
  38. return &Logger{
  39. database: db,
  40. }, nil
  41. }
  42. //Log the current authentication to record, Require the request object and login status
  43. func (l *Logger) LogAuth(r *http.Request, loginStatus bool) error {
  44. username, _ := utils.Mv(r, "username", true)
  45. timestamp := time.Now().Unix()
  46. //handling the reverse proxy remote IP issue
  47. remoteIP := r.Header.Get("X-FORWARDED-FOR")
  48. if remoteIP != "" {
  49. //grab the last known remote IP from header
  50. remoteIPs := strings.Split(remoteIP, ", ")
  51. remoteIP = remoteIPs[len(remoteIPs)-1]
  52. } else {
  53. //if there is no X-FORWARDED-FOR, use default remote IP
  54. remoteIP = r.RemoteAddr
  55. }
  56. return l.LogAuthByRequestInfo(username, remoteIP, timestamp, loginStatus, "web")
  57. }
  58. //Log the current authentication to record by custom filled information. Use LogAuth if your module is authenticating via web interface
  59. func (l *Logger) LogAuthByRequestInfo(username string, remoteAddr string, timestamp int64, loginSucceed bool, authType string) error {
  60. //Get the current month as the table name, create table if not exists
  61. current := time.Now().UTC()
  62. tableName := current.Format("Jan-2006")
  63. //Create table if not exists
  64. if !l.database.TableExists(tableName) {
  65. l.database.NewTable(tableName)
  66. }
  67. //Split the remote address into ipaddr and port
  68. remoteAddrInfo := []string{"unknown", "N/A"}
  69. if strings.Contains(remoteAddr, ":") {
  70. //For general IPv4 address
  71. remoteAddrInfo = strings.Split(remoteAddr, ":")
  72. }
  73. //Check for IPV6
  74. if strings.Contains(remoteAddr, "[") && strings.Contains(remoteAddr, "]") {
  75. //This is an IPV6 address. Rewrite the split
  76. //IPv6 should have the format of something like this [::1]:80
  77. ipv6info := strings.Split(remoteAddr, ":")
  78. port := ipv6info[len(ipv6info)-1:]
  79. ipAddr := ipv6info[:len(ipv6info)-1]
  80. remoteAddrInfo = []string{strings.Join(ipAddr, ":"), strings.Join(port, ":")}
  81. }
  82. port := -1
  83. if len(remoteAddrInfo) > 1 {
  84. port, _ = strconv.Atoi(remoteAddrInfo[1])
  85. }
  86. //Create the entry log struct
  87. thisRecord := LoginRecord{
  88. Timestamp: timestamp,
  89. TargetUsername: username,
  90. LoginSucceed: loginSucceed,
  91. IpAddr: remoteAddrInfo[0],
  92. AuthType: authType,
  93. Port: port,
  94. }
  95. //Write the log to it
  96. entryKey := strconv.Itoa(int(time.Now().UnixNano()))
  97. err := l.database.Write(tableName, entryKey, thisRecord)
  98. if err != nil {
  99. log.Println("*ERROR* Failed to write authentication log. Is the storage fulled?")
  100. log.Println(err.Error())
  101. return err
  102. }
  103. return nil
  104. }
  105. //Close the database when system shutdown
  106. func (l *Logger) Close() {
  107. l.database.Close()
  108. }
  109. //List the total number of months recorded in the database
  110. func (l *Logger) ListSummary() []string {
  111. tableNames := []string{}
  112. l.database.Tables.Range(func(tableName, _ interface{}) bool {
  113. tableNames = append(tableNames, tableName.(string))
  114. return true
  115. })
  116. return tableNames
  117. }
  118. func (l *Logger) ListRecords(key string) ([]LoginRecord, error) {
  119. results := []LoginRecord{}
  120. if l.database.TableExists(key) {
  121. //Read all record from the database to login records
  122. entries, err := l.database.ListTable(key)
  123. if err != nil {
  124. return results, err
  125. }
  126. for _, keypairs := range entries {
  127. record := LoginRecord{}
  128. json.Unmarshal(keypairs[1], &record)
  129. results = append(results, record)
  130. }
  131. return results, nil
  132. } else {
  133. return results, errors.New("Table not exists")
  134. }
  135. }
  136. //Extract the address information from the request object, the first one is the remote address from the last hop,
  137. //and the 2nd one is the source address filled in by the client (not accurate)
  138. func getIpAddressFromRequest(r *http.Request) (string, []string) {
  139. lastHopAddress := r.RemoteAddr
  140. possibleSourceAddress := []string{}
  141. rawHeader := r.Header.Get("X-Forwarded-For")
  142. possibleSourceAddress = strings.Split(rawHeader, ", ")
  143. return lastHopAddress, possibleSourceAddress
  144. }