add readme, remove attachment handling

This commit is contained in:
kayos@tcp.direct 2021-08-24 01:56:15 -07:00
parent 6a0d581b89
commit 6dba279c9a
14 changed files with 191 additions and 189 deletions

13
README.md Normal file
View File

@ -0,0 +1,13 @@
# JupeMail
> -- "noSmtp"--
Meant to catch both incoming and outgoing email and route it to specific destinations that will be defined by the destination address
Destinations like:
* irc
* matrix
* mattermost
* RFC1149
pretty much any destination that isn't POP3 or IMAP delivery.

View File

@ -1,13 +0,0 @@
package main
import (
"github.com/jhillyerd/enmime"
)
type Attachment struct {
*enmime.Part
}
func (a *Attachment) MarshalMultipart() (string, []byte) {
return a.FileName, a.Content
}

View File

@ -6,10 +6,10 @@ import (
"time"
)
var LocalPipe chan *Message
var LocalPipe chan []byte
func init() {
LocalPipe = make(chan *Message, 20)
LocalPipe = make(chan []byte, 20)
}
type Ticket struct {
@ -24,14 +24,14 @@ type Status struct {
type Broker interface {
GetName() string
Deliver(*Message) Ticket
Deliver([]byte) Ticket
Available() bool
// GetStatus(Ticket) Status
}
type IRCBroker struct {
Name string
Backlog chan *Message
Backlog chan []byte
Overload chan bool
IsAvailable bool
Results map[Ticket]Status
@ -40,27 +40,18 @@ type IRCBroker struct {
}
func (bus *IRCBroker) PostalService() {
go func() {
for {
select {
case msg := <-bus.Backlog:
println("msg in")
LocalPipe <- msg
case <-bus.Overload:
println("overload")
return
default:
println("default")
bus.mu.Lock()
bus.IsAvailable = false
bus.mu.Unlock()
bus.Overload <- true
}
for {
select {
case msg := <-bus.Backlog:
println("msg in")
LocalPipe <- msg
default:
time.Sleep(time.Duration(25) * time.Millisecond)
}
}()
}
}
func (bus *IRCBroker) Deliver(incoming *Message) Ticket {
func (bus *IRCBroker) Deliver(incoming []byte) Ticket {
bus.Backlog <- incoming
return Ticket{Timestamp: time.Now()}
}
@ -74,12 +65,16 @@ func (bus *IRCBroker) GetName() string {
}
func NewIRCBroker(room string) *IRCBroker {
return &IRCBroker{
bro := &IRCBroker{
Name: "IRCBroker",
Backlog: make(chan *Message, 10),
Backlog: make(chan []byte, 20),
Overload: make(chan bool),
IsAvailable: false,
ToChannel: room,
mu: sync.RWMutex{},
}
go bro.PostalService()
go bro.StartBot(bro.Name)
return bro
}

View File

@ -1,9 +1,10 @@
// Code generated by Gojay. DO NOT EDIT.
package main
// Code generated by Gojay.
import (
"github.com/francoispqt/gojay"
"time"
)
type AddresssPtr []*Address
@ -27,27 +28,6 @@ func (s AddresssPtr) IsNil() bool {
return len(s) == 0
}
type AttachmentsPtr []*Attachment
func (s *AttachmentsPtr) UnmarshalJSONArray(dec *gojay.Decoder) error {
var value = &Attachment{}
if err := dec.Object(value); err != nil {
return err
}
*s = append(*s, value)
return nil
}
func (s AttachmentsPtr) MarshalJSONArray(enc *gojay.Encoder) {
for i := range s {
enc.Object(s[i])
}
}
func (s AttachmentsPtr) IsNil() bool {
return len(s) == 0
}
type Strings []string
// UnmarshalJSONArray decodes JSON array elements into slice
@ -100,28 +80,6 @@ func (a *Address) UnmarshalJSONObject(dec *gojay.Decoder, k string) error {
// NKeys returns the number of keys to unmarshal
func (a *Address) NKeys() int { return 2 }
// MarshalJSONObject implements MarshalerJSONObject
func (a *Attachment) MarshalJSONObject(enc *gojay.Encoder) {
}
// IsNil checks if instance is nil
func (a *Attachment) IsNil() bool {
return a == nil
}
// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject
func (a *Attachment) UnmarshalJSONObject(dec *gojay.Decoder, k string) error {
switch k {
}
return nil
}
// NKeys returns the number of keys to unmarshal
func (a *Attachment) NKeys() int { return 0 }
// MarshalJSONObject implements MarshalerJSONObject
func (m *Mail) MarshalJSONObject(enc *gojay.Encoder) {
enc.StringKey("Sender", m.Sender)
@ -173,15 +131,13 @@ func (m *Message) MarshalJSONObject(enc *gojay.Encoder) {
var replyToSlice = AddresssPtr(m.ReplyTo)
enc.ArrayKey("ReplyTo", replyToSlice)
enc.StringKey("Subject", m.Subject)
enc.ObjectKey("Date", m.Date)
enc.TimeKey("Date", &m.Date, time.RFC3339)
enc.StringKey("MessageID", m.MessageID)
enc.StringKey("InReplyTo", m.InReplyTo)
var referencesSlice = Strings(m.References)
enc.ArrayKey("References", referencesSlice)
enc.StringKey("Text", m.Text)
enc.StringKey("HTML", m.HTML)
var attachmentsSlice = AttachmentsPtr(m.Attachments)
enc.ArrayKey("Attachments", attachmentsSlice)
}
// IsNil checks if instance is nil
@ -237,12 +193,12 @@ func (m *Message) UnmarshalJSONObject(dec *gojay.Decoder, k string) error {
return dec.String(&m.Subject)
case "Date":
var value = &Time{}
err := dec.Object(value)
var format = time.RFC3339
var value = time.Time{}
err := dec.Time(&value, format)
if err == nil {
m.Date = value
}
return err
case "MessageID":
@ -265,39 +221,9 @@ func (m *Message) UnmarshalJSONObject(dec *gojay.Decoder, k string) error {
case "HTML":
return dec.String(&m.HTML)
case "Attachments":
var aSlice = AttachmentsPtr{}
err := dec.Array(&aSlice)
if err == nil && len(aSlice) > 0 {
m.Attachments = []*Attachment(aSlice)
}
return err
}
return nil
}
// NKeys returns the number of keys to unmarshal
func (m *Message) NKeys() int { return 13 }
// MarshalJSONObject implements MarshalerJSONObject
func (t *Time) MarshalJSONObject(enc *gojay.Encoder) {
}
// IsNil checks if instance is nil
func (t *Time) IsNil() bool {
return t == nil
}
// UnmarshalJSONObject implements gojay's UnmarshalerJSONObject
func (t *Time) UnmarshalJSONObject(dec *gojay.Decoder, k string) error {
switch k {
}
return nil
}
// NKeys returns the number of keys to unmarshal
func (t *Time) NKeys() int { return 0 }
func (m *Message) NKeys() int { return 12 }

View File

@ -9,11 +9,6 @@ import (
"os"
)
var (
Map map[string]interface{}
Debug bool
)
const (
Version = "0.0"
Title = "jupemail"
@ -36,6 +31,9 @@ var (
home string
logBuffer *lineBuffer
defaults map[string]map[string]interface{}
Map map[string]interface{}
Debug bool
)
func init() {
@ -175,4 +173,5 @@ func associate() {
if Debug = Config.GetBool("logger.debug"); Debug {
println("debug on")
}
}

View File

@ -1,10 +1,11 @@
package db
import (
jsoniter "github.com/json-iterator/go"
"github.com/prologic/bitcask"
"github.com/rs/zerolog/log"
"jupemail/config"
// "time"
"time"
)
var (
@ -12,13 +13,19 @@ var (
maxSizeObj uint64
dataDir string
err error
key []byte
)
type Key struct {
Owner string
Time int64
}
const megabyte = float64(1024 * 1024)
func DbInit() {
dataDir = config.Map["data.directory"].(string)
maxSizeObj = config.Map["email.maxsize"].(uint64)
dataDir = config.Config.GetString("data.directory")
maxSizeObj = config.Config.GetUint64("email.maxsize")
var dbo = []bitcask.Option{
bitcask.WithMaxValueSize(maxSizeObj * uint64(1024)),
}
@ -28,12 +35,18 @@ func DbInit() {
}
//func CompressAndStore(recpt string, json []byte) error {
// timestamp := time.Now()
// data, err := Gzip(json)
// if err != nil {
// return err
// }
// Storage.Put()
//}
func CompressAndStore(rcpt string, valdata []byte) error {
var json = jsoniter.ConfigCompatibleWithStandardLibrary
key, err = json.Marshal(&Key{
Owner: rcpt,
Time: time.Now().Unix(),
})
if err != nil {
return err
}
data, err := Gzip(valdata)
if err != nil {
return err
}
return Storage.Put(key, data)
}

1
go.mod
View File

@ -6,6 +6,7 @@ require (
github.com/chrj/smtpd v0.3.0
github.com/francoispqt/gojay v1.2.13
github.com/jhillyerd/enmime v0.9.1
github.com/json-iterator/go v1.1.6
github.com/lrstanley/girc v0.0.0-20210611213246-771323f1624b
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/prologic/bitcask v0.3.10

3
go.sum
View File

@ -141,6 +141,7 @@ github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0
github.com/jhillyerd/enmime v0.9.1 h1:HcC2WZA6dMCobs8WeyF/6FRSvdRCrr8O+UiLBae4eNE=
github.com/jhillyerd/enmime v0.9.1/go.mod h1:S5ge4lnv/dDDBbAWwtoOFlj14NHiXdw/EqMB2lJz3b8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
@ -180,7 +181,9 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=

66
ircbot.go Normal file
View File

@ -0,0 +1,66 @@
package main
import (
"github.com/lrstanley/girc"
)
type BotStatus struct {
Ready bool
Error string
Info string
}
func (b *IRCBroker) StartBot(nick string, server string, port int, ssl bool, channel string, incoming chan []byte) chan Status {
var client *girc.Client
var status chan Status
client = girc.New(girc.Config{
Server: server,
Port: port,
Nick: nick,
Name: "jupemail",
User: "jupemail",
SSL: ssl,
AllowFlood: true,
Version: "~ JupeMail v0.0101 ~",
})
client.Handlers.Add(girc.CONNECTED, func(c *girc.Client, e girc.Event) {
status <- &BotStatus{
Available: true,
Info: "[IRC] Successfully connected to: " + server + " joining channel...",
}
c.Cmd.Join(ircHome)
})
client.Handlers.Add(girc.ERR_CANNOTSENDTOCHAN, func(c *girc.Client, e girc.Event) {
status <- &BotStatus{
Available: false,
Error: "ERR_CANNOTSENDTOCHAN",
}
})
sslString := " "
if ircSSL == true {
sslString = "-ssl"
}
status <- &BotStatus{
Available: false,
Info: "[IRC] Connecting...",
}
go botLoop()
return status
}
func botLoop() {
for {
if err := client.Connect(); err != nil {
status <- &BotStatus{
Available: false,
Info: "[IRC] Reconnecting to " + ircHost + "/" + strconv.Itoa(ircPort) + " " + sslString,
Error: err.Error(),
}
time.Sleep(time.Duration(500) * time.Millisecond)
}
}
}

View File

@ -12,6 +12,7 @@ import (
"net"
"os"
"strings"
"time"
)
var (
@ -61,7 +62,6 @@ func init() {
Config := config.Blueprint()
Debug := Config.GetBool("logger.debug")
logDir = Config.GetString("logger.logdir")
dataDir = Config.GetString("data.directory")
bindTo = Config.GetString("email.listen")
err = os.MkdirAll(logDir, 0755)
if err != nil {
@ -86,12 +86,13 @@ func init() {
case dbgstr := <-dbgchan:
log.Debug().Msg(dbgstr)
default:
//
time.Sleep(time.Duration(25) * time.Millisecond)
}
}
}(Rater.DebugChannel())
}
config.PrintConfigLog()
db.DbInit()
bnr()
println("jupemail v0.0101 init")
Quit = make(chan bool)

View File

@ -9,19 +9,18 @@ import (
)
type Message struct {
From []*Address `multipart:"from"`
To []*Address `multipart:"to"`
Cc []*Address `multipart:"cc"`
Bcc []*Address `multipart:"bcc"`
ReplyTo []*Address `multipart:"reply_to"`
Subject string `multipart:"subject"`
Date *Time `multipart:"date"`
MessageID string `multipart:"message_id"`
InReplyTo string `multipart:"in_reply_to"`
References []string `multipart:"references"`
Text string `multipart:"text"`
HTML string `multipart:"html"`
Attachments []*Attachment `multipart:"attachments"`
From []*Address `multipart:"from"`
To []*Address `multipart:"to"`
Cc []*Address `multipart:"cc"`
Bcc []*Address `multipart:"bcc"`
ReplyTo []*Address `multipart:"reply_to"`
Subject string `multipart:"subject"`
Date time.Time `multipart:"date"`
MessageID string `multipart:"message_id"`
InReplyTo string `multipart:"in_reply_to"`
References []string `multipart:"references"`
Text string `multipart:"text"`
HTML string `multipart:"html"`
}
type Address struct {
@ -37,17 +36,12 @@ func NewMessage(r io.Reader) (*Message, error) {
}
msg := &Message{
Subject: env.GetHeader("Subject"),
MessageID: env.GetHeader("Message-ID"),
InReplyTo: env.GetHeader("In-Reply-To"),
References: strings.Fields(env.GetHeader("References")),
Text: env.Text,
HTML: env.HTML,
Attachments: make([]*Attachment, len(env.Attachments)),
}
for i, attachment := range env.Attachments {
msg.Attachments[i] = &Attachment{attachment}
Subject: env.GetHeader("Subject"),
MessageID: env.GetHeader("Message-ID"),
InReplyTo: env.GetHeader("In-Reply-To"),
References: strings.Fields(env.GetHeader("References")),
Text: env.Text,
HTML: env.HTML,
}
if msg.From, err = readAddressListHeader(env, "From"); err != nil {
@ -90,13 +84,13 @@ func readAddressListHeader(env *enmime.Envelope, key string) ([]*Address, error)
return emails, nil
}
func readDateHeader(env *enmime.Envelope) (*Time, error) {
func readDateHeader(env *enmime.Envelope) (time.Time, error) {
hdr := env.GetHeader("Date")
if hdr == "" {
return &Time{time.Now()}, nil
return time.Now(), nil
}
date, err := mail.ParseDate(hdr)
return &Time{date}, err
return date, err
}

View File

@ -21,7 +21,7 @@ func NewDefaultLimiter(id Identity) *Queue {
func (q *Queue) DebugChannel() chan string {
q.Patrons.OnEvicted(func(src string, count interface{}) {
q.debugPrint("ratelimit (expired): ", src, count)
q.debugPrint("ratelimit (expired): ", src, " ", count)
})
q.Debug = true
debugChannel = make(chan string, 10)

View File

@ -2,9 +2,10 @@ package main
import (
"bytes"
"fmt"
"github.com/chrj/smtpd"
"github.com/francoispqt/gojay"
"io/ioutil"
"jupemail/db"
)
type Mail struct {
@ -20,16 +21,19 @@ func NewMail(sender, recipient string, msg *Message) *Mail {
type Server struct {
srv *smtpd.Server
Addresses map[string]Broker
CatchAll bool
}
func NewServer(newsmtpd *smtpd.Server) *Server {
a := make(map[string]Broker)
ibroker := NewIRCBroker("#go")
a["kayos@crip.haus"] = ibroker
a["kayos@crip.haus"] = NewIRCBroker("#go")
mx := &Server{
srv: newsmtpd,
Addresses: a,
}
mx.CatchAll = false
mx.srv.Handler = mx.ServeSMTP
mx.srv.RecipientChecker = mx.CheckRecipient
mx.srv.ConnectionChecker = mx.CheckConn
@ -43,16 +47,21 @@ func (s *Server) CheckConn(p smtpd.Peer) error {
// we're gonna DoS ourselves, maybe start doing batches
return smtpd.Error{
Code: 421,
Message: "r e l a x",
Message: "you are being ratelimited",
}
}
return nil
}
func (s *Server) CheckRecipient(p smtpd.Peer, addr string) error {
if s.CatchAll {
return nil
}
if _, ok := s.Addresses[addr]; ok {
return nil
}
log.Warn().Str("caller", p.Addr.String()).Str("address", addr).
Msg("denied unknown recipient address")
return smtpd.Error{
@ -81,18 +90,26 @@ func (s *Server) ServeSMTP(p smtpd.Peer, env smtpd.Envelope) error {
buf := bytes.NewBuffer(nil)
enc := gojay.NewEncoder(buf)
enc.Encode(NewMail(env.Sender, addr, msg))
//enc := NewMultipartEncoder(buf)
if err := enc.Encode(NewMail(env.Sender, addr, msg)); err != nil {
//if err := enc.Encode("mail", NewMail(env.Sender, addr, msg)); err != nil {
fmt.Println("could not encode request body:", err)
log.Error().Err(err).Msg("could not encode request body")
return smtpd.Error{
Code: 451,
Message: "internal server error",
}
}
fmt.Println(buf)
d, err := ioutil.ReadAll(buf)
if err != nil {
log.Error().Interface("envelope", env).Err(err).Msg("!!!")
}
go s.deliverMessage(addr, d)
}
return nil
}
func (s *Server) deliverMessage(addr string, d []byte) {
err = db.CompressAndStore(addr, d)
if err != nil {
log.Error().Err(err).Msg("!!!")
}
s.Addresses[addr].Deliver(d)
}

13
time.go
View File

@ -1,13 +0,0 @@
package main
import (
"time"
)
type Time struct {
time.Time
}
func (t *Time) MarshalMultipart() string {
return t.Format(time.RFC3339)
}