hydroxide-push/cmd/hydroxide/hydroxide.go

268 lines
6.0 KiB
Go
Raw Normal View History

2017-08-22 01:04:16 +03:00
package main
import (
"bufio"
2017-09-09 16:37:03 +03:00
"flag"
2017-08-22 01:04:16 +03:00
"fmt"
2017-09-09 16:37:03 +03:00
"io"
2017-08-22 01:04:16 +03:00
"log"
2017-09-03 21:11:01 +03:00
"net/http"
2017-08-22 01:04:16 +03:00
"os"
2018-01-12 21:01:08 +02:00
imapmove "github.com/emersion/go-imap-move"
imapspacialuse "github.com/emersion/go-imap-specialuse"
2018-10-21 13:15:20 +03:00
imapserver "github.com/emersion/go-imap/server"
2017-12-02 17:23:06 +02:00
"github.com/emersion/go-smtp"
"github.com/howeyc/gopass"
2019-04-23 00:03:20 +03:00
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/armor"
2017-12-02 17:23:06 +02:00
"github.com/emersion/hydroxide/auth"
2017-09-03 21:11:01 +03:00
"github.com/emersion/hydroxide/carddav"
2018-01-11 19:17:11 +02:00
"github.com/emersion/hydroxide/events"
2017-12-03 15:58:24 +02:00
imapbackend "github.com/emersion/hydroxide/imap"
2018-10-21 13:15:20 +03:00
"github.com/emersion/hydroxide/protonmail"
2017-12-02 17:23:06 +02:00
smtpbackend "github.com/emersion/hydroxide/smtp"
2017-08-22 01:04:16 +03:00
)
2017-09-09 16:37:03 +03:00
func newClient() *protonmail.Client {
return &protonmail.Client{
2017-08-22 11:16:20 +03:00
RootURL: "https://dev.protonmail.com/api",
2019-05-12 21:50:02 +03:00
AppVersion: "Web_3.15.34",
2017-08-22 11:16:20 +03:00
ClientID: "Web",
2017-08-22 01:04:16 +03:00
ClientSecret: "4957cc9a2e0a2a49d02475c9d013478d",
}
2017-09-09 16:37:03 +03:00
}
2017-08-22 01:04:16 +03:00
2017-09-09 16:37:03 +03:00
func main() {
flag.Parse()
2019-04-23 00:06:49 +03:00
cmd := flag.Arg(0)
switch cmd {
2017-09-09 16:37:03 +03:00
case "auth":
username := flag.Arg(1)
2019-04-23 00:03:20 +03:00
if username == "" {
log.Fatal("usage: hydroxide auth <username>")
}
2017-09-09 16:37:03 +03:00
c := newClient()
var a *protonmail.Auth
2017-09-09 16:37:03 +03:00
/*if cachedAuth, ok := auths[username]; ok {
var err error
a, err = c.AuthRefresh(a)
2017-09-09 16:37:03 +03:00
if err != nil {
// TODO: handle expired token error
log.Fatal(err)
}
}*/
var loginPassword string
if a == nil {
2017-09-09 16:37:03 +03:00
fmt.Printf("Password: ")
if pass, err := gopass.GetPasswd(); err != nil {
log.Fatal(err)
} else {
loginPassword = string(pass)
}
2017-09-09 16:37:03 +03:00
authInfo, err := c.AuthInfo(username)
if err != nil {
log.Fatal(err)
}
var twoFactorCode string
if authInfo.TwoFactor == 1 {
scanner := bufio.NewScanner(os.Stdin)
2017-09-09 16:37:03 +03:00
fmt.Printf("2FA code: ")
scanner.Scan()
twoFactorCode = scanner.Text()
}
a, err = c.Auth(username, loginPassword, twoFactorCode, authInfo)
2017-09-09 16:37:03 +03:00
if err != nil {
log.Fatal(err)
}
2017-08-22 13:07:31 +03:00
}
2017-08-22 11:51:48 +03:00
2017-09-09 16:37:03 +03:00
var mailboxPassword string
if a.PasswordMode == protonmail.PasswordSingle {
2017-09-09 16:37:03 +03:00
mailboxPassword = loginPassword
}
if mailboxPassword == "" {
if a.PasswordMode == protonmail.PasswordTwo {
2017-09-09 16:37:03 +03:00
fmt.Printf("Mailbox password: ")
} else {
fmt.Printf("Password: ")
}
if pass, err := gopass.GetPasswd(); err != nil {
log.Fatal(err)
} else {
mailboxPassword = string(pass)
}
2017-09-09 16:37:03 +03:00
}
2017-08-22 01:04:16 +03:00
_, err := c.Unlock(a, mailboxPassword)
2017-08-22 13:07:31 +03:00
if err != nil {
log.Fatal(err)
}
secretKey, bridgePassword, err := auth.GeneratePassword()
if err != nil {
2017-09-09 16:37:03 +03:00
log.Fatal(err)
2017-08-22 13:07:31 +03:00
}
err = auth.EncryptAndSave(&auth.CachedAuth{
*a,
2017-09-09 16:37:03 +03:00
loginPassword,
mailboxPassword,
}, username, secretKey)
2017-08-22 13:07:31 +03:00
if err != nil {
log.Fatal(err)
}
2017-08-22 10:41:47 +03:00
2017-09-09 16:37:03 +03:00
fmt.Println("Bridge password:", bridgePassword)
case "status":
usernames, err := auth.ListUsernames()
if err != nil {
log.Fatal(err)
}
if len(usernames) == 0 {
fmt.Printf("No logged in user.\n")
} else {
fmt.Printf("%v logged in user(s):\n", len(usernames))
for _, u := range usernames {
fmt.Printf("- %v\n", u)
}
}
2019-04-23 00:03:20 +03:00
case "export-secret-keys":
username := flag.Arg(1)
if username == "" {
log.Fatal("usage: hydroxide export-secret-keys <username>")
}
var bridgePassword string
fmt.Printf("Bridge password: ")
if pass, err := gopass.GetPasswd(); err != nil {
log.Fatal(err)
} else {
bridgePassword = string(pass)
}
_, privateKeys, err := auth.NewManager(newClient).Auth(username, bridgePassword)
if err != nil {
log.Fatal(err)
}
wc, err := armor.Encode(os.Stdout, openpgp.PrivateKeyType, nil)
if err != nil {
log.Fatal(err)
}
for _, key := range privateKeys {
if err := key.SerializePrivate(wc, nil); err != nil {
log.Fatal(err)
}
}
if err := wc.Close(); err != nil {
log.Fatal(err)
}
2017-12-02 17:23:06 +02:00
case "smtp":
port := os.Getenv("PORT")
if port == "" {
2017-12-03 15:58:24 +02:00
port = "1025"
2017-12-02 17:23:06 +02:00
}
sessions := auth.NewManager(newClient)
be := smtpbackend.New(sessions)
s := smtp.NewServer(be)
2017-12-03 12:55:59 +02:00
s.Addr = "127.0.0.1:" + port
s.Domain = "localhost" // TODO: make this configurable
2017-12-02 17:23:06 +02:00
s.AllowInsecureAuth = true // TODO: remove this
//s.Debug = os.Stdout
2017-12-02 17:23:06 +02:00
log.Println("SMTP server listening on", s.Addr)
2017-12-02 17:23:06 +02:00
log.Fatal(s.ListenAndServe())
2017-12-03 15:58:24 +02:00
case "imap":
port := os.Getenv("PORT")
if port == "" {
port = "1143"
}
sessions := auth.NewManager(newClient)
2018-01-12 14:20:17 +02:00
eventsManager := events.NewManager()
2017-12-03 15:58:24 +02:00
2018-01-12 14:20:17 +02:00
be := imapbackend.New(sessions, eventsManager)
2017-12-03 15:58:24 +02:00
s := imapserver.New(be)
s.Addr = "127.0.0.1:" + port
s.AllowInsecureAuth = true // TODO: remove this
2017-12-12 14:50:37 +02:00
//s.Debug = os.Stdout
s.Enable(imapspacialuse.NewExtension())
2018-01-12 21:01:08 +02:00
s.Enable(imapmove.NewExtension())
2017-12-03 15:58:24 +02:00
log.Println("IMAP server listening on", s.Addr)
2017-12-03 15:58:24 +02:00
log.Fatal(s.ListenAndServe())
2017-12-02 17:23:06 +02:00
case "carddav":
2017-09-09 17:13:26 +03:00
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
sessions := auth.NewManager(newClient)
2018-01-12 14:20:17 +02:00
eventsManager := events.NewManager()
handlers := make(map[string]http.Handler)
2017-08-22 11:51:48 +03:00
2017-09-09 16:37:03 +03:00
s := &http.Server{
2017-09-13 12:43:12 +03:00
Addr: "127.0.0.1:" + port,
2017-09-09 16:37:03 +03:00
Handler: http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
resp.Header().Set("WWW-Authenticate", "Basic")
2017-08-22 11:51:48 +03:00
2017-09-09 16:37:03 +03:00
username, password, ok := req.BasicAuth()
if !ok {
resp.WriteHeader(http.StatusUnauthorized)
2017-09-10 13:49:01 +03:00
io.WriteString(resp, "Credentials are required")
2017-09-09 16:37:03 +03:00
return
}
2017-09-03 21:11:01 +03:00
c, privateKeys, err := sessions.Auth(username, password)
if err != nil {
if err == auth.ErrUnauthorized {
2017-09-09 16:37:03 +03:00
resp.WriteHeader(http.StatusUnauthorized)
} else {
2017-09-09 16:37:03 +03:00
resp.WriteHeader(http.StatusInternalServerError)
}
io.WriteString(resp, err.Error())
return
}
2017-09-09 16:37:03 +03:00
h, ok := handlers[username]
if !ok {
2018-01-11 19:17:11 +02:00
ch := make(chan *protonmail.Event)
2018-01-12 14:20:17 +02:00
eventsManager.Register(c, username, ch, nil)
2018-01-11 19:17:11 +02:00
h = carddav.NewHandler(c, privateKeys, ch)
2017-09-09 16:37:03 +03:00
handlers[username] = h
2017-09-09 16:37:03 +03:00
}
h.ServeHTTP(resp, req)
}),
}
log.Println("CardDAV server listening on", s.Addr)
2017-09-09 16:37:03 +03:00
log.Fatal(s.ListenAndServe())
default:
2019-04-23 00:06:49 +03:00
log.Println("usage: hydroxide smtp")
log.Println("usage: hydroxide imap")
log.Println("usage: hydroxide carddav")
2019-04-23 00:06:49 +03:00
log.Println("usage: hydroxide auth <username>")
log.Println("usage: hydroxide export-secret-keys <username>")
log.Println("usage: hydroxide status")
if cmd != "help" {
log.Fatal("Unrecognized command")
}
2017-09-09 16:37:03 +03:00
}
2017-08-22 01:04:16 +03:00
}