Added double check
This commit is contained in:
parent
cca899e659
commit
5dd61df087
16 changed files with 255 additions and 24 deletions
0
.gitignore
vendored
Normal file → Executable file
0
.gitignore
vendored
Normal file → Executable file
0
ARCHITECTURE.md
Normal file → Executable file
0
ARCHITECTURE.md
Normal file → Executable file
0
DATABASE_SCHEMA.md
Normal file → Executable file
0
DATABASE_SCHEMA.md
Normal file → Executable file
0
LICENSE
Normal file → Executable file
0
LICENSE
Normal file → Executable file
0
README.md
Normal file → Executable file
0
README.md
Normal file → Executable file
0
api.go
Normal file → Executable file
0
api.go
Normal file → Executable file
0
compare.go
Normal file → Executable file
0
compare.go
Normal file → Executable file
12
config.go
Normal file → Executable file
12
config.go
Normal file → Executable file
|
|
@ -13,9 +13,9 @@ import (
|
||||||
var (
|
var (
|
||||||
// API
|
// API
|
||||||
bearerToken string
|
bearerToken string
|
||||||
originURL = "https://api.servicepipe.ru/api/v1/l7/origin/global?limit=1000&l7ResourceId="
|
originURL = "https://api.test.ru/api/v1/l7/origin/global?limit=1000&l7ResourceId="
|
||||||
aliasURL = "https://api.servicepipe.ru/api/v1/l7/alias/global?limit=1000&l7ResourceId="
|
aliasURL = "https://api.test.ru/api/v1/l7/alias/global?limit=1000&l7ResourceId="
|
||||||
whoisURL = "https://api.servicepipe.ru/api/v1/l7/resource/"
|
whoisURL = "https://api.test.ru/api/v1/l7/resource/"
|
||||||
|
|
||||||
// DB
|
// DB
|
||||||
dbHost string
|
dbHost string
|
||||||
|
|
@ -46,6 +46,7 @@ var (
|
||||||
smtpPassword string
|
smtpPassword string
|
||||||
smtpFrom string
|
smtpFrom string
|
||||||
smtpTo string
|
smtpTo string
|
||||||
|
smtpSkipTLS bool
|
||||||
|
|
||||||
// Git
|
// Git
|
||||||
gitRepoURL string
|
gitRepoURL string
|
||||||
|
|
@ -54,6 +55,9 @@ var (
|
||||||
|
|
||||||
// Concurrency
|
// Concurrency
|
||||||
maxConcurrentWorkers int
|
maxConcurrentWorkers int
|
||||||
|
|
||||||
|
// Features
|
||||||
|
checkDuplicates bool
|
||||||
)
|
)
|
||||||
|
|
||||||
// Загружаем переменные из sp_sync.env при старте
|
// Загружаем переменные из sp_sync.env при старте
|
||||||
|
|
@ -88,10 +92,12 @@ func init() {
|
||||||
smtpPassword = getEnv("EMAIL_SMTP_PASSWORD", "")
|
smtpPassword = getEnv("EMAIL_SMTP_PASSWORD", "")
|
||||||
smtpFrom = getEnv("EMAIL_FROM", "")
|
smtpFrom = getEnv("EMAIL_FROM", "")
|
||||||
smtpTo = getEnv("EMAIL_TO", "")
|
smtpTo = getEnv("EMAIL_TO", "")
|
||||||
|
smtpSkipTLS = getEnv("EMAIL_SKIP_TLS_VERIFY", "") == "true"
|
||||||
gitRepoURL = getEnv("GIT_REPO_URL", "https://token@svc-git.cirex.ru/wmx/waf_whitelist.git")
|
gitRepoURL = getEnv("GIT_REPO_URL", "https://token@svc-git.cirex.ru/wmx/waf_whitelist.git")
|
||||||
gitRepoPath = getEnv("GIT_REPO_PATH", "/home/install/waf_whitelist")
|
gitRepoPath = getEnv("GIT_REPO_PATH", "/home/install/waf_whitelist")
|
||||||
whitelistFile = getEnv("WHITELIST_FILE", "whitelist_ptaf.txt")
|
whitelistFile = getEnv("WHITELIST_FILE", "whitelist_ptaf.txt")
|
||||||
maxConcurrentWorkers = getEnvAsInt("MAX_CONCURRENT_WORKERS", 5)
|
maxConcurrentWorkers = getEnvAsInt("MAX_CONCURRENT_WORKERS", 5)
|
||||||
|
checkDuplicates = getEnv("CHECK_DUPLICATES", "true") != "false"
|
||||||
|
|
||||||
// Отладка загрузки переменных
|
// Отладка загрузки переменных
|
||||||
log.Printf("[Config] DB_USER loaded: '%s'", dbUser)
|
log.Printf("[Config] DB_USER loaded: '%s'", dbUser)
|
||||||
|
|
|
||||||
159
database.go
Normal file → Executable file
159
database.go
Normal file → Executable file
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
@ -156,6 +157,21 @@ func upsertSPInfo(
|
||||||
wafVendor string,
|
wafVendor string,
|
||||||
instanceSP string,
|
instanceSP string,
|
||||||
) error {
|
) error {
|
||||||
|
// Получаем client_title из apps_settings и waf_provider из client_info
|
||||||
|
var clientTitle string
|
||||||
|
db.QueryRow(`SELECT client_title FROM apps_settings WHERE l7resourceid = $1 LIMIT 1`, sid).Scan(&clientTitle)
|
||||||
|
if clientTitle == "" {
|
||||||
|
clientTitle = "—"
|
||||||
|
}
|
||||||
|
|
||||||
|
var wafProviderClient string
|
||||||
|
if clientTitle != "—" {
|
||||||
|
db.QueryRow(`SELECT waf_provider FROM client_info WHERE client_title = $1`, clientTitle).Scan(&wafProviderClient)
|
||||||
|
}
|
||||||
|
if wafProviderClient == "" {
|
||||||
|
wafProviderClient = "—"
|
||||||
|
}
|
||||||
|
|
||||||
// Проверяем существующие данные
|
// Проверяем существующие данные
|
||||||
var existingDomain string
|
var existingDomain string
|
||||||
var existingOriginsJSON, existingAliasesJSON []byte
|
var existingOriginsJSON, existingAliasesJSON []byte
|
||||||
|
|
@ -192,7 +208,7 @@ func upsertSPInfo(
|
||||||
hasChanges = true
|
hasChanges = true
|
||||||
needsPTAFUpdate = true
|
needsPTAFUpdate = true
|
||||||
changeDetails = append(changeDetails,
|
changeDetails = append(changeDetails,
|
||||||
fmt.Sprintf("<b>🔄 Домен изменён:</b>\n Было: %s\n Стало: %s", existingDomain, domain))
|
fmt.Sprintf("Домен изменён: %s -> %s", existingDomain, domain))
|
||||||
}
|
}
|
||||||
|
|
||||||
originChanges := compareOrigins(existingOrigins, newOrigins)
|
originChanges := compareOrigins(existingOrigins, newOrigins)
|
||||||
|
|
@ -225,7 +241,7 @@ func upsertSPInfo(
|
||||||
if oldWafEnabled != wafEnabledSP {
|
if oldWafEnabled != wafEnabledSP {
|
||||||
hasWAFChanges = true
|
hasWAFChanges = true
|
||||||
wafChangeDetails = append(wafChangeDetails,
|
wafChangeDetails = append(wafChangeDetails,
|
||||||
fmt.Sprintf("<b>WAF Enabled:</b>\n <b>Было:</b> %d\n <b>Стало:</b> %d", oldWafEnabled, wafEnabledSP))
|
fmt.Sprintf("WAF Enabled: %d -> %d", oldWafEnabled, wafEnabledSP))
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldWafVendor != wafVendor {
|
if oldWafVendor != wafVendor {
|
||||||
|
|
@ -239,7 +255,7 @@ func upsertSPInfo(
|
||||||
newDisplay = "(не указан)"
|
newDisplay = "(не указан)"
|
||||||
}
|
}
|
||||||
wafChangeDetails = append(wafChangeDetails,
|
wafChangeDetails = append(wafChangeDetails,
|
||||||
fmt.Sprintf("<b>WAF Provider:</b>\n <b>Было:</b> %s\n <b>Стало:</b> %s", oldDisplay, newDisplay))
|
fmt.Sprintf("WAF Provider: %s -> %s", oldDisplay, newDisplay))
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldInstanceSP != instanceSP {
|
if oldInstanceSP != instanceSP {
|
||||||
|
|
@ -253,7 +269,7 @@ func upsertSPInfo(
|
||||||
newDisplay = "(не указан)"
|
newDisplay = "(не указан)"
|
||||||
}
|
}
|
||||||
wafChangeDetails = append(wafChangeDetails,
|
wafChangeDetails = append(wafChangeDetails,
|
||||||
fmt.Sprintf("<b>WAF Instance:</b>\n <b>Было:</b> %s\n <b>Стало:</b> %s", oldDisplay, newDisplay))
|
fmt.Sprintf("WAF Instance: %s -> %s", oldDisplay, newDisplay))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -300,18 +316,22 @@ func upsertSPInfo(
|
||||||
if hasChanges {
|
if hasChanges {
|
||||||
ptafWarning := ""
|
ptafWarning := ""
|
||||||
if needsPTAFUpdate {
|
if needsPTAFUpdate {
|
||||||
ptafWarning = "\n\n<b>⚠️ ВНИМАНИЕ: Необходимо внести изменения в кабинете PT AF!</b>"
|
ptafWarning = "\n<b>⚠️ ВНИМАНИЕ: Необходимо внести изменения в кабинете " + wafVendorName(wafVendor) + "!</b>"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
now := time.Now().In(time.FixedZone("UTC+3", 3*60*60))
|
||||||
message := fmt.Sprintf(
|
message := fmt.Sprintf(
|
||||||
"<b>🔔 Обновление WAF Info</b>\n\n"+
|
"🟡 <b>Обновление WAF Info</b>\n\n"+
|
||||||
"<b>SID:</b> %d\n"+
|
"<b>SID:</b> %d <b>Домен:</b> %s <b>TENANT:</b> %s <b>WAF provider:</b> %s\n\n"+
|
||||||
"<b>Домен:</b> %s\n\n"+
|
"<b>Изменения:</b>\n%s%s\n\n"+
|
||||||
"<b>Изменения:</b>\n\n%s%s",
|
"<b>Время проверки:</b> %s",
|
||||||
sid,
|
sid,
|
||||||
domain,
|
domain,
|
||||||
strings.Join(changeDetails, "\n\n"),
|
clientTitle,
|
||||||
|
wafProviderClient,
|
||||||
|
strings.Join(changeDetails, "\n"),
|
||||||
ptafWarning,
|
ptafWarning,
|
||||||
|
now.Format("2006-01-02 15:04:05 UTC+3"),
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := sendAlert(message); err != nil {
|
if err := sendAlert(message); err != nil {
|
||||||
|
|
@ -323,14 +343,17 @@ func upsertSPInfo(
|
||||||
|
|
||||||
// Отправляем отдельный алерт для изменений WAF настроек
|
// Отправляем отдельный алерт для изменений WAF настроек
|
||||||
if hasWAFChanges {
|
if hasWAFChanges {
|
||||||
|
now := time.Now().In(time.FixedZone("UTC+3", 3*60*60))
|
||||||
wafMessage := fmt.Sprintf(
|
wafMessage := fmt.Sprintf(
|
||||||
"<b>⚙️ Изменения настроек инстанса на стороне SP</b>\n\n"+
|
"⚪ <b>Изменения настроек инстанса на стороне SP</b>\n\n"+
|
||||||
"<b>SID:</b> %d\n"+
|
"<b>SID:</b> %d <b>Домен:</b> %s <b>TENANT:</b> %s\n\n"+
|
||||||
"<b>Домен:</b> %s\n\n"+
|
"<b>Изменения:</b>\n%s\n\n"+
|
||||||
"<b>Изменения:</b>\n\n%s",
|
"<b>Время проверки:</b> %s",
|
||||||
sid,
|
sid,
|
||||||
domain,
|
domain,
|
||||||
strings.Join(wafChangeDetails, "\n\n"),
|
clientTitle,
|
||||||
|
strings.Join(wafChangeDetails, "\n"),
|
||||||
|
now.Format("2006-01-02 15:04:05 UTC+3"),
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := sendAlert(wafMessage); err != nil {
|
if err := sendAlert(wafMessage); err != nil {
|
||||||
|
|
@ -343,6 +366,19 @@ func upsertSPInfo(
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func wafVendorName(vendor string) string {
|
||||||
|
switch strings.ToLower(vendor) {
|
||||||
|
case "ptaf":
|
||||||
|
return "PT AF"
|
||||||
|
case "sw":
|
||||||
|
return "SW"
|
||||||
|
case "wmx":
|
||||||
|
return "WMX"
|
||||||
|
default:
|
||||||
|
return "WAF"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func wafVendorWarning(vendor string) string {
|
func wafVendorWarning(vendor string) string {
|
||||||
switch strings.ToLower(vendor) {
|
switch strings.ToLower(vendor) {
|
||||||
case "ptaf":
|
case "ptaf":
|
||||||
|
|
@ -438,3 +474,96 @@ func cleanupRemovedSIDs(db *sql.DB, activeSIDs []int64) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkDuplicateDomains(db *sql.DB) error {
|
||||||
|
rows, err := db.Query(`SELECT sid, domain_name, aliases FROM sp_info`)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to query sp_info: %w", err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
// Единая карта: домен/алиас -> список SID где встречается
|
||||||
|
valueToSIDs := make(map[string][]int64)
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var sid int64
|
||||||
|
var domain string
|
||||||
|
var aliasesJSON []byte
|
||||||
|
if err := rows.Scan(&sid, &domain, &aliasesJSON); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if domain != "" {
|
||||||
|
valueToSIDs[domain] = append(valueToSIDs[domain], sid)
|
||||||
|
}
|
||||||
|
|
||||||
|
var aliases []string
|
||||||
|
if err := json.Unmarshal(aliasesJSON, &aliases); err == nil {
|
||||||
|
for _, alias := range aliases {
|
||||||
|
// Добавляем только если этот SID ещё не учтён для данного значения
|
||||||
|
alreadyAdded := false
|
||||||
|
for _, s := range valueToSIDs[alias] {
|
||||||
|
if s == sid {
|
||||||
|
alreadyAdded = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !alreadyAdded {
|
||||||
|
valueToSIDs[alias] = append(valueToSIDs[alias], sid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Собираем дубли — значения встречающиеся у более чем одного SID
|
||||||
|
type duplicate struct {
|
||||||
|
value string
|
||||||
|
sids []int64
|
||||||
|
}
|
||||||
|
var dups []duplicate
|
||||||
|
|
||||||
|
for value, sids := range valueToSIDs {
|
||||||
|
if len(sids) > 1 {
|
||||||
|
dups = append(dups, duplicate{value, sids})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(dups) == 0 {
|
||||||
|
log.Println("[Duplicate Check] No duplicates found")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сортируем для стабильного вывода
|
||||||
|
sort.Slice(dups, func(i, j int) bool {
|
||||||
|
return dups[i].value < dups[j].value
|
||||||
|
})
|
||||||
|
|
||||||
|
log.Printf("[Duplicate Check] Found %d duplicate(s)", len(dups))
|
||||||
|
for _, d := range dups {
|
||||||
|
var sidStrs []string
|
||||||
|
for _, sid := range d.sids {
|
||||||
|
sidStrs = append(sidStrs, fmt.Sprintf("%d", sid))
|
||||||
|
}
|
||||||
|
log.Printf("[Duplicate Check] %s → SID: %s", d.value, strings.Join(sidStrs, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
var msg strings.Builder
|
||||||
|
msg.WriteString("🔴 <b>Обнаружены дублирующиеся домены/алиасы</b>\n\n")
|
||||||
|
|
||||||
|
for _, d := range dups {
|
||||||
|
var sidStrs []string
|
||||||
|
for _, sid := range d.sids {
|
||||||
|
sidStrs = append(sidStrs, fmt.Sprintf("%d", sid))
|
||||||
|
}
|
||||||
|
msg.WriteString(fmt.Sprintf(" %s → SID: %s\n", d.value, strings.Join(sidStrs, ", ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().In(time.FixedZone("UTC+3", 3*60*60))
|
||||||
|
msg.WriteString(fmt.Sprintf("\n<b>Время проверки:</b> %s", now.Format("2006-01-02 15:04:05 UTC+3")))
|
||||||
|
|
||||||
|
if err := sendAlert(msg.String()); err != nil {
|
||||||
|
log.Printf("[Duplicate Check] Failed to send alert: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
0
dns.go
Normal file → Executable file
0
dns.go
Normal file → Executable file
0
git.go
Normal file → Executable file
0
git.go
Normal file → Executable file
0
go.mod
Normal file → Executable file
0
go.mod
Normal file → Executable file
0
go.sum
Normal file → Executable file
0
go.sum
Normal file → Executable file
11
main.go
Normal file → Executable file
11
main.go
Normal file → Executable file
|
|
@ -71,6 +71,17 @@ func main() {
|
||||||
}
|
}
|
||||||
log.Println("Finish cleanup of removed SIDs")
|
log.Println("Finish cleanup of removed SIDs")
|
||||||
|
|
||||||
|
// Проверка дублирующихся domain_name и aliases между разными SID
|
||||||
|
if checkDuplicates {
|
||||||
|
log.Println("Start duplicate domain/alias check")
|
||||||
|
if err := checkDuplicateDomains(db); err != nil {
|
||||||
|
log.Printf("Duplicate check error: %v", err)
|
||||||
|
}
|
||||||
|
log.Println("Finish duplicate domain/alias check")
|
||||||
|
} else {
|
||||||
|
log.Println("Duplicate domain/alias check is disabled")
|
||||||
|
}
|
||||||
|
|
||||||
// Обновление WAF whitelist
|
// Обновление WAF whitelist
|
||||||
log.Println("Start WAF whitelist update")
|
log.Println("Start WAF whitelist update")
|
||||||
if err := updateWAFWhitelist(db); err != nil {
|
if err := updateWAFWhitelist(db); err != nil {
|
||||||
|
|
|
||||||
97
notify.go
Normal file → Executable file
97
notify.go
Normal file → Executable file
|
|
@ -3,6 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
@ -190,20 +191,75 @@ func sendEmailAlert(message string) error {
|
||||||
body := fmt.Sprintf("From: %s\r\nTo: %s\r\nSubject: %s\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\n%s",
|
body := fmt.Sprintf("From: %s\r\nTo: %s\r\nSubject: %s\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\n%s",
|
||||||
smtpFrom, smtpTo, subject, message)
|
smtpFrom, smtpTo, subject, message)
|
||||||
|
|
||||||
var auth smtp.Auth
|
|
||||||
if smtpUser != "" && smtpPassword != "" {
|
|
||||||
auth = smtp.PlainAuth("", smtpUser, smtpPassword, smtpHost)
|
|
||||||
}
|
|
||||||
|
|
||||||
to := strings.Split(smtpTo, ",")
|
to := strings.Split(smtpTo, ",")
|
||||||
for i := range to {
|
for i := range to {
|
||||||
to[i] = strings.TrimSpace(to[i])
|
to[i] = strings.TrimSpace(to[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if smtpSkipTLS {
|
||||||
|
// STARTTLS с отключённой проверкой сертификата
|
||||||
|
tlsConfig := &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
ServerName: smtpHost,
|
||||||
|
}
|
||||||
|
client, err := smtp.Dial(addr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("smtp.Dial: %w", err)
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
if err := client.StartTLS(tlsConfig); err != nil {
|
||||||
|
return fmt.Errorf("smtp.StartTLS: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if smtpUser != "" && smtpPassword != "" {
|
||||||
|
var authMech smtp.Auth
|
||||||
|
|
||||||
|
if ok, ext := client.Extension("AUTH"); ok {
|
||||||
|
log.Printf("[Email] Server supported AUTH mechanisms: %s", ext)
|
||||||
|
switch {
|
||||||
|
case strings.Contains(ext, "LOGIN"):
|
||||||
|
log.Printf("[Email] Using LOGIN auth")
|
||||||
|
authMech = loginAuth(smtpUser, smtpPassword)
|
||||||
|
default:
|
||||||
|
log.Printf("[Email] Using PLAIN auth")
|
||||||
|
authMech = smtp.PlainAuth("", smtpUser, smtpPassword, smtpHost)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
authMech = smtp.PlainAuth("", smtpUser, smtpPassword, smtpHost)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := client.Auth(authMech); err != nil {
|
||||||
|
return fmt.Errorf("smtp.Auth: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := client.Mail(smtpFrom); err != nil {
|
||||||
|
return fmt.Errorf("smtp.Mail: %w", err)
|
||||||
|
}
|
||||||
|
for _, recipient := range to {
|
||||||
|
if err := client.Rcpt(recipient); err != nil {
|
||||||
|
return fmt.Errorf("smtp.Rcpt: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w, err := client.Data()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("smtp.Data: %w", err)
|
||||||
|
}
|
||||||
|
if _, err := w.Write([]byte(body)); err != nil {
|
||||||
|
return fmt.Errorf("smtp write: %w", err)
|
||||||
|
}
|
||||||
|
return w.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Стандартная отправка с проверкой сертификата
|
||||||
|
var auth smtp.Auth
|
||||||
|
if smtpUser != "" && smtpPassword != "" {
|
||||||
|
auth = smtp.PlainAuth("", smtpUser, smtpPassword, smtpHost)
|
||||||
|
}
|
||||||
if err := smtp.SendMail(addr, auth, smtpFrom, to, []byte(body)); err != nil {
|
if err := smtp.SendMail(addr, auth, smtpFrom, to, []byte(body)); err != nil {
|
||||||
return fmt.Errorf("smtp.SendMail: %w", err)
|
return fmt.Errorf("smtp.SendMail: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -313,3 +369,32 @@ func formatCommitMessage(added, removed []string) string {
|
||||||
|
|
||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* LOGIN AUTH */
|
||||||
|
|
||||||
|
// loginAuth реализует механизм аутентификации LOGIN для SMTP
|
||||||
|
type loginAuthType struct {
|
||||||
|
username, password string
|
||||||
|
}
|
||||||
|
|
||||||
|
func loginAuth(username, password string) smtp.Auth {
|
||||||
|
return &loginAuthType{username, password}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *loginAuthType) Start(server *smtp.ServerInfo) (string, []byte, error) {
|
||||||
|
return "LOGIN", nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *loginAuthType) Next(fromServer []byte, more bool) ([]byte, error) {
|
||||||
|
if more {
|
||||||
|
switch string(fromServer) {
|
||||||
|
case "Username:":
|
||||||
|
return []byte(a.username), nil
|
||||||
|
case "Password:":
|
||||||
|
return []byte(a.password), nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unexpected server challenge: %s", fromServer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
0
whitelist.go
Normal file → Executable file
0
whitelist.go
Normal file → Executable file
Loading…
Add table
Reference in a new issue