diff --git a/README.md b/README.md index 0238122..41c13f4 100644 --- a/README.md +++ b/README.md @@ -1,103 +1,50 @@ -# hydroxide +# hydroxide-push +### *Forked from [Hydroxide](https://github.com/emersion/hydroxide)* -A third-party, open-source ProtonMail bridge. For power users only, designed to -run on a server. +## Push notifications for Proton Mail mobile via a UP provider -hydroxide supports CardDAV, IMAP and SMTP. - -Rationale: - -* No GUI, only a CLI (so it runs in headless environments) -* Standard-compliant (we don't care about Microsoft Outlook) -* Fully open-source - -Feel free to join the IRC channel: #emersion on Libera Chat. - -## How does it work? - -hydroxide is a server that translates standard protocols (SMTP, IMAP, CardDAV) -into ProtonMail API requests. It allows you to use your preferred e-mail clients -and `git-send-email` with ProtonMail. - - +-----------------+ +-------------+ ProtonMail +--------------+ - | | IMAP, SMTP | | API | | - | E-mail client <-------------> hydroxide <--------------> ProtonMail | - | | | | | | - +-----------------+ +-------------+ +--------------+ +Protonmail depends on Google services to deliver push notifications, +This is a stripped down version of [Hydroxide](https://github.com/emersion/hydroxide) +to get notified of new mail. See original repo for details on operation. ## Setup -### Go +Download (soon), build the binary or the container image or pull the container image yourself. +Simplest way is to run the container image. -hydroxide is implemented in Go. Head to [Go website](https://golang.org) for -setup information. +Login and push gateway details are saved under `$HOME/.config/hydroxide`. The container +image saves configuration under `/data`, so mount a named volume or host directory there. +The examples below use a named volume. -### Installing - -Start by installing hydroxide: +If using Docker, substitute `podman` with `docker` in the examples. +Binary: ```shell -git clone https://github.com/emersion/hydroxide.git -go build ./cmd/hydroxide +./hydroxide-push auth your.proton@email.address ``` - -Then you'll need to login to ProtonMail via hydroxide, so that hydroxide can -retrieve e-mails from ProtonMail. You can do so with this command: - +Container: ```shell -hydroxide auth +podman run -it --rm -v hydroxide-config:/data ghcr.io/0ranki/hydroxide-push auth your.proton@email.address ``` +You will be prompted for the Proton account credentials and the details for the push server. Proton credentials are stored encrypted form. -Once you're logged in, a "bridge password" will be printed. Don't close your -terminal yet, as this password is not stored anywhere by hydroxide and will be -needed when configuring your e-mail client. - -Your ProtonMail credentials are stored on disk encrypted with this bridge -password (a 32-byte random password generated when logging in). - -## Usage - -hydroxide can be used in multiple modes. - -> Don't start hydroxide multiple times, instead you can use `hydroxide serve`. -> This requires ports 1025 (smtp), 1143 (imap), and 8080 (carddav). - -### SMTP - -To run hydroxide as an SMTP server: +The auth flow generates a separate password for the bridge, which is stored in plaintext +to `$HOME/.config/notify.json`. Unlike upstream `hydroxide`, there is no service listening on any port, +all communications is internal to the program. +### Reconfigure push server +Binary: ```shell -hydroxide smtp +hydroxide-push setup-ntfy ``` - -Once the bridge is started, you can configure your e-mail client with the -following settings: - -* Hostname: `localhost` -* Port: 1025 -* Security: none -* Username: your ProtonMail username -* Password: the bridge password (not your ProtonMail password) - -### CardDAV - -You must setup an HTTPS reverse proxy to forward requests to `hydroxide`. - +Container: ```shell -hydroxide carddav +podman run -it --rm -v hydroxide-config:/data ghcr.io/0ranki/hydroxide-push setup-ntfy ``` +You'll be asked for the base URL of the push server, and the topic. These will probably +be combined to a single string in future versions. -Tested on GNOME (Evolution) and Android (DAVDroid). - -### IMAP - -⚠️ **Warning**: IMAP support is work-in-progress. Here be dragons. - -For now, it only supports unencrypted local connections. - -```shell -hydroxide imap -``` +**NOTE:** Authentication for the push endpoint is not yet supported. ## License diff --git a/cmd/hydroxide-push/main.go b/cmd/hydroxide-push/main.go index aabca5b..c36ff73 100644 --- a/cmd/hydroxide-push/main.go +++ b/cmd/hydroxide-push/main.go @@ -74,6 +74,38 @@ func listenEventsAndNotify(addr string, debug bool, authManager *auth.Manager, e return nil } +func setupNtfy() { + err := cfg.Read() + if err != nil { + fmt.Println(err) + } + var n string + if cfg.URL != "" && cfg.Topic != "" { + fmt.Printf("Current push endpoint: %s\n", cfg.String()) + n = "new " + } + scanner := bufio.NewScanner(os.Stdin) + fmt.Printf("Input %spush server URL (e.g. 'http://ntfy.sh') : ", n) + scanner.Scan() + cfg.URL = scanner.Text() + scanner = bufio.NewScanner(os.Stdin) + fmt.Printf("Input push topic (e.g. my-proton-notifications): ") + scanner.Scan() + cfg.Topic = scanner.Text() + fmt.Printf("Using URL %s\n", cfg.String()) + err = cfg.Save() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + err = ntfy.LoginBridge(&cfg) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + fmt.Println("Notification configuration saved") +} + const usage = `usage: hydroxide-push [options...] Commands: auth Login to ProtonMail via hydroxide @@ -211,8 +243,15 @@ func main() { if err != nil { log.Fatal(err) } - - fmt.Println("Bridge password:", bridgePassword) + cfg.BridgePw = bridgePassword + reply, err := ntfy.AskToSaveBridgePw(&cfg) + if err != nil { + log.Fatal(err) + } + if reply != "yes" { + fmt.Println("Bridge password:", bridgePassword) + } + setupNtfy() case "status": usernames, err := auth.ListUsernames() if err != nil { @@ -229,30 +268,7 @@ func main() { } case "setup-ntfy": - err = cfg.Read() - if err != nil { - fmt.Println(err) - } - var new string - if cfg.URL != "" && cfg.Topic != "" { - fmt.Printf("Current push endpoint: %s\n", cfg.String()) - new = "new " - } - scanner := bufio.NewScanner(os.Stdin) - fmt.Printf("Input %spush server URL (e.g. 'http://ntfy.sh') : ", new) - scanner.Scan() - cfg.URL = scanner.Text() - scanner = bufio.NewScanner(os.Stdin) - fmt.Printf("Input push topic (e.g. my-proton-notifications): ") - scanner.Scan() - cfg.Topic = scanner.Text() - fmt.Printf("Using URL %s\n", cfg.String()) - err = cfg.Save() - if err != nil { - fmt.Println(err) - os.Exit(1) - } - fmt.Println("Notification configuration saved") + setupNtfy() case "notify": authManager := auth.NewManager(newClient) eventsManager := events.NewManager() diff --git a/ntfy/ntfy.go b/ntfy/ntfy.go index 01e3432..474e94b 100644 --- a/ntfy/ntfy.go +++ b/ntfy/ntfy.go @@ -3,6 +3,7 @@ package ntfy import ( "bufio" "encoding/json" + "errors" "fmt" "github.com/0ranki/hydroxide-push/auth" "github.com/0ranki/hydroxide-push/config" @@ -63,6 +64,34 @@ func (cfg *NtfyConfig) Read() error { return nil } +func AskToSaveBridgePw(cfg *NtfyConfig) (string, error) { + scanner := bufio.NewScanner(os.Stdin) + //fmt.Printf("Save bridge password to config?\nThe password is stored in plain text, but (yes/n): ") + //scanner.Scan() + //if scanner.Text() == "yes" { + if err := cfg.Save(); err != nil { + return "", errors.New("failed to save notification config") + } + //} + return scanner.Text(), nil +} + +func LoginBridge(cfg *NtfyConfig) error { + if cfg.BridgePw == "" { + cfg.BridgePw = os.Getenv("HYDROXIDE_BRIDGE_PASSWORD") + } + if cfg.BridgePw == "" { + scanner := bufio.NewScanner(os.Stdin) + fmt.Printf("Bridge password: ") + scanner.Scan() + cfg.BridgePw = scanner.Text() + _, err := AskToSaveBridgePw(cfg) + if err != nil { + return err + } + } + return nil +} func Login(cfg *NtfyConfig, be backend.Backend) { //time.Sleep(1 * time.Second) c, _ := net.ResolveIPAddr("ip", "127.0.0.1") @@ -88,20 +117,9 @@ func Login(cfg *NtfyConfig, be backend.Backend) { log.Fatalln("then setup ntfy using " + executable + "setup-ntfy") } if cfg.BridgePw == "" { - cfg.BridgePw = os.Getenv("HYDROXIDE_BRIDGE_PASSWORD") - } - if cfg.BridgePw == "" { - scanner := bufio.NewScanner(os.Stdin) - fmt.Printf("Bridge password: ") - scanner.Scan() - cfg.BridgePw = scanner.Text() - scanner = bufio.NewScanner(os.Stdin) - fmt.Printf("Save password to config? The password is stored in plain text! (yes/n): ") - scanner.Scan() - if scanner.Text() == "yes" { - if err = cfg.Save(); err != nil { - log.Fatal("failed to save notification config") - } + err = LoginBridge(cfg) + if err != nil { + log.Fatal(err) } } _, err = be.Login(&conn, usernames[0], cfg.BridgePw)