From 406221dc8d42a6beda5aca96dd7d668751e53635 Mon Sep 17 00:00:00 2001
From: Simeon Simeonov
Date: Wed, 8 Apr 2020 21:19:40 +0200
Subject: Initial release based on sgs' private overlay
---
README.md | 34 +
dev-python/pyotp2289/Manifest | 3 +
dev-python/pyotp2289/metadata.xml | 13 +
dev-python/pyotp2289/pyotp2289-1.0.0.ebuild | 38 +
mail-client/sylpheed/Manifest | 8 +
.../sylpheed/files/sylpheed-3.7.0-ALL.patch | 2149 ++++++++++++++++++++
.../files/sylpheed-3.7.0-PGP-signature-fix.patch | 18 +
.../files/sylpheed-3.7.0-master-password.patch | 1850 +++++++++++++++++
.../files/sylpheed-3.7.0-signature-box.patch | 267 +++
.../sylpheed/files/sylpheed-3.7.0-version.patch | 26 +
mail-client/sylpheed/metadata.xml | 10 +
mail-client/sylpheed/sylpheed-3.7.0-r1.ebuild | 76 +
metadata/layout.conf | 9 +
profiles/repo_name | 1 +
x11-misc/i3lock-extended/Manifest | 3 +
.../i3lock-extended/i3lock-extended-1.12.1.ebuild | 44 +
x11-misc/i3lock-extended/metadata.xml | 11 +
x11-terms/sakura/Manifest | 5 +
x11-terms/sakura/files/sakura-3.7.0-gentoo.patch | 23 +
.../sakura/files/sakura-3.7.0-single-tab.patch | 13 +
x11-terms/sakura/metadata.xml | 20 +
x11-terms/sakura/sakura-3.7.0.ebuild | 63 +
22 files changed, 4684 insertions(+)
create mode 100644 README.md
create mode 100644 dev-python/pyotp2289/Manifest
create mode 100644 dev-python/pyotp2289/metadata.xml
create mode 100644 dev-python/pyotp2289/pyotp2289-1.0.0.ebuild
create mode 100644 mail-client/sylpheed/Manifest
create mode 100644 mail-client/sylpheed/files/sylpheed-3.7.0-ALL.patch
create mode 100644 mail-client/sylpheed/files/sylpheed-3.7.0-PGP-signature-fix.patch
create mode 100644 mail-client/sylpheed/files/sylpheed-3.7.0-master-password.patch
create mode 100644 mail-client/sylpheed/files/sylpheed-3.7.0-signature-box.patch
create mode 100644 mail-client/sylpheed/files/sylpheed-3.7.0-version.patch
create mode 100644 mail-client/sylpheed/metadata.xml
create mode 100644 mail-client/sylpheed/sylpheed-3.7.0-r1.ebuild
create mode 100644 metadata/layout.conf
create mode 100644 profiles/repo_name
create mode 100644 x11-misc/i3lock-extended/Manifest
create mode 100644 x11-misc/i3lock-extended/i3lock-extended-1.12.1.ebuild
create mode 100644 x11-misc/i3lock-extended/metadata.xml
create mode 100644 x11-terms/sakura/Manifest
create mode 100644 x11-terms/sakura/files/sakura-3.7.0-gentoo.patch
create mode 100644 x11-terms/sakura/files/sakura-3.7.0-single-tab.patch
create mode 100644 x11-terms/sakura/metadata.xml
create mode 100644 x11-terms/sakura/sakura-3.7.0.ebuild
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..791bef7
--- /dev/null
+++ b/README.md
@@ -0,0 +1,34 @@
+# sgs - overlay
+
+*sgs - overlay* is Simeon Simeonov's personal Gentoo overlay.
+
+The main purpose is to present unofficial ports (ebuilds) to the users of
+Gentoo GNU/Linux.
+
+
+## Ports / ebuilds
+
+The following ports are currently available:
+
+* dev-python/pyotp2289 - a pure Python 3 implementation of "A One-Time
+Password System" - RFC-2289. - https://github.com/blackm0re/pyotp2289
+
+* mail-client/sylpheed - Sylpheed fork that implements features and fixes not
+present in the official Sylpheed release - https://github.com/blackm0re/sylpheed
+
+* x11-misc/i3lock-extended - a fork of i3lock offering some additional features
+and niceties - https://github.com/blackm0re/i3lock-extended
+
+* x11-terms/sakura - contains own patch that prevents tab events to be consumed
+when no multiple tabs are spawned. This patch is expected to go upstream in the
+next official release of *sakura*.
+
+
+## Setup
+
+TODO: The overlay is not added to the official overlay list as of now
+
+
+## Author
+
+Simeon Simeonov - sgs @ Freenode
diff --git a/dev-python/pyotp2289/Manifest b/dev-python/pyotp2289/Manifest
new file mode 100644
index 0000000..449494b
--- /dev/null
+++ b/dev-python/pyotp2289/Manifest
@@ -0,0 +1,3 @@
+DIST pyotp2289-1.0.0.tar.gz 30245 BLAKE2B be873220aa5d377b52088559291110fea227c6b0ffee45b6526fd72be3c4b0785186bf96817245639b6d79c03d4e2a0e653059b5b214ff0834cf68787de693de SHA512 1c73fcdfcdecc2db11e598601c8a5f962a571c2aaa0287299df78f189c6512ffe8cfeac0b65ef402f1def69d6e12a82195a2f6851d1379e6fec4e5320f0914e6
+EBUILD pyotp2289-1.0.0.ebuild 886 BLAKE2B 6d7df8b81556dc208d790d627826692914de65725a72f61cbc5eae05fd92f3a8b5a8d524de0256a043a0bd916cb923542d30f350ee66114c8760060decb9c52a SHA512 47414bfcfcae01d24b84397b70ecebe6b393a857b425eb554d16e4cb1afd5cdc655ff2263a05001a11f61ab6ae09e521dd08e7abc934d1d206c9acc2f3d34b69
+MISC metadata.xml 402 BLAKE2B 3169f6b02fb30ba6db5edd85532a4caf08670e44e27a9b690063549b5561a765e8d2a4b3cd129d196827e2d51e97b7e6da04347c07387cd25aec2792516b89a5 SHA512 2268e0dd77a8771835d0cd97ec124fd47d51a5ceec7f5c21baf62979e021c0aa38aa506740147b9fde97f9af3094f8e4635424043fe06d5db2ddf45398912928
diff --git a/dev-python/pyotp2289/metadata.xml b/dev-python/pyotp2289/metadata.xml
new file mode 100644
index 0000000..bc26e88
--- /dev/null
+++ b/dev-python/pyotp2289/metadata.xml
@@ -0,0 +1,13 @@
+
+
+
+
+ sgs@pichove.org
+ Simeon Simeonov
+
+
+
+ pyotp2289
+ blackm0re/pyotp2289
+
+
diff --git a/dev-python/pyotp2289/pyotp2289-1.0.0.ebuild b/dev-python/pyotp2289/pyotp2289-1.0.0.ebuild
new file mode 100644
index 0000000..7afcc97
--- /dev/null
+++ b/dev-python/pyotp2289/pyotp2289-1.0.0.ebuild
@@ -0,0 +1,38 @@
+# Copyright 1999-2020 Gentoo Authors
+# Distributed under the terms of the GNU General Public License v2
+
+EAPI=7
+
+DISTUTILS_USE_SETUPTOOLS=rdepend
+PYTHON_COMPAT=( python3_{6,7,8} )
+
+inherit distutils-r1
+
+DESCRIPTION="A pure Python 3 implementation of RFC-2289 - 'A One-Time Password System'"
+HOMEPAGE="https://github.com/blackm0re/pyotp2289"
+MY_PN="pyotp2289"
+MY_P="${MY_PN}-${PV}"
+SRC_URI="mirror://pypi/${MY_P:0:1}/${MY_PN}/${MY_P}.tar.gz"
+KEYWORDS="amd64 arm ~arm64 ppc ppc64 x86 ~amd64-linux ~x86-linux"
+S="${WORKDIR}/${MY_P}"
+
+LICENSE="BSD"
+SLOT="0"
+IUSE="test"
+RESTRICT="!test? ( test )"
+
+DEPEND="
+ dev-python/setuptools[${PYTHON_USEDEP}]
+ test? (
+ ${RDEPEND}
+ dev-python/pytest[${PYTHON_USEDEP}]
+ )"
+
+python_test() {
+ PYTHONPATH=${S}:${PYTHONPATH} \
+ pytest -vv -p no:httpbin || die "Testing failed with ${EPYTHON}"
+}
+
+python_install_all() {
+ distutils-r1_python_install_all
+}
diff --git a/mail-client/sylpheed/Manifest b/mail-client/sylpheed/Manifest
new file mode 100644
index 0000000..557767e
--- /dev/null
+++ b/mail-client/sylpheed/Manifest
@@ -0,0 +1,8 @@
+AUX sylpheed-3.7.0-ALL.patch 63415 BLAKE2B c40c55b039d5c457bf555e0b738f8c97925966f75b0357dad7cc4f10f9fbaa9e6156e84e65c7e89880e28b36e286de96761693718faac9d1056f720a2cae8b1e SHA512 96d4edfea92439d2f0dd4dec11305710e8d5513979d4bd96113acdbe2338c9452da64ded98204b7af7c84ed86f22337a0248be109a6a8e20d3d1b62929ce70a3
+AUX sylpheed-3.7.0-PGP-signature-fix.patch 565 BLAKE2B c97f48f7844498323d944d1dc1925601ee470e50da26d4e434eb497b29b59fdf5144d019e30efc7392093422111f7c4bb0ff71a148d553db9fb2a2d26e86599a SHA512 089883841eae1c2ed806db1992ff94dd2906b1e825dbab7ae1b5d484815e446979d9f031851d4506ad0f4b00c462997d3704cbf605f7cbc9292e4b53bff5a0fe
+AUX sylpheed-3.7.0-master-password.patch 53668 BLAKE2B c2e898dbbaa4351fa9306a925c5aab56b6b63df4cebc130be7d44a33434524cf61e279fdc9b743d4b9d0c23dc84eda02c56a8c1b33c2309959a4c03c497ad232 SHA512 dcc987a9835ca2c38f06fe6aafdc9d05b750d691b1d1838b818557776be1129cc90b2982169510495ad30bc5e684b69dd614c747fff27b938620c7e0c8133498
+AUX sylpheed-3.7.0-signature-box.patch 9787 BLAKE2B 664b60b3a3421f12cc76a1a1cea64cd773509f17583e32acd9445581e0bed75289674ce82e018dd688204f932228ed379c6f68c31ff25c09e912ad74cbfe7394 SHA512 f1d1c165b701469cacf24eaab5512750d307145a551013e3ecd20e49817b4cd4d4113bd690d8ff6b1e6e9b1d96a45fb25c9417f5aa455e4063f85fc2d48ed464
+AUX sylpheed-3.7.0-version.patch 626 BLAKE2B fe3b90263c384429f9520bc357066f6cea80f81389cdc915ac30932276d5d3dbde5fdea16f649fb553f81d3487afbbdd41151604bd388e9d3ec12f44c7b9097a SHA512 26765397fe73ea03306513363f217f71b9a344590ad8716b34532279b1ade8c5feac0a9b981951bc58e85ae399c8efaab7435534441002c14a963c4c0b001eb8
+DIST sylpheed-3.7.0.tar.bz2 3612328 BLAKE2B bd8182db8a46b956e12b3da4b15d3ee8184a612e2fb216aca20fd4a022610b17416f994d36b390a5a92835915e95f08bd59bf71154a86962c9564162be891f21 SHA512 490837528bf7ba9d26994cd5fff00b6e5390a127419b9d0efd9fc25c38be1291d55c5b8daebdf5ca9d9159a51c938449e76212328f3eae40cc039db88cb5caa4
+EBUILD sylpheed-3.7.0-r1.ebuild 1801 BLAKE2B 35c5232beea2bf460817409bd552e3914b7d5453db4e5b2438cfd70be617b08d278b019873a48cc6430f75319289d077c3840023513cac31d4e4609a1d710a41 SHA512 13a808da1cb04f34c727cf1eaf3c8bca2158073efa459eb446c065fc46a6798d232d18af0aa8c221d2b22399f3c4c47f778ca45bcb5a59bffb24c03a91452e79
+MISC metadata.xml 333 BLAKE2B 45fdb2ceec80729593ac9d58a70399596ebff40c7eb68010f02a6025347b19775c8ec7483006e6c2abe826e6426cd53685835ccf2ed2b4670b16be8ea3da974c SHA512 0e5e8e8dcf6bbb73a907313cbc5bd234bc31c645759bd9ec33193fae5bd9b9b08cd884ea4adcb941b6693049f27522cd210bc3e7e6d14acaf1db595883ddbd4e
diff --git a/mail-client/sylpheed/files/sylpheed-3.7.0-ALL.patch b/mail-client/sylpheed/files/sylpheed-3.7.0-ALL.patch
new file mode 100644
index 0000000..0a34542
--- /dev/null
+++ b/mail-client/sylpheed/files/sylpheed-3.7.0-ALL.patch
@@ -0,0 +1,2149 @@
+diff --git a/.gitignore b/.gitignore
+new file mode 100644
+index 0000000..9742351
+--- /dev/null
++++ b/.gitignore
+@@ -0,0 +1,21 @@
++*.o
++*.lo
++*.la
++Makefile
++config.h
++config.log
++config.status
++libsylph/.deps/
++libsylph/.libs/
++libtool
++plugin/attachment_tool/.deps/
++plugin/attachment_tool/.libs/
++plugin/test/.deps/
++plugin/test/.libs/
++po/Makefile.in
++po/POTFILES
++src/.deps/
++src/.libs/
++src/sylpheed
++src/sylpheed.rc
++stamp-h1
+diff --git a/README.md b/README.md
+new file mode 100644
+index 0000000..1cf6d21
+--- /dev/null
++++ b/README.md
+@@ -0,0 +1,219 @@
++## Implemented features and fixes not present in the official Sylpheed release
++
++- (fix)
++ PGP signature not verified properly when the message has no newline
++ at the end. https://sylpheed.sraoss.jp/redmine/issues/288
++
++- (feature)
++ Make it possible to select "Show signature check result in a popup window"
++ only for bad signatures.
++
++- (feature)
++ Support for encrypting and storing encrypted passwords using a master password.
++ Read bellow for more details!
++
++
++## Master password
++
++The master password feature is developed by Simeon Simeonov (sgs)
++and is currently in an experimental state.
++
++
++### Motivation
++
++Currently Sylpheed is storing passwords in plain-text. One can always refrain
++from storing passwords and let Sylpheed prompt for them, but the more accounts
++one has, the more annoying this becomes.
++
++The goal is to have the passwords stored in a secure way and let Sylpheed only
++prompt for the master password.
++
++
++### Security goals
++
++- attacker (A) should not be able to derive the password from the digest.
++
++- A should not be able to derive the master password even if she has
++ read and write access to the storage.
++
++- A should not be able to determine the length of the encrypted password
++ even if she has read and write access to the storage.
++
++- A should not be able to craft an edited password without obtaining the
++ master password.
++
++- A should not be able to compromise the master password or force Sylpheed
++ to store decrypted passwords even if she has access to a running Sylpheed.
++
++- a warning / prompt should be given if a user accidentally types in a "wrong"
++ master password before decryption is initiated.
++
++
++### Usage in Sylpheed
++
++- backup your Sylpheed profile (often $HOME/.sylpheed-2.0)!
++
++- start Sylpheed and open "Configuration" -> "Common preferences..."!
++
++- select the "Master password" tab, enable "Use master password" and
++ apply the changes!
++
++- restart Sylpheed (exit and then start Sylpheed again)!
++
++- you will be asked to type and verify a new master password.
++
++- set your new passwords from the "Configuration" -> "Edit accounts..."!
++
++- select "Automatically unload master password after session initialization"
++ for increased security and decreased convenience.
++
++Note:
++Sylpheed will automatically convert existing stored passwords, but it will not
++touch your backups. You will have to remove all remnants of plain-text
++passwords manually.
++
++
++### Choice of cryptographic primitives
++
++The primary concern when selecting cryptographic primitives was portability.
++The desire was to go for primitives that are both strong and available in all
++supported production distributions of OpenSSL and LibreSSL.
++
++
++#### Cipher
++
++When it comes to implementation, there are several advantages in using stream
++cipher or a block cipher that behaves like a stream cipher when used in a
++certain mode of operation. One is avoiding to deal with padding.
++
++AES-256 operating in CFB was selected for these reasons.
++ChaCha20 should be considered as a replacement in the future.
++
++
++#### Hash-function
++
++Hash-functions are used for:
++- key derivation
++- plain-text digest
++
++Those operations do not have to use the same hash-function.
++(See the "Encryption & decryption scheme" section for more details!)
++
++Key-derivation:
++Since AES-256 uses a 256 bits key, we need a hash-function with at least
++the same digest size or bigger.
++Since the digest (which is the key itself) is considered confidential and is
++stored only in memory for only a limited amount of time, SHA-256 is considered
++sufficiently strong for that purpose.
++
++Size + plain-text + padding digest:
++Since the digest is created of both the plain-text and the plain-text size,
++as well as being encrypted, SHA-256 is considered sufficiently strong.
++SHA-512 may increase security at the price of adding additional 32 bytes
++to the encrypted password digest.
++
++Stronger hash-functions like SHA-3 or BLAKE2b can be considered as a
++replacement in the future.
++
++
++#### Master password digest
++
++In order to be able to decide whether the user typed a "wrong" master password,
++before attempting to decrypt, Sylpheed stores a digest of the master password
++in 'master_password_hash' in sylpheedrc.
++100000 iterations of PBKDF2_HMAC with SHA-512 and 16 bytes salt is used.
++Note that this digest is useless as a key and even if a plain-text that
++produces the same digest is found, it will most probably be useless as a
++master-password.
++
++
++### Encryption & decryption scheme
++
++
++#### Encryption
++
++
++Input:
++
++- plain-text password to be encrypted (P)
++
++- plain-text master-password used for key derivation (M)
++
++- integer minimum password length (0 < L < 100)
++
++
++Output:
++
++- an encrypted password digest (base64) (B)
++
++
++Operation:
++
++- generate 16 bytes of random data to be used as a salt (S)
++
++- derive the key (K): K = SHA_256(S + M)
++
++- produce a 2 byte string (N) indicating the length of P
++
++- generate random padding and set N:
++ - if the length of P < L, produce L - P bytes of random data (R), N = "%02d"
++ - if the length of P >= L, N = "-1"
++
++- produce a hash digest (H): H = SHA_256(N + P + R (if the length of P < L))
++
++- encrypt (E): E = AES_256_CFB_ENCRYPT(H + N + P + R (if the length of P < L), K)
++
++- B = mpes1:BASE64_ENCODE(S + E)
++
++example:
++mpes1:vo7lsIpD7i6byBA6+vlUoF4OVDfEe+aYRRk4FRtfJ2gMY8M43Kj6WfdfgbViIOl83bI4XEc96okhPW5Mla813aAR1gbPjDg0xmCyIbWOiUv/dg==
++
++
++#### Decryption
++
++
++Input:
++
++- encrypted password digest (base64) (B)
++
++- plain-text master-password used for key derivation (M)
++
++
++Output:
++
++- plain-text password (P)
++
++
++Operation:
++
++- remove the prefix (mpes1:) and base64-decode the rest of the digest: B = BASE64_DECODE(B)
++
++- fetch the first 16 bytes for the salt: S = B[0 : 15]
++
++- derive the key (K): K = SHA_256(S + M)
++
++- decrypt the rest of B (D): D = AES_256_CFB_DECRYPT(B[16 :], K)
++
++- extract the first 16 bytes for the hash digest (H): H = D[0 : 15]
++
++- in order to detect data-inconsistency, assert H == SHA_256(D[16 :])
++
++- extract the next 2 bytes for the length of P (N): N = D[16 : 17]
++
++- fetch the plain-text:
++ - if N == "-1" the password is the remaining bytes of D: P = D[18 :]
++ - if N != "-1", extract the next N-bytes from D: P = D[18 : (18 + N)]
++
++
++### Limitations
++
++- currently only the 'password' and 'smtp_password' keys in accountrc
++ are encrypted.
++ A mechanism that allows for any key and even folders to be encrypted
++ should be considered in the future.
++
++- currently it is not possible to select alternative ciphers, hash-functions
++ and modes of operation (without editing the source code).
++
++- currently it is not possible to change your master password without having to
++ (re)set your passwords manually.
+diff --git a/libsylph/Makefile.am b/libsylph/Makefile.am
+index 8f2b76b..40d26a9 100644
+--- a/libsylph/Makefile.am
++++ b/libsylph/Makefile.am
+@@ -19,6 +19,7 @@ libsylph_0_la_SOURCES = \
+ folder.c \
+ html.c \
+ imap.c \
++ masterpassword.c \
+ mbox.c \
+ md5.c \
+ md5_hmac.c \
+@@ -62,6 +63,7 @@ libsylph_0include_HEADERS = \
+ folder.h \
+ html.h \
+ imap.h \
++ masterpassword.h \
+ mbox.h \
+ md5.h \
+ md5_hmac.h \
+diff --git a/libsylph/Makefile.in b/libsylph/Makefile.in
+index 6e3c999..ac8fefd 100644
+--- a/libsylph/Makefile.in
++++ b/libsylph/Makefile.in
+@@ -129,8 +129,8 @@ libsylph_0_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+ am_libsylph_0_la_OBJECTS = account.lo base64.lo codeconv.lo \
+ customheader.lo displayheader.lo filter.lo folder.lo html.lo \
+- imap.lo mbox.lo md5.lo md5_hmac.lo mh.lo news.lo nntp.lo \
+- pop.lo prefs.lo prefs_account.lo prefs_common.lo procheader.lo \
++ imap.lo masterpassword.lo mbox.lo md5.lo md5_hmac.lo mh.lo news.lo \
++ nntp.lo pop.lo prefs.lo prefs_account.lo prefs_common.lo procheader.lo \
+ procmime.lo procmsg.lo quoted-printable.lo recv.lo session.lo \
+ smtp.lo socket.lo socks.lo ssl.lo ssl_hostname_validation.lo \
+ stringtable.lo sylmain.lo unmime.lo utils.lo uuencode.lo \
+@@ -402,6 +402,7 @@ libsylph_0_la_SOURCES = \
+ folder.c \
+ html.c \
+ imap.c \
++ masterpassword.c \
+ mbox.c \
+ md5.c \
+ md5_hmac.c \
+@@ -445,6 +446,7 @@ libsylph_0include_HEADERS = \
+ folder.h \
+ html.h \
+ imap.h \
++ masterpassword.h \
+ mbox.h \
+ md5.h \
+ md5_hmac.h \
+@@ -579,6 +581,7 @@ distclean-compile:
+ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/folder.Plo@am__quote@
+ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/html.Plo@am__quote@
+ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap.Plo@am__quote@
++@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/masterpassword.Plo@am__quote@
+ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mbox.Plo@am__quote@
+ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/md5.Plo@am__quote@
+ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/md5_hmac.Plo@am__quote@
+diff --git a/libsylph/defs.h b/libsylph/defs.h
+index 9e3f82b..67325bf 100644
+--- a/libsylph/defs.h
++++ b/libsylph/defs.h
+@@ -58,6 +58,9 @@
+ #define DISPLAY_HEADER_RC "dispheaderrc"
+ #define MENU_RC "menurc"
+ #define ACTIONS_RC "actionsrc"
++#ifdef USE_SSL
++#define SECURE_RC "securerc"
++#endif
+ #define COMMAND_HISTORY "command_history"
+ #define TEMPLATE_DIR "templates"
+ #define TMP_DIR "tmp"
+diff --git a/libsylph/imap.c b/libsylph/imap.c
+index aa0d737..e1c7cfd 100644
+--- a/libsylph/imap.c
++++ b/libsylph/imap.c
+@@ -52,6 +52,7 @@
+ #include "utils.h"
+ #include "prefs_common.h"
+ #include "virtual.h"
++#include "masterpassword.h"
+
+ #define IMAP4_PORT 143
+ #if USE_SSL
+@@ -705,9 +706,13 @@ static gint imap_session_connect(IMAPSession *session)
+ account = (PrefsAccount *)(SESSION(session)->data);
+
+ log_message(_("creating IMAP4 connection to %s:%d ...\n"),
+- SESSION(session)->server, SESSION(session)->port);
+-
+- pass = account->passwd;
++ SESSION(session)->server, SESSION(session)->port);
++ if (master_password_active()) {
++ pass = decrypt_with_master_password(account->passwd);
++ /* a new string is allocated. To be removed ... */
++ } else {
++ pass = account->passwd;
++ }
+ if (!pass)
+ pass = account->tmp_pass;
+ if (!pass) {
+@@ -770,8 +775,11 @@ static gint imap_session_connect(IMAPSession *session)
+ #endif
+
+ if (!session->authenticated &&
+- imap_auth(session, account->userid, pass, account->imap_auth_type)
+- != IMAP_SUCCESS) {
++ imap_auth(session, account->userid, pass, account->imap_auth_type)
++ != IMAP_SUCCESS) {
++ if (master_password_active()) {
++ g_free(pass); /* remove the decrypted password */
++ }
+ if (account->tmp_pass) {
+ g_free(account->tmp_pass);
+ account->tmp_pass = NULL;
+@@ -780,6 +788,10 @@ static gint imap_session_connect(IMAPSession *session)
+ return IMAP_AUTHFAIL;
+ }
+
++ if (master_password_active()) {
++ g_free(pass); /* remove the decrypted password */
++ }
++
+ return IMAP_SUCCESS;
+ }
+
+diff --git a/libsylph/masterpassword.c b/libsylph/masterpassword.c
+new file mode 100644
+index 0000000..3d7af94
+--- /dev/null
++++ b/libsylph/masterpassword.c
+@@ -0,0 +1,216 @@
++/*
++ * LibSylph -- E-Mail client library
++ * Copyright (C) 1999-2018 Hiroyuki Yamamoto
++ *
++ * This library is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2.1 of the License, or (at your option) any later version.
++ *
++ * This library 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
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General Public
++ * License along with this library; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
++ */
++
++#ifdef HAVE_CONFIG_H
++#include "config.h"
++#endif
++
++#include
++
++#include "prefs_common.h"
++#include "ssl.h"
++#include "utils.h"
++#include "masterpassword.h"
++
++
++gchar *master_password;
++gboolean master_password_enabled_on_init;
++
++void set_master_password(const char *password) {
++ master_password = password;
++}
++
++gchar *get_master_password(void) {
++ return master_password;
++}
++
++void cleanse_buffer(void *buf, size_t len) {
++#if USE_SSL
++ OPENSSL_cleanse(buf, len);
++#else
++ memset(buf, 0, len); /* better than nothing */
++#endif
++}
++
++void unload_master_password(void) {
++
++ debug_print("Unloading master password\n");
++ if (master_password == NULL) {
++ /* not loaded / already unloaded */
++ return;
++ }
++ cleanse_buffer(master_password, strlen(master_password));
++ g_free(master_password);
++ master_password = NULL;
++ debug_print("Master password unloaded\n");
++
++}
++
++gint mpes_string_prefix(const gchar *str) {
++
++ /* this function will be expanded in time as the format changes */
++ if (g_str_has_prefix(str, "mpes1:"))
++ return 6;
++ return 0;
++
++}
++
++gboolean master_password_active(void) {
++
++#if USE_SSL
++ return ((master_password != NULL) &&
++ prefs_common.use_master_password);
++#else
++ return FALSE;
++#endif
++
++}
++
++gchar *decrypt_with_master_password(const gchar *str) {
++
++#if USE_SSL
++ gchar *new_str;
++ gint str_prefix;
++
++ if ((!str) || (!prefs_common.use_master_password))
++ return g_strdup(str);
++
++ if (master_password == NULL) {
++ /* we have empty or auto unloaded master password */
++ if ((!prefs_common.auto_unload_master_password) ||
++ (check_master_password_interactively(3) != MP_RC_OK)) {
++ return g_strdup(str);
++ }
++ debug_print("Reloaded master password\n");
++ }
++
++ str_prefix = mpes_string_prefix(str);
++ if (!str_prefix)
++ return g_strdup(str);
++
++ if (decrypt_data(&new_str,
++ str + str_prefix,
++ master_password,
++ strlen(str) + 1 - str_prefix) != MP_RC_OK) {
++ OPENSSL_cleanse(new_str, strlen(new_str));
++ g_free(new_str);
++ return g_strdup(str);
++ }
++
++ return new_str;
++#else
++ return g_strdup(str);
++#endif
++
++}
++
++gchar *encrypt_with_master_password(const gchar *str) {
++
++#if USE_SSL
++ gchar *new_str, *mpes1_str;
++ gint length_encrypted;
++
++ if ((!str) || (!master_password_active()))
++ return NULL;
++
++ /*
++ * unlike the decrypt function, here it is up to the caller
++ * to make sure that auto unloaded master password is handled properly
++ */
++
++ if (encrypt_data(&new_str,
++ &length_encrypted,
++ str,
++ master_password,
++ strlen(str) + 1,
++ prefs_common.encrypted_password_min_length,
++ TRUE) != MP_RC_OK) {
++ g_free(new_str);
++ return NULL;
++ }
++
++ mpes1_str = g_strdup_printf("mpes1:%s", new_str);
++ g_free(new_str);
++
++ return mpes1_str;
++#else
++ return NULL;
++#endif
++
++}
++
++#if USE_SSL
++gint set_master_password_interactively(guint max_attempts) {
++
++ if (master_password == NULL)
++ master_password = input_set_new_password(max_attempts);
++
++ if (master_password == NULL)
++ return 1;
++
++ if (generate_password_hash(
++ &prefs_common.master_password_hash,
++ master_password,
++ NULL) != MP_RC_OK) {
++ /* should not really happen unless buggy code / library */
++ g_free(prefs_common.master_password_hash);
++ prefs_common.master_password_hash = NULL;
++ debug_print(_("Could not generate master password hash"));
++ return 1;
++ }
++
++ prefs_common_write_config();
++ return 0;
++
++}
++
++gint check_master_password_interactively(guint max_attempts) {
++
++ guint cnt;
++
++ g_return_val_if_fail(max_attempts > 0, 1);
++ g_return_val_if_fail(prefs_common.master_password_hash != NULL, 1);
++
++ if (master_password != NULL) {
++ /* password already cached */
++ debug_print("Master password already cached\n");
++ return check_password(master_password,
++ prefs_common.master_password_hash);
++ }
++
++ for (cnt = 0; cnt < max_attempts; ++cnt) {
++ master_password = input_query_master_password();
++ if (master_password == NULL) {
++ /* input canceled or query_master_password_func not set */
++ continue;
++ }
++ if (check_password(master_password,
++ prefs_common.master_password_hash) == MP_RC_OK) {
++ return MP_RC_OK; /* match */
++ }
++ debug_print(_("Wrong master password entered (%d)\n"), cnt);
++ OPENSSL_cleanse(master_password, strlen(master_password));
++ g_free(master_password);
++ master_password = NULL;
++ }
++
++ return 1; /* no match */
++
++}
++#endif /* USE_SSL */
+diff --git a/libsylph/masterpassword.h b/libsylph/masterpassword.h
+new file mode 100644
+index 0000000..7254643
+--- /dev/null
++++ b/libsylph/masterpassword.h
+@@ -0,0 +1,47 @@
++/*
++ * LibSylph -- E-Mail client library
++ * Copyright (C) 1999-2018 Hiroyuki Yamamoto
++ *
++ * This library is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2.1 of the License, or (at your option) any later version.
++ *
++ * This library 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
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General Public
++ * License along with this library; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
++ */
++
++#ifndef __MASTERPASSWORD_H__
++#define __MASTERPASSWORD_H__
++
++#ifdef HAVE_CONFIG_H
++#include "config.h"
++#endif
++
++#define MP_RC_OK 0
++#define MP_RC_WRONG_HASH_OR_KEY 1
++#define MP_RC_INVALID_FORMAT 2 /* invalid digest format */
++
++extern gchar *master_password;
++extern gboolean master_password_enabled_on_init; /* m.p. enabled on init? */
++void set_master_password(const char *password);
++gchar *get_master_password(void);
++void cleanse_buffer(void *buf, size_t len);
++void unload_master_password(void);
++gint mpes_string_prefix(const gchar *str);
++gboolean master_password_active(void);
++gchar *decrypt_with_master_password(const gchar *str);
++gchar *encrypt_with_master_password(const gchar *str);
++
++#if USE_SSL
++gint set_master_password_interactively(guint max_attempts);
++gint check_master_password_interactively(guint max_attempts);
++#endif /* USE_SSL */
++
++#endif /* __MASTERPASSWORD_H__ */
+diff --git a/libsylph/news.c b/libsylph/news.c
+index ffff9f9..36d3b10 100644
+--- a/libsylph/news.c
++++ b/libsylph/news.c
+@@ -44,6 +44,7 @@
+ #include "utils.h"
+ #include "prefs_common.h"
+ #include "prefs_account.h"
++#include "masterpassword.h"
+ #if USE_SSL
+ # include "ssl.h"
+ #endif
+@@ -249,10 +250,11 @@ static Session *news_session_new_for_folder(Folder *folder)
+ ac = folder->account;
+ if (ac->use_nntp_auth && ac->userid && ac->userid[0]) {
+ userid = ac->userid;
+- if (ac->passwd && ac->passwd[0])
+- passwd = g_strdup(ac->passwd);
+- else
++ if (ac->passwd && ac->passwd[0]) {
++ passwd = decrypt_with_master_password(ac->passwd);
++ } else {
+ passwd = input_query_password(ac->nntp_server, userid);
++ }
+ }
+
+ if (ac->use_socks && ac->use_socks_for_recv && ac->proxy_host) {
+diff --git a/libsylph/pop.c b/libsylph/pop.c
+index 8cb7f5c..85f3ed9 100644
+--- a/libsylph/pop.c
++++ b/libsylph/pop.c
+@@ -39,6 +39,7 @@
+ #include "prefs_account.h"
+ #include "utils.h"
+ #include "recv.h"
++#include "masterpassword.h"
+
+ gint pop3_greeting_recv (Pop3Session *session,
+ const gchar *msg);
+@@ -437,8 +438,9 @@ Session *pop3_session_new(PrefsAccount *account)
+ session->error_msg = NULL;
+
+ session->user = g_strdup(account->userid);
+- session->pass = account->passwd ? g_strdup(account->passwd) :
+- account->tmp_pass ? g_strdup(account->tmp_pass) : NULL;
++ session->pass = account->passwd ? decrypt_with_master_password(
++ account->passwd) : account->tmp_pass ? g_strdup(
++ account->tmp_pass) : NULL;
+
+ SESSION(session)->server = g_strdup(account->recv_server);
+
+diff --git a/libsylph/prefs_account.c b/libsylph/prefs_account.c
+index 1aecba9..54cbc89 100644
+--- a/libsylph/prefs_account.c
++++ b/libsylph/prefs_account.c
+@@ -34,6 +34,7 @@
+ #include "customheader.h"
+ #include "account.h"
+ #include "utils.h"
++#include "masterpassword.h"
+
+ static PrefsAccount tmp_ac_prefs;
+
+@@ -205,7 +206,7 @@ PrefsAccount *prefs_account_new(void)
+ void prefs_account_read_config(PrefsAccount *ac_prefs, const gchar *label)
+ {
+ const gchar *p = label;
+- gchar *rcpath;
++ gchar *rcpath, *tmp_str;
+ gint id;
+
+ g_return_if_fail(ac_prefs != NULL);
+@@ -229,6 +230,34 @@ void prefs_account_read_config(PrefsAccount *ac_prefs, const gchar *label)
+ ac_prefs->use_apop_auth = TRUE;
+ }
+
++ if (master_password_active()) {
++ if ((ac_prefs->passwd != NULL) &&
++ !mpes_string_prefix(ac_prefs->passwd)) {
++ /* TODO: Perhaps some prompt? */
++ debug_print(
++ "%s -> converting passwd cleartext to encrypted\n",
++ label);
++ tmp_str = ac_prefs->passwd;
++ ac_prefs->passwd = encrypt_with_master_password(ac_prefs->passwd);
++ cleanse_buffer(tmp_str, strlen(tmp_str));
++ g_free(tmp_str);
++ }
++
++ if ((ac_prefs->smtp_passwd != NULL) &&
++ !mpes_string_prefix(ac_prefs->smtp_passwd)) {
++ /* TODO: Perhaps some prompt? */
++ debug_print(
++ "%s -> converting smtp_passwd from cleartext to encrypted\n",
++ label);
++ tmp_str = ac_prefs->smtp_passwd;
++ ac_prefs->smtp_passwd = encrypt_with_master_password(
++ ac_prefs->smtp_passwd);
++ cleanse_buffer(tmp_str, strlen(tmp_str));
++ g_free(tmp_str);
++ }
++
++ }
++
+ custom_header_read_config(ac_prefs);
+ }
+
+diff --git a/libsylph/prefs_common.c b/libsylph/prefs_common.c
+index 8ed74a3..192964e 100644
+--- a/libsylph/prefs_common.c
++++ b/libsylph/prefs_common.c
+@@ -406,6 +406,8 @@ static PrefParam param[] = {
+ P_BOOL},
+ {"gpg_signature_popup", "FALSE", &prefs_common.gpg_signature_popup,
+ P_BOOL},
++ {"gpg_signature_popup_mode", "1", &prefs_common.gpg_signature_popup_mode,
++ P_INT},
+ {"store_passphrase", "FALSE", &prefs_common.store_passphrase, P_BOOL},
+ {"store_passphrase_timeout", "0",
+ &prefs_common.store_passphrase_timeout, P_INT},
+@@ -416,6 +418,17 @@ static PrefParam param[] = {
+ {"show_gpg_warning", "TRUE", &prefs_common.gpg_warning, P_BOOL},
+ #endif
+
++ /* Master password */
++ {"use_master_password", "FALSE", &prefs_common.use_master_password,
++ P_BOOL},
++ {"master_password_hash", NULL, &prefs_common.master_password_hash,
++ P_STRING},
++ {"encrypted_password_min_length", "32",
++ &prefs_common.encrypted_password_min_length, P_INT},
++ {"auto_unload_master_password", "FALSE",
++ &prefs_common.auto_unload_master_password,
++ P_BOOL},
++
+ /* Interface */
+ {"separate_folder", "FALSE", &prefs_common.sep_folder, P_BOOL},
+ {"separate_message", "FALSE", &prefs_common.sep_msg, P_BOOL},
+diff --git a/libsylph/prefs_common.h b/libsylph/prefs_common.h
+index ba9ddb5..6f9c364 100644
+--- a/libsylph/prefs_common.h
++++ b/libsylph/prefs_common.h
+@@ -243,11 +243,18 @@ struct _PrefsCommon
+ /* Privacy */
+ gboolean auto_check_signatures;
+ gboolean gpg_signature_popup;
++ gint gpg_signature_popup_mode;
+ gboolean store_passphrase;
+ gint store_passphrase_timeout;
+ gboolean passphrase_grab;
+ gboolean gpg_warning;
+
++ /* Master password */
++ gboolean use_master_password;
++ gchar *master_password_hash;
++ guint encrypted_password_min_length;
++ gboolean auto_unload_master_password;
++
+ /* Interface */
+ gboolean sep_folder;
+ gboolean sep_msg;
+diff --git a/libsylph/ssl.c b/libsylph/ssl.c
+index 8413925..e782b0e 100644
+--- a/libsylph/ssl.c
++++ b/libsylph/ssl.c
+@@ -32,6 +32,13 @@
+ #include "ssl.h"
+ #include "ssl_hostname_validation.h"
+
++#define SALT_SIZE 16
++#define CIPHER EVP_aes_256_cfb()
++#define KEY_HASH EVP_sha256()
++#define DIGEST_HASH EVP_sha256()
++#define PBKDF2_DIGEST_SIZE 64
++#define PBKDF2_ITERATIONS 100000
++
+ static SSL_CTX *ssl_ctx_SSLv23 = NULL;
+ static SSL_CTX *ssl_ctx_TLSv1 = NULL;
+
+@@ -402,4 +409,468 @@ void ssl_set_verify_func(SSLVerifyFunc func)
+ verify_ui_func = func;
+ }
+
++/* master password related functions */
++static gint secure_derive_key(guchar *key,
++ gint length_key,
++ const gchar *passphrase,
++ const guchar *salt) {
++
++ guint length_buffer, length_hash;
++ guchar *buffer, *ptr_hash;
++
++ EVP_MD_CTX *mdctx;
++
++ OPENSSL_cleanse(key, length_key);
++
++ length_buffer = SALT_SIZE + strlen(passphrase);
++ buffer = OPENSSL_malloc(length_buffer);
++ OPENSSL_cleanse(buffer, length_buffer);
++
++ memcpy(buffer, salt, SALT_SIZE);
++ memcpy(buffer + SALT_SIZE, passphrase, strlen(passphrase));
++
++ mdctx = EVP_MD_CTX_create();
++ EVP_DigestInit_ex(mdctx, KEY_HASH, NULL);
++ EVP_DigestUpdate(mdctx, buffer, length_buffer);
++ OPENSSL_cleanse(buffer, length_buffer);
++
++ ptr_hash = OPENSSL_malloc(EVP_MD_size(KEY_HASH));
++ EVP_DigestFinal_ex(mdctx, ptr_hash, &length_hash);
++
++ memcpy(key,
++ ptr_hash,
++ (length_hash > length_key) ? length_key : length_hash);
++
++ OPENSSL_cleanse(ptr_hash, length_hash);
++ OPENSSL_free(ptr_hash);
++ OPENSSL_free(buffer);
++ EVP_MD_CTX_destroy(mdctx);
++
++ return SSL_RC_OK;
++
++}
++
++gint encrypt_data(gchar **encrypted,
++ gint *length_encrypted,
++ const gchar *data,
++ const gchar *passphrase,
++ gint length_data,
++ guint min_data_length,
++ gboolean rnd_salt) {
++
++ gint crypt_buffer_cnt, rc;
++ guint key_size, length_hash;
++ guint length_cleartext, length_ciphertext, length_total;
++ guchar salt[SALT_SIZE];
++ guchar *ciphertext_buffer, *total_buffer;
++ /* sensitive buffers and counters */
++ guint length_data_payload, length_padding;
++ gchar str_data_size[3];
++ guchar *data_payload_buffer, *hash_buffer, *padding_buffer, *key;
++ guchar *cleartext_buffer;
++
++ EVP_CIPHER_CTX *ctx;
++ EVP_MD_CTX *mdctx;
++
++ rc = SSL_RC_ERROR;
++
++ if (length_data < 1) {
++ return -1;
++ }
++ if (rnd_salt) {
++ if (RAND_bytes(salt, SALT_SIZE) != 1) {
++ debug_print("Random problems...\n");
++ goto cleanup;
++ }
++ } else {
++ strncpy((gchar *)salt, "FOR TESTING ONLY", SALT_SIZE);
++ }
++
++ key_size = EVP_CIPHER_key_length(CIPHER);
++ key = OPENSSL_malloc(key_size);
++ OPENSSL_cleanse(key, key_size);
++ if (secure_derive_key(key,
++ key_size,
++ passphrase,
++ salt) != SSL_RC_OK) {
++ OPENSSL_cleanse(key, key_size);
++ debug_print("Could not generate secure key\n");
++ goto cleanup;
++ }
++
++ /* prepare the data-buffer */
++ length_padding = 0;
++ if (length_data < min_data_length) {
++ g_snprintf(str_data_size, 3, "%02d", length_data);
++ length_padding = min_data_length - length_data;
++ padding_buffer = OPENSSL_malloc(length_padding);
++ OPENSSL_cleanse(padding_buffer, length_padding);
++ if (RAND_bytes(padding_buffer, length_padding) != 1) {
++ debug_print("Random problems...\n");
++ goto cleanup;
++ }
++ } else {
++ g_snprintf(str_data_size, 3, "-1");
++ }
++
++ length_data_payload = 2 + length_data + length_padding;
++ data_payload_buffer = OPENSSL_malloc(length_data_payload);
++ OPENSSL_cleanse(data_payload_buffer, length_data_payload);
++
++ memcpy(data_payload_buffer, str_data_size, 2);
++ memcpy(data_payload_buffer + 2, data, length_data);
++ if (length_padding > 0) {
++ memcpy(data_payload_buffer + (2 + length_data),
++ padding_buffer,
++ length_padding);
++ }
++
++ mdctx = EVP_MD_CTX_create();
++ EVP_DigestInit_ex(mdctx, DIGEST_HASH, NULL);
++ EVP_DigestUpdate(mdctx, data_payload_buffer, length_data_payload);
++
++ length_hash = EVP_MD_size(DIGEST_HASH);
++ hash_buffer = OPENSSL_malloc(length_hash);
++ OPENSSL_cleanse(hash_buffer, length_hash);
++ EVP_DigestFinal_ex(mdctx, hash_buffer, NULL);
++
++ length_cleartext = length_hash + length_data_payload;
++
++ cleartext_buffer = OPENSSL_malloc(length_cleartext);
++ OPENSSL_cleanse(cleartext_buffer, length_cleartext);
++
++ /* assemble cleartext-buffer */
++ memcpy(cleartext_buffer, hash_buffer, length_hash);
++ memcpy(cleartext_buffer + length_hash,
++ data_payload_buffer,
++ length_data_payload);
++ OPENSSL_cleanse(data_payload_buffer, length_data_payload); /* sensitive */
++
++ /* encryption */
++ if (!(ctx = EVP_CIPHER_CTX_new())) {
++ debug_print("New ctx failed\n");
++ goto cleanup;
++ }
++
++ length_ciphertext = 0;
++ if (EVP_EncryptInit_ex(ctx, CIPHER, NULL, key, salt) != 1) {
++ debug_print("EVP_EncryptInit_ex failed\n");
++ goto cleanup;
++ }
++
++ ciphertext_buffer = OPENSSL_malloc(length_cleartext);
++
++ crypt_buffer_cnt = 0;
++ while(1) {
++ if (EVP_EncryptUpdate(ctx,
++ ciphertext_buffer + length_ciphertext,
++ &crypt_buffer_cnt,
++ cleartext_buffer + length_ciphertext,
++ 1) != 1) { /* one byte at a time */
++ debug_print("EVP_EncryptUpdate failed\n");
++ goto cleanup;
++ }
++
++ if (crypt_buffer_cnt != 1) { /* paranoia */
++ debug_print("The sizes of enc and dec text do not correspond\n");
++ goto cleanup;
++ }
++
++ ++length_ciphertext;
++
++ if (length_ciphertext >= length_cleartext) {
++ break;
++ }
++ }
++ /* No padding required for the CFB mode */
++
++ OPENSSL_cleanse(cleartext_buffer, length_cleartext); /* sensitive */
++ length_total = SALT_SIZE + length_ciphertext;
++ total_buffer = OPENSSL_malloc(length_total);
++
++ memcpy(total_buffer, salt, SALT_SIZE);
++ memcpy(total_buffer + SALT_SIZE, ciphertext_buffer, length_ciphertext);
++
++ *encrypted = g_base64_encode(total_buffer, length_total);
++ *length_encrypted = strlen(*encrypted);
++
++ rc = SSL_RC_OK;
++
++cleanup:
++ /* key */
++ OPENSSL_cleanse(key, key_size);
++ OPENSSL_free(key);
++ /* cleartext buffer */
++ OPENSSL_cleanse(cleartext_buffer, length_cleartext);
++ OPENSSL_free(cleartext_buffer);
++ /* payload buffer */
++ OPENSSL_cleanse(data_payload_buffer, length_data_payload);
++ OPENSSL_free(data_payload_buffer);
++ /* hash */
++ OPENSSL_cleanse(hash_buffer, length_hash);
++ OPENSSL_free(hash_buffer);
++ /* padding */
++ if (length_padding > 0) {
++ OPENSSL_cleanse(padding_buffer, length_padding);
++ OPENSSL_free(padding_buffer);
++ }
++
++ OPENSSL_cleanse(str_data_size, 3); /* paranoia */
++
++ /* ciphertext buffer */
++ OPENSSL_free(ciphertext_buffer);
++
++ /* total buffer */
++ OPENSSL_free(total_buffer);
++
++ length_data_payload = 0;
++ length_padding = 0;
++
++ EVP_CIPHER_CTX_free(ctx);
++ EVP_MD_CTX_destroy(mdctx);
++
++ return rc;
++
++}
++
++gint decrypt_data(gchar **decrypted,
++ const gchar *data,
++ const gchar *passphrase,
++ gint length_data) {
++
++ gint rc; /* return code */
++ gint decrypt_buffer_cnt, length_ciphertext, length_decrypted;
++ guint key_size, length_hash, length_cleartext;
++ gsize length_total;
++ guchar salt[SALT_SIZE];
++ guchar *ciphertext_buffer, *total_buffer;
++ /* sensitive buffers and counters */
++ gint data_size;
++ guint length_data_payload;
++ gchar str_data_size[3];
++ guchar *data_payload_buffer, *hash_buffer, *key;
++ guchar *cleartext_buffer;
++
++ EVP_CIPHER_CTX *ctx;
++ EVP_MD_CTX *mdctx;
++
++ rc = -1;
++
++ if (length_data < 1) {
++ return -1;
++ }
++
++ total_buffer = g_base64_decode(data, &length_total);
++ length_ciphertext = length_total - SALT_SIZE;
++ ciphertext_buffer = OPENSSL_malloc(length_ciphertext);
++ OPENSSL_cleanse(ciphertext_buffer, length_ciphertext);
++
++ memcpy(salt, total_buffer, SALT_SIZE);
++ memcpy(ciphertext_buffer, total_buffer + SALT_SIZE, length_ciphertext);
++
++ key_size = EVP_CIPHER_key_length(CIPHER);
++
++ /* decryption */
++ if(!(ctx = EVP_CIPHER_CTX_new())) {
++ debug_print("New ctx failed\n");
++ goto cleanup;
++ }
++
++ key = OPENSSL_malloc(key_size);
++ OPENSSL_cleanse(key, key_size);
++ if (secure_derive_key(key,
++ key_size,
++ passphrase,
++ salt) != 0) {
++ OPENSSL_cleanse(key, key_size);
++ debug_print("Could not generate secure key\n");
++ goto cleanup;
++ }
++
++ if (EVP_DecryptInit_ex(ctx, CIPHER, NULL, key, salt) != 1) {
++ debug_print("EVP_DecryptInit_ex failed\n");
++ goto cleanup;
++ }
++ OPENSSL_cleanse(key, key_size); /* highly sensitive */
++ cleartext_buffer = OPENSSL_malloc(length_ciphertext);
++ OPENSSL_cleanse(cleartext_buffer, length_ciphertext);
++
++ length_cleartext = 0;
++ decrypt_buffer_cnt = 0;
++
++ while(1) {
++ if (EVP_DecryptUpdate(ctx,
++ cleartext_buffer + length_cleartext,
++ &decrypt_buffer_cnt,
++ ciphertext_buffer + length_cleartext,
++ 1) != 1) { /* one byte at a time */
++ debug_print("EVP_EncryptUpdate failed\n");
++ goto cleanup;
++ }
++
++ if (decrypt_buffer_cnt != 1) { /* paranoia */
++ debug_print("The sizes of enc and dec text do not correspond");
++ goto cleanup;
++ }
++
++ ++length_cleartext;
++
++ if (length_cleartext >= length_ciphertext) {
++ break;
++ }
++ }
++
++
++ length_hash = EVP_MD_size(DIGEST_HASH);
++ hash_buffer = OPENSSL_malloc(length_hash);
++ length_data_payload = length_cleartext - length_hash;
++ data_payload_buffer = OPENSSL_malloc(length_data_payload);
++ OPENSSL_cleanse(data_payload_buffer, length_data_payload);
++
++ memcpy(data_payload_buffer,
++ cleartext_buffer + length_hash,
++ length_data_payload);
++
++ mdctx = EVP_MD_CTX_create();
++ EVP_DigestInit_ex(mdctx, DIGEST_HASH, NULL);
++ EVP_DigestUpdate(mdctx, data_payload_buffer, length_data_payload);
++ EVP_DigestFinal_ex(mdctx, hash_buffer, &length_hash);
++
++ if (strncmp((const gchar*) hash_buffer,
++ (const gchar*) cleartext_buffer,
++ length_hash) != 0) {
++ debug_print("Invalid hash\n");
++ rc = SSL_RC_WRONG_HASH_OR_KEY;
++ goto cleanup;
++ }
++
++ memcpy(str_data_size, cleartext_buffer + length_hash, 2);
++ data_size = atoi(str_data_size);
++
++ if (data_size < 0) {
++ length_decrypted = length_data_payload - 2;
++ } else {
++ length_decrypted = data_size;
++ }
++ *decrypted = OPENSSL_malloc(length_decrypted);
++ memcpy(*decrypted, data_payload_buffer + 2, length_decrypted);
++
++ rc = SSL_RC_OK;
++
++cleanup:
++
++ /* key */
++ OPENSSL_cleanse(key, key_size);
++ OPENSSL_free(key);
++ /* payload buffer */
++ OPENSSL_cleanse(data_payload_buffer, length_data_payload);
++ OPENSSL_free(data_payload_buffer);
++ /* hash */
++ OPENSSL_cleanse(hash_buffer, length_hash);
++ OPENSSL_free(hash_buffer);
++ /* ciphertext */
++ OPENSSL_free(ciphertext_buffer);
++ OPENSSL_free(total_buffer);
++
++ EVP_CIPHER_CTX_free(ctx);
++ EVP_MD_CTX_destroy(mdctx);
++
++ return rc;
++
++}
++
++gint generate_password_hash(gchar **password_hash,
++ const gchar *password,
++ const guchar *salt) {
++ /*
++ * Hashes 'password' using PKCS5_PBKDF2_HMAC with SHA512 and 'salt',
++ * and assigns a string to 'password_hash' with the following format:
++ * pbkdf2_sha512$iterations$base64(salt)$base64(password_hash)
++ */
++ guchar lsalt[SALT_SIZE];
++ gchar *PBKDF2_digest, *salt_b64, *digest_b64;
++
++ if (salt == NULL) {
++ if (RAND_bytes(lsalt, SALT_SIZE) != 1) {
++ debug_print("Random problems...\n");
++ return SSL_RC_ERROR;
++ }
++ } else {
++ memcpy(lsalt, salt, SALT_SIZE);
++ }
++
++ PBKDF2_digest = OPENSSL_malloc(PBKDF2_DIGEST_SIZE);
++ OPENSSL_cleanse(PBKDF2_digest, PBKDF2_DIGEST_SIZE);
++
++ PKCS5_PBKDF2_HMAC(password,
++ strlen(password),
++ lsalt,
++ SALT_SIZE,
++ PBKDF2_ITERATIONS,
++ EVP_sha512(),
++ PBKDF2_DIGEST_SIZE,
++ (guchar *) PBKDF2_digest);
++ digest_b64 = g_base64_encode((guchar *) PBKDF2_digest, PBKDF2_DIGEST_SIZE);
++ OPENSSL_cleanse(PBKDF2_digest, PBKDF2_DIGEST_SIZE);
++ OPENSSL_free(PBKDF2_digest);
++
++ salt_b64 = g_base64_encode(lsalt, SALT_SIZE);
++
++ *password_hash = g_strdup_printf("pbkdf2_sha512$%d$%s$%s",
++ PBKDF2_ITERATIONS,
++ salt_b64,
++ digest_b64);
++
++ OPENSSL_cleanse(digest_b64, strlen(digest_b64));
++ OPENSSL_free(digest_b64);
++
++ OPENSSL_cleanse(salt_b64, strlen(salt_b64));
++ OPENSSL_free(salt_b64);
++
++ return SSL_RC_OK;
++
++}
++
++gint check_password(const gchar *password, const gchar *password_hash) {
++
++ gint token_counter, rc;
++ guchar *salt;
++ gchar **tokens, *new_hash;
++ gsize salt_length;
++
++ rc = SSL_RC_ERROR;
++ tokens = g_strsplit(password_hash,
++ "$",
++ -1);
++ token_counter = 0;
++ while (*(tokens + token_counter) != NULL) {
++ ++token_counter;
++ }
++
++ if (token_counter != 4) {
++ debug_print("Invalid password hash...\n");
++ goto cleanup;
++ }
++
++ salt = g_base64_decode(*(tokens + 2), &salt_length);
++ if (salt_length != SALT_SIZE) {
++ debug_print("Salt size does not match\n");
++ goto cleanup;
++ }
++
++ if (generate_password_hash(&new_hash, password, salt) != SSL_RC_OK) {
++ debug_print("Password hash generation failed\n");
++ goto cleanup;
++ }
++
++ rc = g_strcmp0(password_hash, new_hash);
++
++cleanup:
++ g_free(salt);
++ g_free(new_hash);
++ g_strfreev(tokens);
++ return rc;
++
++}
++
+ #endif /* USE_SSL */
+diff --git a/libsylph/ssl.h b/libsylph/ssl.h
+index a9f690d..4c0ccd1 100644
+--- a/libsylph/ssl.h
++++ b/libsylph/ssl.h
+@@ -32,9 +32,15 @@
+ #include
+ #include
+ #include
++#include
++#include
+
+ #include "socket.h"
+
++#define SSL_RC_OK 0
++#define SSL_RC_ERROR -1
++#define SSL_RC_WRONG_HASH_OR_KEY 1
++
+ typedef enum {
+ SSL_METHOD_SSLv23,
+ SSL_METHOD_TLSv1
+@@ -60,6 +66,26 @@ void ssl_done_socket (SockInfo *sockinfo);
+
+ void ssl_set_verify_func (SSLVerifyFunc func);
+
++/* master password related code */
++gint encrypt_data(gchar **encrypted,
++ gint *length_encrypted,
++ const gchar *data,
++ const gchar *passphrase,
++ gint length_data,
++ guint min_data_length,
++ gboolean rnd_salt);
++
++gint decrypt_data(gchar **decrypted,
++ const gchar *data,
++ const gchar *passphrase,
++ gint length_data);
++
++gint generate_password_hash(gchar **password_hash,
++ const gchar *password,
++ const guchar *salt);
++
++gint check_password(const gchar *password, const gchar *password_hash);
++/* ---------------------------- */
+ #endif /* USE_SSL */
+
+ #endif /* __SSL_H__ */
+diff --git a/libsylph/utils.c b/libsylph/utils.c
+index aabce06..3bbcbcf 100644
+--- a/libsylph/utils.c
++++ b/libsylph/utils.c
+@@ -3306,10 +3306,10 @@ gint canonicalize_file(const gchar *src, const gchar *dest)
+ }
+ }
+
+- if (last_linebreak == TRUE) {
+- if (fputs("\r\n", dest_fp) == EOF)
+- err = TRUE;
+- }
++ /* if (last_linebreak == TRUE) { */
++ /* if (fputs("\r\n", dest_fp) == EOF) */
++ /* err = TRUE; */
++ /* } */
+
+ if (ferror(src_fp)) {
+ FILE_OP_ERROR(src, "fgets");
+@@ -4598,6 +4598,36 @@ gchar *input_query_password(const gchar *server, const gchar *user)
+ return NULL;
+ }
+
++static QueryMasterPasswordFunc query_master_password_func = NULL;
++
++void set_input_query_master_password_func(QueryMasterPasswordFunc func)
++{
++ query_master_password_func = func;
++}
++
++gchar *input_query_master_password(void)
++{
++ if (query_master_password_func)
++ return query_master_password_func();
++ else
++ return NULL;
++}
++
++static SetNewPasswordFunc set_new_password_func = NULL;
++
++void set_input_set_new_password_func(SetNewPasswordFunc func)
++{
++ set_new_password_func = func;
++}
++
++gchar *input_set_new_password(guint max_attempts)
++{
++ if (set_new_password_func)
++ return set_new_password_func(max_attempts);
++ else
++ return NULL;
++}
++
+ /* logging */
+
+ static FILE *log_fp = NULL;
+diff --git a/libsylph/utils.h b/libsylph/utils.h
+index 9ac65cf..ede55ff 100644
+--- a/libsylph/utils.h
++++ b/libsylph/utils.h
+@@ -202,6 +202,8 @@ typedef void (*ProgressFunc) (gint cur,
+ gint total);
+ typedef gchar * (*QueryPasswordFunc) (const gchar *server,
+ const gchar *user);
++typedef gchar * (*QueryMasterPasswordFunc) (void);
++typedef gchar * (*SetNewPasswordFunc) (guint max_attempts);
+ typedef void (*LogFunc) (const gchar *str);
+ typedef void (*LogFlushFunc) (void);
+
+@@ -560,9 +562,12 @@ void progress_show (gint cur,
+
+ /* user input */
+ void set_input_query_password_func (QueryPasswordFunc func);
+-
+ gchar *input_query_password (const gchar *server,
+ const gchar *user);
++void set_input_query_master_password_func(QueryMasterPasswordFunc func);
++gchar *input_query_master_password(void);
++void set_input_set_new_password_func(SetNewPasswordFunc func);
++gchar *input_set_new_password(guint max_attempts);
+
+ /* logging */
+ void set_log_file (const gchar *filename);
+diff --git a/src/inputdialog.c b/src/inputdialog.c
+index a601085..cdfb997 100644
+--- a/src/inputdialog.c
++++ b/src/inputdialog.c
+@@ -44,6 +44,7 @@
+ #include "filesel.h"
+ #include "prefs_common.h"
+ #include "gtkutils.h"
++#include "masterpassword.h"
+ #include "utils.h"
+
+ #define DIALOG_WIDTH 420
+@@ -156,6 +157,57 @@ gchar *input_dialog_query_password(const gchar *server, const gchar *user)
+ return pass;
+ }
+
++gchar *input_dialog_query_master_password(void)
++{
++ gchar *mp_input;
++
++ mp_input = input_dialog_with_invisible(_("Input password"),
++ _("Master password"),
++ NULL);
++
++ if (prefs_common.use_master_password &&
++ prefs_common.auto_unload_master_password)
++ {
++ g_timeout_add(1000 * 30, unload_master_password, NULL);
++ debug_print("Master password to be unloaded in 30 seconds\n");
++ }
++
++ return mp_input;
++}
++
++gchar *input_dialog_set_new_password(guint max_attempts)
++{
++ guint cnt;
++ gchar *pass1, *pass2;
++
++ if (max_attempts < 1)
++ return NULL;
++
++ for (cnt = 0; cnt < max_attempts; ++cnt) {
++ pass1 = input_dialog_with_invisible(
++ _("Input password"),
++ _("New password"),
++ NULL);
++ pass2 = input_dialog_with_invisible(
++ _("Input password"),
++ _("Confirm new password"),
++ NULL);
++
++ if (pass1 != NULL && pass2 != NULL && strcmp(pass1, pass2) == 0) {
++ cleanse_buffer(pass2, strlen(pass2));
++ g_free(pass2);
++ break;
++ }
++ cleanse_buffer(pass1, strlen(pass1));
++ cleanse_buffer(pass2, strlen(pass2));
++ g_free(pass2);
++ g_free(pass1);
++ pass1 = NULL;
++ }
++
++ return pass1;
++}
++
+ gchar *input_dialog_with_filesel(const gchar *title, const gchar *message,
+ const gchar *default_string,
+ GtkFileChooserAction action)
+diff --git a/src/inputdialog.h b/src/inputdialog.h
+index 02bdda3..5364d1a 100644
+--- a/src/inputdialog.h
++++ b/src/inputdialog.h
+@@ -36,7 +36,8 @@ gchar *input_dialog_combo (const gchar *title,
+ gboolean case_sensitive);
+ gchar *input_dialog_query_password (const gchar *server,
+ const gchar *user);
+-
++gchar *input_dialog_query_master_password(void);
++gchar *input_dialog_set_new_password(guint max_attempts);
+ gchar *input_dialog_with_filesel (const gchar *title,
+ const gchar *message,
+ const gchar *default_string,
+diff --git a/src/main.c b/src/main.c
+index 77d8192..97a4bd0 100644
+--- a/src/main.c
++++ b/src/main.c
+@@ -87,6 +87,7 @@
+ #include "foldersel.h"
+ #include "update_check.h"
+ #include "colorlabel.h"
++#include "masterpassword.h"
+
+ #if USE_GPGME
+ # include "rfc2015.h"
+@@ -265,14 +266,54 @@ int main(int argc, char *argv[])
+ set_ui_update_func(gtkut_events_flush);
+ set_progress_func(main_window_progress_show);
+ set_input_query_password_func(input_dialog_query_password);
++ set_input_set_new_password_func(input_dialog_set_new_password);
+ #if USE_SSL
+ ssl_init();
+ ssl_set_verify_func(ssl_manager_verify_cert);
++ set_input_query_master_password_func(input_dialog_query_master_password);
+ #endif
+
+ CHDIR_EXIT_IF_FAIL(get_home_dir(), 1);
+
+ prefs_common_read_config();
++ set_master_password(NULL);
++#if USE_SSL
++ if (prefs_common.use_master_password) {
++ if (prefs_common.master_password_hash != NULL) {
++ if (check_master_password_interactively(3) != MP_RC_OK) {
++ if (alertpanel(_("Master password"),
++ _("Invalid master password"),
++ GTK_STOCK_DISCARD,
++ GTK_STOCK_QUIT,
++ NULL) != G_ALERTDEFAULT) {
++ exit(1);
++ }
++ }
++ } else {
++ alertpanel_notice(
++ _("Master password enabled but not set. Setting one now"));
++ if (set_master_password_interactively(3) != MP_RC_OK) {
++ if (alertpanel(_("Master password"),
++ _("Unable to set master password"),
++ GTK_STOCK_DISCARD,
++ GTK_STOCK_QUIT,
++ NULL) != G_ALERTDEFAULT) {
++ exit(1);
++ }
++ }
++ }
++ }
++ /* security goal:
++ * if the master password is enabled and loaded on init,
++ * an attacker can potentially set use_master_password - disabled
++ * afterwards and then force Sylpheed to save account data - the result
++ * being passwords saved to accountrc in plain-text.
++ * possible solution:
++ * Do not allow the passwords to be stored in plain-text if Sylpheed
++ * was started with use_master_password - enabled.
++ */
++ master_password_enabled_on_init = prefs_common.use_master_password;
++#endif
+ filter_set_addressbook_func(addressbook_has_address);
+ filter_read_config();
+ prefs_actions_read_config();
+@@ -381,6 +422,13 @@ int main(int argc, char *argv[])
+
+ remote_command_exec();
+
++#if USE_SSL
++ if (prefs_common.auto_unload_master_password && master_password_active()) {
++ debug_print("Auto unloading master password\n");
++ unload_master_password();
++ }
++#endif
++
+ #if USE_UPDATE_CHECK
+ if (prefs_common.auto_update_check)
+ update_check(FALSE);
+@@ -993,6 +1041,7 @@ void app_will_exit(gboolean force)
+
+ /* remove temporary files, close log file, socket cleanup */
+ #if USE_SSL
++ unload_master_password();
+ ssl_done();
+ #endif
+ syl_cleanup();
+diff --git a/src/prefs_account_dialog.c b/src/prefs_account_dialog.c
+index e9cba13..e215b8e 100644
+--- a/src/prefs_account_dialog.c
++++ b/src/prefs_account_dialog.c
+@@ -267,7 +267,7 @@ static PrefsUIData ui_data[] = {
+ {"user_id", &basic.uid_entry,
+ prefs_set_data_from_entry, prefs_set_entry},
+ {"password", &basic.pass_entry,
+- prefs_set_data_from_entry, prefs_set_entry},
++ prefs_set_data_from_epass_entry, prefs_set_entry},
+
+ /* Receive */
+ {"use_apop_auth", &receive.use_apop_chkbtn,
+@@ -313,7 +313,7 @@ static PrefsUIData ui_data[] = {
+ {"smtp_user_id", &p_send.smtp_uid_entry,
+ prefs_set_data_from_entry, prefs_set_entry},
+ {"smtp_password", &p_send.smtp_pass_entry,
+- prefs_set_data_from_entry, prefs_set_entry},
++ prefs_set_data_from_epass_entry, prefs_set_entry},
+
+ {"pop_before_smtp", &p_send.pop_bfr_smtp_chkbtn,
+ prefs_set_data_from_toggle, prefs_set_toggle},
+diff --git a/src/prefs_common_dialog.c b/src/prefs_common_dialog.c
+index b46c9b7..119a056 100644
+--- a/src/prefs_common_dialog.c
++++ b/src/prefs_common_dialog.c
+@@ -205,6 +205,7 @@ static struct JunkMail {
+ static struct Privacy {
+ GtkWidget *checkbtn_auto_check_signatures;
+ GtkWidget *checkbtn_gpg_signature_popup;
++ GtkWidget *radiobtn_gpg_signature_all;
+ GtkWidget *checkbtn_store_passphrase;
+ GtkWidget *spinbtn_store_passphrase;
+ GtkObject *spinbtn_store_passphrase_adj;
+@@ -215,6 +216,14 @@ static struct Privacy {
+ } privacy;
+ #endif
+
++#if USE_SSL
++static struct MasterPassword {
++ GtkWidget *checkbtn_use_master_password;
++ GtkWidget *checkbtn_auto_unload_master_password;
++ GtkWidget *spinbtn_encrypted_password_min_length;
++} master_password;
++#endif
++
+ static struct Interface {
+ GtkWidget *checkbtn_always_show_msg;
+ GtkWidget *checkbtn_always_mark_read;
+@@ -314,6 +323,13 @@ static void prefs_common_attach_toolbtn_pos_set_radiobtn (PrefParam *pparam);
+ static void prefs_common_online_mode_set_data_from_radiobtn(PrefParam *pparam);
+ static void prefs_common_online_mode_set_radiobtn (PrefParam *pparam);
+
++#if USE_GPGME
++static void prefs_common_gpg_signature_popup_mode_set_data_from_radiobtn(
++ PrefParam *pparam);
++static void prefs_common_gpg_signature_popup_mode_set_radiobtn(
++ PrefParam *pparam);
++#endif
++
+ static PrefsUIData ui_data[] = {
+ /* Receive */
+ {"autochk_newmail", &receive.checkbtn_autochk,
+@@ -540,6 +556,9 @@ static PrefsUIData ui_data[] = {
+ prefs_set_data_from_toggle, prefs_set_toggle},
+ {"gpg_signature_popup", &privacy.checkbtn_gpg_signature_popup,
+ prefs_set_data_from_toggle, prefs_set_toggle},
++ {"gpg_signature_popup_mode", &privacy.radiobtn_gpg_signature_all,
++ prefs_common_gpg_signature_popup_mode_set_data_from_radiobtn,
++ prefs_common_gpg_signature_popup_mode_set_radiobtn},
+ {"store_passphrase", &privacy.checkbtn_store_passphrase,
+ prefs_set_data_from_toggle, prefs_set_toggle},
+ {"store_passphrase_timeout", &privacy.spinbtn_store_passphrase,
+@@ -552,6 +571,18 @@ static PrefsUIData ui_data[] = {
+ prefs_set_data_from_toggle, prefs_set_toggle},
+ #endif /* USE_GPGME */
+
++#if USE_SSL
++ {"use_master_password", &master_password.checkbtn_use_master_password,
++ prefs_set_data_from_toggle, prefs_set_toggle},
++ {"auto_unload_master_password",
++ &master_password.checkbtn_auto_unload_master_password,
++ prefs_set_data_from_toggle, prefs_set_toggle},
++ {"encrypted_password_min_length",
++ &master_password.spinbtn_encrypted_password_min_length,
++ prefs_set_data_from_spinbtn,
++ prefs_set_spinbtn},
++#endif
++
+ /* Interface */
+ {"always_show_message_when_selected",
+ &iface.checkbtn_always_show_msg,
+@@ -682,6 +713,9 @@ static void prefs_junk_create (void);
+ #if USE_GPGME
+ static void prefs_privacy_create (void);
+ #endif
++#if USE_SSL
++static void prefs_master_password_create (void);
++#endif
+ static void prefs_details_create (void);
+ static GtkWidget *prefs_other_create (void);
+ static GtkWidget *prefs_extcmd_create (void);
+@@ -841,6 +875,10 @@ static void prefs_common_create(void)
+ #if USE_GPGME
+ prefs_privacy_create();
+ SET_NOTEBOOK_LABEL(dialog.notebook, _("Privacy"), page++);
++#endif
++#if USE_SSL
++ prefs_master_password_create();
++ SET_NOTEBOOK_LABEL(dialog.notebook, _("Master password"), page++);
+ #endif
+ prefs_details_create();
+ SET_NOTEBOOK_LABEL(dialog.notebook, _("Details"), page++);
+@@ -2478,10 +2516,14 @@ static void prefs_privacy_create(void)
+ GtkWidget *vbox2;
+ GtkWidget *vbox3;
+ GtkWidget *hbox1;
++ GtkWidget *vbox_sign_popup_mode;
++ GtkWidget *hbox_sign_popup_mode;
+ GtkWidget *hbox_spc;
+ GtkWidget *label;
+ GtkWidget *checkbtn_auto_check_signatures;
+ GtkWidget *checkbtn_gpg_signature_popup;
++ GtkWidget *radiobtn_gpg_signature_all;
++ GtkWidget *radiobtn_gpg_signature_bad;
+ GtkWidget *checkbtn_store_passphrase;
+ GtkObject *spinbtn_store_passphrase_adj;
+ GtkWidget *spinbtn_store_passphrase;
+@@ -2505,6 +2547,56 @@ static void prefs_privacy_create(void)
+ PACK_CHECK_BUTTON (vbox2, checkbtn_gpg_signature_popup,
+ _("Show signature check result in a popup window"));
+
++ vbox_sign_popup_mode = gtk_vbox_new (FALSE, VSPACING_NARROW);
++ gtk_widget_show (vbox_sign_popup_mode);
++ gtk_box_pack_start (GTK_BOX (vbox2), vbox_sign_popup_mode, FALSE, FALSE, 0);
++
++ hbox_sign_popup_mode = gtk_hbox_new (FALSE, 8);
++ gtk_widget_show (hbox_sign_popup_mode);
++ gtk_box_pack_start (GTK_BOX (vbox_sign_popup_mode),
++ hbox_sign_popup_mode,
++ FALSE,
++ FALSE,
++ 0);
++
++ hbox_spc = gtk_hbox_new (FALSE, 0);
++ gtk_widget_show (hbox_spc);
++ gtk_box_pack_start (GTK_BOX (hbox_sign_popup_mode),
++ hbox_spc,
++ FALSE,
++ FALSE,
++ 0);
++ gtk_widget_set_size_request (hbox_spc, 12, -1);
++
++ radiobtn_gpg_signature_all = gtk_radio_button_new_with_label(
++ NULL,
++ _("All signatures"));
++ gtk_widget_show(radiobtn_gpg_signature_all);
++ gtk_box_pack_start(GTK_BOX (hbox_sign_popup_mode),
++ radiobtn_gpg_signature_all,
++ FALSE,
++ FALSE,
++ 0);
++ g_object_set_data(G_OBJECT (radiobtn_gpg_signature_all),
++ MENU_VAL_ID,
++ GINT_TO_POINTER (1));
++
++ radiobtn_gpg_signature_bad = gtk_radio_button_new_with_label_from_widget(
++ GTK_RADIO_BUTTON (radiobtn_gpg_signature_all),
++ _("Bad signatures only"));
++ gtk_widget_show(radiobtn_gpg_signature_bad);
++ gtk_box_pack_start(GTK_BOX (hbox_sign_popup_mode),
++ radiobtn_gpg_signature_bad,
++ FALSE,
++ FALSE,
++ 0);
++ g_object_set_data(G_OBJECT (radiobtn_gpg_signature_bad),
++ MENU_VAL_ID,
++ GINT_TO_POINTER (0));
++
++ SET_TOGGLE_SENSITIVITY (checkbtn_gpg_signature_popup,
++ hbox_sign_popup_mode);
++
+ PACK_CHECK_BUTTON (vbox2, checkbtn_store_passphrase,
+ _("Store passphrase in memory temporarily"));
+
+@@ -2572,7 +2664,8 @@ static void prefs_privacy_create(void)
+ = checkbtn_auto_check_signatures;
+ privacy.checkbtn_gpg_signature_popup
+ = checkbtn_gpg_signature_popup;
+- privacy.checkbtn_store_passphrase = checkbtn_store_passphrase;
++ privacy.radiobtn_gpg_signature_all = radiobtn_gpg_signature_all;
++ privacy.checkbtn_store_passphrase = checkbtn_store_passphrase;
+ privacy.spinbtn_store_passphrase = spinbtn_store_passphrase;
+ privacy.spinbtn_store_passphrase_adj = spinbtn_store_passphrase_adj;
+ #ifndef G_OS_WIN32
+@@ -2582,6 +2675,93 @@ static void prefs_privacy_create(void)
+ }
+ #endif /* USE_GPGME */
+
++#if USE_SSL
++static void prefs_master_password_create(void)
++{
++ GtkWidget *vbox_main;
++ GtkWidget *vbox_master_password_options;
++ GtkWidget *vbox_master_password_suboptions;
++ GtkWidget *hbox1;
++ GtkWidget *hbox_spc;
++ GtkWidget *label;
++ GtkWidget *checkbtn_use_master_password;
++ GtkWidget *checkbtn_auto_unload_master_password;
++ GtkWidget *spinbtn_encrypted_password_min_length;
++ GtkObject *spinbtn_encrypted_password_min_length_adj;
++
++ vbox_main = gtk_vbox_new (FALSE, VSPACING);
++ gtk_widget_show (vbox_main);
++ gtk_container_add (GTK_CONTAINER (dialog.notebook), vbox_main);
++ gtk_container_set_border_width (GTK_CONTAINER (vbox_main), VBOX_BORDER);
++
++ vbox_master_password_options = gtk_vbox_new (FALSE, 0);
++ gtk_widget_show (vbox_master_password_options);
++ gtk_box_pack_start (
++ GTK_BOX (vbox_main), vbox_master_password_options, FALSE, FALSE, 0);
++
++ PACK_CHECK_BUTTON (vbox_master_password_options,
++ checkbtn_use_master_password,
++ _("Use master password"));
++
++ vbox_master_password_suboptions = gtk_vbox_new (FALSE, VSPACING_NARROW);
++ gtk_widget_show (vbox_master_password_suboptions);
++ gtk_box_pack_start (GTK_BOX (vbox_master_password_options),
++ vbox_master_password_suboptions,
++ FALSE,
++ FALSE,
++ 0);
++
++ PACK_CHECK_BUTTON (vbox_master_password_suboptions,
++ checkbtn_auto_unload_master_password,
++ _("Automatically unload master password "
++ "after session initialization"));
++
++ hbox1 = gtk_hbox_new (FALSE, 8);
++ gtk_widget_show (hbox1);
++ gtk_box_pack_start (GTK_BOX (vbox_master_password_suboptions),
++ hbox1,
++ FALSE,
++ FALSE,
++ 0);
++
++ hbox_spc = gtk_hbox_new (FALSE, 0);
++ gtk_widget_show (hbox_spc);
++ gtk_box_pack_start (GTK_BOX (hbox1), hbox_spc, FALSE, FALSE, 0);
++ gtk_widget_set_size_request (hbox_spc, 12, -1);
++
++ label = gtk_label_new (_("Min. password length"));
++ gtk_widget_show (label);
++ gtk_box_pack_start (
++ GTK_BOX (hbox1), label, FALSE, FALSE, 0);
++
++ spinbtn_encrypted_password_min_length_adj = gtk_adjustment_new (
++ 32, 0, 99, 1, 5, 0);
++ spinbtn_encrypted_password_min_length = gtk_spin_button_new(
++ GTK_ADJUSTMENT (spinbtn_encrypted_password_min_length_adj), 1, 0);
++ gtk_widget_show (spinbtn_encrypted_password_min_length);
++ gtk_box_pack_start (GTK_BOX (hbox1),
++ spinbtn_encrypted_password_min_length,
++ FALSE,
++ FALSE,
++ 0);
++ gtk_spin_button_set_numeric (
++ GTK_SPIN_BUTTON (spinbtn_encrypted_password_min_length), TRUE);
++ gtk_widget_set_size_request (spinbtn_encrypted_password_min_length,
++ 64,
++ -1);
++
++ SET_TOGGLE_SENSITIVITY (checkbtn_use_master_password,
++ vbox_master_password_suboptions);
++
++ master_password.checkbtn_use_master_password
++ = checkbtn_use_master_password;
++ master_password.checkbtn_auto_unload_master_password
++ = checkbtn_auto_unload_master_password;
++ master_password.spinbtn_encrypted_password_min_length
++ = spinbtn_encrypted_password_min_length;
++}
++#endif /* USE_SSL */
++
+ static void prefs_details_create(void)
+ {
+ GtkWidget *vbox1;
+@@ -4719,6 +4899,60 @@ static void prefs_common_online_mode_set_radiobtn(PrefParam *pparam)
+ }
+ }
+
++#if USE_GPGME
++static void prefs_common_gpg_signature_popup_mode_set_data_from_radiobtn(
++ PrefParam *pparam)
++{
++ PrefsUIData *ui_data;
++ GtkRadioButton *radiobtn;
++ GSList *group;
++
++ ui_data = (PrefsUIData *)pparam->ui_data;
++ g_return_if_fail(ui_data != NULL);
++ g_return_if_fail(*ui_data->widget != NULL);
++
++ radiobtn = GTK_RADIO_BUTTON(*ui_data->widget);
++ group = gtk_radio_button_get_group(radiobtn);
++ while (group != NULL) {
++ GtkToggleButton *btn = GTK_TOGGLE_BUTTON(group->data);
++
++ if (gtk_toggle_button_get_active(btn)) {
++ prefs_common.gpg_signature_popup_mode =
++ GPOINTER_TO_INT(g_object_get_data(G_OBJECT(btn), MENU_VAL_ID));
++ break;
++ }
++ group = group->next;
++ }
++}
++
++static void prefs_common_gpg_signature_popup_mode_set_radiobtn(
++ PrefParam *pparam)
++{
++ PrefsUIData *ui_data;
++ GtkRadioButton *radiobtn;
++ GSList *group;
++
++ ui_data = (PrefsUIData *)pparam->ui_data;
++ g_return_if_fail(ui_data != NULL);
++ g_return_if_fail(*ui_data->widget != NULL);
++
++ radiobtn = GTK_RADIO_BUTTON(*ui_data->widget);
++ group = gtk_radio_button_get_group(radiobtn);
++ while (group != NULL) {
++ GtkToggleButton *btn = GTK_TOGGLE_BUTTON(group->data);
++ gint data;
++
++ data = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(btn),
++ MENU_VAL_ID));
++ if (data == prefs_common.gpg_signature_popup_mode) {
++ gtk_toggle_button_set_active(btn, TRUE);
++ break;
++ }
++ group = group->next;
++ }
++}
++#endif
++
+ static void prefs_common_dispitem_clicked(void)
+ {
+ prefs_summary_column_open(FOLDER_ITEM_IS_SENT_FOLDER
+diff --git a/src/prefs_ui.c b/src/prefs_ui.c
+index a3a8f74..2ab1d45 100644
+--- a/src/prefs_ui.c
++++ b/src/prefs_ui.c
+@@ -31,11 +31,13 @@
+ #include
+
+ #include "prefs.h"
++#include "prefs_common.h"
+ #include "prefs_ui.h"
+ #include "menu.h"
+ #include "codeconv.h"
+ #include "utils.h"
+ #include "gtkutils.h"
++#include "masterpassword.h"
+
+ typedef enum
+ {
+@@ -268,6 +270,65 @@ void prefs_set_data_from_entry(PrefParam *pparam)
+ }
+ }
+
++void prefs_set_data_from_epass_entry(PrefParam *pparam)
++{
++#if USE_SSL
++ PrefsUIData *ui_data;
++ gchar **str;
++ const gchar *entry_str;
++
++ /* This is where decrypted passwords are encrypted and stored again */
++
++ /* master_password == NULL for any of the following reasons:
++ * - use_master_password is not enabled (we just save without encrypting)
++ * - master_password is auto unloaded (prompt for the master password)
++ * - undefined reason (refure to store the password)
++ */
++ if (!prefs_common.use_master_password) {
++ /* check if use_master_password was enabled when Sylpheed started */
++ if (!master_password_enabled_on_init) {
++ prefs_set_data_from_entry(pparam);
++ }
++ return;
++ } else if (master_password == NULL) {
++ if (!prefs_common.auto_unload_master_password) {
++ debug_print("Master password enabled, but not loaded for no "
++ "apparent reason. Not storing\n");
++ return;
++ } else {
++ if (check_master_password_interactively(3) != MP_RC_OK) {
++ debug_print("Failed to reload the master password\n");
++ return;
++ }
++ }
++ }
++
++ ui_data = (PrefsUIData *)pparam->ui_data;
++ g_return_if_fail(ui_data != NULL);
++ g_return_if_fail(*ui_data->widget != NULL);
++
++ entry_str = gtk_entry_get_text(GTK_ENTRY(*ui_data->widget));
++
++ switch (pparam->type) {
++ case P_STRING:
++ str = (gchar **)pparam->data;
++ g_free(*str);
++ if ((entry_str != NULL) && (!mpes_string_prefix(entry_str))) {
++ debug_print("Encrypting GTK password entry\n");
++ *str = encrypt_with_master_password(entry_str);
++ } else {
++ *str = entry_str[0] ? g_strdup(entry_str) : NULL;
++ }
++ break;
++ default:
++ g_warning("Invalid PrefType for GtkEntry widget: %d\n",
++ pparam->type);
++ }
++#else
++ prefs_set_data_from_entry(pparam);
++#endif
++}
++
+ void prefs_set_entry(PrefParam *pparam)
+ {
+ PrefsUIData *ui_data;
+diff --git a/src/prefs_ui.h b/src/prefs_ui.h
+index cfdf353..f7bd903 100644
+--- a/src/prefs_ui.h
++++ b/src/prefs_ui.h
+@@ -164,6 +164,7 @@ void prefs_set_data_from_dialog (PrefParam *param);
+ void prefs_set_dialog_to_default(PrefParam *param);
+
+ void prefs_set_data_from_entry (PrefParam *pparam);
++void prefs_set_data_from_epass_entry(PrefParam *pparam);
+ void prefs_set_entry (PrefParam *pparam);
+ void prefs_set_data_from_text (PrefParam *pparam);
+ void prefs_set_text (PrefParam *pparam);
+diff --git a/src/rfc2015.c b/src/rfc2015.c
+index ebfa96c..48825e2 100644
+--- a/src/rfc2015.c
++++ b/src/rfc2015.c
+@@ -210,8 +210,12 @@ static void check_signature(MimeInfo *mimeinfo, MimeInfo *partinfo, FILE *fp)
+ gchar *tmp_file;
+ gint n_exclude_chars = 0;
+
+- if (prefs_common.gpg_signature_popup)
++ if (prefs_common.gpg_signature_popup &&
++ prefs_common.gpg_signature_popup_mode == 1)
++ {
++ /* FIXME: Perhaps some macro instead of 1 */
+ statuswindow = gpgmegtk_sig_status_create();
++ }
+
+ err = gpgme_new(&ctx);
+ if (err) {
+@@ -309,8 +313,21 @@ leave:
+ result = _("Error verifying the signature");
+ }
+ debug_print("verification status: %s\n", result);
+- if (prefs_common.gpg_signature_popup)
++ if (prefs_common.gpg_signature_popup &&
++ prefs_common.gpg_signature_popup_mode == 1)
++ {
++ /* FIXME: Perhaps some macro instead of 1 */
++ gpgmegtk_sig_status_update(statuswindow, ctx);
++ } else if (prefs_common.gpg_signature_popup &&
++ verifyresult->signatures->status != GPG_ERR_NO_DATA &&
++ verifyresult->signatures->status != GPG_ERR_NO_ERROR)
++ {
++ /* prefs_common.gpg_signature_popup_mode == 0 is useless as long as
++ * there are only two modes (0 and 1)
++ */
++ statuswindow = gpgmegtk_sig_status_create();
+ gpgmegtk_sig_status_update(statuswindow, ctx);
++ }
+
+ g_free (partinfo->sigstatus);
+ partinfo->sigstatus = g_strdup (result);
+@@ -319,8 +336,11 @@ leave:
+ gpgme_data_release(text);
+ if (ctx)
+ gpgme_release(ctx);
+- if (prefs_common.gpg_signature_popup)
++ if (prefs_common.gpg_signature_popup) {
++ /* gpgmegtk_sig_status_destroy handles NULL params so no complicated
++ check is needed */
+ gpgmegtk_sig_status_destroy(statuswindow);
++ }
+ }
+
+ /*
+@@ -427,7 +447,11 @@ static gpgme_data_t pgp_decrypt(MsgInfo *msginfo, MimeInfo *partinfo, FILE *fp)
+ debug_print("verification status: %s\n", result);
+ debug_print("full status: %s\n",
+ msginfo->encinfo->sigstatus_full);
+- if (prefs_common.gpg_signature_popup) {
++ if (prefs_common.gpg_signature_popup &&
++ ((prefs_common.gpg_signature_popup_mode == 1) ||
++ (verifyresult->signatures->status != GPG_ERR_NO_DATA &&
++ verifyresult->signatures->status != GPG_ERR_NO_ERROR)))
++ {
+ GpgmegtkSigStatus statuswindow;
+ statuswindow = gpgmegtk_sig_status_create();
+ gpgmegtk_sig_status_update(statuswindow, ctx);
+diff --git a/src/send_message.c b/src/send_message.c
+index a8c2c7a..537c3c8 100644
+--- a/src/send_message.c
++++ b/src/send_message.c
+@@ -58,6 +58,7 @@
+ #include "inc.h"
+ #include "mainwindow.h"
+ #include "summaryview.h"
++#include "masterpassword.h"
+
+ #define SMTP_PORT 25
+ #if USE_SSL
+@@ -648,13 +649,13 @@ static gint send_message_smtp(PrefsAccount *ac_prefs, GSList *to_list, FILE *fp)
+
+ if (ac_prefs->smtp_userid) {
+ smtp_session->user = g_strdup(ac_prefs->smtp_userid);
+- if (ac_prefs->smtp_passwd)
+- smtp_session->pass =
+- g_strdup(ac_prefs->smtp_passwd);
+- else if (ac_prefs->tmp_smtp_pass)
++ if (ac_prefs->smtp_passwd) {
++ smtp_session->pass = decrypt_with_master_password(
++ ac_prefs->smtp_passwd);
++ } else if (ac_prefs->tmp_smtp_pass) {
+ smtp_session->pass =
+ g_strdup(ac_prefs->tmp_smtp_pass);
+- else {
++ } else {
+ smtp_session->pass =
+ input_query_password
+ (ac_prefs->smtp_server,
diff --git a/mail-client/sylpheed/files/sylpheed-3.7.0-PGP-signature-fix.patch b/mail-client/sylpheed/files/sylpheed-3.7.0-PGP-signature-fix.patch
new file mode 100644
index 0000000..f41a22f
--- /dev/null
+++ b/mail-client/sylpheed/files/sylpheed-3.7.0-PGP-signature-fix.patch
@@ -0,0 +1,18 @@
+diff -rupN sylpheed-3.7.0/libsylph/utils.c sylpheed-3.7.0.new/libsylph/utils.c
+--- sylpheed-3.7.0/libsylph/utils.c 2017-11-30 03:34:14.000000000 +0100
++++ sylpheed-3.7.0.new/libsylph/utils.c 2018-02-08 08:39:06.586381290 +0100
+@@ -3306,10 +3306,10 @@ gint canonicalize_file(const gchar *src,
+ }
+ }
+
+- if (last_linebreak == TRUE) {
+- if (fputs("\r\n", dest_fp) == EOF)
+- err = TRUE;
+- }
++ /* if (last_linebreak == TRUE) { */
++ /* if (fputs("\r\n", dest_fp) == EOF) */
++ /* err = TRUE; */
++ /* } */
+
+ if (ferror(src_fp)) {
+ FILE_OP_ERROR(src, "fgets");
diff --git a/mail-client/sylpheed/files/sylpheed-3.7.0-master-password.patch b/mail-client/sylpheed/files/sylpheed-3.7.0-master-password.patch
new file mode 100644
index 0000000..b7fc53a
--- /dev/null
+++ b/mail-client/sylpheed/files/sylpheed-3.7.0-master-password.patch
@@ -0,0 +1,1850 @@
+diff --git a/README.md b/README.md
+new file mode 100644
+index 0000000..1cf6d21
+--- /dev/null
++++ b/README.md
+@@ -0,0 +1,219 @@
++## Implemented features and fixes not present in the official Sylpheed release
++
++- (fix)
++ PGP signature not verified properly when the message has no newline
++ at the end. https://sylpheed.sraoss.jp/redmine/issues/288
++
++- (feature)
++ Make it possible to select "Show signature check result in a popup window"
++ only for bad signatures.
++
++- (feature)
++ Support for encrypting and storing encrypted passwords using a master password.
++ Read bellow for more details!
++
++
++## Master password
++
++The master password feature is developed by Simeon Simeonov (sgs)
++and is currently in an experimental state.
++
++
++### Motivation
++
++Currently Sylpheed is storing passwords in plain-text. One can always refrain
++from storing passwords and let Sylpheed prompt for them, but the more accounts
++one has, the more annoying this becomes.
++
++The goal is to have the passwords stored in a secure way and let Sylpheed only
++prompt for the master password.
++
++
++### Security goals
++
++- attacker (A) should not be able to derive the password from the digest.
++
++- A should not be able to derive the master password even if she has
++ read and write access to the storage.
++
++- A should not be able to determine the length of the encrypted password
++ even if she has read and write access to the storage.
++
++- A should not be able to craft an edited password without obtaining the
++ master password.
++
++- A should not be able to compromise the master password or force Sylpheed
++ to store decrypted passwords even if she has access to a running Sylpheed.
++
++- a warning / prompt should be given if a user accidentally types in a "wrong"
++ master password before decryption is initiated.
++
++
++### Usage in Sylpheed
++
++- backup your Sylpheed profile (often $HOME/.sylpheed-2.0)!
++
++- start Sylpheed and open "Configuration" -> "Common preferences..."!
++
++- select the "Master password" tab, enable "Use master password" and
++ apply the changes!
++
++- restart Sylpheed (exit and then start Sylpheed again)!
++
++- you will be asked to type and verify a new master password.
++
++- set your new passwords from the "Configuration" -> "Edit accounts..."!
++
++- select "Automatically unload master password after session initialization"
++ for increased security and decreased convenience.
++
++Note:
++Sylpheed will automatically convert existing stored passwords, but it will not
++touch your backups. You will have to remove all remnants of plain-text
++passwords manually.
++
++
++### Choice of cryptographic primitives
++
++The primary concern when selecting cryptographic primitives was portability.
++The desire was to go for primitives that are both strong and available in all
++supported production distributions of OpenSSL and LibreSSL.
++
++
++#### Cipher
++
++When it comes to implementation, there are several advantages in using stream
++cipher or a block cipher that behaves like a stream cipher when used in a
++certain mode of operation. One is avoiding to deal with padding.
++
++AES-256 operating in CFB was selected for these reasons.
++ChaCha20 should be considered as a replacement in the future.
++
++
++#### Hash-function
++
++Hash-functions are used for:
++- key derivation
++- plain-text digest
++
++Those operations do not have to use the same hash-function.
++(See the "Encryption & decryption scheme" section for more details!)
++
++Key-derivation:
++Since AES-256 uses a 256 bits key, we need a hash-function with at least
++the same digest size or bigger.
++Since the digest (which is the key itself) is considered confidential and is
++stored only in memory for only a limited amount of time, SHA-256 is considered
++sufficiently strong for that purpose.
++
++Size + plain-text + padding digest:
++Since the digest is created of both the plain-text and the plain-text size,
++as well as being encrypted, SHA-256 is considered sufficiently strong.
++SHA-512 may increase security at the price of adding additional 32 bytes
++to the encrypted password digest.
++
++Stronger hash-functions like SHA-3 or BLAKE2b can be considered as a
++replacement in the future.
++
++
++#### Master password digest
++
++In order to be able to decide whether the user typed a "wrong" master password,
++before attempting to decrypt, Sylpheed stores a digest of the master password
++in 'master_password_hash' in sylpheedrc.
++100000 iterations of PBKDF2_HMAC with SHA-512 and 16 bytes salt is used.
++Note that this digest is useless as a key and even if a plain-text that
++produces the same digest is found, it will most probably be useless as a
++master-password.
++
++
++### Encryption & decryption scheme
++
++
++#### Encryption
++
++
++Input:
++
++- plain-text password to be encrypted (P)
++
++- plain-text master-password used for key derivation (M)
++
++- integer minimum password length (0 < L < 100)
++
++
++Output:
++
++- an encrypted password digest (base64) (B)
++
++
++Operation:
++
++- generate 16 bytes of random data to be used as a salt (S)
++
++- derive the key (K): K = SHA_256(S + M)
++
++- produce a 2 byte string (N) indicating the length of P
++
++- generate random padding and set N:
++ - if the length of P < L, produce L - P bytes of random data (R), N = "%02d"
++ - if the length of P >= L, N = "-1"
++
++- produce a hash digest (H): H = SHA_256(N + P + R (if the length of P < L))
++
++- encrypt (E): E = AES_256_CFB_ENCRYPT(H + N + P + R (if the length of P < L), K)
++
++- B = mpes1:BASE64_ENCODE(S + E)
++
++example:
++mpes1:vo7lsIpD7i6byBA6+vlUoF4OVDfEe+aYRRk4FRtfJ2gMY8M43Kj6WfdfgbViIOl83bI4XEc96okhPW5Mla813aAR1gbPjDg0xmCyIbWOiUv/dg==
++
++
++#### Decryption
++
++
++Input:
++
++- encrypted password digest (base64) (B)
++
++- plain-text master-password used for key derivation (M)
++
++
++Output:
++
++- plain-text password (P)
++
++
++Operation:
++
++- remove the prefix (mpes1:) and base64-decode the rest of the digest: B = BASE64_DECODE(B)
++
++- fetch the first 16 bytes for the salt: S = B[0 : 15]
++
++- derive the key (K): K = SHA_256(S + M)
++
++- decrypt the rest of B (D): D = AES_256_CFB_DECRYPT(B[16 :], K)
++
++- extract the first 16 bytes for the hash digest (H): H = D[0 : 15]
++
++- in order to detect data-inconsistency, assert H == SHA_256(D[16 :])
++
++- extract the next 2 bytes for the length of P (N): N = D[16 : 17]
++
++- fetch the plain-text:
++ - if N == "-1" the password is the remaining bytes of D: P = D[18 :]
++ - if N != "-1", extract the next N-bytes from D: P = D[18 : (18 + N)]
++
++
++### Limitations
++
++- currently only the 'password' and 'smtp_password' keys in accountrc
++ are encrypted.
++ A mechanism that allows for any key and even folders to be encrypted
++ should be considered in the future.
++
++- currently it is not possible to select alternative ciphers, hash-functions
++ and modes of operation (without editing the source code).
++
++- currently it is not possible to change your master password without having to
++ (re)set your passwords manually.
+diff --git a/libsylph/Makefile.am b/libsylph/Makefile.am
+index 8f2b76b..40d26a9 100644
+--- a/libsylph/Makefile.am
++++ b/libsylph/Makefile.am
+@@ -19,6 +19,7 @@ libsylph_0_la_SOURCES = \
+ folder.c \
+ html.c \
+ imap.c \
++ masterpassword.c \
+ mbox.c \
+ md5.c \
+ md5_hmac.c \
+@@ -62,6 +63,7 @@ libsylph_0include_HEADERS = \
+ folder.h \
+ html.h \
+ imap.h \
++ masterpassword.h \
+ mbox.h \
+ md5.h \
+ md5_hmac.h \
+diff --git a/libsylph/Makefile.in b/libsylph/Makefile.in
+index 6e3c999..ac8fefd 100644
+--- a/libsylph/Makefile.in
++++ b/libsylph/Makefile.in
+@@ -129,8 +129,8 @@ libsylph_0_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+ am_libsylph_0_la_OBJECTS = account.lo base64.lo codeconv.lo \
+ customheader.lo displayheader.lo filter.lo folder.lo html.lo \
+- imap.lo mbox.lo md5.lo md5_hmac.lo mh.lo news.lo nntp.lo \
+- pop.lo prefs.lo prefs_account.lo prefs_common.lo procheader.lo \
++ imap.lo masterpassword.lo mbox.lo md5.lo md5_hmac.lo mh.lo news.lo \
++ nntp.lo pop.lo prefs.lo prefs_account.lo prefs_common.lo procheader.lo \
+ procmime.lo procmsg.lo quoted-printable.lo recv.lo session.lo \
+ smtp.lo socket.lo socks.lo ssl.lo ssl_hostname_validation.lo \
+ stringtable.lo sylmain.lo unmime.lo utils.lo uuencode.lo \
+@@ -402,6 +402,7 @@ libsylph_0_la_SOURCES = \
+ folder.c \
+ html.c \
+ imap.c \
++ masterpassword.c \
+ mbox.c \
+ md5.c \
+ md5_hmac.c \
+@@ -445,6 +446,7 @@ libsylph_0include_HEADERS = \
+ folder.h \
+ html.h \
+ imap.h \
++ masterpassword.h \
+ mbox.h \
+ md5.h \
+ md5_hmac.h \
+@@ -579,6 +581,7 @@ distclean-compile:
+ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/folder.Plo@am__quote@
+ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/html.Plo@am__quote@
+ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap.Plo@am__quote@
++@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/masterpassword.Plo@am__quote@
+ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mbox.Plo@am__quote@
+ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/md5.Plo@am__quote@
+ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/md5_hmac.Plo@am__quote@
+diff --git a/libsylph/defs.h b/libsylph/defs.h
+index 9e3f82b..67325bf 100644
+--- a/libsylph/defs.h
++++ b/libsylph/defs.h
+@@ -58,6 +58,9 @@
+ #define DISPLAY_HEADER_RC "dispheaderrc"
+ #define MENU_RC "menurc"
+ #define ACTIONS_RC "actionsrc"
++#ifdef USE_SSL
++#define SECURE_RC "securerc"
++#endif
+ #define COMMAND_HISTORY "command_history"
+ #define TEMPLATE_DIR "templates"
+ #define TMP_DIR "tmp"
+diff --git a/libsylph/imap.c b/libsylph/imap.c
+index aa0d737..e1c7cfd 100644
+--- a/libsylph/imap.c
++++ b/libsylph/imap.c
+@@ -52,6 +52,7 @@
+ #include "utils.h"
+ #include "prefs_common.h"
+ #include "virtual.h"
++#include "masterpassword.h"
+
+ #define IMAP4_PORT 143
+ #if USE_SSL
+@@ -705,9 +706,13 @@ static gint imap_session_connect(IMAPSession *session)
+ account = (PrefsAccount *)(SESSION(session)->data);
+
+ log_message(_("creating IMAP4 connection to %s:%d ...\n"),
+- SESSION(session)->server, SESSION(session)->port);
+-
+- pass = account->passwd;
++ SESSION(session)->server, SESSION(session)->port);
++ if (master_password_active()) {
++ pass = decrypt_with_master_password(account->passwd);
++ /* a new string is allocated. To be removed ... */
++ } else {
++ pass = account->passwd;
++ }
+ if (!pass)
+ pass = account->tmp_pass;
+ if (!pass) {
+@@ -770,8 +775,11 @@ static gint imap_session_connect(IMAPSession *session)
+ #endif
+
+ if (!session->authenticated &&
+- imap_auth(session, account->userid, pass, account->imap_auth_type)
+- != IMAP_SUCCESS) {
++ imap_auth(session, account->userid, pass, account->imap_auth_type)
++ != IMAP_SUCCESS) {
++ if (master_password_active()) {
++ g_free(pass); /* remove the decrypted password */
++ }
+ if (account->tmp_pass) {
+ g_free(account->tmp_pass);
+ account->tmp_pass = NULL;
+@@ -780,6 +788,10 @@ static gint imap_session_connect(IMAPSession *session)
+ return IMAP_AUTHFAIL;
+ }
+
++ if (master_password_active()) {
++ g_free(pass); /* remove the decrypted password */
++ }
++
+ return IMAP_SUCCESS;
+ }
+
+diff --git a/libsylph/masterpassword.c b/libsylph/masterpassword.c
+new file mode 100644
+index 0000000..3d7af94
+--- /dev/null
++++ b/libsylph/masterpassword.c
+@@ -0,0 +1,216 @@
++/*
++ * LibSylph -- E-Mail client library
++ * Copyright (C) 1999-2018 Hiroyuki Yamamoto
++ *
++ * This library is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2.1 of the License, or (at your option) any later version.
++ *
++ * This library 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
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General Public
++ * License along with this library; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
++ */
++
++#ifdef HAVE_CONFIG_H
++#include "config.h"
++#endif
++
++#include
++
++#include "prefs_common.h"
++#include "ssl.h"
++#include "utils.h"
++#include "masterpassword.h"
++
++
++gchar *master_password;
++gboolean master_password_enabled_on_init;
++
++void set_master_password(const char *password) {
++ master_password = password;
++}
++
++gchar *get_master_password(void) {
++ return master_password;
++}
++
++void cleanse_buffer(void *buf, size_t len) {
++#if USE_SSL
++ OPENSSL_cleanse(buf, len);
++#else
++ memset(buf, 0, len); /* better than nothing */
++#endif
++}
++
++void unload_master_password(void) {
++
++ debug_print("Unloading master password\n");
++ if (master_password == NULL) {
++ /* not loaded / already unloaded */
++ return;
++ }
++ cleanse_buffer(master_password, strlen(master_password));
++ g_free(master_password);
++ master_password = NULL;
++ debug_print("Master password unloaded\n");
++
++}
++
++gint mpes_string_prefix(const gchar *str) {
++
++ /* this function will be expanded in time as the format changes */
++ if (g_str_has_prefix(str, "mpes1:"))
++ return 6;
++ return 0;
++
++}
++
++gboolean master_password_active(void) {
++
++#if USE_SSL
++ return ((master_password != NULL) &&
++ prefs_common.use_master_password);
++#else
++ return FALSE;
++#endif
++
++}
++
++gchar *decrypt_with_master_password(const gchar *str) {
++
++#if USE_SSL
++ gchar *new_str;
++ gint str_prefix;
++
++ if ((!str) || (!prefs_common.use_master_password))
++ return g_strdup(str);
++
++ if (master_password == NULL) {
++ /* we have empty or auto unloaded master password */
++ if ((!prefs_common.auto_unload_master_password) ||
++ (check_master_password_interactively(3) != MP_RC_OK)) {
++ return g_strdup(str);
++ }
++ debug_print("Reloaded master password\n");
++ }
++
++ str_prefix = mpes_string_prefix(str);
++ if (!str_prefix)
++ return g_strdup(str);
++
++ if (decrypt_data(&new_str,
++ str + str_prefix,
++ master_password,
++ strlen(str) + 1 - str_prefix) != MP_RC_OK) {
++ OPENSSL_cleanse(new_str, strlen(new_str));
++ g_free(new_str);
++ return g_strdup(str);
++ }
++
++ return new_str;
++#else
++ return g_strdup(str);
++#endif
++
++}
++
++gchar *encrypt_with_master_password(const gchar *str) {
++
++#if USE_SSL
++ gchar *new_str, *mpes1_str;
++ gint length_encrypted;
++
++ if ((!str) || (!master_password_active()))
++ return NULL;
++
++ /*
++ * unlike the decrypt function, here it is up to the caller
++ * to make sure that auto unloaded master password is handled properly
++ */
++
++ if (encrypt_data(&new_str,
++ &length_encrypted,
++ str,
++ master_password,
++ strlen(str) + 1,
++ prefs_common.encrypted_password_min_length,
++ TRUE) != MP_RC_OK) {
++ g_free(new_str);
++ return NULL;
++ }
++
++ mpes1_str = g_strdup_printf("mpes1:%s", new_str);
++ g_free(new_str);
++
++ return mpes1_str;
++#else
++ return NULL;
++#endif
++
++}
++
++#if USE_SSL
++gint set_master_password_interactively(guint max_attempts) {
++
++ if (master_password == NULL)
++ master_password = input_set_new_password(max_attempts);
++
++ if (master_password == NULL)
++ return 1;
++
++ if (generate_password_hash(
++ &prefs_common.master_password_hash,
++ master_password,
++ NULL) != MP_RC_OK) {
++ /* should not really happen unless buggy code / library */
++ g_free(prefs_common.master_password_hash);
++ prefs_common.master_password_hash = NULL;
++ debug_print(_("Could not generate master password hash"));
++ return 1;
++ }
++
++ prefs_common_write_config();
++ return 0;
++
++}
++
++gint check_master_password_interactively(guint max_attempts) {
++
++ guint cnt;
++
++ g_return_val_if_fail(max_attempts > 0, 1);
++ g_return_val_if_fail(prefs_common.master_password_hash != NULL, 1);
++
++ if (master_password != NULL) {
++ /* password already cached */
++ debug_print("Master password already cached\n");
++ return check_password(master_password,
++ prefs_common.master_password_hash);
++ }
++
++ for (cnt = 0; cnt < max_attempts; ++cnt) {
++ master_password = input_query_master_password();
++ if (master_password == NULL) {
++ /* input canceled or query_master_password_func not set */
++ continue;
++ }
++ if (check_password(master_password,
++ prefs_common.master_password_hash) == MP_RC_OK) {
++ return MP_RC_OK; /* match */
++ }
++ debug_print(_("Wrong master password entered (%d)\n"), cnt);
++ OPENSSL_cleanse(master_password, strlen(master_password));
++ g_free(master_password);
++ master_password = NULL;
++ }
++
++ return 1; /* no match */
++
++}
++#endif /* USE_SSL */
+diff --git a/libsylph/masterpassword.h b/libsylph/masterpassword.h
+new file mode 100644
+index 0000000..7254643
+--- /dev/null
++++ b/libsylph/masterpassword.h
+@@ -0,0 +1,47 @@
++/*
++ * LibSylph -- E-Mail client library
++ * Copyright (C) 1999-2018 Hiroyuki Yamamoto
++ *
++ * This library is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2.1 of the License, or (at your option) any later version.
++ *
++ * This library 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
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General Public
++ * License along with this library; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
++ */
++
++#ifndef __MASTERPASSWORD_H__
++#define __MASTERPASSWORD_H__
++
++#ifdef HAVE_CONFIG_H
++#include "config.h"
++#endif
++
++#define MP_RC_OK 0
++#define MP_RC_WRONG_HASH_OR_KEY 1
++#define MP_RC_INVALID_FORMAT 2 /* invalid digest format */
++
++extern gchar *master_password;
++extern gboolean master_password_enabled_on_init; /* m.p. enabled on init? */
++void set_master_password(const char *password);
++gchar *get_master_password(void);
++void cleanse_buffer(void *buf, size_t len);
++void unload_master_password(void);
++gint mpes_string_prefix(const gchar *str);
++gboolean master_password_active(void);
++gchar *decrypt_with_master_password(const gchar *str);
++gchar *encrypt_with_master_password(const gchar *str);
++
++#if USE_SSL
++gint set_master_password_interactively(guint max_attempts);
++gint check_master_password_interactively(guint max_attempts);
++#endif /* USE_SSL */
++
++#endif /* __MASTERPASSWORD_H__ */
+diff --git a/libsylph/news.c b/libsylph/news.c
+index ffff9f9..36d3b10 100644
+--- a/libsylph/news.c
++++ b/libsylph/news.c
+@@ -44,6 +44,7 @@
+ #include "utils.h"
+ #include "prefs_common.h"
+ #include "prefs_account.h"
++#include "masterpassword.h"
+ #if USE_SSL
+ # include "ssl.h"
+ #endif
+@@ -249,10 +250,11 @@ static Session *news_session_new_for_folder(Folder *folder)
+ ac = folder->account;
+ if (ac->use_nntp_auth && ac->userid && ac->userid[0]) {
+ userid = ac->userid;
+- if (ac->passwd && ac->passwd[0])
+- passwd = g_strdup(ac->passwd);
+- else
++ if (ac->passwd && ac->passwd[0]) {
++ passwd = decrypt_with_master_password(ac->passwd);
++ } else {
+ passwd = input_query_password(ac->nntp_server, userid);
++ }
+ }
+
+ if (ac->use_socks && ac->use_socks_for_recv && ac->proxy_host) {
+diff --git a/libsylph/pop.c b/libsylph/pop.c
+index 8cb7f5c..85f3ed9 100644
+--- a/libsylph/pop.c
++++ b/libsylph/pop.c
+@@ -39,6 +39,7 @@
+ #include "prefs_account.h"
+ #include "utils.h"
+ #include "recv.h"
++#include "masterpassword.h"
+
+ gint pop3_greeting_recv (Pop3Session *session,
+ const gchar *msg);
+@@ -437,8 +438,9 @@ Session *pop3_session_new(PrefsAccount *account)
+ session->error_msg = NULL;
+
+ session->user = g_strdup(account->userid);
+- session->pass = account->passwd ? g_strdup(account->passwd) :
+- account->tmp_pass ? g_strdup(account->tmp_pass) : NULL;
++ session->pass = account->passwd ? decrypt_with_master_password(
++ account->passwd) : account->tmp_pass ? g_strdup(
++ account->tmp_pass) : NULL;
+
+ SESSION(session)->server = g_strdup(account->recv_server);
+
+diff --git a/libsylph/prefs_account.c b/libsylph/prefs_account.c
+index 1aecba9..54cbc89 100644
+--- a/libsylph/prefs_account.c
++++ b/libsylph/prefs_account.c
+@@ -34,6 +34,7 @@
+ #include "customheader.h"
+ #include "account.h"
+ #include "utils.h"
++#include "masterpassword.h"
+
+ static PrefsAccount tmp_ac_prefs;
+
+@@ -205,7 +206,7 @@ PrefsAccount *prefs_account_new(void)
+ void prefs_account_read_config(PrefsAccount *ac_prefs, const gchar *label)
+ {
+ const gchar *p = label;
+- gchar *rcpath;
++ gchar *rcpath, *tmp_str;
+ gint id;
+
+ g_return_if_fail(ac_prefs != NULL);
+@@ -229,6 +230,34 @@ void prefs_account_read_config(PrefsAccount *ac_prefs, const gchar *label)
+ ac_prefs->use_apop_auth = TRUE;
+ }
+
++ if (master_password_active()) {
++ if ((ac_prefs->passwd != NULL) &&
++ !mpes_string_prefix(ac_prefs->passwd)) {
++ /* TODO: Perhaps some prompt? */
++ debug_print(
++ "%s -> converting passwd cleartext to encrypted\n",
++ label);
++ tmp_str = ac_prefs->passwd;
++ ac_prefs->passwd = encrypt_with_master_password(ac_prefs->passwd);
++ cleanse_buffer(tmp_str, strlen(tmp_str));
++ g_free(tmp_str);
++ }
++
++ if ((ac_prefs->smtp_passwd != NULL) &&
++ !mpes_string_prefix(ac_prefs->smtp_passwd)) {
++ /* TODO: Perhaps some prompt? */
++ debug_print(
++ "%s -> converting smtp_passwd from cleartext to encrypted\n",
++ label);
++ tmp_str = ac_prefs->smtp_passwd;
++ ac_prefs->smtp_passwd = encrypt_with_master_password(
++ ac_prefs->smtp_passwd);
++ cleanse_buffer(tmp_str, strlen(tmp_str));
++ g_free(tmp_str);
++ }
++
++ }
++
+ custom_header_read_config(ac_prefs);
+ }
+
+diff --git a/libsylph/prefs_common.c b/libsylph/prefs_common.c
+index 91814fd..192964e 100644
+--- a/libsylph/prefs_common.c
++++ b/libsylph/prefs_common.c
+@@ -418,6 +418,17 @@ static PrefParam param[] = {
+ {"show_gpg_warning", "TRUE", &prefs_common.gpg_warning, P_BOOL},
+ #endif
+
++ /* Master password */
++ {"use_master_password", "FALSE", &prefs_common.use_master_password,
++ P_BOOL},
++ {"master_password_hash", NULL, &prefs_common.master_password_hash,
++ P_STRING},
++ {"encrypted_password_min_length", "32",
++ &prefs_common.encrypted_password_min_length, P_INT},
++ {"auto_unload_master_password", "FALSE",
++ &prefs_common.auto_unload_master_password,
++ P_BOOL},
++
+ /* Interface */
+ {"separate_folder", "FALSE", &prefs_common.sep_folder, P_BOOL},
+ {"separate_message", "FALSE", &prefs_common.sep_msg, P_BOOL},
+diff --git a/libsylph/prefs_common.h b/libsylph/prefs_common.h
+index 910f370..6f9c364 100644
+--- a/libsylph/prefs_common.h
++++ b/libsylph/prefs_common.h
+@@ -249,6 +249,12 @@ struct _PrefsCommon
+ gboolean passphrase_grab;
+ gboolean gpg_warning;
+
++ /* Master password */
++ gboolean use_master_password;
++ gchar *master_password_hash;
++ guint encrypted_password_min_length;
++ gboolean auto_unload_master_password;
++
+ /* Interface */
+ gboolean sep_folder;
+ gboolean sep_msg;
+diff --git a/libsylph/ssl.c b/libsylph/ssl.c
+index 8413925..e782b0e 100644
+--- a/libsylph/ssl.c
++++ b/libsylph/ssl.c
+@@ -32,6 +32,13 @@
+ #include "ssl.h"
+ #include "ssl_hostname_validation.h"
+
++#define SALT_SIZE 16
++#define CIPHER EVP_aes_256_cfb()
++#define KEY_HASH EVP_sha256()
++#define DIGEST_HASH EVP_sha256()
++#define PBKDF2_DIGEST_SIZE 64
++#define PBKDF2_ITERATIONS 100000
++
+ static SSL_CTX *ssl_ctx_SSLv23 = NULL;
+ static SSL_CTX *ssl_ctx_TLSv1 = NULL;
+
+@@ -402,4 +409,468 @@ void ssl_set_verify_func(SSLVerifyFunc func)
+ verify_ui_func = func;
+ }
+
++/* master password related functions */
++static gint secure_derive_key(guchar *key,
++ gint length_key,
++ const gchar *passphrase,
++ const guchar *salt) {
++
++ guint length_buffer, length_hash;
++ guchar *buffer, *ptr_hash;
++
++ EVP_MD_CTX *mdctx;
++
++ OPENSSL_cleanse(key, length_key);
++
++ length_buffer = SALT_SIZE + strlen(passphrase);
++ buffer = OPENSSL_malloc(length_buffer);
++ OPENSSL_cleanse(buffer, length_buffer);
++
++ memcpy(buffer, salt, SALT_SIZE);
++ memcpy(buffer + SALT_SIZE, passphrase, strlen(passphrase));
++
++ mdctx = EVP_MD_CTX_create();
++ EVP_DigestInit_ex(mdctx, KEY_HASH, NULL);
++ EVP_DigestUpdate(mdctx, buffer, length_buffer);
++ OPENSSL_cleanse(buffer, length_buffer);
++
++ ptr_hash = OPENSSL_malloc(EVP_MD_size(KEY_HASH));
++ EVP_DigestFinal_ex(mdctx, ptr_hash, &length_hash);
++
++ memcpy(key,
++ ptr_hash,
++ (length_hash > length_key) ? length_key : length_hash);
++
++ OPENSSL_cleanse(ptr_hash, length_hash);
++ OPENSSL_free(ptr_hash);
++ OPENSSL_free(buffer);
++ EVP_MD_CTX_destroy(mdctx);
++
++ return SSL_RC_OK;
++
++}
++
++gint encrypt_data(gchar **encrypted,
++ gint *length_encrypted,
++ const gchar *data,
++ const gchar *passphrase,
++ gint length_data,
++ guint min_data_length,
++ gboolean rnd_salt) {
++
++ gint crypt_buffer_cnt, rc;
++ guint key_size, length_hash;
++ guint length_cleartext, length_ciphertext, length_total;
++ guchar salt[SALT_SIZE];
++ guchar *ciphertext_buffer, *total_buffer;
++ /* sensitive buffers and counters */
++ guint length_data_payload, length_padding;
++ gchar str_data_size[3];
++ guchar *data_payload_buffer, *hash_buffer, *padding_buffer, *key;
++ guchar *cleartext_buffer;
++
++ EVP_CIPHER_CTX *ctx;
++ EVP_MD_CTX *mdctx;
++
++ rc = SSL_RC_ERROR;
++
++ if (length_data < 1) {
++ return -1;
++ }
++ if (rnd_salt) {
++ if (RAND_bytes(salt, SALT_SIZE) != 1) {
++ debug_print("Random problems...\n");
++ goto cleanup;
++ }
++ } else {
++ strncpy((gchar *)salt, "FOR TESTING ONLY", SALT_SIZE);
++ }
++
++ key_size = EVP_CIPHER_key_length(CIPHER);
++ key = OPENSSL_malloc(key_size);
++ OPENSSL_cleanse(key, key_size);
++ if (secure_derive_key(key,
++ key_size,
++ passphrase,
++ salt) != SSL_RC_OK) {
++ OPENSSL_cleanse(key, key_size);
++ debug_print("Could not generate secure key\n");
++ goto cleanup;
++ }
++
++ /* prepare the data-buffer */
++ length_padding = 0;
++ if (length_data < min_data_length) {
++ g_snprintf(str_data_size, 3, "%02d", length_data);
++ length_padding = min_data_length - length_data;
++ padding_buffer = OPENSSL_malloc(length_padding);
++ OPENSSL_cleanse(padding_buffer, length_padding);
++ if (RAND_bytes(padding_buffer, length_padding) != 1) {
++ debug_print("Random problems...\n");
++ goto cleanup;
++ }
++ } else {
++ g_snprintf(str_data_size, 3, "-1");
++ }
++
++ length_data_payload = 2 + length_data + length_padding;
++ data_payload_buffer = OPENSSL_malloc(length_data_payload);
++ OPENSSL_cleanse(data_payload_buffer, length_data_payload);
++
++ memcpy(data_payload_buffer, str_data_size, 2);
++ memcpy(data_payload_buffer + 2, data, length_data);
++ if (length_padding > 0) {
++ memcpy(data_payload_buffer + (2 + length_data),
++ padding_buffer,
++ length_padding);
++ }
++
++ mdctx = EVP_MD_CTX_create();
++ EVP_DigestInit_ex(mdctx, DIGEST_HASH, NULL);
++ EVP_DigestUpdate(mdctx, data_payload_buffer, length_data_payload);
++
++ length_hash = EVP_MD_size(DIGEST_HASH);
++ hash_buffer = OPENSSL_malloc(length_hash);
++ OPENSSL_cleanse(hash_buffer, length_hash);
++ EVP_DigestFinal_ex(mdctx, hash_buffer, NULL);
++
++ length_cleartext = length_hash + length_data_payload;
++
++ cleartext_buffer = OPENSSL_malloc(length_cleartext);
++ OPENSSL_cleanse(cleartext_buffer, length_cleartext);
++
++ /* assemble cleartext-buffer */
++ memcpy(cleartext_buffer, hash_buffer, length_hash);
++ memcpy(cleartext_buffer + length_hash,
++ data_payload_buffer,
++ length_data_payload);
++ OPENSSL_cleanse(data_payload_buffer, length_data_payload); /* sensitive */
++
++ /* encryption */
++ if (!(ctx = EVP_CIPHER_CTX_new())) {
++ debug_print("New ctx failed\n");
++ goto cleanup;
++ }
++
++ length_ciphertext = 0;
++ if (EVP_EncryptInit_ex(ctx, CIPHER, NULL, key, salt) != 1) {
++ debug_print("EVP_EncryptInit_ex failed\n");
++ goto cleanup;
++ }
++
++ ciphertext_buffer = OPENSSL_malloc(length_cleartext);
++
++ crypt_buffer_cnt = 0;
++ while(1) {
++ if (EVP_EncryptUpdate(ctx,
++ ciphertext_buffer + length_ciphertext,
++ &crypt_buffer_cnt,
++ cleartext_buffer + length_ciphertext,
++ 1) != 1) { /* one byte at a time */
++ debug_print("EVP_EncryptUpdate failed\n");
++ goto cleanup;
++ }
++
++ if (crypt_buffer_cnt != 1) { /* paranoia */
++ debug_print("The sizes of enc and dec text do not correspond\n");
++ goto cleanup;
++ }
++
++ ++length_ciphertext;
++
++ if (length_ciphertext >= length_cleartext) {
++ break;
++ }
++ }
++ /* No padding required for the CFB mode */
++
++ OPENSSL_cleanse(cleartext_buffer, length_cleartext); /* sensitive */
++ length_total = SALT_SIZE + length_ciphertext;
++ total_buffer = OPENSSL_malloc(length_total);
++
++ memcpy(total_buffer, salt, SALT_SIZE);
++ memcpy(total_buffer + SALT_SIZE, ciphertext_buffer, length_ciphertext);
++
++ *encrypted = g_base64_encode(total_buffer, length_total);
++ *length_encrypted = strlen(*encrypted);
++
++ rc = SSL_RC_OK;
++
++cleanup:
++ /* key */
++ OPENSSL_cleanse(key, key_size);
++ OPENSSL_free(key);
++ /* cleartext buffer */
++ OPENSSL_cleanse(cleartext_buffer, length_cleartext);
++ OPENSSL_free(cleartext_buffer);
++ /* payload buffer */
++ OPENSSL_cleanse(data_payload_buffer, length_data_payload);
++ OPENSSL_free(data_payload_buffer);
++ /* hash */
++ OPENSSL_cleanse(hash_buffer, length_hash);
++ OPENSSL_free(hash_buffer);
++ /* padding */
++ if (length_padding > 0) {
++ OPENSSL_cleanse(padding_buffer, length_padding);
++ OPENSSL_free(padding_buffer);
++ }
++
++ OPENSSL_cleanse(str_data_size, 3); /* paranoia */
++
++ /* ciphertext buffer */
++ OPENSSL_free(ciphertext_buffer);
++
++ /* total buffer */
++ OPENSSL_free(total_buffer);
++
++ length_data_payload = 0;
++ length_padding = 0;
++
++ EVP_CIPHER_CTX_free(ctx);
++ EVP_MD_CTX_destroy(mdctx);
++
++ return rc;
++
++}
++
++gint decrypt_data(gchar **decrypted,
++ const gchar *data,
++ const gchar *passphrase,
++ gint length_data) {
++
++ gint rc; /* return code */
++ gint decrypt_buffer_cnt, length_ciphertext, length_decrypted;
++ guint key_size, length_hash, length_cleartext;
++ gsize length_total;
++ guchar salt[SALT_SIZE];
++ guchar *ciphertext_buffer, *total_buffer;
++ /* sensitive buffers and counters */
++ gint data_size;
++ guint length_data_payload;
++ gchar str_data_size[3];
++ guchar *data_payload_buffer, *hash_buffer, *key;
++ guchar *cleartext_buffer;
++
++ EVP_CIPHER_CTX *ctx;
++ EVP_MD_CTX *mdctx;
++
++ rc = -1;
++
++ if (length_data < 1) {
++ return -1;
++ }
++
++ total_buffer = g_base64_decode(data, &length_total);
++ length_ciphertext = length_total - SALT_SIZE;
++ ciphertext_buffer = OPENSSL_malloc(length_ciphertext);
++ OPENSSL_cleanse(ciphertext_buffer, length_ciphertext);
++
++ memcpy(salt, total_buffer, SALT_SIZE);
++ memcpy(ciphertext_buffer, total_buffer + SALT_SIZE, length_ciphertext);
++
++ key_size = EVP_CIPHER_key_length(CIPHER);
++
++ /* decryption */
++ if(!(ctx = EVP_CIPHER_CTX_new())) {
++ debug_print("New ctx failed\n");
++ goto cleanup;
++ }
++
++ key = OPENSSL_malloc(key_size);
++ OPENSSL_cleanse(key, key_size);
++ if (secure_derive_key(key,
++ key_size,
++ passphrase,
++ salt) != 0) {
++ OPENSSL_cleanse(key, key_size);
++ debug_print("Could not generate secure key\n");
++ goto cleanup;
++ }
++
++ if (EVP_DecryptInit_ex(ctx, CIPHER, NULL, key, salt) != 1) {
++ debug_print("EVP_DecryptInit_ex failed\n");
++ goto cleanup;
++ }
++ OPENSSL_cleanse(key, key_size); /* highly sensitive */
++ cleartext_buffer = OPENSSL_malloc(length_ciphertext);
++ OPENSSL_cleanse(cleartext_buffer, length_ciphertext);
++
++ length_cleartext = 0;
++ decrypt_buffer_cnt = 0;
++
++ while(1) {
++ if (EVP_DecryptUpdate(ctx,
++ cleartext_buffer + length_cleartext,
++ &decrypt_buffer_cnt,
++ ciphertext_buffer + length_cleartext,
++ 1) != 1) { /* one byte at a time */
++ debug_print("EVP_EncryptUpdate failed\n");
++ goto cleanup;
++ }
++
++ if (decrypt_buffer_cnt != 1) { /* paranoia */
++ debug_print("The sizes of enc and dec text do not correspond");
++ goto cleanup;
++ }
++
++ ++length_cleartext;
++
++ if (length_cleartext >= length_ciphertext) {
++ break;
++ }
++ }
++
++
++ length_hash = EVP_MD_size(DIGEST_HASH);
++ hash_buffer = OPENSSL_malloc(length_hash);
++ length_data_payload = length_cleartext - length_hash;
++ data_payload_buffer = OPENSSL_malloc(length_data_payload);
++ OPENSSL_cleanse(data_payload_buffer, length_data_payload);
++
++ memcpy(data_payload_buffer,
++ cleartext_buffer + length_hash,
++ length_data_payload);
++
++ mdctx = EVP_MD_CTX_create();
++ EVP_DigestInit_ex(mdctx, DIGEST_HASH, NULL);
++ EVP_DigestUpdate(mdctx, data_payload_buffer, length_data_payload);
++ EVP_DigestFinal_ex(mdctx, hash_buffer, &length_hash);
++
++ if (strncmp((const gchar*) hash_buffer,
++ (const gchar*) cleartext_buffer,
++ length_hash) != 0) {
++ debug_print("Invalid hash\n");
++ rc = SSL_RC_WRONG_HASH_OR_KEY;
++ goto cleanup;
++ }
++
++ memcpy(str_data_size, cleartext_buffer + length_hash, 2);
++ data_size = atoi(str_data_size);
++
++ if (data_size < 0) {
++ length_decrypted = length_data_payload - 2;
++ } else {
++ length_decrypted = data_size;
++ }
++ *decrypted = OPENSSL_malloc(length_decrypted);
++ memcpy(*decrypted, data_payload_buffer + 2, length_decrypted);
++
++ rc = SSL_RC_OK;
++
++cleanup:
++
++ /* key */
++ OPENSSL_cleanse(key, key_size);
++ OPENSSL_free(key);
++ /* payload buffer */
++ OPENSSL_cleanse(data_payload_buffer, length_data_payload);
++ OPENSSL_free(data_payload_buffer);
++ /* hash */
++ OPENSSL_cleanse(hash_buffer, length_hash);
++ OPENSSL_free(hash_buffer);
++ /* ciphertext */
++ OPENSSL_free(ciphertext_buffer);
++ OPENSSL_free(total_buffer);
++
++ EVP_CIPHER_CTX_free(ctx);
++ EVP_MD_CTX_destroy(mdctx);
++
++ return rc;
++
++}
++
++gint generate_password_hash(gchar **password_hash,
++ const gchar *password,
++ const guchar *salt) {
++ /*
++ * Hashes 'password' using PKCS5_PBKDF2_HMAC with SHA512 and 'salt',
++ * and assigns a string to 'password_hash' with the following format:
++ * pbkdf2_sha512$iterations$base64(salt)$base64(password_hash)
++ */
++ guchar lsalt[SALT_SIZE];
++ gchar *PBKDF2_digest, *salt_b64, *digest_b64;
++
++ if (salt == NULL) {
++ if (RAND_bytes(lsalt, SALT_SIZE) != 1) {
++ debug_print("Random problems...\n");
++ return SSL_RC_ERROR;
++ }
++ } else {
++ memcpy(lsalt, salt, SALT_SIZE);
++ }
++
++ PBKDF2_digest = OPENSSL_malloc(PBKDF2_DIGEST_SIZE);
++ OPENSSL_cleanse(PBKDF2_digest, PBKDF2_DIGEST_SIZE);
++
++ PKCS5_PBKDF2_HMAC(password,
++ strlen(password),
++ lsalt,
++ SALT_SIZE,
++ PBKDF2_ITERATIONS,
++ EVP_sha512(),
++ PBKDF2_DIGEST_SIZE,
++ (guchar *) PBKDF2_digest);
++ digest_b64 = g_base64_encode((guchar *) PBKDF2_digest, PBKDF2_DIGEST_SIZE);
++ OPENSSL_cleanse(PBKDF2_digest, PBKDF2_DIGEST_SIZE);
++ OPENSSL_free(PBKDF2_digest);
++
++ salt_b64 = g_base64_encode(lsalt, SALT_SIZE);
++
++ *password_hash = g_strdup_printf("pbkdf2_sha512$%d$%s$%s",
++ PBKDF2_ITERATIONS,
++ salt_b64,
++ digest_b64);
++
++ OPENSSL_cleanse(digest_b64, strlen(digest_b64));
++ OPENSSL_free(digest_b64);
++
++ OPENSSL_cleanse(salt_b64, strlen(salt_b64));
++ OPENSSL_free(salt_b64);
++
++ return SSL_RC_OK;
++
++}
++
++gint check_password(const gchar *password, const gchar *password_hash) {
++
++ gint token_counter, rc;
++ guchar *salt;
++ gchar **tokens, *new_hash;
++ gsize salt_length;
++
++ rc = SSL_RC_ERROR;
++ tokens = g_strsplit(password_hash,
++ "$",
++ -1);
++ token_counter = 0;
++ while (*(tokens + token_counter) != NULL) {
++ ++token_counter;
++ }
++
++ if (token_counter != 4) {
++ debug_print("Invalid password hash...\n");
++ goto cleanup;
++ }
++
++ salt = g_base64_decode(*(tokens + 2), &salt_length);
++ if (salt_length != SALT_SIZE) {
++ debug_print("Salt size does not match\n");
++ goto cleanup;
++ }
++
++ if (generate_password_hash(&new_hash, password, salt) != SSL_RC_OK) {
++ debug_print("Password hash generation failed\n");
++ goto cleanup;
++ }
++
++ rc = g_strcmp0(password_hash, new_hash);
++
++cleanup:
++ g_free(salt);
++ g_free(new_hash);
++ g_strfreev(tokens);
++ return rc;
++
++}
++
+ #endif /* USE_SSL */
+diff --git a/libsylph/ssl.h b/libsylph/ssl.h
+index a9f690d..4c0ccd1 100644
+--- a/libsylph/ssl.h
++++ b/libsylph/ssl.h
+@@ -32,9 +32,15 @@
+ #include
+ #include
+ #include
++#include
++#include
+
+ #include "socket.h"
+
++#define SSL_RC_OK 0
++#define SSL_RC_ERROR -1
++#define SSL_RC_WRONG_HASH_OR_KEY 1
++
+ typedef enum {
+ SSL_METHOD_SSLv23,
+ SSL_METHOD_TLSv1
+@@ -60,6 +66,26 @@ void ssl_done_socket (SockInfo *sockinfo);
+
+ void ssl_set_verify_func (SSLVerifyFunc func);
+
++/* master password related code */
++gint encrypt_data(gchar **encrypted,
++ gint *length_encrypted,
++ const gchar *data,
++ const gchar *passphrase,
++ gint length_data,
++ guint min_data_length,
++ gboolean rnd_salt);
++
++gint decrypt_data(gchar **decrypted,
++ const gchar *data,
++ const gchar *passphrase,
++ gint length_data);
++
++gint generate_password_hash(gchar **password_hash,
++ const gchar *password,
++ const guchar *salt);
++
++gint check_password(const gchar *password, const gchar *password_hash);
++/* ---------------------------- */
+ #endif /* USE_SSL */
+
+ #endif /* __SSL_H__ */
+diff --git a/libsylph/utils.c b/libsylph/utils.c
+index 6ecf328..3bbcbcf 100644
+--- a/libsylph/utils.c
++++ b/libsylph/utils.c
+@@ -4598,6 +4598,36 @@ gchar *input_query_password(const gchar *server, const gchar *user)
+ return NULL;
+ }
+
++static QueryMasterPasswordFunc query_master_password_func = NULL;
++
++void set_input_query_master_password_func(QueryMasterPasswordFunc func)
++{
++ query_master_password_func = func;
++}
++
++gchar *input_query_master_password(void)
++{
++ if (query_master_password_func)
++ return query_master_password_func();
++ else
++ return NULL;
++}
++
++static SetNewPasswordFunc set_new_password_func = NULL;
++
++void set_input_set_new_password_func(SetNewPasswordFunc func)
++{
++ set_new_password_func = func;
++}
++
++gchar *input_set_new_password(guint max_attempts)
++{
++ if (set_new_password_func)
++ return set_new_password_func(max_attempts);
++ else
++ return NULL;
++}
++
+ /* logging */
+
+ static FILE *log_fp = NULL;
+diff --git a/libsylph/utils.h b/libsylph/utils.h
+index 9ac65cf..ede55ff 100644
+--- a/libsylph/utils.h
++++ b/libsylph/utils.h
+@@ -202,6 +202,8 @@ typedef void (*ProgressFunc) (gint cur,
+ gint total);
+ typedef gchar * (*QueryPasswordFunc) (const gchar *server,
+ const gchar *user);
++typedef gchar * (*QueryMasterPasswordFunc) (void);
++typedef gchar * (*SetNewPasswordFunc) (guint max_attempts);
+ typedef void (*LogFunc) (const gchar *str);
+ typedef void (*LogFlushFunc) (void);
+
+@@ -560,9 +562,12 @@ void progress_show (gint cur,
+
+ /* user input */
+ void set_input_query_password_func (QueryPasswordFunc func);
+-
+ gchar *input_query_password (const gchar *server,
+ const gchar *user);
++void set_input_query_master_password_func(QueryMasterPasswordFunc func);
++gchar *input_query_master_password(void);
++void set_input_set_new_password_func(SetNewPasswordFunc func);
++gchar *input_set_new_password(guint max_attempts);
+
+ /* logging */
+ void set_log_file (const gchar *filename);
+diff --git a/src/inputdialog.c b/src/inputdialog.c
+index a601085..cdfb997 100644
+--- a/src/inputdialog.c
++++ b/src/inputdialog.c
+@@ -44,6 +44,7 @@
+ #include "filesel.h"
+ #include "prefs_common.h"
+ #include "gtkutils.h"
++#include "masterpassword.h"
+ #include "utils.h"
+
+ #define DIALOG_WIDTH 420
+@@ -156,6 +157,57 @@ gchar *input_dialog_query_password(const gchar *server, const gchar *user)
+ return pass;
+ }
+
++gchar *input_dialog_query_master_password(void)
++{
++ gchar *mp_input;
++
++ mp_input = input_dialog_with_invisible(_("Input password"),
++ _("Master password"),
++ NULL);
++
++ if (prefs_common.use_master_password &&
++ prefs_common.auto_unload_master_password)
++ {
++ g_timeout_add(1000 * 30, unload_master_password, NULL);
++ debug_print("Master password to be unloaded in 30 seconds\n");
++ }
++
++ return mp_input;
++}
++
++gchar *input_dialog_set_new_password(guint max_attempts)
++{
++ guint cnt;
++ gchar *pass1, *pass2;
++
++ if (max_attempts < 1)
++ return NULL;
++
++ for (cnt = 0; cnt < max_attempts; ++cnt) {
++ pass1 = input_dialog_with_invisible(
++ _("Input password"),
++ _("New password"),
++ NULL);
++ pass2 = input_dialog_with_invisible(
++ _("Input password"),
++ _("Confirm new password"),
++ NULL);
++
++ if (pass1 != NULL && pass2 != NULL && strcmp(pass1, pass2) == 0) {
++ cleanse_buffer(pass2, strlen(pass2));
++ g_free(pass2);
++ break;
++ }
++ cleanse_buffer(pass1, strlen(pass1));
++ cleanse_buffer(pass2, strlen(pass2));
++ g_free(pass2);
++ g_free(pass1);
++ pass1 = NULL;
++ }
++
++ return pass1;
++}
++
+ gchar *input_dialog_with_filesel(const gchar *title, const gchar *message,
+ const gchar *default_string,
+ GtkFileChooserAction action)
+diff --git a/src/inputdialog.h b/src/inputdialog.h
+index 02bdda3..5364d1a 100644
+--- a/src/inputdialog.h
++++ b/src/inputdialog.h
+@@ -36,7 +36,8 @@ gchar *input_dialog_combo (const gchar *title,
+ gboolean case_sensitive);
+ gchar *input_dialog_query_password (const gchar *server,
+ const gchar *user);
+-
++gchar *input_dialog_query_master_password(void);
++gchar *input_dialog_set_new_password(guint max_attempts);
+ gchar *input_dialog_with_filesel (const gchar *title,
+ const gchar *message,
+ const gchar *default_string,
+diff --git a/src/main.c b/src/main.c
+index 77d8192..97a4bd0 100644
+--- a/src/main.c
++++ b/src/main.c
+@@ -87,6 +87,7 @@
+ #include "foldersel.h"
+ #include "update_check.h"
+ #include "colorlabel.h"
++#include "masterpassword.h"
+
+ #if USE_GPGME
+ # include "rfc2015.h"
+@@ -265,14 +266,54 @@ int main(int argc, char *argv[])
+ set_ui_update_func(gtkut_events_flush);
+ set_progress_func(main_window_progress_show);
+ set_input_query_password_func(input_dialog_query_password);
++ set_input_set_new_password_func(input_dialog_set_new_password);
+ #if USE_SSL
+ ssl_init();
+ ssl_set_verify_func(ssl_manager_verify_cert);
++ set_input_query_master_password_func(input_dialog_query_master_password);
+ #endif
+
+ CHDIR_EXIT_IF_FAIL(get_home_dir(), 1);
+
+ prefs_common_read_config();
++ set_master_password(NULL);
++#if USE_SSL
++ if (prefs_common.use_master_password) {
++ if (prefs_common.master_password_hash != NULL) {
++ if (check_master_password_interactively(3) != MP_RC_OK) {
++ if (alertpanel(_("Master password"),
++ _("Invalid master password"),
++ GTK_STOCK_DISCARD,
++ GTK_STOCK_QUIT,
++ NULL) != G_ALERTDEFAULT) {
++ exit(1);
++ }
++ }
++ } else {
++ alertpanel_notice(
++ _("Master password enabled but not set. Setting one now"));
++ if (set_master_password_interactively(3) != MP_RC_OK) {
++ if (alertpanel(_("Master password"),
++ _("Unable to set master password"),
++ GTK_STOCK_DISCARD,
++ GTK_STOCK_QUIT,
++ NULL) != G_ALERTDEFAULT) {
++ exit(1);
++ }
++ }
++ }
++ }
++ /* security goal:
++ * if the master password is enabled and loaded on init,
++ * an attacker can potentially set use_master_password - disabled
++ * afterwards and then force Sylpheed to save account data - the result
++ * being passwords saved to accountrc in plain-text.
++ * possible solution:
++ * Do not allow the passwords to be stored in plain-text if Sylpheed
++ * was started with use_master_password - enabled.
++ */
++ master_password_enabled_on_init = prefs_common.use_master_password;
++#endif
+ filter_set_addressbook_func(addressbook_has_address);
+ filter_read_config();
+ prefs_actions_read_config();
+@@ -381,6 +422,13 @@ int main(int argc, char *argv[])
+
+ remote_command_exec();
+
++#if USE_SSL
++ if (prefs_common.auto_unload_master_password && master_password_active()) {
++ debug_print("Auto unloading master password\n");
++ unload_master_password();
++ }
++#endif
++
+ #if USE_UPDATE_CHECK
+ if (prefs_common.auto_update_check)
+ update_check(FALSE);
+@@ -993,6 +1041,7 @@ void app_will_exit(gboolean force)
+
+ /* remove temporary files, close log file, socket cleanup */
+ #if USE_SSL
++ unload_master_password();
+ ssl_done();
+ #endif
+ syl_cleanup();
+diff --git a/src/prefs_account_dialog.c b/src/prefs_account_dialog.c
+index e9cba13..e215b8e 100644
+--- a/src/prefs_account_dialog.c
++++ b/src/prefs_account_dialog.c
+@@ -267,7 +267,7 @@ static PrefsUIData ui_data[] = {
+ {"user_id", &basic.uid_entry,
+ prefs_set_data_from_entry, prefs_set_entry},
+ {"password", &basic.pass_entry,
+- prefs_set_data_from_entry, prefs_set_entry},
++ prefs_set_data_from_epass_entry, prefs_set_entry},
+
+ /* Receive */
+ {"use_apop_auth", &receive.use_apop_chkbtn,
+@@ -313,7 +313,7 @@ static PrefsUIData ui_data[] = {
+ {"smtp_user_id", &p_send.smtp_uid_entry,
+ prefs_set_data_from_entry, prefs_set_entry},
+ {"smtp_password", &p_send.smtp_pass_entry,
+- prefs_set_data_from_entry, prefs_set_entry},
++ prefs_set_data_from_epass_entry, prefs_set_entry},
+
+ {"pop_before_smtp", &p_send.pop_bfr_smtp_chkbtn,
+ prefs_set_data_from_toggle, prefs_set_toggle},
+diff --git a/src/prefs_common_dialog.c b/src/prefs_common_dialog.c
+index fa4be4e..119a056 100644
+--- a/src/prefs_common_dialog.c
++++ b/src/prefs_common_dialog.c
+@@ -216,6 +216,14 @@ static struct Privacy {
+ } privacy;
+ #endif
+
++#if USE_SSL
++static struct MasterPassword {
++ GtkWidget *checkbtn_use_master_password;
++ GtkWidget *checkbtn_auto_unload_master_password;
++ GtkWidget *spinbtn_encrypted_password_min_length;
++} master_password;
++#endif
++
+ static struct Interface {
+ GtkWidget *checkbtn_always_show_msg;
+ GtkWidget *checkbtn_always_mark_read;
+@@ -563,6 +571,18 @@ static PrefsUIData ui_data[] = {
+ prefs_set_data_from_toggle, prefs_set_toggle},
+ #endif /* USE_GPGME */
+
++#if USE_SSL
++ {"use_master_password", &master_password.checkbtn_use_master_password,
++ prefs_set_data_from_toggle, prefs_set_toggle},
++ {"auto_unload_master_password",
++ &master_password.checkbtn_auto_unload_master_password,
++ prefs_set_data_from_toggle, prefs_set_toggle},
++ {"encrypted_password_min_length",
++ &master_password.spinbtn_encrypted_password_min_length,
++ prefs_set_data_from_spinbtn,
++ prefs_set_spinbtn},
++#endif
++
+ /* Interface */
+ {"always_show_message_when_selected",
+ &iface.checkbtn_always_show_msg,
+@@ -693,6 +713,9 @@ static void prefs_junk_create (void);
+ #if USE_GPGME
+ static void prefs_privacy_create (void);
+ #endif
++#if USE_SSL
++static void prefs_master_password_create (void);
++#endif
+ static void prefs_details_create (void);
+ static GtkWidget *prefs_other_create (void);
+ static GtkWidget *prefs_extcmd_create (void);
+@@ -852,6 +875,10 @@ static void prefs_common_create(void)
+ #if USE_GPGME
+ prefs_privacy_create();
+ SET_NOTEBOOK_LABEL(dialog.notebook, _("Privacy"), page++);
++#endif
++#if USE_SSL
++ prefs_master_password_create();
++ SET_NOTEBOOK_LABEL(dialog.notebook, _("Master password"), page++);
+ #endif
+ prefs_details_create();
+ SET_NOTEBOOK_LABEL(dialog.notebook, _("Details"), page++);
+@@ -2648,6 +2675,93 @@ static void prefs_privacy_create(void)
+ }
+ #endif /* USE_GPGME */
+
++#if USE_SSL
++static void prefs_master_password_create(void)
++{
++ GtkWidget *vbox_main;
++ GtkWidget *vbox_master_password_options;
++ GtkWidget *vbox_master_password_suboptions;
++ GtkWidget *hbox1;
++ GtkWidget *hbox_spc;
++ GtkWidget *label;
++ GtkWidget *checkbtn_use_master_password;
++ GtkWidget *checkbtn_auto_unload_master_password;
++ GtkWidget *spinbtn_encrypted_password_min_length;
++ GtkObject *spinbtn_encrypted_password_min_length_adj;
++
++ vbox_main = gtk_vbox_new (FALSE, VSPACING);
++ gtk_widget_show (vbox_main);
++ gtk_container_add (GTK_CONTAINER (dialog.notebook), vbox_main);
++ gtk_container_set_border_width (GTK_CONTAINER (vbox_main), VBOX_BORDER);
++
++ vbox_master_password_options = gtk_vbox_new (FALSE, 0);
++ gtk_widget_show (vbox_master_password_options);
++ gtk_box_pack_start (
++ GTK_BOX (vbox_main), vbox_master_password_options, FALSE, FALSE, 0);
++
++ PACK_CHECK_BUTTON (vbox_master_password_options,
++ checkbtn_use_master_password,
++ _("Use master password"));
++
++ vbox_master_password_suboptions = gtk_vbox_new (FALSE, VSPACING_NARROW);
++ gtk_widget_show (vbox_master_password_suboptions);
++ gtk_box_pack_start (GTK_BOX (vbox_master_password_options),
++ vbox_master_password_suboptions,
++ FALSE,
++ FALSE,
++ 0);
++
++ PACK_CHECK_BUTTON (vbox_master_password_suboptions,
++ checkbtn_auto_unload_master_password,
++ _("Automatically unload master password "
++ "after session initialization"));
++
++ hbox1 = gtk_hbox_new (FALSE, 8);
++ gtk_widget_show (hbox1);
++ gtk_box_pack_start (GTK_BOX (vbox_master_password_suboptions),
++ hbox1,
++ FALSE,
++ FALSE,
++ 0);
++
++ hbox_spc = gtk_hbox_new (FALSE, 0);
++ gtk_widget_show (hbox_spc);
++ gtk_box_pack_start (GTK_BOX (hbox1), hbox_spc, FALSE, FALSE, 0);
++ gtk_widget_set_size_request (hbox_spc, 12, -1);
++
++ label = gtk_label_new (_("Min. password length"));
++ gtk_widget_show (label);
++ gtk_box_pack_start (
++ GTK_BOX (hbox1), label, FALSE, FALSE, 0);
++
++ spinbtn_encrypted_password_min_length_adj = gtk_adjustment_new (
++ 32, 0, 99, 1, 5, 0);
++ spinbtn_encrypted_password_min_length = gtk_spin_button_new(
++ GTK_ADJUSTMENT (spinbtn_encrypted_password_min_length_adj), 1, 0);
++ gtk_widget_show (spinbtn_encrypted_password_min_length);
++ gtk_box_pack_start (GTK_BOX (hbox1),
++ spinbtn_encrypted_password_min_length,
++ FALSE,
++ FALSE,
++ 0);
++ gtk_spin_button_set_numeric (
++ GTK_SPIN_BUTTON (spinbtn_encrypted_password_min_length), TRUE);
++ gtk_widget_set_size_request (spinbtn_encrypted_password_min_length,
++ 64,
++ -1);
++
++ SET_TOGGLE_SENSITIVITY (checkbtn_use_master_password,
++ vbox_master_password_suboptions);
++
++ master_password.checkbtn_use_master_password
++ = checkbtn_use_master_password;
++ master_password.checkbtn_auto_unload_master_password
++ = checkbtn_auto_unload_master_password;
++ master_password.spinbtn_encrypted_password_min_length
++ = spinbtn_encrypted_password_min_length;
++}
++#endif /* USE_SSL */
++
+ static void prefs_details_create(void)
+ {
+ GtkWidget *vbox1;
+diff --git a/src/prefs_ui.c b/src/prefs_ui.c
+index a3a8f74..2ab1d45 100644
+--- a/src/prefs_ui.c
++++ b/src/prefs_ui.c
+@@ -31,11 +31,13 @@
+ #include
+
+ #include "prefs.h"
++#include "prefs_common.h"
+ #include "prefs_ui.h"
+ #include "menu.h"
+ #include "codeconv.h"
+ #include "utils.h"
+ #include "gtkutils.h"
++#include "masterpassword.h"
+
+ typedef enum
+ {
+@@ -268,6 +270,65 @@ void prefs_set_data_from_entry(PrefParam *pparam)
+ }
+ }
+
++void prefs_set_data_from_epass_entry(PrefParam *pparam)
++{
++#if USE_SSL
++ PrefsUIData *ui_data;
++ gchar **str;
++ const gchar *entry_str;
++
++ /* This is where decrypted passwords are encrypted and stored again */
++
++ /* master_password == NULL for any of the following reasons:
++ * - use_master_password is not enabled (we just save without encrypting)
++ * - master_password is auto unloaded (prompt for the master password)
++ * - undefined reason (refure to store the password)
++ */
++ if (!prefs_common.use_master_password) {
++ /* check if use_master_password was enabled when Sylpheed started */
++ if (!master_password_enabled_on_init) {
++ prefs_set_data_from_entry(pparam);
++ }
++ return;
++ } else if (master_password == NULL) {
++ if (!prefs_common.auto_unload_master_password) {
++ debug_print("Master password enabled, but not loaded for no "
++ "apparent reason. Not storing\n");
++ return;
++ } else {
++ if (check_master_password_interactively(3) != MP_RC_OK) {
++ debug_print("Failed to reload the master password\n");
++ return;
++ }
++ }
++ }
++
++ ui_data = (PrefsUIData *)pparam->ui_data;
++ g_return_if_fail(ui_data != NULL);
++ g_return_if_fail(*ui_data->widget != NULL);
++
++ entry_str = gtk_entry_get_text(GTK_ENTRY(*ui_data->widget));
++
++ switch (pparam->type) {
++ case P_STRING:
++ str = (gchar **)pparam->data;
++ g_free(*str);
++ if ((entry_str != NULL) && (!mpes_string_prefix(entry_str))) {
++ debug_print("Encrypting GTK password entry\n");
++ *str = encrypt_with_master_password(entry_str);
++ } else {
++ *str = entry_str[0] ? g_strdup(entry_str) : NULL;
++ }
++ break;
++ default:
++ g_warning("Invalid PrefType for GtkEntry widget: %d\n",
++ pparam->type);
++ }
++#else
++ prefs_set_data_from_entry(pparam);
++#endif
++}
++
+ void prefs_set_entry(PrefParam *pparam)
+ {
+ PrefsUIData *ui_data;
+diff --git a/src/prefs_ui.h b/src/prefs_ui.h
+index cfdf353..f7bd903 100644
+--- a/src/prefs_ui.h
++++ b/src/prefs_ui.h
+@@ -164,6 +164,7 @@ void prefs_set_data_from_dialog (PrefParam *param);
+ void prefs_set_dialog_to_default(PrefParam *param);
+
+ void prefs_set_data_from_entry (PrefParam *pparam);
++void prefs_set_data_from_epass_entry(PrefParam *pparam);
+ void prefs_set_entry (PrefParam *pparam);
+ void prefs_set_data_from_text (PrefParam *pparam);
+ void prefs_set_text (PrefParam *pparam);
+diff --git a/src/send_message.c b/src/send_message.c
+index a8c2c7a..537c3c8 100644
+--- a/src/send_message.c
++++ b/src/send_message.c
+@@ -58,6 +58,7 @@
+ #include "inc.h"
+ #include "mainwindow.h"
+ #include "summaryview.h"
++#include "masterpassword.h"
+
+ #define SMTP_PORT 25
+ #if USE_SSL
+@@ -648,13 +649,13 @@ static gint send_message_smtp(PrefsAccount *ac_prefs, GSList *to_list, FILE *fp)
+
+ if (ac_prefs->smtp_userid) {
+ smtp_session->user = g_strdup(ac_prefs->smtp_userid);
+- if (ac_prefs->smtp_passwd)
+- smtp_session->pass =
+- g_strdup(ac_prefs->smtp_passwd);
+- else if (ac_prefs->tmp_smtp_pass)
++ if (ac_prefs->smtp_passwd) {
++ smtp_session->pass = decrypt_with_master_password(
++ ac_prefs->smtp_passwd);
++ } else if (ac_prefs->tmp_smtp_pass) {
+ smtp_session->pass =
+ g_strdup(ac_prefs->tmp_smtp_pass);
+- else {
++ } else {
+ smtp_session->pass =
+ input_query_password
+ (ac_prefs->smtp_server,
diff --git a/mail-client/sylpheed/files/sylpheed-3.7.0-signature-box.patch b/mail-client/sylpheed/files/sylpheed-3.7.0-signature-box.patch
new file mode 100644
index 0000000..af8cdaa
--- /dev/null
+++ b/mail-client/sylpheed/files/sylpheed-3.7.0-signature-box.patch
@@ -0,0 +1,267 @@
+diff -x .git -rupN sylpheed-3.7.0/libsylph/prefs_common.c sylpheed-3.7.0.new/libsylph/prefs_common.c
+--- sylpheed-3.7.0/libsylph/prefs_common.c 2017-11-24 05:17:23.000000000 +0100
++++ sylpheed-3.7.0.new/libsylph/prefs_common.c 2018-01-31 10:52:57.094885505 +0100
+@@ -406,6 +406,8 @@ static PrefParam param[] = {
+ P_BOOL},
+ {"gpg_signature_popup", "FALSE", &prefs_common.gpg_signature_popup,
+ P_BOOL},
++ {"gpg_signature_popup_mode", "1", &prefs_common.gpg_signature_popup_mode,
++ P_INT},
+ {"store_passphrase", "FALSE", &prefs_common.store_passphrase, P_BOOL},
+ {"store_passphrase_timeout", "0",
+ &prefs_common.store_passphrase_timeout, P_INT},
+diff -x .git -rupN sylpheed-3.7.0/libsylph/prefs_common.h sylpheed-3.7.0.new/libsylph/prefs_common.h
+--- sylpheed-3.7.0/libsylph/prefs_common.h 2017-11-24 05:16:18.000000000 +0100
++++ sylpheed-3.7.0.new/libsylph/prefs_common.h 2018-01-31 10:52:57.052886536 +0100
+@@ -243,6 +243,7 @@ struct _PrefsCommon
+ /* Privacy */
+ gboolean auto_check_signatures;
+ gboolean gpg_signature_popup;
++ gint gpg_signature_popup_mode;
+ gboolean store_passphrase;
+ gint store_passphrase_timeout;
+ gboolean passphrase_grab;
+diff -x .git -rupN sylpheed-3.7.0/src/prefs_common_dialog.c sylpheed-3.7.0.new/src/prefs_common_dialog.c
+--- sylpheed-3.7.0/src/prefs_common_dialog.c 2014-11-05 08:28:13.000000000 +0100
++++ sylpheed-3.7.0.new/src/prefs_common_dialog.c 2018-01-31 10:52:57.184883295 +0100
+@@ -205,6 +205,7 @@ static struct JunkMail {
+ static struct Privacy {
+ GtkWidget *checkbtn_auto_check_signatures;
+ GtkWidget *checkbtn_gpg_signature_popup;
++ GtkWidget *radiobtn_gpg_signature_all;
+ GtkWidget *checkbtn_store_passphrase;
+ GtkWidget *spinbtn_store_passphrase;
+ GtkObject *spinbtn_store_passphrase_adj;
+@@ -314,6 +315,13 @@ static void prefs_common_attach_toolbtn_
+ static void prefs_common_online_mode_set_data_from_radiobtn(PrefParam *pparam);
+ static void prefs_common_online_mode_set_radiobtn (PrefParam *pparam);
+
++#if USE_GPGME
++static void prefs_common_gpg_signature_popup_mode_set_data_from_radiobtn(
++ PrefParam *pparam);
++static void prefs_common_gpg_signature_popup_mode_set_radiobtn(
++ PrefParam *pparam);
++#endif
++
+ static PrefsUIData ui_data[] = {
+ /* Receive */
+ {"autochk_newmail", &receive.checkbtn_autochk,
+@@ -540,6 +548,9 @@ static PrefsUIData ui_data[] = {
+ prefs_set_data_from_toggle, prefs_set_toggle},
+ {"gpg_signature_popup", &privacy.checkbtn_gpg_signature_popup,
+ prefs_set_data_from_toggle, prefs_set_toggle},
++ {"gpg_signature_popup_mode", &privacy.radiobtn_gpg_signature_all,
++ prefs_common_gpg_signature_popup_mode_set_data_from_radiobtn,
++ prefs_common_gpg_signature_popup_mode_set_radiobtn},
+ {"store_passphrase", &privacy.checkbtn_store_passphrase,
+ prefs_set_data_from_toggle, prefs_set_toggle},
+ {"store_passphrase_timeout", &privacy.spinbtn_store_passphrase,
+@@ -2478,10 +2489,14 @@ static void prefs_privacy_create(void)
+ GtkWidget *vbox2;
+ GtkWidget *vbox3;
+ GtkWidget *hbox1;
++ GtkWidget *vbox_sign_popup_mode;
++ GtkWidget *hbox_sign_popup_mode;
+ GtkWidget *hbox_spc;
+ GtkWidget *label;
+ GtkWidget *checkbtn_auto_check_signatures;
+ GtkWidget *checkbtn_gpg_signature_popup;
++ GtkWidget *radiobtn_gpg_signature_all;
++ GtkWidget *radiobtn_gpg_signature_bad;
+ GtkWidget *checkbtn_store_passphrase;
+ GtkObject *spinbtn_store_passphrase_adj;
+ GtkWidget *spinbtn_store_passphrase;
+@@ -2505,6 +2520,56 @@ static void prefs_privacy_create(void)
+ PACK_CHECK_BUTTON (vbox2, checkbtn_gpg_signature_popup,
+ _("Show signature check result in a popup window"));
+
++ vbox_sign_popup_mode = gtk_vbox_new (FALSE, VSPACING_NARROW);
++ gtk_widget_show (vbox_sign_popup_mode);
++ gtk_box_pack_start (GTK_BOX (vbox2), vbox_sign_popup_mode, FALSE, FALSE, 0);
++
++ hbox_sign_popup_mode = gtk_hbox_new (FALSE, 8);
++ gtk_widget_show (hbox_sign_popup_mode);
++ gtk_box_pack_start (GTK_BOX (vbox_sign_popup_mode),
++ hbox_sign_popup_mode,
++ FALSE,
++ FALSE,
++ 0);
++
++ hbox_spc = gtk_hbox_new (FALSE, 0);
++ gtk_widget_show (hbox_spc);
++ gtk_box_pack_start (GTK_BOX (hbox_sign_popup_mode),
++ hbox_spc,
++ FALSE,
++ FALSE,
++ 0);
++ gtk_widget_set_size_request (hbox_spc, 12, -1);
++
++ radiobtn_gpg_signature_all = gtk_radio_button_new_with_label(
++ NULL,
++ _("All signatures"));
++ gtk_widget_show(radiobtn_gpg_signature_all);
++ gtk_box_pack_start(GTK_BOX (hbox_sign_popup_mode),
++ radiobtn_gpg_signature_all,
++ FALSE,
++ FALSE,
++ 0);
++ g_object_set_data(G_OBJECT (radiobtn_gpg_signature_all),
++ MENU_VAL_ID,
++ GINT_TO_POINTER (1));
++
++ radiobtn_gpg_signature_bad = gtk_radio_button_new_with_label_from_widget(
++ GTK_RADIO_BUTTON (radiobtn_gpg_signature_all),
++ _("Bad signatures only"));
++ gtk_widget_show(radiobtn_gpg_signature_bad);
++ gtk_box_pack_start(GTK_BOX (hbox_sign_popup_mode),
++ radiobtn_gpg_signature_bad,
++ FALSE,
++ FALSE,
++ 0);
++ g_object_set_data(G_OBJECT (radiobtn_gpg_signature_bad),
++ MENU_VAL_ID,
++ GINT_TO_POINTER (0));
++
++ SET_TOGGLE_SENSITIVITY (checkbtn_gpg_signature_popup,
++ hbox_sign_popup_mode);
++
+ PACK_CHECK_BUTTON (vbox2, checkbtn_store_passphrase,
+ _("Store passphrase in memory temporarily"));
+
+@@ -2572,7 +2637,8 @@ static void prefs_privacy_create(void)
+ = checkbtn_auto_check_signatures;
+ privacy.checkbtn_gpg_signature_popup
+ = checkbtn_gpg_signature_popup;
+- privacy.checkbtn_store_passphrase = checkbtn_store_passphrase;
++ privacy.radiobtn_gpg_signature_all = radiobtn_gpg_signature_all;
++ privacy.checkbtn_store_passphrase = checkbtn_store_passphrase;
+ privacy.spinbtn_store_passphrase = spinbtn_store_passphrase;
+ privacy.spinbtn_store_passphrase_adj = spinbtn_store_passphrase_adj;
+ #ifndef G_OS_WIN32
+@@ -4719,6 +4785,60 @@ static void prefs_common_online_mode_set
+ }
+ }
+
++#if USE_GPGME
++static void prefs_common_gpg_signature_popup_mode_set_data_from_radiobtn(
++ PrefParam *pparam)
++{
++ PrefsUIData *ui_data;
++ GtkRadioButton *radiobtn;
++ GSList *group;
++
++ ui_data = (PrefsUIData *)pparam->ui_data;
++ g_return_if_fail(ui_data != NULL);
++ g_return_if_fail(*ui_data->widget != NULL);
++
++ radiobtn = GTK_RADIO_BUTTON(*ui_data->widget);
++ group = gtk_radio_button_get_group(radiobtn);
++ while (group != NULL) {
++ GtkToggleButton *btn = GTK_TOGGLE_BUTTON(group->data);
++
++ if (gtk_toggle_button_get_active(btn)) {
++ prefs_common.gpg_signature_popup_mode =
++ GPOINTER_TO_INT(g_object_get_data(G_OBJECT(btn), MENU_VAL_ID));
++ break;
++ }
++ group = group->next;
++ }
++}
++
++static void prefs_common_gpg_signature_popup_mode_set_radiobtn(
++ PrefParam *pparam)
++{
++ PrefsUIData *ui_data;
++ GtkRadioButton *radiobtn;
++ GSList *group;
++
++ ui_data = (PrefsUIData *)pparam->ui_data;
++ g_return_if_fail(ui_data != NULL);
++ g_return_if_fail(*ui_data->widget != NULL);
++
++ radiobtn = GTK_RADIO_BUTTON(*ui_data->widget);
++ group = gtk_radio_button_get_group(radiobtn);
++ while (group != NULL) {
++ GtkToggleButton *btn = GTK_TOGGLE_BUTTON(group->data);
++ gint data;
++
++ data = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(btn),
++ MENU_VAL_ID));
++ if (data == prefs_common.gpg_signature_popup_mode) {
++ gtk_toggle_button_set_active(btn, TRUE);
++ break;
++ }
++ group = group->next;
++ }
++}
++#endif
++
+ static void prefs_common_dispitem_clicked(void)
+ {
+ prefs_summary_column_open(FOLDER_ITEM_IS_SENT_FOLDER
+diff -x .git -rupN sylpheed-3.7.0/src/rfc2015.c sylpheed-3.7.0.new/src/rfc2015.c
+--- sylpheed-3.7.0/src/rfc2015.c 2014-03-26 06:55:35.000000000 +0100
++++ sylpheed-3.7.0.new/src/rfc2015.c 2018-01-31 11:10:44.946662147 +0100
+@@ -210,8 +210,12 @@ static void check_signature(MimeInfo *mi
+ gchar *tmp_file;
+ gint n_exclude_chars = 0;
+
+- if (prefs_common.gpg_signature_popup)
++ if (prefs_common.gpg_signature_popup &&
++ prefs_common.gpg_signature_popup_mode == 1)
++ {
++ /* FIXME: Perhaps some macro instead of 1 */
+ statuswindow = gpgmegtk_sig_status_create();
++ }
+
+ err = gpgme_new(&ctx);
+ if (err) {
+@@ -309,8 +313,21 @@ leave:
+ result = _("Error verifying the signature");
+ }
+ debug_print("verification status: %s\n", result);
+- if (prefs_common.gpg_signature_popup)
++ if (prefs_common.gpg_signature_popup &&
++ prefs_common.gpg_signature_popup_mode == 1)
++ {
++ /* FIXME: Perhaps some macro instead of 1 */
++ gpgmegtk_sig_status_update(statuswindow, ctx);
++ } else if (prefs_common.gpg_signature_popup &&
++ verifyresult->signatures->status != GPG_ERR_NO_DATA &&
++ verifyresult->signatures->status != GPG_ERR_NO_ERROR)
++ {
++ /* prefs_common.gpg_signature_popup_mode == 0 is useless as long as
++ * there are only two modes (0 and 1)
++ */
++ statuswindow = gpgmegtk_sig_status_create();
+ gpgmegtk_sig_status_update(statuswindow, ctx);
++ }
+
+ g_free (partinfo->sigstatus);
+ partinfo->sigstatus = g_strdup (result);
+@@ -319,8 +336,11 @@ leave:
+ gpgme_data_release(text);
+ if (ctx)
+ gpgme_release(ctx);
+- if (prefs_common.gpg_signature_popup)
++ if (prefs_common.gpg_signature_popup) {
++ /* gpgmegtk_sig_status_destroy handles NULL params so no complicated
++ check is needed */
+ gpgmegtk_sig_status_destroy(statuswindow);
++ }
+ }
+
+ /*
+@@ -427,7 +447,11 @@ static gpgme_data_t pgp_decrypt(MsgInfo
+ debug_print("verification status: %s\n", result);
+ debug_print("full status: %s\n",
+ msginfo->encinfo->sigstatus_full);
+- if (prefs_common.gpg_signature_popup) {
++ if (prefs_common.gpg_signature_popup &&
++ ((prefs_common.gpg_signature_popup_mode == 1) ||
++ (verifyresult->signatures->status != GPG_ERR_NO_DATA &&
++ verifyresult->signatures->status != GPG_ERR_NO_ERROR)))
++ {
+ GpgmegtkSigStatus statuswindow;
+ statuswindow = gpgmegtk_sig_status_create();
+ gpgmegtk_sig_status_update(statuswindow, ctx);
diff --git a/mail-client/sylpheed/files/sylpheed-3.7.0-version.patch b/mail-client/sylpheed/files/sylpheed-3.7.0-version.patch
new file mode 100644
index 0000000..65d560f
--- /dev/null
+++ b/mail-client/sylpheed/files/sylpheed-3.7.0-version.patch
@@ -0,0 +1,26 @@
+diff --git a/configure b/configure
+index 6984cce..cc2b770 100755
+--- a/configure
++++ b/configure
+@@ -2581,7 +2581,7 @@ MINOR_VERSION=7
+ MICRO_VERSION=0
+ INTERFACE_AGE=0
+ BINARY_AGE=0
+-EXTRA_VERSION=
++EXTRA_VERSION=-sgs
+ BUILD_REVISION=1185
+ VERSION=$MAJOR_VERSION.$MINOR_VERSION.$MICRO_VERSION$EXTRA_VERSION
+
+diff --git a/configure.ac b/configure.ac
+index 090177d..ae65343 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -9,7 +9,7 @@ MINOR_VERSION=7
+ MICRO_VERSION=0
+ INTERFACE_AGE=0
+ BINARY_AGE=0
+-EXTRA_VERSION=
++EXTRA_VERSION=-sgs
+ BUILD_REVISION=1185
+ VERSION=$MAJOR_VERSION.$MINOR_VERSION.$MICRO_VERSION$EXTRA_VERSION
+
diff --git a/mail-client/sylpheed/metadata.xml b/mail-client/sylpheed/metadata.xml
new file mode 100644
index 0000000..2facbf5
--- /dev/null
+++ b/mail-client/sylpheed/metadata.xml
@@ -0,0 +1,10 @@
+
+
+
+
+ blackmore@pichove.org
+
+
+
diff --git a/mail-client/sylpheed/sylpheed-3.7.0-r1.ebuild b/mail-client/sylpheed/sylpheed-3.7.0-r1.ebuild
new file mode 100644
index 0000000..f94d992
--- /dev/null
+++ b/mail-client/sylpheed/sylpheed-3.7.0-r1.ebuild
@@ -0,0 +1,76 @@
+# Copyright 2018 Simeon Simeonov
+# Distributed under the terms of the GNU General Public License v2
+
+EAPI="6"
+
+inherit eutils
+
+DESCRIPTION="A lightweight email client and newsreader (patched by sgs)"
+HOMEPAGE="http://sylpheed.sraoss.jp/"
+SRC_URI="http://${PN}.sraoss.jp/${PN}/v${PV%.*}/${P}.tar.bz2"
+
+LICENSE="GPL-2 LGPL-2.1"
+SLOT="0"
+KEYWORDS="amd64 x86"
+IUSE="crypt ipv6 ldap libressl nls oniguruma pda spell ssl xface"
+
+CDEPEND="net-libs/liblockfile
+ x11-libs/gtk+:2
+ crypt? ( app-crypt/gpgme )
+ ldap? ( net-nds/openldap )
+ nls? ( sys-devel/gettext )
+ oniguruma? ( dev-libs/oniguruma:= )
+ pda? ( app-pda/jpilot )
+ spell? (
+ app-text/gtkspell:2
+ dev-libs/dbus-glib
+ )
+ ssl? (
+ !libressl? ( dev-libs/openssl:0 )
+ libressl? ( dev-libs/libressl )
+ )"
+RDEPEND="${CDEPEND}
+ app-misc/mime-types
+ net-misc/curl"
+DEPEND="${CDEPEND}
+ virtual/pkgconfig
+ xface? ( media-libs/compface )"
+
+PATCHES=(
+ "${FILESDIR}/${P}-signature-box.patch"
+ "${FILESDIR}/${P}-PGP-signature-fix.patch"
+ "${FILESDIR}/${P}-master-password.patch"
+ "${FILESDIR}/${P}-version.patch"
+)
+
+DOCS="AUTHORS ChangeLog* NEW* PLUGIN* README* TODO*"
+
+src_configure() {
+ local htmldir="${EPREFIX}"/usr/share/doc/${PF}/html
+ econf \
+ $(use_enable crypt gpgme) \
+ $(use_enable ipv6) \
+ $(use_enable ldap) \
+ $(use_enable oniguruma) \
+ $(use_enable pda jpilot) \
+ $(use_enable spell gtkspell) \
+ $(use_enable ssl) \
+ $(use_enable xface compface) \
+ --with-plugindir="${EPREFIX}"/usr/$(get_libdir)/${PN}/plugins \
+ --with-manualdir="${htmldir}"/manual \
+ --with-faqdir="${htmldir}"/faq \
+ --disable-updatecheckplugin \
+ --disable-updatecheck
+}
+
+src_install() {
+ default
+
+ doicon *.png
+ domenu *.desktop
+
+ cd plugin/attachment_tool
+ emake DESTDIR="${D}" install-plugin
+ docinto plugin/attachment_tool
+ dodoc README
+}
diff --git a/metadata/layout.conf b/metadata/layout.conf
new file mode 100644
index 0000000..94f37c2
--- /dev/null
+++ b/metadata/layout.conf
@@ -0,0 +1,9 @@
+masters = gentoo
+
+manifest-hashes = BLAKE2B SHA512
+manifest-required-hashes = BLAKE2B
+
+thin-manifests = false
+
+sign-commits = true
+sign-manifests = false
diff --git a/profiles/repo_name b/profiles/repo_name
new file mode 100644
index 0000000..09ad2ae
--- /dev/null
+++ b/profiles/repo_name
@@ -0,0 +1 @@
+sgs
diff --git a/x11-misc/i3lock-extended/Manifest b/x11-misc/i3lock-extended/Manifest
new file mode 100644
index 0000000..fc2fe88
--- /dev/null
+++ b/x11-misc/i3lock-extended/Manifest
@@ -0,0 +1,3 @@
+DIST i3lock-extended-1.12.1.tar.xz 151036 BLAKE2B ac418762c42140b5c864247583681550d9867ba318bd373d426eaf8bdd9079564475ac950964dde7a821d252299a79c4770f42f9f2a9681fa511aec478b3689a SHA512 2a5f6363978a325e550e337a70a6d65d88fa9cae0d81b35ac85435d3b6520f7ce0af91ded41939f9da5ffc9b3893271545c1bba1e790b3a3e52f0f6fff7946e3
+EBUILD i3lock-extended-1.12.1.ebuild 811 BLAKE2B 0e84bdae98489dd04fbf5e029f517b7aa66c55882ad47c3769dc6a1df4430f1f3b90ff857fa5428e403278536fc87e2b657a6ea490111e9f594eac98c48aa18f SHA512 ab558600f95af7c06cc5a887e3bb9c8012c635f2f09dec99437d73668570c2b379485dc7fd55f9fa91d10109ffd6b16cb376205a1621cdc2a4d9bbc5cbf2d9fb
+MISC metadata.xml 355 BLAKE2B bba87c29271fc80bfbf932888a2415e122f58cac4f91e057af50cb73209c84b0f2783117d76505f8a13b8475ec7c6dc2816e78d4cadfed231b60860739de70ca SHA512 b6587d7c1f830f8b6f2658a31a608875d1e1b2aa792137e87fe87228a8e89ccc0bd8ecc33269a628e72b24e21773be1ca90aa604d0ac3e280e38a50240a869cf
diff --git a/x11-misc/i3lock-extended/i3lock-extended-1.12.1.ebuild b/x11-misc/i3lock-extended/i3lock-extended-1.12.1.ebuild
new file mode 100644
index 0000000..e3f728e
--- /dev/null
+++ b/x11-misc/i3lock-extended/i3lock-extended-1.12.1.ebuild
@@ -0,0 +1,44 @@
+# Copyright 1999-2020 Gentoo Authors
+# Distributed under the terms of the GNU General Public License v2
+
+EAPI=7
+inherit toolchain-funcs
+
+DESCRIPTION="i3lock-extended - i3lock fork with additional functionality"
+HOMEPAGE="https://simeon.simeonov.no"
+SRC_URI="https://simeon.simeonov.no/programs/i3lock_extended/downloads/${P}.tar.xz"
+
+LICENSE="GPL-3+ BSD"
+SLOT="0"
+KEYWORDS="amd64 ~arm ~arm64 ~ppc64 ~x86"
+
+RDEPEND="
+ >=x11-libs/libxkbcommon-0.5.0[X]
+ dev-libs/libev
+ sys-libs/pam
+ x11-libs/cairo[X,xcb(+)]
+ x11-libs/libxcb[xkb]
+ x11-libs/xcb-util
+ x11-libs/xcb-util-xrm
+"
+DEPEND="
+ ${RDEPEND}
+ virtual/pkgconfig
+"
+DOCS=( CHANGELOG README.md )
+
+src_prepare() {
+ default
+
+ sed -i -e 's:login:system-auth:g' pam/${PN} || die
+}
+
+src_configure() {
+ tc-export CC
+ default
+}
+
+src_install() {
+ default
+ doman ${PN}.1
+}
diff --git a/x11-misc/i3lock-extended/metadata.xml b/x11-misc/i3lock-extended/metadata.xml
new file mode 100644
index 0000000..4b3c492
--- /dev/null
+++ b/x11-misc/i3lock-extended/metadata.xml
@@ -0,0 +1,11 @@
+
+
+
+
+ sgs@pichove.org
+ Simeon Simeonov's i3lock-extended
+
+
+ blackm0re/i3lock-extended
+
+
diff --git a/x11-terms/sakura/Manifest b/x11-terms/sakura/Manifest
new file mode 100644
index 0000000..3113207
--- /dev/null
+++ b/x11-terms/sakura/Manifest
@@ -0,0 +1,5 @@
+AUX sakura-3.7.0-gentoo.patch 866 BLAKE2B a75ff98fc9b13c2d73e1f03930beae00e6260ffdb9b3e91b62386bbd90b2379c88fd00dd45ec1956eac9e08134c428b6c5616b50af7c83a3a1856d9fc46be96b SHA512 a7c9c49218180a5ce59ea4e1e27b662cf1897a15079e642d8c9cc97accfc9cb3cf03926edb91684f87e25e44542fcfbabeeb336dde8a869721f8364111a87532
+AUX sakura-3.7.0-single-tab.patch 722 BLAKE2B ffe3847fc8fe73e63e6ece0f9c4eedc5ec55034e8a777cf6bcff86e1a52c0ad55d186bddd1290c9a3486b19cafd07e3f8d189105b932ad9e91c7381772a92841 SHA512 26b77961df78aadbefb886b165dcc5996ed690023336cd8254df8042ff20bac85e88c96ccce10cf510300c93df690fbad360e0171c1814a062636a72dc6084b8
+DIST sakura-3.7.0.tar.gz 64728 BLAKE2B 637d8c9a393fe2823943190ec3c1676d3dd900b574f58fc3f6d61709ac3b70081736f0804c0e7d8ec5a1409cc2651aa1c3bee04a04c5d3df4ac1432fa2a5c41b SHA512 1018ea952d491f68a05ee3b3d8ac70cbfb485687f76224f9a395ff6508e0e75ff58bfd12dcb534c86430da54803a3016619f441d8bd14e496df32f81e00ca72e
+EBUILD sakura-3.7.0.ebuild 1362 BLAKE2B 8960b814e31128fd8b40991adcd75d7064c574bd18490240a11f1abecd5260465ff9206eeb9c10102226a840272cd51b33a872aaaa6d1289bd5fbf49655ee5af SHA512 06fed686c54ff6cc772c920a5f53fa823b1999051558aea04c6fb9ddb2eacaeac757c5fe8c88b02be3831afaddf2f13331e8df356c7aab4316f1b099b676f8a7
+MISC metadata.xml 928 BLAKE2B 91926b81400896fef6be017fe1d0dacc25bb2331e84b7604223464c2f0862b0935aadf944dcd272a989ae9de5b180f3576725f30fa2a161ba24f27c5ba1884e0 SHA512 95be972f218e92365f8084f3a796199c3f7a182bc42c098761096f2146323afcdb2fc45e6c4ec975f03decdac05b9c01083c768162bf97c88c61dab892419ffb
diff --git a/x11-terms/sakura/files/sakura-3.7.0-gentoo.patch b/x11-terms/sakura/files/sakura-3.7.0-gentoo.patch
new file mode 100644
index 0000000..ab1d8be
--- /dev/null
+++ b/x11-terms/sakura/files/sakura-3.7.0-gentoo.patch
@@ -0,0 +1,23 @@
+--- a/CMakeLists.txt
++++ b/CMakeLists.txt
+@@ -38,12 +38,6 @@
+ ADD_DEFINITIONS (-DDATADIR="${CMAKE_INSTALL_PREFIX}/share")
+ ADD_DEFINITIONS (-DBUILDTYPE="${CMAKE_BUILD_TYPE}")
+
+-IF (${CMAKE_BUILD_TYPE} MATCHES "Debug")
+- SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall")
+-ELSE (${CMAKE_BUILD_TYPE} NOT MATCHES "Debug")
+- SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2 -Wno-deprecated-declarations")
+-ENDIF (${CMAKE_BUILD_TYPE} MATCHES "Debug")
+-
+ INCLUDE_DIRECTORIES (. ${GTK_INCLUDE_DIRS} ${VTE_INCLUDE_DIRS})
+ LINK_DIRECTORIES (${GTK_LIBRARY_DIRS} ${VTE_LIBRARY_DIRS} ${X11_LIBRARY_DIRS})
+ LINK_LIBRARIES (${GTK_LIBRARIES} ${VTE_LIBRARIES} ${X11_LIBRARIES} m)
+@@ -57,7 +51,6 @@
+ IF (POD2MAN)
+ INSTALL (FILES ${sakura_BINARY_DIR}/sakura.1 DESTINATION share/man/man1)
+ ENDIF (POD2MAN)
+-INSTALL (FILES INSTALL DESTINATION share/doc/sakura)
+
+ FILE (GLOB MO_FILES po/*.mo)
+
diff --git a/x11-terms/sakura/files/sakura-3.7.0-single-tab.patch b/x11-terms/sakura/files/sakura-3.7.0-single-tab.patch
new file mode 100644
index 0000000..ddd0cec
--- /dev/null
+++ b/x11-terms/sakura/files/sakura-3.7.0-single-tab.patch
@@ -0,0 +1,13 @@
+diff -rupN sakura-3.7.0.old/src/sakura.c sakura-3.7.0/src/sakura.c
+--- sakura-3.7.0.old/src/sakura.c 2019-08-12 23:13:08.000000000 +0200
++++ sakura-3.7.0/src/sakura.c 2019-12-01 20:21:29.067142517 +0100
+@@ -511,6 +511,9 @@ sakura_key_press (GtkWidget *widget, Gdk
+ if ( ((event->state & sakura.switch_tab_accelerator) == sakura.switch_tab_accelerator) &&
+ ((event->state & sakura.move_tab_accelerator) != sakura.move_tab_accelerator) ) {
+
++ /* Just propagate the event if there is only one tab */
++ if(npages < 2) return FALSE;
++
+ if ((keycode>=sakura_tokeycode(GDK_KEY_1)) && (keycode<=sakura_tokeycode( GDK_KEY_9))) {
+
+ /* User has explicitly disabled this branch, make sure to propagate the event */
diff --git a/x11-terms/sakura/metadata.xml b/x11-terms/sakura/metadata.xml
new file mode 100644
index 0000000..9825940
--- /dev/null
+++ b/x11-terms/sakura/metadata.xml
@@ -0,0 +1,20 @@
+
+
+
+
+ desktop-misc@gentoo.org
+ Gentoo Desktop Miscellaneous Project
+
+
+ sakura is a terminal emulator based on GTK and VTE. It's a terminal emulator
+ with few dependencies, so you don't need a full GNOME desktop installed to
+ have a decent terminal emulator. Current terminal emulators based on VTE are
+ gnome-terminal, XFCE Terminal, TermIt and a small sample program included
+ in the vte sources. Sakura differences from the last one is that it uses a
+ notebook to provide several terminals in one window and adds a contextual
+ menu with some basic options. No more no less.
+
+
+ sakura
+
+
diff --git a/x11-terms/sakura/sakura-3.7.0.ebuild b/x11-terms/sakura/sakura-3.7.0.ebuild
new file mode 100644
index 0000000..e53be0d
--- /dev/null
+++ b/x11-terms/sakura/sakura-3.7.0.ebuild
@@ -0,0 +1,63 @@
+# Copyright 1999-2019 Gentoo Authors
+# Distributed under the terms of the GNU General Public License v2
+
+EAPI=7
+inherit cmake-utils eutils flag-o-matic xdg-utils
+
+DESCRIPTION="sakura is a terminal emulator based on GTK and VTE"
+HOMEPAGE="http://www.pleyades.net/david/projects/sakura/"
+SRC_URI="https://launchpad.net/${PN}/trunk/${PV}/+download/${P}.tar.gz"
+
+LICENSE="GPL-2"
+SLOT="0"
+KEYWORDS="amd64 ~x86 ~x86-linux"
+
+RDEPEND="
+ >=dev-libs/glib-2.20:2
+ >=x11-libs/gtk+-3.20:3[X]
+ x11-libs/libX11
+ >=x11-libs/vte-0.50:2.91
+"
+DEPEND="
+ ${RDEPEND}
+ >=dev-lang/perl-5.10.1
+ virtual/pkgconfig
+"
+PATCHES=(
+ "${FILESDIR}"/${PN}-3.7.0-gentoo.patch
+ "${FILESDIR}"/${PN}-3.7.0-single-tab.patch
+)
+DOCS=(
+ AUTHORS
+)
+
+src_prepare() {
+ strip-linguas -i po/
+ local lingua
+ for lingua in po/*.po; do
+ lingua="${lingua/po\/}"
+ lingua="${lingua/.po}"
+ if ! has ${lingua} ${LINGUAS}; then
+ rm po/${lingua}.po || die
+ fi
+ done
+
+ cmake-utils_src_prepare
+ # sakura.c:1740:3: warning: implicit declaration of function ‘readlink’
+ # [-Wimplicit-function-declaration]
+ append-cppflags -D_DEFAULT_SOURCE
+
+ # sakura.c:1348:9: error: ‘for’ loop initial declarations are only allowed
+ # in C99 or C11 mode
+ append-cflags -std=c99
+}
+
+pkg_postinst() {
+ xdg_desktop_database_update
+ xdg_icon_cache_update
+}
+
+pkg_postrm() {
+ xdg_desktop_database_update
+ xdg_icon_cache_update
+}
--
cgit v1.3