// Copyright (c) 2021 Proton Technologies AG // // This file is part of ProtonMail Bridge. // // ProtonMail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // ProtonMail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with ProtonMail Bridge. If not, see . // +build darwin package autoconfig import ( "io/ioutil" "os" "os/exec" "path/filepath" "strconv" "strings" "time" "github.com/ProtonMail/proton-bridge/internal/bridge" "github.com/ProtonMail/proton-bridge/internal/config/useragent" "github.com/ProtonMail/proton-bridge/internal/frontend/types" "github.com/ProtonMail/proton-bridge/pkg/mobileconfig" ) const ( bigSurPreferncesPane = "/System/Library/PreferencePanes/Profiles.prefPane" ) func init() { //nolint[gochecknoinit] available = append(available, &appleMail{}) } type appleMail struct{} func (c *appleMail) Name() string { return "Apple Mail" } func (c *appleMail) Configure(imapPort, smtpPort int, imapSSL, smtpSSL bool, user types.User, addressIndex int) error { mc := prepareMobileConfig(imapPort, smtpPort, imapSSL, smtpSSL, user, addressIndex) confPath, err := saveConfigTemporarily(mc) if err != nil { return err } if useragent.IsBigSurOrNewer() { return exec.Command("open", bigSurPreferncesPane, confPath).Run() //nolint[gosec] G204: open command is safe, mobileconfig is generated by us } return exec.Command("open", confPath).Run() //nolint[gosec] G204: open command is safe, mobileconfig is generated by us } func prepareMobileConfig(imapPort, smtpPort int, imapSSL, smtpSSL bool, user types.User, addressIndex int) *mobileconfig.Config { var addresses string var displayName string if user.IsCombinedAddressMode() { displayName = user.GetPrimaryAddress() addresses = strings.Join(user.GetAddresses(), ",") } else { for idx, address := range user.GetAddresses() { if idx == addressIndex { displayName = address break } } addresses = displayName } timestamp := strconv.FormatInt(time.Now().Unix(), 10) return &mobileconfig.Config{ EmailAddress: addresses, DisplayName: displayName, Identifier: "protonmail " + displayName + timestamp, IMAP: &mobileconfig.IMAP{ Hostname: bridge.Host, Port: imapPort, TLS: imapSSL, Username: displayName, Password: user.GetBridgePassword(), }, SMTP: &mobileconfig.SMTP{ Hostname: bridge.Host, Port: smtpPort, TLS: smtpSSL, Username: displayName, }, } } func saveConfigTemporarily(mc *mobileconfig.Config) (fname string, err error) { dir, err := ioutil.TempDir("", "protonmail-autoconfig") if err != nil { return } // Make sure the temporary file is deleted. go (func() { <-time.After(10 * time.Minute) _ = os.RemoveAll(dir) })() // Make sure the file is only readable for the current user. fname = filepath.Clean(filepath.Join(dir, "protonmail.mobileconfig")) f, err := os.OpenFile(fname, os.O_RDWR|os.O_CREATE, 0600) if err != nil { return } if err = mc.WriteOut(f); err != nil { _ = f.Close() return } _ = f.Close() return }