Compare commits
1 Commits
master
...
9b1df155ec
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b1df155ec |
@@ -1,6 +1,6 @@
|
||||
module gitea.inhome.blapointe.com/contact/cmd/recv
|
||||
|
||||
go 1.24.0
|
||||
go 1.20
|
||||
|
||||
replace gitea.inhome.blapointe.com/local-sandbox/contact => ../../
|
||||
|
||||
@@ -10,12 +10,9 @@ 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/emersion/go-imap v1.2.0 // indirect
|
||||
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
golang.org/x/oauth2 v0.35.0 // indirect
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
@@ -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/go.mod h1:SqCOE3bE3wvrztVIQGHuyxHKfDjRKU9EWhBdkmkiwyc=
|
||||
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-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-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/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.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"net/mail"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -14,6 +16,32 @@ import (
|
||||
"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() {
|
||||
emailer := contact.NewEmailer()
|
||||
as := args.NewArgSet()
|
||||
@@ -24,8 +52,6 @@ func main() {
|
||||
as.Append(args.STRING, "p", "password", emailer.Password)
|
||||
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.STRING, "oauth", "", emailer.OAuth)
|
||||
as.Append(args.STRING, "inbox", "", emailer.Inbox)
|
||||
if err := as.Parse(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -35,8 +61,6 @@ func main() {
|
||||
emailer.Limit = as.Get("n").GetInt()
|
||||
emailer.From = as.GetString("u")
|
||||
emailer.Password = as.GetString("p")
|
||||
emailer.OAuth = as.GetString("oauth")
|
||||
emailer.Inbox = as.GetString("inbox")
|
||||
|
||||
var msgs <-chan *mail.Message
|
||||
var err error
|
||||
@@ -51,7 +75,17 @@ func main() {
|
||||
}
|
||||
emails := make([]map[string]any, 0, emailer.Limit)
|
||||
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(s), "\n", "\n")
|
||||
s = strings.ReplaceAll(string(s), "\r", "\n")
|
||||
|
||||
BIN
cmd/recv/recv
Executable file
BIN
cmd/recv/recv
Executable file
Binary file not shown.
123
email.go
123
email.go
@@ -1,30 +1,17 @@
|
||||
package contact
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/mail"
|
||||
"net/smtp"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bytbox/go-pop3"
|
||||
"github.com/emersion/go-imap"
|
||||
"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 {
|
||||
@@ -34,8 +21,6 @@ type Emailer struct {
|
||||
IMAP string
|
||||
Password string
|
||||
Limit int
|
||||
OAuth string
|
||||
Inbox string
|
||||
}
|
||||
|
||||
func NewEmailer() *Emailer {
|
||||
@@ -49,8 +34,6 @@ func NewEmailer() *Emailer {
|
||||
From: envOr("FROM", "breellocaldev@gmail.com"),
|
||||
SMTP: envOr("SMTP", "smtp.gmail.com:465"),
|
||||
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) {
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if e.OAuth == "" {
|
||||
if err := c.Login(authU, authP); err != nil {
|
||||
if err := c.Login(e.From, e.Password); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
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
|
||||
mbox, err := c.Select("INBOX", true) //readonly
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
5
go.mod
5
go.mod
@@ -1,16 +1,13 @@
|
||||
module gitea.inhome.blapointe.com/local-sandbox/contact
|
||||
|
||||
go 1.24.0
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/bytbox/go-pop3 v0.0.0-20120201222208-3046caf0763e
|
||||
github.com/emersion/go-imap v1.2.0
|
||||
github.com/google/uuid v1.6.0
|
||||
golang.org/x/oauth2 v0.35.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute/metadata v0.3.0 // indirect
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
)
|
||||
|
||||
6
go.sum
6
go.sum
@@ -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/go.mod h1:alXX+s7a4cKaIprgjeEboqi4Tm7XR/HXEwUTxUV/ywU=
|
||||
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/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||
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.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
|
||||
Reference in New Issue
Block a user