package main import ( "database/sql" "fmt" "os" "time" _ "github.com/mattn/go-sqlite3" "golang.org/x/crypto/bcrypt" ) /** * Exit codes: * 0: authentication successful * 1: authentication failed (email address not found or password mismatch * 2: other error */ func main() { // Opens the SQLite database dbFile, dbFileSet := os.LookupEnv("HOSTUX_EMAIL_DATABASE_SQLITE") if !dbFileSet { logMessage("Environment variable HOSTUX_EMAIL_DATABASE_SQLITE must be set.") os.Exit(2) } db, err := sql.Open("sqlite3", dbFile) if err != nil { logMessage("Error opening database:", err) os.Exit(2) } defer db.Close() // Reads stdin input (email address, password, each ends with \n) var emailAddress, password string logMessage("Enter email address:") fmt.Scanln(&emailAddress) logMessage("Enter password:") fmt.Scanln(&password) // Finds database records rows, err := db.Query(`SELECT eap.id, eap.password FROM "email-addresses" AS ea LEFT JOIN "email-addresses-passwords" AS eap ON ea.emailAddress = eap.emailAddress WHERE ea.emailAddress = ?`, emailAddress) if err != nil { logMessage("Database error: ", err) os.Exit(2) } defer rows.Close() // Loops through records for rows.Next() { var hashedPassword string var id int err := rows.Scan(&id, &hashedPassword) if err != nil { logMessage("Database error: ", err) os.Exit(2) } // Compares provided password with database err = bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) if err == nil { // No error = the passwords match // Update lastAccess time rows.Close() _, err := db.Exec(`UPDATE "email-addresses-passwords" SET lastAccess = ? WHERE id = ?`, time.Now().Format(time.RFC3339), id) if err != nil { logMessage("Database error: ", err) os.Exit(2) } logMessage("Authentication successful") os.Exit(0) } } // If we have not returned by now, it means that: // * either there were no records // * or no record matched the provided password logMessage("Authentication failed: account not found or password incorrect") os.Exit(1) } func logMessage(args ...interface{}) error { // Print the message to the console fmt.Println(args...) // Log to file if required logFile, logFileSet := os.LookupEnv("HOSTUX_EMAIL_CHECKPW_LOGFILE") if logFileSet { // Open the file in append mode, create it if it doesn't exist file, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return err } defer file.Close() // Write the message to the file _, err = fmt.Fprintln(file, args...) if err != nil { return err } } return nil }