mirror of
https://github.com/shenxn/protonmail-bridge-docker.git
synced 2026-01-18 14:44:41 +01:00
Initial commit for http rest bridge
This commit is contained in:
11
.github/workflows/build.yaml
vendored
11
.github/workflows/build.yaml
vendored
@@ -5,13 +5,8 @@ on:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
paths:
|
||||
- .github/workflows/build.yaml
|
||||
- build/*
|
||||
- http-rest
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/build.yaml
|
||||
- build/*
|
||||
|
||||
env:
|
||||
DOCKER_REPO: shenxn/protonmail-bridge
|
||||
@@ -31,7 +26,7 @@ jobs:
|
||||
uses: actions/checkout@master
|
||||
- name: Set version
|
||||
id: version
|
||||
run: echo "::set-output name=version::`cat build/VERSION`"
|
||||
run: echo "::set-output name=version::`cat VERSION`"
|
||||
- name: Set repo
|
||||
id: repo
|
||||
run: if [[ $GITHUB_REF == "refs/heads/master" ]]; then echo "::set-output name=repo::${DOCKER_REPO}"; else echo "::set-output name=repo::${DOCKER_REPO_DEV}"; fi
|
||||
@@ -49,8 +44,6 @@ jobs:
|
||||
- name: Build image without push to registry
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: ./build
|
||||
file: ./build/Dockerfile
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
push: true
|
||||
tags: localhost:5000/protonmail-bridge:latest
|
||||
|
||||
80
.github/workflows/deb.yaml
vendored
80
.github/workflows/deb.yaml
vendored
@@ -1,80 +0,0 @@
|
||||
name: pack from deb
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
paths:
|
||||
- .github/workflows/deb.yaml
|
||||
- deb/*
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/deb.yaml
|
||||
- deb/*
|
||||
|
||||
env:
|
||||
DOCKER_REPO: shenxn/protonmail-bridge
|
||||
DOCKER_REPO_DEV: ghcr.io/shenxn/protonmail-bridge-dev
|
||||
|
||||
jobs:
|
||||
deb:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@master
|
||||
- name: Set version
|
||||
id: version
|
||||
run: echo "::set-output name=version::`cat deb/VERSION`"
|
||||
- name: Set repo
|
||||
id: repo
|
||||
run: if [[ $GITHUB_REF == "refs/heads/master" ]]; then echo "::set-output name=repo::${DOCKER_REPO}"; else echo "::set-output name=repo::${DOCKER_REPO_DEV}"; fi
|
||||
- name: Docker meta
|
||||
id: docker_meta
|
||||
uses: crazy-max/ghaction-docker-meta@v1
|
||||
with:
|
||||
images: ${{ steps.repo.outputs.repo }}
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: Build image without push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: ./deb
|
||||
file: ./deb/Dockerfile
|
||||
load: true
|
||||
tags: protonmail-bridge:latest
|
||||
- name: Scan image
|
||||
id: scan
|
||||
uses: anchore/scan-action@v2
|
||||
with:
|
||||
image: protonmail-bridge:latest
|
||||
fail-build: true
|
||||
severity-cutoff: critical
|
||||
acs-report-enable: true
|
||||
- name: Upload Anchore scan SARIF report
|
||||
uses: github/codeql-action/upload-sarif@v1
|
||||
with:
|
||||
sarif_file: ${{ steps.scan.outputs.sarif }}
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/master' }}
|
||||
with:
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1
|
||||
if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/dev' }}
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.CR_PAT }}
|
||||
- name: Push image
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: ./deb
|
||||
file: ./deb/Dockerfile
|
||||
tags: |
|
||||
${{ steps.repo.outputs.repo }}:latest
|
||||
${{ steps.repo.outputs.repo }}:${{ steps.version.outputs.version }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -0,0 +1 @@
|
||||
.vscode
|
||||
|
||||
@@ -6,6 +6,7 @@ RUN apt-get update && apt-get install -y libsecret-1-dev
|
||||
# Build
|
||||
WORKDIR /build/
|
||||
COPY build.sh VERSION /build/
|
||||
COPY http_rest_frontend /build/http_rest_frontend
|
||||
RUN bash build.sh
|
||||
|
||||
FROM ubuntu:bionic
|
||||
@@ -16,13 +17,13 @@ EXPOSE 143/tcp
|
||||
|
||||
# Install dependencies and protonmail bridge
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends socat pass libsecret-1-0 ca-certificates \
|
||||
&& apt-get install -y --no-install-recommends socat pass libsecret-1-0 ca-certificates dbus \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy bash scripts
|
||||
COPY gpgparams entrypoint.sh /protonmail/
|
||||
COPY gpgparams entrypoint.sh /srv/protonmail/
|
||||
|
||||
# Copy protonmail
|
||||
COPY --from=build /build/proton-bridge/proton-bridge /protonmail/
|
||||
COPY --from=build /build/proton-bridge/proton-bridge /srv/protonmail/
|
||||
|
||||
ENTRYPOINT ["bash", "/protonmail/entrypoint.sh"]
|
||||
ENTRYPOINT ["bash", "/srv/protonmail/entrypoint.sh"]
|
||||
@@ -9,14 +9,23 @@ git clone https://github.com/ProtonMail/proton-bridge.git
|
||||
cd proton-bridge
|
||||
git checkout v$VERSION
|
||||
|
||||
ls /build
|
||||
|
||||
# Patch HTTP REST frontend
|
||||
rm -rf internal/frontend/cli
|
||||
cp -r /build/http_rest_frontend/cli internal/frontend/cli
|
||||
|
||||
# Build
|
||||
if ! make build-nogui ; then
|
||||
# If build fails it's probably because it is a 32bit
|
||||
# system and there was a overflow error on the parser
|
||||
# This is a workaround for this problem found at:
|
||||
# https://github.com/antlr/antlr4/issues/2433#issuecomment-774514106
|
||||
find $(go env GOPATH)/pkg/mod/github.com/\!proton\!mail/go-rfc5322*/ -type f -exec sed -i.bak 's/(1<</(int64(1)<</g' {} +
|
||||
# find $(go env GOPATH)/pkg/mod/github.com/\!proton\!mail/go-rfc5322*/ -type f -exec sed -i.bak 's/(1<</(int64(1)<</g' {} +
|
||||
|
||||
# Try again after implementing the workaround
|
||||
make build-nogui
|
||||
# make build-nogui
|
||||
|
||||
uname -m
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,8 +0,0 @@
|
||||
*
|
||||
|
||||
!.dockerignore
|
||||
!VERSION
|
||||
!entrypoint.sh
|
||||
!gpgparams
|
||||
!Dockerfile
|
||||
!build.sh
|
||||
@@ -1,29 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -ex
|
||||
|
||||
# Initialize
|
||||
if [[ $1 == init ]]; then
|
||||
|
||||
# Initialize pass
|
||||
gpg --generate-key --batch /protonmail/gpgparams
|
||||
pass init pass-key
|
||||
|
||||
# Login
|
||||
/protonmail/proton-bridge --cli $@
|
||||
|
||||
else
|
||||
|
||||
# socat will make the conn appear to come from 127.0.0.1
|
||||
# ProtonMail Bridge currently expects that.
|
||||
# It also allows us to bind to the real ports :)
|
||||
socat TCP-LISTEN:25,fork TCP:127.0.0.1:1025 &
|
||||
socat TCP-LISTEN:143,fork TCP:127.0.0.1:1143 &
|
||||
|
||||
# Start protonmail
|
||||
# Fake a terminal, so it does not quit because of EOF...
|
||||
rm -f faketty
|
||||
mkfifo faketty
|
||||
cat faketty | /protonmail/proton-bridge --cli $@
|
||||
|
||||
fi
|
||||
77
cli.sh
Executable file
77
cli.sh
Executable file
@@ -0,0 +1,77 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
URL_BASE=http://127.0.0.1:1080
|
||||
|
||||
print_help() {
|
||||
echo "Available commands:"
|
||||
echo " login: Calls up the login procedure to add or connect accounts."
|
||||
echo " delete <account>: Remove the account from keychain. You can use index or account name as the parameter."
|
||||
echo " list: Print list of your accounts."
|
||||
echo " info <account>: Print account configuration. You can use index or account name as the parameter."
|
||||
echo " help: Print help messages."
|
||||
echo " exit: Exit the CLI"
|
||||
}
|
||||
|
||||
account_list() {
|
||||
curl ${URL_BASE}/accounts
|
||||
}
|
||||
|
||||
account_login() {
|
||||
read -p "Username: " USERNAME
|
||||
read -sp "Password: " PASSWORD
|
||||
echo
|
||||
read -p "2FA Code (leave empty if not set): " TWO_FACTOR
|
||||
read -p "Mailbox Password (leave empty if not set): " MAILBOX_PASSWORD
|
||||
|
||||
curl ${URL_BASE}/accounts -XPUT \
|
||||
--data-urlencode "username=${USERNAME}" \
|
||||
--data-urlencode "password=${PASSWORD}" \
|
||||
--data-urlencode "two-factor=${TWO_FACTOR}" \
|
||||
--data-urlencode "mailbox-password=${MAILBOX_PASSWORD}"
|
||||
}
|
||||
|
||||
account_delete() {
|
||||
if [[ -z $1 ]]; then
|
||||
echo "Error: delete requires one parameter, which is the index or account name."
|
||||
else
|
||||
read -p "Are you sure you want to delete account $1? " REPLY
|
||||
if [[ $REPLY =~ ^[Yy] ]]; then
|
||||
curl ${URL_BASE}/accounts/$1 -XDELETE
|
||||
else
|
||||
echo "Abort"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
account_info() {
|
||||
if [[ -z $1 ]]; then
|
||||
echo "Error: info requires one parameter, which is the index or account name."
|
||||
else
|
||||
curl ${URL_BASE}/accounts/$1
|
||||
fi
|
||||
}
|
||||
|
||||
echo "CLI to interacte with Proton Bridge HTTP REST interface"
|
||||
print_help
|
||||
while true; do
|
||||
echo
|
||||
read -p ">> " COMMAND ARG
|
||||
case "$COMMAND" in
|
||||
login)
|
||||
account_login;;
|
||||
delete)
|
||||
account_delete $ARG;;
|
||||
list)
|
||||
account_list;;
|
||||
info)
|
||||
account_info $ARG;;
|
||||
help)
|
||||
print_help;;
|
||||
exit)
|
||||
exit 0;;
|
||||
*)
|
||||
echo "Invalid command"
|
||||
esac
|
||||
done
|
||||
@@ -1,8 +0,0 @@
|
||||
*
|
||||
|
||||
!.dockerignore
|
||||
!VERSION
|
||||
!entrypoint.sh
|
||||
!install.sh
|
||||
!gpgparams
|
||||
!Dockerfile
|
||||
@@ -1,15 +0,0 @@
|
||||
FROM ubuntu:bionic
|
||||
LABEL maintainer="Xiaonan Shen <s@sxn.dev>"
|
||||
|
||||
EXPOSE 25/tcp
|
||||
EXPOSE 143/tcp
|
||||
|
||||
WORKDIR /protonmail
|
||||
|
||||
# Copy bash scripts
|
||||
COPY gpgparams install.sh entrypoint.sh VERSION /protonmail/
|
||||
|
||||
# Install dependencies and protonmail bridge
|
||||
RUN bash install.sh
|
||||
|
||||
ENTRYPOINT ["bash", "/protonmail/entrypoint.sh"]
|
||||
@@ -1 +0,0 @@
|
||||
2.1.1-1
|
||||
@@ -1,49 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -ex
|
||||
|
||||
# Initialize
|
||||
if [[ $1 == init ]]; then
|
||||
|
||||
# # Parse parameters
|
||||
# TFP="" # Default empty two factor passcode
|
||||
# shift # skip `init`
|
||||
# while [[ $# -gt 0 ]]; do
|
||||
# key="$1"
|
||||
# case $key in
|
||||
# -u|--username)
|
||||
# USERNAME="$2"
|
||||
# ;;
|
||||
# -p|--password)
|
||||
# PASSWORD="$2"
|
||||
# ;;
|
||||
# -t|--twofactor)
|
||||
# TWOFACTOR="$2"
|
||||
# ;;
|
||||
# esac
|
||||
# shift
|
||||
# shift
|
||||
# done
|
||||
|
||||
# Initialize pass
|
||||
gpg --generate-key --batch /protonmail/gpgparams
|
||||
pass init pass-key
|
||||
|
||||
# Login
|
||||
protonmail-bridge --cli
|
||||
|
||||
else
|
||||
|
||||
# socat will make the conn appear to come from 127.0.0.1
|
||||
# ProtonMail Bridge currently expects that.
|
||||
# It also allows us to bind to the real ports :)
|
||||
socat TCP-LISTEN:25,fork TCP:127.0.0.1:1025 &
|
||||
socat TCP-LISTEN:143,fork TCP:127.0.0.1:1143 &
|
||||
|
||||
# Start protonmail
|
||||
# Fake a terminal, so it does not quit because of EOF...
|
||||
rm -f faketty
|
||||
mkfifo faketty
|
||||
cat faketty | protonmail-bridge --cli
|
||||
|
||||
fi
|
||||
@@ -1,8 +0,0 @@
|
||||
%no-protection
|
||||
%echo Generating a basic OpenPGP key
|
||||
Key-Type: RSA
|
||||
Key-Length: 2048
|
||||
Name-Real: pass-key
|
||||
Expire-Date: 0
|
||||
%commit
|
||||
%echo done
|
||||
@@ -1,36 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -ex
|
||||
|
||||
VERSION=`cat VERSION`
|
||||
DEB_FILE=protonmail-bridge_${VERSION}_amd64.deb
|
||||
|
||||
# Install dependents
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends socat pass ca-certificates
|
||||
|
||||
# Build time dependencies
|
||||
apt-get install -y wget binutils xz-utils
|
||||
|
||||
# Repack deb (remove unnecessary dependencies)
|
||||
mkdir deb
|
||||
cd deb
|
||||
wget -q https://protonmail.com/download/bridge/${DEB_FILE}
|
||||
ar x -v ${DEB_FILE}
|
||||
mkdir control
|
||||
tar zxvf control.tar.gz -C control
|
||||
sed -i "s/^Depends: .*$/Depends: libgl1, libc6, libsecret-1-0, libstdc++6, libgcc1/" control/control
|
||||
cd control
|
||||
tar zcvf ../control.tar.gz .
|
||||
cd ../
|
||||
ar rcs -v ${DEB_FILE} debian-binary control.tar.gz data.tar.gz
|
||||
cd ../
|
||||
|
||||
# Install protonmail bridge
|
||||
apt-get install -y --no-install-recommends ./deb/${DEB_FILE}
|
||||
|
||||
# Cleanup
|
||||
apt-get purge -y wget binutils xz-utils
|
||||
apt-get autoremove -y
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
rm -rf deb
|
||||
37
entrypoint.sh
Normal file
37
entrypoint.sh
Normal file
@@ -0,0 +1,37 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Generate gpg keys
|
||||
if [ ! -f ${HOME}/.gnupg ]; then
|
||||
echo "Generateing gpg keys..."
|
||||
# set GNUPGHOME as a workaround for
|
||||
#
|
||||
# gpg-agent[106]: error binding socket to '/root/.gnupg/S.gpg-agent': File name too long
|
||||
#
|
||||
# when using docker volume mount
|
||||
#
|
||||
# ref: https://dev.gnupg.org/T2964
|
||||
#
|
||||
export GNUPGHOME=/tmp/gnupg
|
||||
mkdir ${GNUPGHOME}
|
||||
chmod 700 ${GNUPGHOME}
|
||||
gpg --generate-key --batch /srv/protonmail/gpgparams
|
||||
pkill gpg-agent
|
||||
mv ${GNUPGHOME} ${HOME}/.gnupg
|
||||
export GNUPGHOME=""
|
||||
fi
|
||||
|
||||
# Initialize pass
|
||||
if [ ! -f ${HOME}/.password-store/.gpg-id ]; then
|
||||
echo "Initializing pass"
|
||||
pass init pass-key
|
||||
fi
|
||||
|
||||
# socat will make the conn appear to come from 127.0.0.1
|
||||
# ProtonMail Bridge currently expects that.
|
||||
# It also allows us to bind to the real ports :)
|
||||
socat TCP-LISTEN:25,fork TCP:127.0.0.1:1025 &
|
||||
socat TCP-LISTEN:143,fork TCP:127.0.0.1:1143 &
|
||||
|
||||
/srv/protonmail/proton-bridge --cli $@
|
||||
147
http_rest_frontend/cli/accounts.go
Normal file
147
http_rest_frontend/cli/accounts.go
Normal file
@@ -0,0 +1,147 @@
|
||||
// Package cli provides HTTP interface of the bridge
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
func (f *frontendCLI) loginAccount(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
fmt.Fprintf(w, "ParseForm() err: %v", err)
|
||||
return
|
||||
}
|
||||
username := r.FormValue("username")
|
||||
password := r.FormValue("password")
|
||||
twoFactor := r.FormValue("two-factor")
|
||||
mailboxPassword := r.FormValue("mailbox-password")
|
||||
client, auth, err := f.bridge.Login(username, []byte(password))
|
||||
if err != nil {
|
||||
f.processAPIError(err)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
fmt.Fprintln(w, "Server error:", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if auth.HasTwoFactor() {
|
||||
if twoFactor == "" {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
fmt.Fprintln(w, "2FA enabled for the account but a 2FA code was not provided.")
|
||||
return
|
||||
}
|
||||
err = client.Auth2FA(context.Background(), twoFactor)
|
||||
if err != nil {
|
||||
f.processAPIError(err)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
fmt.Fprintln(w, "Server error:", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if auth.HasMailboxPassword() {
|
||||
if mailboxPassword == "" {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
fmt.Fprintln(w, "Two password mode enabled but a mailbox password was not provided.")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
mailboxPassword = password
|
||||
}
|
||||
user, err := f.bridge.FinishLogin(client, auth, []byte(mailboxPassword))
|
||||
if err != nil {
|
||||
f.processAPIError(err)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
fmt.Fprintln(w, "Server error:", err.Error())
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, "Account %s was added successfully.\n", user.Username())
|
||||
f.printAccountInfo(w, user)
|
||||
}
|
||||
|
||||
func (f *frontendCLI) deleteAccount(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
account := params.ByName("account")
|
||||
user := f.getUserByIndexOrName(account)
|
||||
if user == nil {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
fmt.Fprintf(w, "Account %s does not exist.\n", account)
|
||||
return
|
||||
}
|
||||
account = user.Username()
|
||||
if err := f.bridge.DeleteUser(user.ID(), true); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprintln(w, "Cannot delete account: ", err)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, "Account %s was deleted successfully.\n", account)
|
||||
}
|
||||
|
||||
func (f *frontendCLI) listAccounts(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
users := f.bridge.GetUsers()
|
||||
if len(users) == 0 {
|
||||
fmt.Fprintln(w, "No account found.")
|
||||
return
|
||||
}
|
||||
spacing := "%-2d: %-20s (%-15s, %-15s)\n"
|
||||
fmt.Fprintf(w, strings.ReplaceAll(spacing, "d", "s"), "#", "account", "status", "address mode")
|
||||
for idx, user := range users {
|
||||
connected := "disconnected"
|
||||
if user.IsConnected() {
|
||||
connected = "connected"
|
||||
}
|
||||
mode := "split"
|
||||
if user.IsCombinedAddressMode() {
|
||||
mode = "combined"
|
||||
}
|
||||
fmt.Fprintf(w, spacing, idx, user.Username(), connected, mode)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) showAccountInfo(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
account := params.ByName("account")
|
||||
user := f.getUserByIndexOrName(account)
|
||||
if user == nil {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
fmt.Fprintf(w, "Account %s does not exist.\n", account)
|
||||
return
|
||||
}
|
||||
if !user.IsConnected() {
|
||||
fmt.Fprintf(w, "Please login to %s to get email client configuration.\n", user.Username())
|
||||
return
|
||||
}
|
||||
f.printAccountInfo(w, user)
|
||||
}
|
||||
|
||||
func (f *frontendCLI) printAccountInfo(w io.Writer, user types.User) {
|
||||
if user.IsCombinedAddressMode() {
|
||||
f.printAccountAddressInfo(w, user, user.GetPrimaryAddress())
|
||||
} else {
|
||||
for _, address := range user.GetAddresses() {
|
||||
f.printAccountAddressInfo(w, user, address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) printAccountAddressInfo(w io.Writer, user types.User, address string) {
|
||||
fmt.Fprintln(w, "Configuration for", address)
|
||||
smtpSecurity := "STARTTLS"
|
||||
if f.settings.GetBool(settings.SMTPSSLKey) {
|
||||
smtpSecurity = "SSL"
|
||||
}
|
||||
fmt.Fprintf(w, "IMAP port: %d\nIMAP security: %s\nSMTP port: %d\nSMTP security: %s\nUsername: %s\nPassword: %s\n",
|
||||
143,
|
||||
"STARTTLS",
|
||||
25,
|
||||
smtpSecurity,
|
||||
address,
|
||||
user.GetBridgePassword(),
|
||||
)
|
||||
fmt.Fprintln(w, "")
|
||||
}
|
||||
24
http_rest_frontend/cli/accounts_util.go
Normal file
24
http_rest_frontend/cli/accounts_util.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||
)
|
||||
|
||||
func (f *frontendCLI) getUserByIndexOrName(account string) types.User {
|
||||
users := f.bridge.GetUsers()
|
||||
numberOfAccounts := len(users)
|
||||
if index, err := strconv.Atoi(account); err == nil {
|
||||
if index < 0 || index >= numberOfAccounts {
|
||||
return nil
|
||||
}
|
||||
return users[index]
|
||||
}
|
||||
for _, user := range users {
|
||||
if user.Username() == account {
|
||||
return user
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
157
http_rest_frontend/cli/frontend.go
Normal file
157
http_rest_frontend/cli/frontend.go
Normal file
@@ -0,0 +1,157 @@
|
||||
// Package cli provides HTTP interface of the bridge
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||
"github.com/ProtonMail/proton-bridge/internal/locations"
|
||||
"github.com/ProtonMail/proton-bridge/internal/updater"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type frontendCLI struct {
|
||||
*httprouter.Router
|
||||
|
||||
locations *locations.Locations
|
||||
settings *settings.Settings
|
||||
eventListener listener.Listener
|
||||
updater types.Updater
|
||||
bridge types.Bridger
|
||||
restarter types.Restarter
|
||||
}
|
||||
|
||||
func New(
|
||||
panicHandler types.PanicHandler,
|
||||
|
||||
locations *locations.Locations,
|
||||
settings *settings.Settings,
|
||||
eventListener listener.Listener,
|
||||
updater types.Updater,
|
||||
bridge types.Bridger,
|
||||
restarter types.Restarter,
|
||||
) *frontendCLI {
|
||||
fe := &frontendCLI{
|
||||
Router: httprouter.New(),
|
||||
locations: locations,
|
||||
settings: settings,
|
||||
eventListener: eventListener,
|
||||
updater: updater,
|
||||
bridge: bridge,
|
||||
restarter: restarter,
|
||||
}
|
||||
|
||||
fe.PUT("/accounts", fe.loginAccount)
|
||||
fe.GET("/accounts", fe.listAccounts)
|
||||
fe.GET("/accounts/:account", fe.showAccountInfo)
|
||||
fe.DELETE("/accounts/:account", fe.deleteAccount)
|
||||
|
||||
return fe
|
||||
}
|
||||
|
||||
func (f *frontendCLI) loginWithEnv() {
|
||||
if len(f.bridge.GetUsers()) > 0 {
|
||||
fmt.Println("More than 0 accounts found. Skip auto login.")
|
||||
return
|
||||
}
|
||||
username := os.Getenv("PROTON_USERNAME")
|
||||
password := os.Getenv("PROTON_PASSWORD")
|
||||
if username == "" {
|
||||
logrus.Info("PROTON_USERNAME and PROTON_PASSWORD are not set. Skip auto login.")
|
||||
return
|
||||
}
|
||||
client, auth, err := f.bridge.Login(username, []byte(password))
|
||||
if err != nil {
|
||||
f.processAPIError(err)
|
||||
logrus.WithError(err).Warn("Login failed.")
|
||||
return
|
||||
}
|
||||
|
||||
if auth.HasTwoFactor() {
|
||||
twoFactor := os.Getenv("PROTON_2FA")
|
||||
if twoFactor == "" {
|
||||
logrus.Warn("Login failed: 2FA enabled for the account but PROTON_2FA was not set.")
|
||||
return
|
||||
}
|
||||
err = client.Auth2FA(context.Background(), twoFactor)
|
||||
if err != nil {
|
||||
f.processAPIError(err)
|
||||
logrus.WithError(err).Warn("Login failed.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
mailboxPassword := password
|
||||
if auth.HasMailboxPassword() {
|
||||
mailboxPassword = os.Getenv("PROTON_MAILBOX_PASSWORD")
|
||||
if mailboxPassword == "" {
|
||||
logrus.Warn("Login failed: Two password mode enabled but PROTON_MAILBOX_PASSWORD was not set.")
|
||||
return
|
||||
}
|
||||
}
|
||||
user, err := f.bridge.FinishLogin(client, auth, []byte(mailboxPassword))
|
||||
if err != nil {
|
||||
f.processAPIError(err)
|
||||
logrus.WithError(err).Warn("Login failed.")
|
||||
return
|
||||
}
|
||||
logrus.Infof("Account %s was added successfully.\n", user.Username())
|
||||
if strings.ToLower(os.Getenv("PROTON_PRINT_ACCOUNT_INFO")) != "off" {
|
||||
f.printAccountInfo(os.Stdout, user)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) watchEvents() {
|
||||
errorCh := f.eventListener.ProvideChannel(events.ErrorEvent)
|
||||
credentialsErrorCh := f.eventListener.ProvideChannel(events.CredentialsErrorEvent)
|
||||
internetOffCh := f.eventListener.ProvideChannel(events.InternetOffEvent)
|
||||
internetOnCh := f.eventListener.ProvideChannel(events.InternetOnEvent)
|
||||
addressChangedCh := f.eventListener.ProvideChannel(events.AddressChangedEvent)
|
||||
addressChangedLogoutCh := f.eventListener.ProvideChannel(events.AddressChangedLogoutEvent)
|
||||
logoutCh := f.eventListener.ProvideChannel(events.LogoutEvent)
|
||||
certIssue := f.eventListener.ProvideChannel(events.TLSCertIssue)
|
||||
for {
|
||||
select {
|
||||
case errorDetails := <-errorCh:
|
||||
fmt.Println("Bridge failed:", errorDetails)
|
||||
case <-credentialsErrorCh:
|
||||
f.notifyCredentialsError()
|
||||
case <-internetOffCh:
|
||||
f.notifyInternetOff()
|
||||
case <-internetOnCh:
|
||||
f.notifyInternetOn()
|
||||
case address := <-addressChangedCh:
|
||||
fmt.Printf("Address changed for %s. You may need to reconfigure your email client.", address)
|
||||
case address := <-addressChangedLogoutCh:
|
||||
f.notifyLogout(address)
|
||||
case userID := <-logoutCh:
|
||||
user, err := f.bridge.GetUser(userID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
f.notifyLogout(user.Username())
|
||||
case <-certIssue:
|
||||
f.notifyCertIssue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) Loop() error {
|
||||
f.loginWithEnv()
|
||||
http.ListenAndServe(":1080", f)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *frontendCLI) NotifyManualUpdate(update updater.VersionInfo, canInstall bool) {}
|
||||
func (f *frontendCLI) WaitUntilFrontendIsReady() {}
|
||||
func (f *frontendCLI) SetVersion(version updater.VersionInfo) {}
|
||||
func (f *frontendCLI) NotifySilentUpdateInstalled() {}
|
||||
func (f *frontendCLI) NotifySilentUpdateError(err error) {}
|
||||
57
http_rest_frontend/cli/utils.go
Normal file
57
http_rest_frontend/cli/utils.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (f *frontendCLI) processAPIError(err error) {
|
||||
switch err {
|
||||
case pmapi.ErrNoConnection:
|
||||
f.notifyInternetOff()
|
||||
case pmapi.ErrUpgradeApplication:
|
||||
f.notifyNeedUpgrade()
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) notifyInternetOff() {
|
||||
logrus.Warn("Internet connection is not available.")
|
||||
}
|
||||
|
||||
func (f *frontendCLI) notifyInternetOn() {
|
||||
logrus.Info("Internet connection is available again.")
|
||||
}
|
||||
|
||||
func (f *frontendCLI) notifyLogout(address string) {
|
||||
logrus.Infof("Account %s is disconnected. Login to continue using this account with email client.", address)
|
||||
}
|
||||
|
||||
func (f *frontendCLI) notifyNeedUpgrade() {
|
||||
logrus.Info("Upgrade needed. Please download and install the newest version of application.")
|
||||
}
|
||||
|
||||
func (f *frontendCLI) notifyCredentialsError() {
|
||||
logrus.Error(`ProtonMail Bridge is not able to detect a supported password manager
|
||||
(secret-service or pass). Please install and set up a supported password manager
|
||||
and restart the application.
|
||||
`)
|
||||
}
|
||||
|
||||
func (f *frontendCLI) notifyCertIssue() {
|
||||
// Print in 80-column width.
|
||||
logrus.Error(`Connection security error: Your network connection to Proton services may
|
||||
be insecure.
|
||||
|
||||
Description:
|
||||
ProtonMail Bridge was not able to establish a secure connection to Proton
|
||||
servers due to a TLS certificate error. This means your connection may
|
||||
potentially be insecure and susceptible to monitoring by third parties.
|
||||
|
||||
Recommendation:
|
||||
* If you trust your network operator, you can continue to use ProtonMail
|
||||
as usual.
|
||||
* If you don't trust your network operator, reconnect to ProtonMail over a VPN
|
||||
(such as ProtonVPN) which encrypts your Internet connection, or use
|
||||
a different network to access ProtonMail.
|
||||
`)
|
||||
}
|
||||
Reference in New Issue
Block a user