all repos — emessage @ 90d9fff40df22e96a11a479011c3c77e700d4416

The EMessage email client

mailaccount.go (view raw)

 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
package main

import (
	"log"
	"strconv"

	"github.com/emersion/go-imap/v2/imapclient"
	"github.com/emersion/go-sasl"
	"github.com/emersion/go-smtp"
	"github.com/zalando/go-keyring"
	"gorm.io/gorm"
)

type MailAccountConnection struct {
	gorm.Model
	Email        string `gorm:"uniqueIndex"`
	ImapServer   string
	ImapPort     int
	ImapUsername string
	SmtpServer   string
	SmtpPort     int
	SmtpUsername string
	Password     string             `gorm:"-:all"`
	ImapClient   *imapclient.Client `gorm:"-:all"`
	SmtpClient   *smtp.Client       `gorm:"-:all"`
	SmtpSasl     *sasl.Client       `gorm:"-:all"`
}

var mailConnections = make(map[string]MailAccountConnection)

func (app *App) GetMailConnections() ([]MailAccountConnection, error) {
	userContact := app.GetUserContact()
	connections := make([]MailAccountConnection, len(userContact.Emails))
	for index, email := range userContact.Emails {
		if err := app.DB.
			Where("email = ?", email.Value).
			First(&connections[index]).
			Error; err != nil {
			connections[index].Email = email.Value
			connections[index].ImapUsername = email.Value
			connections[index].ImapPort = 993
			connections[index].SmtpUsername = email.Value
			connections[index].SmtpPort = 465
			continue
		}
		mailPassword, err := keyring.Get(
			app.GetMailPasswordKeyringService(),
			email.Value,
		)
		if err != nil {
			continue
		}
		connections[index].Password = mailPassword

		if connections[index].Email != "" &&
			connections[index].ImapServer != "" &&
			connections[index].ImapPort != 0 &&
			connections[index].ImapUsername != "" &&
			connections[index].SmtpServer != "" &&
			connections[index].SmtpPort != 0 &&
			connections[index].SmtpUsername != "" &&
			connections[index].Password != "" {
			mailConnections[email.Value] = connections[index]
		}
	}

	return connections, nil
}

func (app *App) SaveMailConnection(conn MailAccountConnection) error {
	keyring.Set(
		app.GetMailPasswordKeyringService(),
		conn.Email,
		conn.Password,
	)
	return app.DB.Save(&conn).Error
}

func RemoveMailConnection(address string) {
	if conn, ok := mailConnections[address]; ok {
		if conn.ImapClient != nil {
			conn.ImapClient.Close()
		}
		if conn.SmtpClient != nil {
			conn.SmtpClient.Close()
		}
		delete(mailConnections, address)
	}
}

func (app *App) EstablishMailConnections() {
	_, _ = app.GetMailConnections()
	for key, conn := range mailConnections {
		log.Printf("Attempting to connect to %s...\n", conn.Email)
		var err error

		if conn.ImapClient != nil {
			conn.ImapClient.Close()
		}
		switch conn.ImapPort {
		case 143:
			conn.ImapClient, err = imapclient.DialStartTLS(
				conn.ImapServer+":"+strconv.Itoa(conn.ImapPort),
				nil,
			)
			break
		case 993:
			conn.ImapClient, err = imapclient.DialTLS(
				conn.ImapServer+":"+strconv.Itoa(conn.ImapPort),
				nil,
			)
			break
		default:
			conn.ImapClient, err = imapclient.DialTLS(
				conn.ImapServer+":"+strconv.Itoa(conn.ImapPort),
				nil,
			)
			break
		}
		if err != nil {
			RemoveMailConnection(conn.Email)
			log.Print(err.Error())
			continue
		}

		if err := conn.ImapClient.Login(conn.ImapUsername, conn.Password).Wait(); err != nil {
			RemoveMailConnection(conn.Email)
			log.Print(err.Error())
			continue
		}

		if conn.SmtpClient != nil {
			conn.SmtpClient.Close()
		}
		switch conn.SmtpPort {
		case 25:
			conn.SmtpClient, err = smtp.Dial(conn.SmtpServer + ":" + strconv.Itoa(conn.SmtpPort))
			break
		case 587:
			conn.SmtpClient, err = smtp.DialStartTLS(
				conn.SmtpServer+":"+strconv.Itoa(conn.SmtpPort),
				nil,
			)
			break
		case 465:
			conn.SmtpClient, err = smtp.DialTLS(
				conn.SmtpServer+":"+strconv.Itoa(conn.SmtpPort),
				nil,
			)
			break
		default:
			conn.SmtpClient, err = smtp.DialTLS(
				conn.SmtpServer+":"+strconv.Itoa(conn.SmtpPort),
				nil,
			)
			break
		}
		if err != nil {
			RemoveMailConnection(conn.Email)
			log.Print(err.Error())
			continue
		}

		auth := sasl.NewPlainClient("", conn.SmtpUsername, conn.Password)
		if err := conn.SmtpClient.Auth(auth); err != nil {
			RemoveMailConnection(conn.Email)
			log.Print(err.Error())
			continue
		}
		conn.SmtpSasl = &auth

		mailConnections[key] = conn
		log.Printf("Successfully connected %s!\n", conn.Email)
	}
}

func CloseMailConnections() {
	for key, _ := range mailConnections {
		RemoveMailConnection(key)
	}
}