gitignore

This commit is contained in:
2025-09-28 08:20:48 +01:00
parent 662824d50b
commit 251bf071f0
11 changed files with 178 additions and 190 deletions
+28 -4
View File
@@ -11,10 +11,11 @@ func NewFTPHandler(log LoggerFunc) Handler {
return func(conn net.Conn) {
defer conn.Close()
remote := conn.RemoteAddr().String()
_, _ = conn.Write([]byte("220 Welcome to FTP Server\r\n"))
_, _ = conn.Write([]byte("220 (vsFTPd 3.0.3)\r\n"))
conn.SetDeadline(time.Now().Add(5 * time.Minute))
scanner := bufio.NewScanner(conn)
var username string
var cwd = "/"
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" {
@@ -24,16 +25,39 @@ func NewFTPHandler(log LoggerFunc) Handler {
cmd := strings.ToUpper(parts[0])
arg := ""
if len(parts) > 1 { arg = parts[1] }
// log every command minimally
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "ftp", Details: map[string]string{"event":"cmd","cmd":cmd,"arg":arg}})
switch cmd {
case "USER":
username = arg
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "ftp", Details: map[string]string{"event":"username_attempt","username":username}})
_, _ = conn.Write([]byte("331 Password required for "+username+"\r\n"))
_, _ = conn.Write([]byte("331 Please specify the password.\r\n"))
case "PASS":
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "ftp", Details: map[string]string{"event":"password_attempt","username":username,"password":arg}})
_, _ = conn.Write([]byte("530 Login incorrect\r\n"))
// stay unauthenticated but pretend success to keep them interacting
_, _ = conn.Write([]byte("230 Login successful.\r\n"))
case "SYST":
_, _ = conn.Write([]byte("215 UNIX Type: L8\r\n"))
case "FEAT":
_, _ = conn.Write([]byte("211-Features:\r\n MLSD\r\n SIZE\r\n211 End\r\n"))
case "PWD":
_, _ = conn.Write([]byte("257 \"" + cwd + "\" is the current directory\r\n"))
case "TYPE":
_, _ = conn.Write([]byte("200 Switching to Binary mode.\r\n"))
case "CWD":
if arg == "" { arg = "/" }
cwd = arg
_, _ = conn.Write([]byte("250 Directory successfully changed.\r\n"))
case "PASV":
// Emulate passive mode without real data channel
_, _ = conn.Write([]byte("227 Entering Passive Mode (127,0,0,1,195,80)\r\n"))
case "LIST":
// Fake transfer over control channel (not RFC-correct, but many bots accept the banner)
_, _ = conn.Write([]byte("150 Here comes the directory listing.\r\n"))
_, _ = conn.Write([]byte("-rw-r--r-- 1 root root 4096 Jan 01 00:00 README\r\n"))
_, _ = conn.Write([]byte("226 Directory send OK.\r\n"))
case "QUIT":
_, _ = conn.Write([]byte("221 Goodbye\r\n")); return
_, _ = conn.Write([]byte("221 Goodbye.\r\n")); return
default:
_, _ = conn.Write([]byte("502 Command not implemented\r\n"))
}
+20 -2
View File
@@ -14,6 +14,9 @@ func NewIMAPHandler(log LoggerFunc) Handler {
_, _ = conn.Write([]byte("* OK IMAP4rev1 Service Ready\r\n"))
conn.SetDeadline(time.Now().Add(2 * time.Minute))
scanner := bufio.NewScanner(conn)
authed := false
selected := false
mailbox := "INBOX"
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" { continue }
@@ -28,10 +31,25 @@ func NewIMAPHandler(log LoggerFunc) Handler {
pass := strings.Trim(parts[3], "\"")
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "imap", Details: map[string]string{"event":"login_attempt","username":user,"password":pass}})
}
_, _ = conn.Write([]byte(tag + " NO LOGIN failed\r\n"))
authed = true // pretend success to keep interaction
_, _ = conn.Write([]byte(tag + " OK LOGIN completed\r\n"))
case "CAPABILITY":
_, _ = conn.Write([]byte("* CAPABILITY IMAP4rev1 AUTH=PLAIN\r\n"))
_, _ = conn.Write([]byte("* CAPABILITY IMAP4rev1 AUTH=PLAIN IDLE\r\n"))
_, _ = conn.Write([]byte(tag + " OK CAPABILITY completed\r\n"))
case "NOOP":
_, _ = conn.Write([]byte(tag + " OK NOOP completed\r\n"))
case "SELECT", "EXAMINE":
if len(parts) >= 3 { mailbox = strings.Trim(parts[2], "\"") }
selected = true
// fake mailbox with 2 messages
_, _ = conn.Write([]byte("* 2 EXISTS\r\n"))
_, _ = conn.Write([]byte("* OK [UIDVALIDITY 1] UIDs valid\r\n"))
_, _ = conn.Write([]byte(tag + " OK [READ-WRITE] SELECT completed\r\n"))
case "FETCH":
if !selected { _, _ = conn.Write([]byte(tag + " BAD No mailbox selected\r\n")); continue }
// minimal fake fetch
_, _ = conn.Write([]byte("* 1 FETCH (FLAGS (\\Seen) RFC822.SIZE 1234)\r\n"))
_, _ = conn.Write([]byte(tag + " OK FETCH completed\r\n"))
case "LOGOUT":
_, _ = conn.Write([]byte("* BYE IMAP4rev1 Server logging out\r\n"))
_, _ = conn.Write([]byte(tag + " OK LOGOUT completed\r\n"))
+31 -3
View File
@@ -13,8 +13,36 @@ func NewSIPHandler(log LoggerFunc) Handler {
remote := conn.RemoteAddr().String()
conn.SetDeadline(time.Now().Add(8 * time.Second))
r := bufio.NewReader(conn)
line, _ := r.ReadString('\n')
if strings.TrimSpace(line) == "" { return }
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "sip", Details: map[string]string{"event":"first_line","first_line":strings.TrimSpace(line)}, RawPayload: line})
// Read request line
reqLine, _ := r.ReadString('\n')
reqLine = strings.TrimSpace(reqLine)
if reqLine == "" { return }
// Read headers until blank line
headers := map[string]string{}
for {
h, _ := r.ReadString('\n')
h = strings.TrimRight(h, "\r\n")
if h == "" { break }
if i := strings.Index(h, ":"); i > 0 {
k := strings.TrimSpace(h[:i])
v := strings.TrimSpace(h[i+1:])
headers[strings.ToLower(k)] = v
}
}
det := map[string]string{"event":"request","first_line":reqLine}
for _, k := range []string{"from","to","call-id","user-agent"} {
if v, ok := headers[k]; ok { det[k] = v }
}
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "sip", Details: det})
// Respond with 401 to solicit credentials
resp := "SIP/2.0 401 Unauthorized\r\n" +
"Via: " + headers["via"] + "\r\n" +
"From: " + headers["from"] + "\r\n" +
"To: " + headers["to"] + ";tag=123456\r\n" +
"Call-ID: " + headers["call-id"] + "\r\n" +
"CSeq: 1 REGISTER\r\n" +
"WWW-Authenticate: Digest realm=\"example.com\", nonce=\"abc123\", qop=\"auth\"\r\n" +
"Content-Length: 0\r\n\r\n"
_, _ = conn.Write([]byte(resp))
}
}
+96 -44
View File
@@ -1,51 +1,103 @@
package services
import (
"bufio"
"encoding/base64"
"net"
"strings"
"time"
"bufio"
"encoding/base64"
"net"
"strings"
"time"
)
func NewSMTPHandler(log LoggerFunc) Handler {
return func(conn net.Conn) {
defer conn.Close()
remote := conn.RemoteAddr().String()
_, _ = conn.Write([]byte("220 mail.example.com ESMTP Postfix\r\n"))
conn.SetDeadline(time.Now().Add(5 * time.Minute))
scanner := bufio.NewScanner(conn)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" { continue }
parts := strings.SplitN(line, " ", 2)
cmd := strings.ToUpper(parts[0])
arg := ""; if len(parts)>1 { arg = parts[1] }
switch cmd {
case "HELO":
_, _ = conn.Write([]byte("250 mail.example.com\r\n"))
case "EHLO":
_, _ = conn.Write([]byte("250-mail.example.com\r\n250-AUTH PLAIN LOGIN\r\n250 OK\r\n"))
case "AUTH":
fields := strings.Fields(arg)
if len(fields)>0 && strings.ToUpper(fields[0])=="PLAIN" && len(fields)>1 {
if b, err := base64.StdEncoding.DecodeString(fields[1]); err==nil {
parts := strings.Split(string(b), "\x00")
if len(parts)>=3 {
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "smtp", Details: map[string]string{"event":"auth_attempt","method":"PLAIN","username":parts[1],"password":parts[2]}})
}
}
}
_, _ = conn.Write([]byte("535 Authentication failed\r\n"))
case "MAIL","RCPT":
_, _ = conn.Write([]byte("250 OK\r\n"))
case "DATA":
_, _ = conn.Write([]byte("354 End data with <CR><LF>.<CR><LF>\r\n"))
case "QUIT":
_, _ = conn.Write([]byte("221 Bye\r\n")); return
default:
_, _ = conn.Write([]byte("502 Command not implemented\r\n"))
}
}
}
return func(conn net.Conn) {
defer conn.Close()
remote := conn.RemoteAddr().String()
_, _ = conn.Write([]byte("220 mail.example.com ESMTP Postfix\r\n"))
conn.SetDeadline(time.Now().Add(5 * time.Minute))
scanner := bufio.NewScanner(conn)
var mailFrom, rcptTo string
authLoginStage := 0 // 0=none,1=expect username,2=expect password (both base64)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" { continue }
parts := strings.SplitN(line, " ", 2)
cmd := strings.ToUpper(parts[0])
arg := ""; if len(parts)>1 { arg = parts[1] }
if authLoginStage == 1 { // expecting base64 username
userBytes, _ := base64.StdEncoding.DecodeString(line)
user := string(userBytes)
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "smtp", Details: map[string]string{"event":"auth_login_username","username":user}})
_, _ = conn.Write([]byte("334 UGFzc3dvcmQ6\r\n")) // "Password:" base64
authLoginStage = 2
continue
}
if authLoginStage == 2 { // expecting base64 password
passBytes, _ := base64.StdEncoding.DecodeString(line)
pass := string(passBytes)
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "smtp", Details: map[string]string{"event":"auth_login_password","password":pass}})
_, _ = conn.Write([]byte("535 Authentication failed\r\n"))
authLoginStage = 0
continue
}
switch cmd {
case "HELO":
_, _ = conn.Write([]byte("250 mail.example.com\r\n"))
case "EHLO":
_, _ = conn.Write([]byte("250-mail.example.com\r\n250-AUTH PLAIN LOGIN\r\n250 OK\r\n"))
case "AUTH":
fields := strings.Fields(arg)
if len(fields) > 0 {
method := strings.ToUpper(fields[0])
switch method {
case "PLAIN":
if len(fields) > 1 {
if b, err := base64.StdEncoding.DecodeString(fields[1]); err == nil {
p := strings.Split(string(b), "\x00")
if len(p) >= 3 {
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "smtp", Details: map[string]string{"event":"auth_attempt","method":"PLAIN","username":p[1],"password":p[2]}})
}
}
} else {
// prompt for base64 blob
_, _ = conn.Write([]byte("334 \r\n"))
continue
}
_, _ = conn.Write([]byte("535 Authentication failed\r\n"))
case "LOGIN":
// 334 Username:
_, _ = conn.Write([]byte("334 VXNlcm5hbWU6\r\n"))
authLoginStage = 1
default:
_, _ = conn.Write([]byte("504 Authentication method not supported\r\n"))
}
}
case "MAIL":
mailFrom = arg
_, _ = conn.Write([]byte("250 OK\r\n"))
case "RCPT":
rcptTo = arg
_, _ = conn.Write([]byte("250 OK\r\n"))
case "DATA":
_, _ = conn.Write([]byte("354 End data with <CR><LF>.<CR><LF>\r\n"))
// read until single '.' on a line
var bodyLines []string
for scanner.Scan() {
l := scanner.Text()
if l == "." { break }
bodyLines = append(bodyLines, l)
}
// log summary
snippet := strings.Join(bodyLines, "\n")
if len(snippet) > 500 { snippet = snippet[:500] }
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "smtp", Details: map[string]string{"event":"message","mail_from":mailFrom,"rcpt_to":rcptTo}, RawPayload: snippet})
_, _ = conn.Write([]byte("250 OK queued as 12345\r\n"))
case "QUIT":
_, _ = conn.Write([]byte("221 Bye\r\n")); return
default:
_, _ = conn.Write([]byte("502 Command not implemented\r\n"))
}
}
}
}