diff --git a/go.mod b/go.mod index 53c708c..6831b74 100644 --- a/go.mod +++ b/go.mod @@ -29,6 +29,8 @@ require ( b612.me/starmap v1.2.4 // indirect b612.me/starnet v0.1.8 // indirect b612.me/win32api v0.0.2 // indirect + github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect + github.com/emersion/go-smtp v0.20.2 // indirect github.com/jlaffaye/ftp v0.1.0 // indirect github.com/kr/fs v0.1.0 // indirect github.com/pkg/sftp v1.13.4 // indirect diff --git a/go.sum b/go.sum index 3669c87..3f6b01f 100644 --- a/go.sum +++ b/go.sum @@ -28,6 +28,10 @@ github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5 h1:m62nsMU279qRD9P github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM= github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= +github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ= +github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= +github.com/emersion/go-smtp v0.20.2 h1:peX42Qnh5Q0q3vrAnRy43R/JwTnnv75AebxbkTL7Ia4= +github.com/emersion/go-smtp v0.20.2/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ= github.com/goftp/file-driver v0.0.0-20180502053751-5d604a0fc0c9 h1:cC0Hbb+18DJ4i6ybqDybvj4wdIDS4vnD0QEci98PgM8= github.com/goftp/file-driver v0.0.0-20180502053751-5d604a0fc0c9/go.mod h1:GpOj6zuVBG3Inr9qjEnuVTgBlk2lZ1S9DcoFiXWyKss= github.com/goftp/server v0.0.0-20200708154336-f64f7c2d8a42 h1:JdOp2qR5PF4O75tzHeqrwnDDv8oHDptWyTbyYS4fD8E= diff --git a/httpserver/cmd.go b/httpserver/cmd.go index e92177f..d23a97c 100644 --- a/httpserver/cmd.go +++ b/httpserver/cmd.go @@ -4,6 +4,7 @@ import ( "b612.me/starlog" "b612.me/staros" "context" + "encoding/json" "github.com/spf13/cobra" "os" "os/signal" @@ -13,8 +14,10 @@ import ( var s HttpServer var daemon bool +var hooks string func init() { + Cmd.Flags().StringVarP(&hooks, "hook", "H", "", "fileget hook for modify") Cmd.Flags().StringVarP(&s.port, "port", "p", "80", "监听端口") Cmd.Flags().StringVarP(&s.addr, "ip", "i", "0.0.0.0", "监听ip") Cmd.Flags().StringVarP(&s.envPath, "folder", "f", "./", "本地文件地址") @@ -36,6 +39,24 @@ var Cmd = &cobra.Command{ Short: "HTTP文件服务器(HTTP File Browser Server)", Long: `HTTP文件服务器(HTTP File Browser Server)`, Run: func(cmd *cobra.Command, args []string) { + if hooks != "" { + if !staros.Exists(hooks) { + starlog.Criticalln("hook file not exists") + os.Exit(2) + } + data, err := os.ReadFile(hooks) + if err != nil { + starlog.Criticalln("read hook file error", err) + os.Exit(3) + } + var hk []ServerHook + err = json.Unmarshal(data, &hk) + if err != nil { + starlog.Errorln("Unmarshal hook Json Failed", err) + os.Exit(4) + } + s.hooks = hk + } apply, _ := cmd.Flags().GetBool("daeapplied") if daemon && !apply { nArgs := append(os.Args[1:], "--daeapplied") diff --git a/httpserver/mime.go b/httpserver/mime.go index 41ee371..bb18de0 100644 --- a/httpserver/mime.go +++ b/httpserver/mime.go @@ -217,6 +217,14 @@ func (h *HttpServer) FileType(name string) string { return mime } +func (h *HttpServer) GetExt(fullpath string) string { + ext := filepath.Ext(filepath.Base(fullpath)) + if len(ext) == 0 || ext == "." { + return "" + } + return ext[1:] +} + func (h *HttpServer) MIME(fullpath string) string { ext := filepath.Ext(filepath.Base(fullpath)) if len(ext) == 0 || ext == "." { diff --git a/httpserver/server.go b/httpserver/server.go index ae03b36..440fa79 100644 --- a/httpserver/server.go +++ b/httpserver/server.go @@ -3,6 +3,7 @@ package httpserver import ( "b612.me/starcrypto" "b612.me/starlog" + "b612.me/starnet" "b612.me/staros" "context" _ "embed" @@ -39,7 +40,16 @@ type HttpServerCfg struct { protectAuthPage []string disableMIME bool ctx context.Context + hooks []ServerHook } + +type ServerHook struct { + MatchType []string `json:"match_type"` + Url string `json:"url"` + Timeout int `json:"timeout"` + MaxHookLength int `json:"max_hook_length"` +} + type HttpServer struct { HttpServerCfg } @@ -267,6 +277,17 @@ var htmlTail = ` ` +func WithHooks(hooks []ServerHook) HttpServerCfgs { + return func(cfg *HttpServerCfg) { + for k, v := range hooks { + if v.MaxHookLength == 0 { + hooks[k].MaxHookLength = 1024 * 1024 + } + } + cfg.hooks = hooks + } +} + func WithTLSCert(cert, key string) HttpServerCfgs { return func(cfg *HttpServerCfg) { cfg.key = key @@ -505,6 +526,9 @@ func (h *HttpServer) BuildHeader(w http.ResponseWriter, r *http.Request, fullpat w.Header().Set("ETag", starcrypto.Md5Str([]byte(finfo.ModTime().String()))) w.Header().Set("Last-Modified", strings.ReplaceAll(finfo.ModTime().UTC().Format("Mon, 2 Jan 2006 15:04:05 MST"), "UTC", "GMT")) if r.Method != "OPTIONS" { + if _, ok := h.willHook(fullpath); ok { + return nil + } w.Header().Set("Content-Length", strconv.FormatInt(finfo.Size(), 10)) start, end := h.CalcRange(r) if start != -1 { @@ -521,6 +545,24 @@ func (h *HttpServer) BuildHeader(w http.ResponseWriter, r *http.Request, fullpat return nil } +func (h *HttpServer) willHook(fullpath string) (ServerHook, bool) { + finfo, err := os.Stat(fullpath) + if err != nil { + return ServerHook{}, false + } + if finfo.Size() < 1024*1024*10 && len(h.hooks) > 0 { + ext := h.GetExt(fullpath) + for _, hk := range h.hooks { + for _, e := range hk.MatchType { + if e == ext { + return hk, true + } + } + } + } + return ServerHook{}, false +} + func (h *HttpServer) ResponseGet(log *starlog.StarLogger, w http.ResponseWriter, r *http.Request, fullpath string) error { if staros.IsFolder(fullpath) { return h.getFolder(log, w, r, fullpath) @@ -610,25 +652,62 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r * } }() if startRange == -1 { - w.WriteHeader(200) - for { - buf := make([]byte, 1048576) - n, err := fp.Read(buf) - if n != 0 { - ns, err := w.Write(buf[0:n]) - transferData += ns + hook, needCurl := h.willHook(fullpath) + if !needCurl { + w.WriteHeader(200) + for { + buf := make([]byte, 1048576) + n, err := fp.Read(buf) + if n != 0 { + ns, err := w.Write(buf[0:n]) + transferData += ns + if err != nil { + log.Errorf("Transfer File %s to Remote Failed:%v\n", fullpath, err) + return err + } + } if err != nil { - log.Errorf("Transfer File %s to Remote Failed:%v\n", fullpath, err) + if err == io.EOF { + break + } + log.Errorln("Read File %s Failed:%v\n", fullpath, err) return err } } + return nil + } + data, err := os.ReadFile(fullpath) + if err != nil { + w.WriteHeader(502) + w.Write([]byte(`B612 Http Server

502 SERVER ERROR


`)) + log.Errorf("Read File %s Failed:%v\n", fullpath, err) + return err + } + b64 := base64.StdEncoding.EncodeToString(data) + req, err := starnet.Curl(starnet.NewRequests(hook.Url, starnet.BuildPostForm(map[string]string{ + "data": b64, + "ip": r.RemoteAddr, + }), + "POST", + starnet.WithTimeout(time.Duration(hook.Timeout)*time.Millisecond))) + if err != nil || len(req.RecvData) == 0 { + w.Header().Set("Content-Length", strconv.Itoa(len(data))) + w.WriteHeader(200) + ns, err := w.Write(data) + transferData += ns if err != nil { - if err == io.EOF { - break - } - log.Errorln("Read File %s Failed:%v\n", fullpath, err) + log.Errorf("Transfer File %s to Remote Failed:%v\n", fullpath, err) return err } + return nil + } + w.WriteHeader(200) + w.Header().Set("Content-Length", strconv.Itoa(len(req.RecvData))) + ns, err := w.Write(req.RecvData) + transferData += ns + if err != nil { + log.Errorf("Transfer File %s to Remote Failed:%v\n", fullpath, err) + return err } return nil } diff --git a/main.go b/main.go index 917ecfe..a874ebe 100644 --- a/main.go +++ b/main.go @@ -22,6 +22,7 @@ import ( "b612.me/apps/b612/net" "b612.me/apps/b612/rmt" "b612.me/apps/b612/search" + "b612.me/apps/b612/smtpserver" "b612.me/apps/b612/socks5" "b612.me/apps/b612/split" "b612.me/apps/b612/tcping" @@ -36,7 +37,7 @@ import ( var cmdRoot = &cobra.Command{ Use: "b612", - Version: "2.1.0", + Version: "2.1.0.alpha", } func init() { @@ -44,7 +45,7 @@ func init() { cmdRoot.AddCommand(tcping.Cmd, uac.Cmd, httpserver.Cmd, httpreverse.Cmd, base64.Cmd, base85.Cmd, base91.Cmd, attach.Cmd, detach.Cmd, df.Cmd, dfinder.Cmd, ftp.Cmd, generate.Cmd, hash.Cmd, image.Cmd, merge.Cmd, search.Cmd, split.Cmd, vic.Cmd, - calc.Cmd, net.Cmd, rmt.Cmds, rmt.Cmdc, keygen.Cmd, dns.Cmd, whois.Cmd, socks5.Cmd, httproxy.Cmd) + calc.Cmd, net.Cmd, rmt.Cmds, rmt.Cmdc, keygen.Cmd, dns.Cmd, whois.Cmd, socks5.Cmd, httproxy.Cmd, smtpserver.Cmd) } func main() { diff --git a/smtpserver/smtp.go b/smtpserver/smtp.go new file mode 100644 index 0000000..f682137 --- /dev/null +++ b/smtpserver/smtp.go @@ -0,0 +1,130 @@ +package smtpserver + +import ( + "b612.me/starlog" + "bytes" + "flag" + "fmt" + "github.com/spf13/cobra" + "io" + "io/ioutil" + "net/mail" + "os" + + "github.com/emersion/go-smtp" +) + +var addr string +var user, pass string +var allowAnyuser bool +var output string +var domain string + +var Cmd = &cobra.Command{ + Use: "smtp", + Short: "smtp server", + Long: "smtp server", + Run: func(cmd *cobra.Command, args []string) { + run() + + }, +} + +func init() { + Cmd.Flags().StringVarP(&addr, "addr", "a", "0.0.0.0:25", "smtp server listen address") + Cmd.Flags().StringVarP(&user, "user", "u", "admin", "smtp server username") + Cmd.Flags().StringVarP(&pass, "pass", "p", "admin", "smtp server password") + Cmd.Flags().BoolVarP(&allowAnyuser, "allow-anyuser", "A", false, "allow any user") + Cmd.Flags().StringVarP(&output, "output", "o", "", "output mail to html") + Cmd.Flags().StringVarP(&domain, "domain", "d", "localhost", "smtp server domain") +} + +type backend struct{} + +func (bkd *backend) NewSession(c *smtp.Conn) (smtp.Session, error) { + return &session{}, nil +} + +type session struct { + username string + password string + to string +} + +func (s *session) AuthPlain(username, password string) error { + s.username = username + s.password = password + starlog.Printf("username:%s,password:%s\n", username, password) + if allowAnyuser { + return nil + } else { + if username != user || password != pass { + return smtp.ErrAuthFailed + } + } + return nil +} + +func (s *session) Mail(from string, opts *smtp.MailOptions) error { + return nil +} + +func (s *session) Rcpt(to string, opts *smtp.RcptOptions) error { + s.to += to + ";" + return nil +} + +func (s *session) Data(r io.Reader) error { + mailData, err := ioutil.ReadAll(r) + if err != nil { + return err + } + msg, err := mail.ReadMessage(bytes.NewReader(mailData)) + if err != nil { + return err + } + header := msg.Header + subject := header.Get("Subject") + cc := header.Get("Cc") + bcc := header.Get("Bcc") + from := header.Get("From") // 获取发件人 + starlog.Println("From:", from) + starlog.Println("Subject:", subject) + starlog.Println("Cc:", cc) + starlog.Println("Bcc:", bcc) + + // Read the body + body, err := ioutil.ReadAll(msg.Body) + if err != nil { + return err + } + starlog.Println("Body:", string(body)) + if output != "" { + path := output + "/" + subject + ".html" + html := fmt.Sprintf(`%s

subject: %s

mali from: %smail to:%s

+

cc:%s

bcc:%s


%s

`, subject, subject, from, s.to, cc, bcc, string(body)) + os.WriteFile(path, []byte(html), 0644) + } + + return nil +} + +func (s *session) Reset() {} + +func (s *session) Logout() error { + return nil +} + +func run() { + flag.Parse() + + s := smtp.NewServer(&backend{}) + + s.Addr = addr + s.Domain = domain + s.AllowInsecureAuth = true + s.Debug = os.Stdout + + starlog.Infoln("Starting SMTP server at", addr) + starlog.Errorln(s.ListenAndServe()) +}