Compare commits

1 Commits

Author SHA1 Message Date
Bel LaPointe
9b1df155ec multipart 2024-10-03 07:08:43 -06:00
7 changed files with 45 additions and 150 deletions

View File

@@ -1,6 +1,6 @@
module gitea.inhome.blapointe.com/contact/cmd/recv module gitea.inhome.blapointe.com/contact/cmd/recv
go 1.24.0 go 1.20
replace gitea.inhome.blapointe.com/local-sandbox/contact => ../../ replace gitea.inhome.blapointe.com/local-sandbox/contact => ../../
@@ -10,12 +10,9 @@ require (
) )
require ( require (
cloud.google.com/go/compute/metadata v0.3.0 // indirect
github.com/bytbox/go-pop3 v0.0.0-20120201222208-3046caf0763e // indirect github.com/bytbox/go-pop3 v0.0.0-20120201222208-3046caf0763e // indirect
github.com/emersion/go-imap v1.2.0 // indirect github.com/emersion/go-imap v1.2.0 // indirect
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect
github.com/google/uuid v1.6.0 // indirect
golang.org/x/oauth2 v0.35.0 // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/text v0.3.7 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
) )

View File

@@ -1,5 +1,3 @@
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
gitea.inhome.blapointe.com/local/args v0.0.0-20231109145953-eb2e1c1b8d56 h1:zTGGZ77KLFagqUvDSgTOnm0qF+iSLwQWiEtGjb2jjlY= gitea.inhome.blapointe.com/local/args v0.0.0-20231109145953-eb2e1c1b8d56 h1:zTGGZ77KLFagqUvDSgTOnm0qF+iSLwQWiEtGjb2jjlY=
gitea.inhome.blapointe.com/local/args v0.0.0-20231109145953-eb2e1c1b8d56/go.mod h1:SqCOE3bE3wvrztVIQGHuyxHKfDjRKU9EWhBdkmkiwyc= gitea.inhome.blapointe.com/local/args v0.0.0-20231109145953-eb2e1c1b8d56/go.mod h1:SqCOE3bE3wvrztVIQGHuyxHKfDjRKU9EWhBdkmkiwyc=
github.com/bytbox/go-pop3 v0.0.0-20120201222208-3046caf0763e h1:mQTN05gz0rDZSABqKMzAPMb5ATWcvvdMljRzEh0LjBo= github.com/bytbox/go-pop3 v0.0.0-20120201222208-3046caf0763e h1:mQTN05gz0rDZSABqKMzAPMb5ATWcvvdMljRzEh0LjBo=
@@ -9,13 +7,7 @@ github.com/emersion/go-imap v1.2.0/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5N
github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4= github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4=
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ= 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-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 h1:oP4q0fw+fOSWn3DfFi4EXdT+B+gTtzx8GC9xsc26Znk=
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U= github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=

View File

@@ -5,6 +5,8 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"mime"
"mime/multipart"
"net/mail" "net/mail"
"os" "os"
"strings" "strings"
@@ -14,6 +16,32 @@ import (
"gitea.inhome.blapointe.com/local/args" "gitea.inhome.blapointe.com/local/args"
) )
type MultipartReaderReader struct {
buff []byte
r *multipart.Reader
}
func (mrr *MultipartReaderReader) Read(b []byte) (int, error) {
n := copy(b, mrr.buff)
mrr.buff = mrr.buff[n:]
for n < len(b) {
part, err := mrr.r.NextPart()
if err != nil {
return n, err
}
defer part.Close()
mrr.buff, err = io.ReadAll(part)
if err != nil {
return n, err
}
m := copy(b, mrr.buff)
mrr.buff = mrr.buff[m:]
n += m
}
return n, nil
}
func main() { func main() {
emailer := contact.NewEmailer() emailer := contact.NewEmailer()
as := args.NewArgSet() as := args.NewArgSet()
@@ -24,8 +52,6 @@ func main() {
as.Append(args.STRING, "p", "password", emailer.Password) as.Append(args.STRING, "p", "password", emailer.Password)
as.Append(args.INT, "b", "dont read more than this many characters", 4096) as.Append(args.INT, "b", "dont read more than this many characters", 4096)
as.Append(args.BOOL, "json", "output as json", false) as.Append(args.BOOL, "json", "output as json", false)
as.Append(args.STRING, "oauth", "", emailer.OAuth)
as.Append(args.STRING, "inbox", "", emailer.Inbox)
if err := as.Parse(); err != nil { if err := as.Parse(); err != nil {
panic(err) panic(err)
} }
@@ -35,8 +61,6 @@ func main() {
emailer.Limit = as.Get("n").GetInt() emailer.Limit = as.Get("n").GetInt()
emailer.From = as.GetString("u") emailer.From = as.GetString("u")
emailer.Password = as.GetString("p") emailer.Password = as.GetString("p")
emailer.OAuth = as.GetString("oauth")
emailer.Inbox = as.GetString("inbox")
var msgs <-chan *mail.Message var msgs <-chan *mail.Message
var err error var err error
@@ -51,7 +75,17 @@ func main() {
} }
emails := make([]map[string]any, 0, emailer.Limit) emails := make([]map[string]any, 0, emailer.Limit)
for msg := range msgs { for msg := range msgs {
b, _ := ioutil.ReadAll(io.LimitReader(msg.Body, int64(as.GetInt("b")))) var r io.Reader
if n := int64(as.GetInt("b")); n >= 0 {
r = io.LimitReader(msg.Body, n)
} else if mediaType, params, _ := mime.ParseMediaType(msg.Header.Get("Content-Type")); strings.HasPrefix(mediaType, "multipart/") {
r = &MultipartReaderReader{
r: multipart.NewReader(msg.Body, params["boundary"]),
}
} else {
r = msg.Body
}
b, _ := ioutil.ReadAll(r)
s := strings.ReplaceAll(string(b), "\r\n", "\n") s := strings.ReplaceAll(string(b), "\r\n", "\n")
s = strings.ReplaceAll(string(s), "\n", "\n") s = strings.ReplaceAll(string(s), "\n", "\n")
s = strings.ReplaceAll(string(s), "\r", "\n") s = strings.ReplaceAll(string(s), "\r", "\n")

BIN
cmd/recv/recv Executable file

Binary file not shown.

123
email.go
View File

@@ -1,30 +1,17 @@
package contact package contact
import ( import (
"bytes"
"context"
"crypto/sha256"
"crypto/tls" "crypto/tls"
"encoding/base64"
"encoding/json"
"fmt" "fmt"
"io"
"log" "log"
"net/http"
"net/mail" "net/mail"
"net/smtp" "net/smtp"
"net/url"
"os" "os"
"strings" "strings"
"time"
"github.com/bytbox/go-pop3" "github.com/bytbox/go-pop3"
"github.com/emersion/go-imap" "github.com/emersion/go-imap"
"github.com/emersion/go-imap/client" "github.com/emersion/go-imap/client"
"github.com/emersion/go-sasl"
"github.com/google/uuid"
"golang.org/x/oauth2/authhandler"
"golang.org/x/oauth2/google"
) )
type Emailer struct { type Emailer struct {
@@ -34,8 +21,6 @@ type Emailer struct {
IMAP string IMAP string
Password string Password string
Limit int Limit int
OAuth string
Inbox string
} }
func NewEmailer() *Emailer { func NewEmailer() *Emailer {
@@ -49,8 +34,6 @@ func NewEmailer() *Emailer {
From: envOr("FROM", "breellocaldev@gmail.com"), From: envOr("FROM", "breellocaldev@gmail.com"),
SMTP: envOr("SMTP", "smtp.gmail.com:465"), SMTP: envOr("SMTP", "smtp.gmail.com:465"),
Password: envOr("PASSWORD", "lhnjijrvqaesiufp"), Password: envOr("PASSWORD", "lhnjijrvqaesiufp"),
OAuth: envOr("OAUTH", ""),
Inbox: envOr("INBOX", "INBOX"),
} }
} }
@@ -59,116 +42,14 @@ func (e *Emailer) Read() (chan *mail.Message, error) {
} }
func (e *Emailer) ReadIMAP() (chan *mail.Message, error) { func (e *Emailer) ReadIMAP() (chan *mail.Message, error) {
if e.OAuth != "" {
return e.oauthThenReadIMAP()
}
return e.readIMAP(e.From, e.Password)
}
func (e *Emailer) oauthThenReadIMAP() (chan *mail.Message, error) {
configJSON, err := e.configJSON()
if err != nil {
return nil, err
}
if token, _ := os.ReadFile(e.OAuth + ".token"); len(token) == 0 {
state := uuid.NewString()
verifier := uuid.NewString()
s256 := sha256.Sum256([]byte(verifier))
challenge := base64.RawURLEncoding.EncodeToString(s256[:])
pkceParams := authhandler.PKCEParams{Challenge: challenge, ChallengeMethod: "S256", Verifier: verifier}
config, err := google.ConfigFromJSON([]byte(configJSON), "https://mail.google.com/")
if err != nil {
return nil, err
}
credentialsParams := google.CredentialsParams{Scopes: config.Scopes, State: state, AuthHandler: func(authCodeURL string) (string, string, error) {
fmt.Println(authCodeURL + "&access_type=offline&prompt=consent")
var callback string
_, err := fmt.Scan(&callback)
u, _ := url.Parse(callback)
fmt.Println()
return u.Query().Get("code"), state, err
}, PKCE: &pkceParams}
credentials, err := google.CredentialsFromJSONWithParams(context.Background(), []byte(configJSON), credentialsParams)
if err != nil {
return nil, err
}
token, err := credentials.TokenSource.Token()
if err != nil {
return nil, err
}
tokenFileJSON, _ := json.Marshal(token)
os.WriteFile(e.OAuth+".token", tokenFileJSON, os.ModePerm)
log.Printf("%s", tokenFileJSON)
}
var tokenStruct struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
Expiry time.Time `json:"expiry"`
ExpiresIn int `json:"expires_in"`
}
token, _ := os.ReadFile(e.OAuth + ".token")
json.Unmarshal(token, &tokenStruct)
if time.Until(tokenStruct.Expiry) < time.Minute*5 {
var appStruct struct {
Installed struct {
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
GrantType string `json:"grant_type"`
RefreshToken string `json:"refresh_token"`
} `json:"installed"`
}
json.Unmarshal([]byte(configJSON), &appStruct)
appStruct.Installed.GrantType = "refresh_token"
appStruct.Installed.RefreshToken = tokenStruct.RefreshToken
b, _ := json.Marshal(appStruct.Installed)
resp, err := http.Post(`https://oauth2.googleapis.com/token`, `application/json`, bytes.NewReader(b))
if err != nil {
return nil, err
}
defer resp.Body.Close()
b, _ = io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("(%d) %s", resp.StatusCode, b)
}
tokenStruct.Expiry = time.Time{}
json.Unmarshal(b, &tokenStruct)
tokenStruct.Expiry = time.Now().Add(time.Duration(tokenStruct.ExpiresIn) * time.Second)
tokenStruct.RefreshToken = appStruct.Installed.RefreshToken
b, _ = json.Marshal(tokenStruct)
os.WriteFile(e.OAuth+".token", b, os.ModePerm)
}
log.Println("OAUTH", e.From, time.Until(tokenStruct.Expiry), tokenStruct.AccessToken)
return e.readIMAP(e.From, tokenStruct.AccessToken)
}
func (e *Emailer) configJSON() (string, error) {
b, err := os.ReadFile(e.OAuth)
return string(b), err
}
func (e *Emailer) readIMAP(authU, authP string) (chan *mail.Message, error) {
c, err := client.DialTLS(e.IMAP, nil) c, err := client.DialTLS(e.IMAP, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if e.OAuth == "" { if err := c.Login(e.From, e.Password); err != nil {
if err := c.Login(authU, authP); err != nil {
return nil, err return nil, err
} }
} else { mbox, err := c.Select("INBOX", true) //readonly
if err := c.Authenticate(sasl.NewOAuthBearerClient(&sasl.OAuthBearerOptions{
Username: authU,
Token: authP,
Host: "",
Port: 0,
})); err != nil {
return nil, err
}
}
mbox, err := c.Select(e.Inbox, true) //readonly
if err != nil { if err != nil {
return nil, err return nil, err
} }

5
go.mod
View File

@@ -1,16 +1,13 @@
module gitea.inhome.blapointe.com/local-sandbox/contact module gitea.inhome.blapointe.com/local-sandbox/contact
go 1.24.0 go 1.17
require ( require (
github.com/bytbox/go-pop3 v0.0.0-20120201222208-3046caf0763e github.com/bytbox/go-pop3 v0.0.0-20120201222208-3046caf0763e
github.com/emersion/go-imap v1.2.0 github.com/emersion/go-imap v1.2.0
github.com/google/uuid v1.6.0
golang.org/x/oauth2 v0.35.0
) )
require ( require (
cloud.google.com/go/compute/metadata v0.3.0 // indirect
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/text v0.3.7 // indirect
) )

6
go.sum
View File

@@ -1,5 +1,3 @@
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
github.com/bytbox/go-pop3 v0.0.0-20120201222208-3046caf0763e h1:mQTN05gz0rDZSABqKMzAPMb5ATWcvvdMljRzEh0LjBo= github.com/bytbox/go-pop3 v0.0.0-20120201222208-3046caf0763e h1:mQTN05gz0rDZSABqKMzAPMb5ATWcvvdMljRzEh0LjBo=
github.com/bytbox/go-pop3 v0.0.0-20120201222208-3046caf0763e/go.mod h1:alXX+s7a4cKaIprgjeEboqi4Tm7XR/HXEwUTxUV/ywU= github.com/bytbox/go-pop3 v0.0.0-20120201222208-3046caf0763e/go.mod h1:alXX+s7a4cKaIprgjeEboqi4Tm7XR/HXEwUTxUV/ywU=
github.com/emersion/go-imap v1.2.0 h1:lyUQ3+EVM21/qbWE/4Ya5UG9r5+usDxlg4yfp3TgHFA= github.com/emersion/go-imap v1.2.0 h1:lyUQ3+EVM21/qbWE/4Ya5UG9r5+usDxlg4yfp3TgHFA=
@@ -8,10 +6,6 @@ github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwd
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ= 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-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U= github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=