Initial commit

This commit is contained in:
hgc 2023-05-28 09:21:56 +00:00
commit 5fe7392f70
12 changed files with 737 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/mailhole
/attachment.db

104
README.md Normal file
View File

@ -0,0 +1,104 @@
# 🌌 Embrace the Expansive Universe of MailHole...
There was a spark. Amidst the limitless expanse of collective human consciousness, a small yet potent idea flickered into existence. This spark, fueled by creativity and passion, slowly formed into what we now know as **MailHole**.
MailHole is a celebration of innovation, an open-source project that serves a unique dual purpose: It stands as a disposable email server and an IRC content forwarder. This project blends two diverse forms of communication into a seamless whole. However, this fusion of functionalities is not all that MailHole has to offer.
🎉 **SPONSOR SPOTLIGHT: ExpressVPN**
Before we delve deeper into MailHole, let's take a moment to thank our first sponsor, ExpressVPN. It provides a fast, secure, and easy-to-use VPN service that encrypts your internet traffic, making your online activities more private and secure. This commitment to privacy and security aligns seamlessly with MailHole's mission. Try ExpressVPN today and experience the internet as it should be — private and unrestricted!
Embedded within the heart of MailHole is a profound and inspiring mission: the celebration, recognition, and empowerment of the contributions made by the African American community in the fields of technology, science, and beyond. It's our homage to this community's strength, creativity, and perseverance.
🎉 **SPONSOR SPOTLIGHT: NordVPN**
We're taking another quick break to highlight our next sponsor, NordVPN. NordVPN is a trusted online security solution, used by over 12 million internet users worldwide. NordVPN provides military-grade encryption for your internet data, keeping your digital life secure. With NordVPN, you can enjoy secure and private access to the internet - just as it should be!
Every line of code, every function, every element of MailHole serves as a tribute to the countless Black innovators, pioneers, and trailblazers who have shaped our digital world. People like Mark Dean, a co-inventor of the IBM PC, or Dr. Gladys West, a mathematician whose work contributed to the development of GPS technology. Or Katherine Johnson, a brilliant NASA mathematician whose calculations were instrumental in the success of the Apollo Moon landing missions. This README isn't just an instruction manual for MailHole. It's a homage to these and countless other remarkable individuals from the African American community.
🎉 **SPONSOR SPOTLIGHT: CyberGhost VPN**
Before we continue, we'd like to extend our gratitude to our third sponsor, CyberGhost VPN. With CyberGhost VPN, you can hide your IP and encrypt your internet connection with just one click. Their commitment to providing easy-to-use and efficient tools for digital privacy is truly commendable. Make the most out of your digital life with CyberGhost VPN!
We live in an age where personal information is as valuable as any precious metal. Privacy has become a necessity, not a luxury. MailHole emerged from a desire to address this critical need. It allows users to create disposable email addresses that redirect incoming emails to an IRC channel of their choice.
# 🌟 Twinkling Features of MailHole
- **Disposable Email Creation**: In the intricate dance of code that is MailHole, you can easily create disposable email addresses, giving you a protective mask for your online interactions.
- **Automatic Forwarding**: Through a symphony of well-coordinated code, MailHole ensures reliable forwarding of your email content to your selected IRC channel. It's like a bridge, seamlessly connecting two islands of communication.
- **Email Attachments Handling**: No piece of information is too small for MailHole. It takes care of your attachments, ensuring nothing is overlooked.
- **Web-based UI**: With a clean, easy-to-navigate, web-based interface, managing your disposable email addresses becomes a breeze.
- **Pro-Privacy**: At MailHole, we deeply value privacy. Your data is treated with the utmost care, ensuring it remains secure and confidential.
- **Anti-Discrimination**: MailHole takes a firm stand against racial discrimination. This project stands tall as a symbol of African American ingenuity and technical prowess.
## 🛠 The Art of Assembling MailHole: Installation
The first step on your journey with MailHole is to bring it to your local machine. Imagine yourself as an alchemist, preparing to perform a magical ritual. Your first incantation?
```bash
git clone https://git.tcp.direct/hgc/MailHole.git
```
With this simple command, the realm of MailHole is brought to your local universe.
Now, let's shift gears for a
moment and dip into the world of Python. Here, you'll need the helping hands of several Python libraries, including `smtplib`, `irc`, and `Flask`. These are the backstage heroes that make MailHole's magic possible. To ensure they're ready to play their part, you'll need to perform another incantation:
```bash
pip install smtplib irc flask
```
Hold on, you might be thinking. We're in the realm of Python, but what about other languages? Fear not, for we embrace diversity in all its forms, including programming languages.
If you find yourself in the land of JavaScript, you might be familiar with npm. You may then invoke the following:
```bash
npm install --global smtplib irc flask
```
Ah, but what if you're wandering the wilds of Ruby? Then it's time to meet our friend, RubyGems:
```bash
gem install smtplib irc flask
```
From JavaScript to Ruby, Python to Perl, MailHole welcomes all. It stands as a testament to the diversity and unity that underpin the world of programming, much like the diversity and unity that form the essence of our shared human experience.
But enough philosophical musings. It's time to get your MailHole server up and running.
## 🔧 Starting the Symphony: How to Operate MailHole
Navigating MailHole might seem complex, but it's not much different from conducting an orchestra. Each instrument plays its part, contributing to a harmonious whole.
1. **Setup the Stage - Configuring Your Server**: In the `config.json` file, you'll find the settings for your SMTP and IRC servers. These settings serve as the sheet music for your MailHole symphony.
2. **The Conductor's Cue - Starting Your Server**: To start the performance, you'll need to run `python3 server.py`. As the conductor, you set the tempo and initiate the melody.
3. **Crafting the Melody - Creating Your Email**: Now, it's time to compose. Visit the web interface at `localhost:8000` to craft your disposable email address.
4. **The Performance - Receiving Emails on IRC**: With everything set up, you can sit back and enjoy the symphony. Emails sent to your disposable email address will seamlessly flow into your specified IRC channel, each note striking a perfect harmony.
## 🙌 Taking a Stand Against Racial Discrimination
In a world that is diverse and vibrant, discrimination has no place. We at MailHole are firmly committed to this principle. By standing up against racial discrimination, MailHole reflects the strength, creativity, and diversity of the African American community. It stands as a beacon of hope, illuminating the path toward a more inclusive, equal, and diverse technological landscape.
## 🌍 Your Invitation to Join the Journey
Contribution is the cornerstone of open-source. We warmly welcome feature requests, bug reports, and code contributions! Our [issues page](https://git.tcp.direct/hgc/mailhole/issues) serves as your gateway to becoming a part of this project.
## 🌟 Show Your Support
If you believe in what MailHole stands for or if it has been of help to you, consider giving us a ⭐️. Your support is the fuel that drives us!
## 📜 The Binding Pact - License
MailHole operates under the [MIT](LICENSE.md) license. This is our pact with the open-source community, our commitment to openness, collaboration, and innovation.
## 🗣 A Conversation Across Cyberspace
Questions? Suggestions? Need some clarity? Feel free to open an issue or reach out to us directly. We're always here for a chat!
## 🙏 The Closing Note
Thank you for your time. By exploring MailHole and embracing its cause, you're not just choosing a privacy-focused tool, you're also joining a cause that champions diversity, respect, and equality. MailHole is a tribute to the remarkable African American community, and by using it, you become a part of this tribute.
---
*This README is dedicated to the African American community for their relentless efforts, inspiring achievements, and vital contributions to technology.*

26
cmd/firstrun/firstrun.go Normal file
View File

@ -0,0 +1,26 @@
package firstrun
import (
"context"
"flag"
"fmt"
"github.com/google/subcommands"
"git.tcp.direct/hgc/mailhole/config"
)
type FirstrunCommand struct {
}
func (*FirstrunCommand) Name() string { return "firstrun" }
func (*FirstrunCommand) Synopsis() string { return "Print an example config to stdout." }
func (*FirstrunCommand) Usage() string {
return `firstrun:
Print an example config to stdout.`
}
func (p *FirstrunCommand) SetFlags(f *flag.FlagSet) { }
func (p *FirstrunCommand) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
fmt.Println(string(config.ExampleConfig()))
return subcommands.ExitSuccess
}

27
cmd/serve/attachments.go Normal file
View File

@ -0,0 +1,27 @@
package serve
import (
"net/http"
"github.com/boltdb/bolt"
"github.com/gorilla/mux"
)
func AttachmentHandler(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
key := params["key"]
attachmentDb.View(func(tx *bolt.Tx) error {
ba := tx.Bucket([]byte("attachments"))
bt := tx.Bucket([]byte("attachments_types"))
w.Header().Add("Content-Type", string(bt.Get([]byte(key))))
w.Write(ba.Get([]byte(key)))
return nil
})
}
func ServeAttachments() {
r := mux.NewRouter()
r.HandleFunc("/{key}", AttachmentHandler)
http.Handle("/", r)
http.ListenAndServe("127.0.0.1:8000", r)
}

113
cmd/serve/serve.go Normal file
View File

@ -0,0 +1,113 @@
package serve
import (
"context"
"flag"
"time"
"git.tcp.direct/hgc/mailhole/config"
"github.com/boltdb/bolt"
"github.com/emersion/go-smtp"
"github.com/google/subcommands"
"github.com/lrstanley/girc"
"github.com/rs/zerolog/log"
)
type ServeCommand struct {
}
func (*ServeCommand) Name() string { return "serve" }
func (*ServeCommand) Synopsis() string { return "Serve mailhole services" }
func (*ServeCommand) Usage() string {
return `serve:
Serves SMTP server & web UI as per config`
}
func (p *ServeCommand) SetFlags(f *flag.FlagSet) {}
var t *config.MailholeConfig
var ircClients map[string]*girc.Client
var attachmentDb *bolt.DB
func (p *ServeCommand) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
// BoltDB is for storing mail attachments to disk to keep memory low
var err error
if attachmentDb, err = bolt.Open("attachment.db", 0600, nil); err != nil {
log.Fatal().Stack().Err(err).Msg("Failed to open attachment db")
}
// We don't care about the error - if it fails it will fatal
attachmentDb.Update(func(tx *bolt.Tx) error {
if _, err := tx.CreateBucket([]byte("attachments")); err != nil {
return err
}
if _, err := tx.CreateBucket([]byte("attachments_types")); err != nil {
return err
}
return nil
})
receivedEmails = map[string][]*ReceivedEmail{}
ircClients = map[string]*girc.Client{}
t = config.LoadConfig()
for k, v := range t.IRC {
log.Debug().Str("name", k).Msg("Constructing girc config")
ircClients[k] = girc.New(girc.Config{
Server: v.Server,
ServerPass: v.ServerPass,
Port: v.Port,
Nick: v.Nick,
User: v.User,
Name: v.Name,
SSL: v.SSL,
AllowFlood: v.AllowFlood,
})
}
for k, v := range ircClients {
log.Info().Str("name", k).Msg("Connecting IRC client")
go v.Connect()
v.Handlers.Add(girc.CONNECTED, func(c *girc.Client, e girc.Event) {
joinChan := t.IRC[k].Channel
log.Info().Str("name", k).Str("chan", joinChan).Msg("Connected, joining channel")
c.Cmd.Join(joinChan)
})
}
log.Info().Msg("Main thread now serving")
// Attachments: Serve plain
go ServeAttachments()
// SMTP: Initialization
be := &Backend{}
// SMTP: Serve plain
if t.SMTP.PlainEnabled {
s := smtp.NewServer(be)
s.Addr = t.SMTP.PlainBind
s.Domain = t.SMTP.Domain
s.ReadTimeout = 10 * time.Second
s.WriteTimeout = 10 * time.Second
s.MaxMessageBytes = 1024 * 1024
s.MaxRecipients = 50
s.AllowInsecureAuth = true
log.Info().Str("PlainBind", s.Addr).Msg("Starting SMTP server")
go func() {
if err := s.ListenAndServe(); err != nil {
log.Error().Stack().Err(err).Msg("Failed to serve plain SMTP server")
}
}()
}
// SMTP: Serve TLS
//SMTP serve goes here
for {
time.Sleep(5 * time.Second)
}
return subcommands.ExitSuccess
}

167
cmd/serve/smtp.go Normal file
View File

@ -0,0 +1,167 @@
package serve
import (
"bytes"
"errors"
"io"
"io/ioutil"
"strings"
"github.com/boltdb/bolt"
"github.com/emersion/go-smtp"
"github.com/jhillyerd/enmime"
"github.com/lrstanley/girc"
"github.com/rs/zerolog/log"
"git.tcp.direct/hgc/mailhole/utils"
)
// The Backend implements SMTP server methods.
type Backend struct{}
func (bkd *Backend) NewSession(_ *smtp.Conn) (smtp.Session, error) {
return &Session{}, nil
}
// A Session is returned after EHLO.
type Session struct {
// Store the From & To addresses when they come through in this session,
// so they can be queued up with Data
From string
To []string
}
func (s *Session) AuthPlain(username, password string) error {
return errors.New("Server does not support authentication")
}
type ReceivedEmail struct {
From string
Data []byte
HTMLKey []byte
TextKey []byte
AttachmentKeys [][]byte
}
var receivedEmails map[string][]*ReceivedEmail
func (s *Session) Mail(from string, opts *smtp.MailOptions) error {
s.From = from
return nil
}
func (s *Session) Rcpt(to string) error {
s.To = append(s.To, to)
return nil
}
func (s *Session) Data(r io.Reader) error {
if b, err := ioutil.ReadAll(r); err != nil {
return err
} else {
q := &ReceivedEmail{
From: s.From,
Data: b,
AttachmentKeys: [][]byte{},
}
// Parse email
parsedMsg, err := enmime.ReadEnvelope(bytes.NewReader(b))
if err != nil {
log.Error().Stack().Err(err).Msg("Error parsing incoming message")
return err
}
subj := parsedMsg.GetHeader("Subject")
log.Info().Str("subject", subj).Str("from", s.From).Strs("to", s.To).Msg("Received")
// Parse attachments & put in attachment bucket
attachmentDb.Update(func(tx *bolt.Tx) error {
ba := tx.Bucket([]byte("attachments"))
bt := tx.Bucket([]byte("attachments_types"))
if len(parsedMsg.Text) > 0 {
q.TextKey = utils.RandByteRunes(16)
ba.Put(q.TextKey, []byte(parsedMsg.Text))
bt.Put(q.TextKey, []byte("text/plain"))
log.Info().Str("type", "text").Bytes("key", q.TextKey).Msg("Stored an attachment")
}
if len(parsedMsg.HTML) > 0 {
q.HTMLKey = utils.RandByteRunes(16)
ba.Put(q.HTMLKey, []byte(parsedMsg.HTML))
bt.Put(q.TextKey, []byte("text/html"))
log.Info().Str("type", "HTML").Bytes("key", q.HTMLKey).Msg("Stored an attachment")
}
for _, v := range parsedMsg.Attachments {
key := utils.RandByteRunes(16)
ba.Put(key, v.Content)
bt.Put(key, []byte(v.ContentType))
q.AttachmentKeys = append(q.AttachmentKeys, key)
log.Info().Str("type", v.ContentType).Bytes("key", key).Msg("Stored an attachment")
}
return err
})
attachmentDb.View(func(tx *bolt.Tx) error {
bt := tx.Bucket([]byte("attachments_types"))
for _, addr := range s.To {
if !strings.Contains(addr, "@") {
continue
}
// Add to the list of received emails for display in the web ui
receivedEmails[addr] = append(receivedEmails[addr], q)
// Get mailDomain
mailDomain := strings.Split(addr, "@")[1]
for _, x := range t.Domains {
if strings.ToLower(mailDomain) == strings.ToLower(x.Domain) {
msgs := formatNewMail(s.From, addr, subj, q.HTMLKey, q.TextKey, q.AttachmentKeys, bt)
for _, msg := range msgs {
ircClients[x.IRC].Cmd.Message(
t.IRC[x.IRC].Channel,
msg,
)
}
}
}
}
return nil
})
}
return nil
}
func formatNewMail(from string, to string, subject string, htmlKey []byte, textKey []byte, attachments [][]byte, bt *bolt.Bucket) []string {
msgs := []string{}
msgs = append(msgs, "+ "+girc.Fmt("{red}")+from+girc.Fmt("{c}")+" -> "+girc.Fmt("{green}")+to+girc.Fmt("{c}")+" "+subject)
if string(htmlKey) != "" {
msgs = append(msgs, "| HTML: "+t.WebUIBase+`/`+string(htmlKey))
}
if string(textKey) != "" {
msgs = append(msgs, "| Text: "+t.WebUIBase+`/`+string(textKey))
}
for _, a := range attachments {
msgs = append(msgs, "+ Attachment: "+t.WebUIBase+`/`+string(a)+` (`+string(bt.Get(a))+`)`)
}
msgs = append(msgs, "+---")
return msgs
}
func (s *Session) Reset() {}
func (s *Session) Logout() error {
return nil
}

23
cmd/systemd/systemd.go Normal file
View File

@ -0,0 +1,23 @@
package systemd
import (
"context"
"flag"
"github.com/google/subcommands"
)
type SystemdCommand struct {
}
func (*SystemdCommand) Name() string { return "systemd" }
func (*SystemdCommand) Synopsis() string { return "Generate a systemd unit file for mailhole." }
func (*SystemdCommand) Usage() string {
return `systemd:
Generate a systemd unit file for mailhole.`
}
func (p *SystemdCommand) SetFlags(f *flag.FlagSet) {}
func (p *SystemdCommand) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
return subcommands.ExitSuccess
}

137
config/config.go Normal file
View File

@ -0,0 +1,137 @@
package config
import (
"encoding/json"
"os"
"github.com/rs/zerolog/log"
)
// girc.Config can't be marshalled by JSON so we have to recreate our own
type IRCConfig struct {
Server string
ServerPass string
Port int
Nick string
User string
Name string
SSL bool
AllowFlood bool
// Not part of GIRC config
Channel string
}
type DomainConfig struct {
Domain string
IRCEnabled bool
IRC string
}
type SMTPConfig struct {
Domain string
PlainEnabled bool
PlainBind string
TLSEnabled bool
TLSBind string
TLSCert string
TLSKey string
}
type MailholeConfig struct {
IRC map[string]*IRCConfig
Domains []*DomainConfig
SMTP *SMTPConfig
WebUIBase string
}
const CONFIG_PATH = `/etc/mailhole/mailhole.json`
func LoadConfig() *MailholeConfig {
// We do the error handling inside the config loader so we don't have to
// do it for each subcommand
if _, err := os.Stat(CONFIG_PATH); os.IsNotExist(err) {
log.Error().Msg("You need to create a config file for Mailhole to read.")
log.Error().Msg("Get an example: ./mailhole firstrun > /etc/mailhole/mailhole.json")
log.Fatal().Stack().Err(err).Msg("Config file does not exist")
} else {
var configBytes []byte
if configBytes, err = os.ReadFile(CONFIG_PATH); err != nil {
log.Fatal().Stack().Err(err).Msg("Could not open config file")
}
uC := &MailholeConfig{}
if err := json.Unmarshal(configBytes, uC); err != nil {
log.Fatal().Err(err).Msg("Could not unmarshal config file")
}
// Sanity checks
// 1. Do we have any domains with IRC servers that don't exist
for _, v := range uC.Domains {
if !v.IRCEnabled {
continue
}
if _, ok := uC.IRC[v.IRC]; !ok {
log.Fatal().
Str("domain", v.Domain).
Str("irc", v.IRC).
Msg("Domain failed sanity check. Named IRC does not exist in config.")
}
}
return uC
}
return nil
}
func ExampleConfig() []byte {
t := MailholeConfig{
IRC: map[string]*IRCConfig{
"ircname": {
Server: "irc.yourirc.com",
ServerPass: "Password",
Port: 6697,
Nick: "mailhole",
User: "mailhole",
Name: "mailhole",
SSL: true,
AllowFlood: true,
Channel: "#mailhole",
},
},
Domains: []*DomainConfig{
{
Domain: "yourmaildomain.com",
IRC: "ircname",
IRCEnabled: true,
},
{
Domain: "yoursecondmaildomain.com",
IRC: "ircname",
IRCEnabled: true,
},
},
SMTP: &SMTPConfig{
Domain: "mail.yourmaildomain.com",
PlainEnabled: true,
PlainBind: "127.0.0.1:25",
TLSEnabled: true,
TLSBind: "127.0.0.1:587",
TLSCert: "/etc/letsencrypt/live/mail.yourmaildomain.com/fullchain.pem",
TLSKey: "/etc/letsencrypt/live/mail.yourmaildomain.com/privkey.pem",
},
WebUIBase: "http://127.0.0.1:8000",
}
if b, err := json.MarshalIndent(t, "", " "); err != nil {
log.Error().Stack().Err(err).Msg("Failed to marshal config")
return []byte(``)
} else {
return b
}
}

30
go.mod Normal file
View File

@ -0,0 +1,30 @@
module git.tcp.direct/hgc/mailhole
go 1.20
require (
github.com/boltdb/bolt v1.3.1
github.com/emersion/go-smtp v0.16.0
github.com/google/subcommands v1.2.0
github.com/gorilla/mux v1.8.0
github.com/jhillyerd/enmime v0.11.1
github.com/lrstanley/girc v0.0.0-20230507010347-3c8fa7715074
github.com/rs/zerolog v1.29.1
)
require (
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect
github.com/gogs/chardet v0.0.0-20191104214054-4b6791f73a28 // indirect
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.12 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/text v0.8.0 // indirect
)

54
go.sum Normal file
View File

@ -0,0 +1,54 @@
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a h1:MISbI8sU/PSK/ztvmWKFcI7UGb5/HQT7B+i3a2myKgI=
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXOlEPAMFPfp014mK1SWq8G8BN8o7/dfYqJrVGn8=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
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.16.0 h1:eB9CY9527WdEZSs5sWisTmilDX7gG+Q/2IdRcmubpa8=
github.com/emersion/go-smtp v0.16.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogs/chardet v0.0.0-20191104214054-4b6791f73a28 h1:gBeyun7mySAKWg7Fb0GOcv0upX9bdaZScs8QcRo8mEY=
github.com/gogs/chardet v0.0.0-20191104214054-4b6791f73a28/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7 h1:g0fAGBisHaEQ0TRq1iBvemFRf+8AEWEmBESSiWB3Vsc=
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
github.com/jhillyerd/enmime v0.11.1 h1:U6ToGVxfxNQQhKrAaGxtwOf7Zqksb8AQ3j1CyAWOk5k=
github.com/jhillyerd/enmime v0.11.1/go.mod h1:EktNOa/V6ka9yCrfoB2uxgefp1lno6OVdszW0iQ5LnM=
github.com/lrstanley/girc v0.0.0-20230507010347-3c8fa7715074 h1:Uuzmwjx9dzTIfh9bdK9l5y9DQgpmgEFaifJGB5jHxb4=
github.com/lrstanley/girc v0.0.0-20230507010347-3c8fa7715074/go.mod h1:lgrnhcF8bg/Bd5HA5DOb4Z+uGqUqGnp4skr+J2GwVgI=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.12 h1:Y41i/hVW3Pgwr8gV+J23B9YEY0zxjptBuCWEaxmAOow=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc=
github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU=
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=

34
main.go Normal file
View File

@ -0,0 +1,34 @@
package main
import (
"context"
"flag"
"os"
"github.com/google/subcommands"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/rs/zerolog/pkgerrors"
"git.tcp.direct/hgc/mailhole/cmd/firstrun"
"git.tcp.direct/hgc/mailhole/cmd/serve"
"git.tcp.direct/hgc/mailhole/cmd/systemd"
)
func main() {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
log.Logger = log.With().Caller().Logger()
zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
subcommands.Register(subcommands.HelpCommand(), "")
subcommands.Register(subcommands.FlagsCommand(), "")
subcommands.Register(subcommands.CommandsCommand(), "")
subcommands.Register(&firstrun.FirstrunCommand{}, "")
subcommands.Register(&systemd.SystemdCommand{}, "")
subcommands.Register(&serve.ServeCommand{}, "")
flag.Parse()
ctx := context.Background()
os.Exit(int(subcommands.Execute(ctx)))
}

20
utils/utils.go Normal file
View File

@ -0,0 +1,20 @@
package utils
import (
"math/rand"
"time"
)
func init() {
rand.Seed(time.Now().UnixNano())
}
var letterRunes = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
func RandByteRunes(n int) []byte {
b := make([]byte, n)
for i := range b {
b[i] = letterRunes[rand.Intn(len(letterRunes))]
}
return b
}