summaryrefslogtreecommitdiff
path: root/mail-client/sylpheed
diff options
context:
space:
mode:
authorSimeon Simeonov2020-04-08 21:19:40 +0200
committerSimeon Simeonov2020-04-08 21:19:40 +0200
commit406221dc8d42a6beda5aca96dd7d668751e53635 (patch)
tree8beef75bc0b0ffe9e2b86abd631d3a9837833b56 /mail-client/sylpheed
Initial release based on sgs' private overlay
Diffstat (limited to 'mail-client/sylpheed')
-rw-r--r--mail-client/sylpheed/Manifest8
-rw-r--r--mail-client/sylpheed/files/sylpheed-3.7.0-ALL.patch2149
-rw-r--r--mail-client/sylpheed/files/sylpheed-3.7.0-PGP-signature-fix.patch18
-rw-r--r--mail-client/sylpheed/files/sylpheed-3.7.0-master-password.patch1850
-rw-r--r--mail-client/sylpheed/files/sylpheed-3.7.0-signature-box.patch267
-rw-r--r--mail-client/sylpheed/files/sylpheed-3.7.0-version.patch26
-rw-r--r--mail-client/sylpheed/metadata.xml10
-rw-r--r--mail-client/sylpheed/sylpheed-3.7.0-r1.ebuild76
8 files changed, 4404 insertions, 0 deletions
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 @@
1AUX sylpheed-3.7.0-ALL.patch 63415 BLAKE2B c40c55b039d5c457bf555e0b738f8c97925966f75b0357dad7cc4f10f9fbaa9e6156e84e65c7e89880e28b36e286de96761693718faac9d1056f720a2cae8b1e SHA512 96d4edfea92439d2f0dd4dec11305710e8d5513979d4bd96113acdbe2338c9452da64ded98204b7af7c84ed86f22337a0248be109a6a8e20d3d1b62929ce70a3
2AUX sylpheed-3.7.0-PGP-signature-fix.patch 565 BLAKE2B c97f48f7844498323d944d1dc1925601ee470e50da26d4e434eb497b29b59fdf5144d019e30efc7392093422111f7c4bb0ff71a148d553db9fb2a2d26e86599a SHA512 089883841eae1c2ed806db1992ff94dd2906b1e825dbab7ae1b5d484815e446979d9f031851d4506ad0f4b00c462997d3704cbf605f7cbc9292e4b53bff5a0fe
3AUX sylpheed-3.7.0-master-password.patch 53668 BLAKE2B c2e898dbbaa4351fa9306a925c5aab56b6b63df4cebc130be7d44a33434524cf61e279fdc9b743d4b9d0c23dc84eda02c56a8c1b33c2309959a4c03c497ad232 SHA512 dcc987a9835ca2c38f06fe6aafdc9d05b750d691b1d1838b818557776be1129cc90b2982169510495ad30bc5e684b69dd614c747fff27b938620c7e0c8133498
4AUX sylpheed-3.7.0-signature-box.patch 9787 BLAKE2B 664b60b3a3421f12cc76a1a1cea64cd773509f17583e32acd9445581e0bed75289674ce82e018dd688204f932228ed379c6f68c31ff25c09e912ad74cbfe7394 SHA512 f1d1c165b701469cacf24eaab5512750d307145a551013e3ecd20e49817b4cd4d4113bd690d8ff6b1e6e9b1d96a45fb25c9417f5aa455e4063f85fc2d48ed464
5AUX sylpheed-3.7.0-version.patch 626 BLAKE2B fe3b90263c384429f9520bc357066f6cea80f81389cdc915ac30932276d5d3dbde5fdea16f649fb553f81d3487afbbdd41151604bd388e9d3ec12f44c7b9097a SHA512 26765397fe73ea03306513363f217f71b9a344590ad8716b34532279b1ade8c5feac0a9b981951bc58e85ae399c8efaab7435534441002c14a963c4c0b001eb8
6DIST sylpheed-3.7.0.tar.bz2 3612328 BLAKE2B bd8182db8a46b956e12b3da4b15d3ee8184a612e2fb216aca20fd4a022610b17416f994d36b390a5a92835915e95f08bd59bf71154a86962c9564162be891f21 SHA512 490837528bf7ba9d26994cd5fff00b6e5390a127419b9d0efd9fc25c38be1291d55c5b8daebdf5ca9d9159a51c938449e76212328f3eae40cc039db88cb5caa4
7EBUILD sylpheed-3.7.0-r1.ebuild 1801 BLAKE2B 35c5232beea2bf460817409bd552e3914b7d5453db4e5b2438cfd70be617b08d278b019873a48cc6430f75319289d077c3840023513cac31d4e4609a1d710a41 SHA512 13a808da1cb04f34c727cf1eaf3c8bca2158073efa459eb446c065fc46a6798d232d18af0aa8c221d2b22399f3c4c47f778ca45bcb5a59bffb24c03a91452e79
8MISC 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 @@
1diff --git a/.gitignore b/.gitignore
2new file mode 100644
3index 0000000..9742351
4--- /dev/null
5+++ b/.gitignore
6@@ -0,0 +1,21 @@
7+*.o
8+*.lo
9+*.la
10+Makefile
11+config.h
12+config.log
13+config.status
14+libsylph/.deps/
15+libsylph/.libs/
16+libtool
17+plugin/attachment_tool/.deps/
18+plugin/attachment_tool/.libs/
19+plugin/test/.deps/
20+plugin/test/.libs/
21+po/Makefile.in
22+po/POTFILES
23+src/.deps/
24+src/.libs/
25+src/sylpheed
26+src/sylpheed.rc
27+stamp-h1
28diff --git a/README.md b/README.md
29new file mode 100644
30index 0000000..1cf6d21
31--- /dev/null
32+++ b/README.md
33@@ -0,0 +1,219 @@
34+## Implemented features and fixes not present in the official Sylpheed release
35+
36+- (fix)
37+ PGP signature not verified properly when the message has no newline
38+ at the end. https://sylpheed.sraoss.jp/redmine/issues/288
39+
40+- (feature)
41+ Make it possible to select "Show signature check result in a popup window"
42+ only for bad signatures.
43+
44+- (feature)
45+ Support for encrypting and storing encrypted passwords using a master password.
46+ Read bellow for more details!
47+
48+
49+## Master password
50+
51+The master password feature is developed by Simeon Simeonov (sgs)
52+and is currently in an experimental state.
53+
54+
55+### Motivation
56+
57+Currently Sylpheed is storing passwords in plain-text. One can always refrain
58+from storing passwords and let Sylpheed prompt for them, but the more accounts
59+one has, the more annoying this becomes.
60+
61+The goal is to have the passwords stored in a secure way and let Sylpheed only
62+prompt for the master password.
63+
64+
65+### Security goals
66+
67+- attacker (A) should not be able to derive the password from the digest.
68+
69+- A should not be able to derive the master password even if she has
70+ read and write access to the storage.
71+
72+- A should not be able to determine the length of the encrypted password
73+ even if she has read and write access to the storage.
74+
75+- A should not be able to craft an edited password without obtaining the
76+ master password.
77+
78+- A should not be able to compromise the master password or force Sylpheed
79+ to store decrypted passwords even if she has access to a running Sylpheed.
80+
81+- a warning / prompt should be given if a user accidentally types in a "wrong"
82+ master password before decryption is initiated.
83+
84+
85+### Usage in Sylpheed
86+
87+- backup your Sylpheed profile (often $HOME/.sylpheed-2.0)!
88+
89+- start Sylpheed and open "Configuration" -> "Common preferences..."!
90+
91+- select the "Master password" tab, enable "Use master password" and
92+ apply the changes!
93+
94+- restart Sylpheed (exit and then start Sylpheed again)!
95+
96+- you will be asked to type and verify a new master password.
97+
98+- set your new passwords from the "Configuration" -> "Edit accounts..."!
99+
100+- select "Automatically unload master password after session initialization"
101+ for increased security and decreased convenience.
102+
103+Note:
104+Sylpheed will automatically convert existing stored passwords, but it will not
105+touch your backups. You will have to remove all remnants of plain-text
106+passwords manually.
107+
108+
109+### Choice of cryptographic primitives
110+
111+The primary concern when selecting cryptographic primitives was portability.
112+The desire was to go for primitives that are both strong and available in all
113+supported production distributions of OpenSSL and LibreSSL.
114+
115+
116+#### Cipher
117+
118+When it comes to implementation, there are several advantages in using stream
119+cipher or a block cipher that behaves like a stream cipher when used in a
120+certain mode of operation. One is avoiding to deal with padding.
121+
122+AES-256 operating in CFB was selected for these reasons.
123+ChaCha20 should be considered as a replacement in the future.
124+
125+
126+#### Hash-function
127+
128+Hash-functions are used for:
129+- key derivation
130+- plain-text digest
131+
132+Those operations do not have to use the same hash-function.
133+(See the "Encryption & decryption scheme" section for more details!)
134+
135+Key-derivation:
136+Since AES-256 uses a 256 bits key, we need a hash-function with at least
137+the same digest size or bigger.
138+Since the digest (which is the key itself) is considered confidential and is
139+stored only in memory for only a limited amount of time, SHA-256 is considered
140+sufficiently strong for that purpose.
141+
142+Size + plain-text + padding digest:
143+Since the digest is created of both the plain-text and the plain-text size,
144+as well as being encrypted, SHA-256 is considered sufficiently strong.
145+SHA-512 may increase security at the price of adding additional 32 bytes
146+to the encrypted password digest.
147+
148+Stronger hash-functions like SHA-3 or BLAKE2b can be considered as a
149+replacement in the future.
150+
151+
152+#### Master password digest
153+
154+In order to be able to decide whether the user typed a "wrong" master password,
155+before attempting to decrypt, Sylpheed stores a digest of the master password
156+in 'master_password_hash' in sylpheedrc.
157+100000 iterations of PBKDF2_HMAC with SHA-512 and 16 bytes salt is used.
158+Note that this digest is useless as a key and even if a plain-text that
159+produces the same digest is found, it will most probably be useless as a
160+master-password.
161+
162+
163+### Encryption & decryption scheme
164+
165+
166+#### Encryption
167+
168+
169+Input:
170+
171+- plain-text password to be encrypted (P)
172+
173+- plain-text master-password used for key derivation (M)
174+
175+- integer minimum password length (0 < L < 100)
176+
177+
178+Output:
179+
180+- an encrypted password digest (base64) (B)
181+
182+
183+Operation:
184+
185+- generate 16 bytes of random data to be used as a salt (S)
186+
187+- derive the key (K): K = SHA_256(S + M)
188+
189+- produce a 2 byte string (N) indicating the length of P
190+
191+- generate random padding and set N:
192+ - if the length of P < L, produce L - P bytes of random data (R), N = "%02d"
193+ - if the length of P >= L, N = "-1"
194+
195+- produce a hash digest (H): H = SHA_256(N + P + R (if the length of P < L))
196+
197+- encrypt (E): E = AES_256_CFB_ENCRYPT(H + N + P + R (if the length of P < L), K)
198+
199+- B = mpes1:BASE64_ENCODE(S + E)
200+
201+example:
202+mpes1:vo7lsIpD7i6byBA6+vlUoF4OVDfEe+aYRRk4FRtfJ2gMY8M43Kj6WfdfgbViIOl83bI4XEc96okhPW5Mla813aAR1gbPjDg0xmCyIbWOiUv/dg==
203+
204+
205+#### Decryption
206+
207+
208+Input:
209+
210+- encrypted password digest (base64) (B)
211+
212+- plain-text master-password used for key derivation (M)
213+
214+
215+Output:
216+
217+- plain-text password (P)
218+
219+
220+Operation:
221+
222+- remove the prefix (mpes1:) and base64-decode the rest of the digest: B = BASE64_DECODE(B)
223+
224+- fetch the first 16 bytes for the salt: S = B[0 : 15]
225+
226+- derive the key (K): K = SHA_256(S + M)
227+
228+- decrypt the rest of B (D): D = AES_256_CFB_DECRYPT(B[16 :], K)
229+
230+- extract the first 16 bytes for the hash digest (H): H = D[0 : 15]
231+
232+- in order to detect data-inconsistency, assert H == SHA_256(D[16 :])
233+
234+- extract the next 2 bytes for the length of P (N): N = D[16 : 17]
235+
236+- fetch the plain-text:
237+ - if N == "-1" the password is the remaining bytes of D: P = D[18 :]
238+ - if N != "-1", extract the next N-bytes from D: P = D[18 : (18 + N)]
239+
240+
241+### Limitations
242+
243+- currently only the 'password' and 'smtp_password' keys in accountrc
244+ are encrypted.
245+ A mechanism that allows for any key and even folders to be encrypted
246+ should be considered in the future.
247+
248+- currently it is not possible to select alternative ciphers, hash-functions
249+ and modes of operation (without editing the source code).
250+
251+- currently it is not possible to change your master password without having to
252+ (re)set your passwords manually.
253diff --git a/libsylph/Makefile.am b/libsylph/Makefile.am
254index 8f2b76b..40d26a9 100644
255--- a/libsylph/Makefile.am
256+++ b/libsylph/Makefile.am
257@@ -19,6 +19,7 @@ libsylph_0_la_SOURCES = \
258 folder.c \
259 html.c \
260 imap.c \
261+ masterpassword.c \
262 mbox.c \
263 md5.c \
264 md5_hmac.c \
265@@ -62,6 +63,7 @@ libsylph_0include_HEADERS = \
266 folder.h \
267 html.h \
268 imap.h \
269+ masterpassword.h \
270 mbox.h \
271 md5.h \
272 md5_hmac.h \
273diff --git a/libsylph/Makefile.in b/libsylph/Makefile.in
274index 6e3c999..ac8fefd 100644
275--- a/libsylph/Makefile.in
276+++ b/libsylph/Makefile.in
277@@ -129,8 +129,8 @@ libsylph_0_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \
278 $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
279 am_libsylph_0_la_OBJECTS = account.lo base64.lo codeconv.lo \
280 customheader.lo displayheader.lo filter.lo folder.lo html.lo \
281- imap.lo mbox.lo md5.lo md5_hmac.lo mh.lo news.lo nntp.lo \
282- pop.lo prefs.lo prefs_account.lo prefs_common.lo procheader.lo \
283+ imap.lo masterpassword.lo mbox.lo md5.lo md5_hmac.lo mh.lo news.lo \
284+ nntp.lo pop.lo prefs.lo prefs_account.lo prefs_common.lo procheader.lo \
285 procmime.lo procmsg.lo quoted-printable.lo recv.lo session.lo \
286 smtp.lo socket.lo socks.lo ssl.lo ssl_hostname_validation.lo \
287 stringtable.lo sylmain.lo unmime.lo utils.lo uuencode.lo \
288@@ -402,6 +402,7 @@ libsylph_0_la_SOURCES = \
289 folder.c \
290 html.c \
291 imap.c \
292+ masterpassword.c \
293 mbox.c \
294 md5.c \
295 md5_hmac.c \
296@@ -445,6 +446,7 @@ libsylph_0include_HEADERS = \
297 folder.h \
298 html.h \
299 imap.h \
300+ masterpassword.h \
301 mbox.h \
302 md5.h \
303 md5_hmac.h \
304@@ -579,6 +581,7 @@ distclean-compile:
305 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/folder.Plo@am__quote@
306 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/html.Plo@am__quote@
307 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap.Plo@am__quote@
308+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/masterpassword.Plo@am__quote@
309 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mbox.Plo@am__quote@
310 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/md5.Plo@am__quote@
311 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/md5_hmac.Plo@am__quote@
312diff --git a/libsylph/defs.h b/libsylph/defs.h
313index 9e3f82b..67325bf 100644
314--- a/libsylph/defs.h
315+++ b/libsylph/defs.h
316@@ -58,6 +58,9 @@
317 #define DISPLAY_HEADER_RC "dispheaderrc"
318 #define MENU_RC "menurc"
319 #define ACTIONS_RC "actionsrc"
320+#ifdef USE_SSL
321+#define SECURE_RC "securerc"
322+#endif
323 #define COMMAND_HISTORY "command_history"
324 #define TEMPLATE_DIR "templates"
325 #define TMP_DIR "tmp"
326diff --git a/libsylph/imap.c b/libsylph/imap.c
327index aa0d737..e1c7cfd 100644
328--- a/libsylph/imap.c
329+++ b/libsylph/imap.c
330@@ -52,6 +52,7 @@
331 #include "utils.h"
332 #include "prefs_common.h"
333 #include "virtual.h"
334+#include "masterpassword.h"
335
336 #define IMAP4_PORT 143
337 #if USE_SSL
338@@ -705,9 +706,13 @@ static gint imap_session_connect(IMAPSession *session)
339 account = (PrefsAccount *)(SESSION(session)->data);
340
341 log_message(_("creating IMAP4 connection to %s:%d ...\n"),
342- SESSION(session)->server, SESSION(session)->port);
343-
344- pass = account->passwd;
345+ SESSION(session)->server, SESSION(session)->port);
346+ if (master_password_active()) {
347+ pass = decrypt_with_master_password(account->passwd);
348+ /* a new string is allocated. To be removed ... */
349+ } else {
350+ pass = account->passwd;
351+ }
352 if (!pass)
353 pass = account->tmp_pass;
354 if (!pass) {
355@@ -770,8 +775,11 @@ static gint imap_session_connect(IMAPSession *session)
356 #endif
357
358 if (!session->authenticated &&
359- imap_auth(session, account->userid, pass, account->imap_auth_type)
360- != IMAP_SUCCESS) {
361+ imap_auth(session, account->userid, pass, account->imap_auth_type)
362+ != IMAP_SUCCESS) {
363+ if (master_password_active()) {
364+ g_free(pass); /* remove the decrypted password */
365+ }
366 if (account->tmp_pass) {
367 g_free(account->tmp_pass);
368 account->tmp_pass = NULL;
369@@ -780,6 +788,10 @@ static gint imap_session_connect(IMAPSession *session)
370 return IMAP_AUTHFAIL;
371 }
372
373+ if (master_password_active()) {
374+ g_free(pass); /* remove the decrypted password */
375+ }
376+
377 return IMAP_SUCCESS;
378 }
379
380diff --git a/libsylph/masterpassword.c b/libsylph/masterpassword.c
381new file mode 100644
382index 0000000..3d7af94
383--- /dev/null
384+++ b/libsylph/masterpassword.c
385@@ -0,0 +1,216 @@
386+/*
387+ * LibSylph -- E-Mail client library
388+ * Copyright (C) 1999-2018 Hiroyuki Yamamoto
389+ *
390+ * This library is free software; you can redistribute it and/or
391+ * modify it under the terms of the GNU Lesser General Public
392+ * License as published by the Free Software Foundation; either
393+ * version 2.1 of the License, or (at your option) any later version.
394+ *
395+ * This library is distributed in the hope that it will be useful,
396+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
397+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
398+ * Lesser General Public License for more details.
399+ *
400+ * You should have received a copy of the GNU Lesser General Public
401+ * License along with this library; if not, write to the Free Software
402+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
403+ */
404+
405+#ifdef HAVE_CONFIG_H
406+#include "config.h"
407+#endif
408+
409+#include <glib/gi18n.h>
410+
411+#include "prefs_common.h"
412+#include "ssl.h"
413+#include "utils.h"
414+#include "masterpassword.h"
415+
416+
417+gchar *master_password;
418+gboolean master_password_enabled_on_init;
419+
420+void set_master_password(const char *password) {
421+ master_password = password;
422+}
423+
424+gchar *get_master_password(void) {
425+ return master_password;
426+}
427+
428+void cleanse_buffer(void *buf, size_t len) {
429+#if USE_SSL
430+ OPENSSL_cleanse(buf, len);
431+#else
432+ memset(buf, 0, len); /* better than nothing */
433+#endif
434+}
435+
436+void unload_master_password(void) {
437+
438+ debug_print("Unloading master password\n");
439+ if (master_password == NULL) {
440+ /* not loaded / already unloaded */
441+ return;
442+ }
443+ cleanse_buffer(master_password, strlen(master_password));
444+ g_free(master_password);
445+ master_password = NULL;
446+ debug_print("Master password unloaded\n");
447+
448+}
449+
450+gint mpes_string_prefix(const gchar *str) {
451+
452+ /* this function will be expanded in time as the format changes */
453+ if (g_str_has_prefix(str, "mpes1:"))
454+ return 6;
455+ return 0;
456+
457+}
458+
459+gboolean master_password_active(void) {
460+
461+#if USE_SSL
462+ return ((master_password != NULL) &&
463+ prefs_common.use_master_password);
464+#else
465+ return FALSE;
466+#endif
467+
468+}
469+
470+gchar *decrypt_with_master_password(const gchar *str) {
471+
472+#if USE_SSL
473+ gchar *new_str;
474+ gint str_prefix;
475+
476+ if ((!str) || (!prefs_common.use_master_password))
477+ return g_strdup(str);
478+
479+ if (master_password == NULL) {
480+ /* we have empty or auto unloaded master password */
481+ if ((!prefs_common.auto_unload_master_password) ||
482+ (check_master_password_interactively(3) != MP_RC_OK)) {
483+ return g_strdup(str);
484+ }
485+ debug_print("Reloaded master password\n");
486+ }
487+
488+ str_prefix = mpes_string_prefix(str);
489+ if (!str_prefix)
490+ return g_strdup(str);
491+
492+ if (decrypt_data(&new_str,
493+ str + str_prefix,
494+ master_password,
495+ strlen(str) + 1 - str_prefix) != MP_RC_OK) {
496+ OPENSSL_cleanse(new_str, strlen(new_str));
497+ g_free(new_str);
498+ return g_strdup(str);
499+ }
500+
501+ return new_str;
502+#else
503+ return g_strdup(str);
504+#endif
505+
506+}
507+
508+gchar *encrypt_with_master_password(const gchar *str) {
509+
510+#if USE_SSL
511+ gchar *new_str, *mpes1_str;
512+ gint length_encrypted;
513+
514+ if ((!str) || (!master_password_active()))
515+ return NULL;
516+
517+ /*
518+ * unlike the decrypt function, here it is up to the caller
519+ * to make sure that auto unloaded master password is handled properly
520+ */
521+
522+ if (encrypt_data(&new_str,
523+ &length_encrypted,
524+ str,
525+ master_password,
526+ strlen(str) + 1,
527+ prefs_common.encrypted_password_min_length,
528+ TRUE) != MP_RC_OK) {
529+ g_free(new_str);
530+ return NULL;
531+ }
532+
533+ mpes1_str = g_strdup_printf("mpes1:%s", new_str);
534+ g_free(new_str);
535+
536+ return mpes1_str;
537+#else
538+ return NULL;
539+#endif
540+
541+}
542+
543+#if USE_SSL
544+gint set_master_password_interactively(guint max_attempts) {
545+
546+ if (master_password == NULL)
547+ master_password = input_set_new_password(max_attempts);
548+
549+ if (master_password == NULL)
550+ return 1;
551+
552+ if (generate_password_hash(
553+ &prefs_common.master_password_hash,
554+ master_password,
555+ NULL) != MP_RC_OK) {
556+ /* should not really happen unless buggy code / library */
557+ g_free(prefs_common.master_password_hash);
558+ prefs_common.master_password_hash = NULL;
559+ debug_print(_("Could not generate master password hash"));
560+ return 1;
561+ }
562+
563+ prefs_common_write_config();
564+ return 0;
565+
566+}
567+
568+gint check_master_password_interactively(guint max_attempts) {
569+
570+ guint cnt;
571+
572+ g_return_val_if_fail(max_attempts > 0, 1);
573+ g_return_val_if_fail(prefs_common.master_password_hash != NULL, 1);
574+
575+ if (master_password != NULL) {
576+ /* password already cached */
577+ debug_print("Master password already cached\n");
578+ return check_password(master_password,
579+ prefs_common.master_password_hash);
580+ }
581+
582+ for (cnt = 0; cnt < max_attempts; ++cnt) {
583+ master_password = input_query_master_password();
584+ if (master_password == NULL) {
585+ /* input canceled or query_master_password_func not set */
586+ continue;
587+ }
588+ if (check_password(master_password,
589+ prefs_common.master_password_hash) == MP_RC_OK) {
590+ return MP_RC_OK; /* match */
591+ }
592+ debug_print(_("Wrong master password entered (%d)\n"), cnt);
593+ OPENSSL_cleanse(master_password, strlen(master_password));
594+ g_free(master_password);
595+ master_password = NULL;
596+ }
597+
598+ return 1; /* no match */
599+
600+}
601+#endif /* USE_SSL */
602diff --git a/libsylph/masterpassword.h b/libsylph/masterpassword.h
603new file mode 100644
604index 0000000..7254643
605--- /dev/null
606+++ b/libsylph/masterpassword.h
607@@ -0,0 +1,47 @@
608+/*
609+ * LibSylph -- E-Mail client library
610+ * Copyright (C) 1999-2018 Hiroyuki Yamamoto
611+ *
612+ * This library is free software; you can redistribute it and/or
613+ * modify it under the terms of the GNU Lesser General Public
614+ * License as published by the Free Software Foundation; either
615+ * version 2.1 of the License, or (at your option) any later version.
616+ *
617+ * This library is distributed in the hope that it will be useful,
618+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
619+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
620+ * Lesser General Public License for more details.
621+ *
622+ * You should have received a copy of the GNU Lesser General Public
623+ * License along with this library; if not, write to the Free Software
624+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
625+ */
626+
627+#ifndef __MASTERPASSWORD_H__
628+#define __MASTERPASSWORD_H__
629+
630+#ifdef HAVE_CONFIG_H
631+#include "config.h"
632+#endif
633+
634+#define MP_RC_OK 0
635+#define MP_RC_WRONG_HASH_OR_KEY 1
636+#define MP_RC_INVALID_FORMAT 2 /* invalid digest format */
637+
638+extern gchar *master_password;
639+extern gboolean master_password_enabled_on_init; /* m.p. enabled on init? */
640+void set_master_password(const char *password);
641+gchar *get_master_password(void);
642+void cleanse_buffer(void *buf, size_t len);
643+void unload_master_password(void);
644+gint mpes_string_prefix(const gchar *str);
645+gboolean master_password_active(void);
646+gchar *decrypt_with_master_password(const gchar *str);
647+gchar *encrypt_with_master_password(const gchar *str);
648+
649+#if USE_SSL
650+gint set_master_password_interactively(guint max_attempts);
651+gint check_master_password_interactively(guint max_attempts);
652+#endif /* USE_SSL */
653+
654+#endif /* __MASTERPASSWORD_H__ */
655diff --git a/libsylph/news.c b/libsylph/news.c
656index ffff9f9..36d3b10 100644
657--- a/libsylph/news.c
658+++ b/libsylph/news.c
659@@ -44,6 +44,7 @@
660 #include "utils.h"
661 #include "prefs_common.h"
662 #include "prefs_account.h"
663+#include "masterpassword.h"
664 #if USE_SSL
665 # include "ssl.h"
666 #endif
667@@ -249,10 +250,11 @@ static Session *news_session_new_for_folder(Folder *folder)
668 ac = folder->account;
669 if (ac->use_nntp_auth && ac->userid && ac->userid[0]) {
670 userid = ac->userid;
671- if (ac->passwd && ac->passwd[0])
672- passwd = g_strdup(ac->passwd);
673- else
674+ if (ac->passwd && ac->passwd[0]) {
675+ passwd = decrypt_with_master_password(ac->passwd);
676+ } else {
677 passwd = input_query_password(ac->nntp_server, userid);
678+ }
679 }
680
681 if (ac->use_socks && ac->use_socks_for_recv && ac->proxy_host) {
682diff --git a/libsylph/pop.c b/libsylph/pop.c
683index 8cb7f5c..85f3ed9 100644
684--- a/libsylph/pop.c
685+++ b/libsylph/pop.c
686@@ -39,6 +39,7 @@
687 #include "prefs_account.h"
688 #include "utils.h"
689 #include "recv.h"
690+#include "masterpassword.h"
691
692 gint pop3_greeting_recv (Pop3Session *session,
693 const gchar *msg);
694@@ -437,8 +438,9 @@ Session *pop3_session_new(PrefsAccount *account)
695 session->error_msg = NULL;
696
697 session->user = g_strdup(account->userid);
698- session->pass = account->passwd ? g_strdup(account->passwd) :
699- account->tmp_pass ? g_strdup(account->tmp_pass) : NULL;
700+ session->pass = account->passwd ? decrypt_with_master_password(
701+ account->passwd) : account->tmp_pass ? g_strdup(
702+ account->tmp_pass) : NULL;
703
704 SESSION(session)->server = g_strdup(account->recv_server);
705
706diff --git a/libsylph/prefs_account.c b/libsylph/prefs_account.c
707index 1aecba9..54cbc89 100644
708--- a/libsylph/prefs_account.c
709+++ b/libsylph/prefs_account.c
710@@ -34,6 +34,7 @@
711 #include "customheader.h"
712 #include "account.h"
713 #include "utils.h"
714+#include "masterpassword.h"
715
716 static PrefsAccount tmp_ac_prefs;
717
718@@ -205,7 +206,7 @@ PrefsAccount *prefs_account_new(void)
719 void prefs_account_read_config(PrefsAccount *ac_prefs, const gchar *label)
720 {
721 const gchar *p = label;
722- gchar *rcpath;
723+ gchar *rcpath, *tmp_str;
724 gint id;
725
726 g_return_if_fail(ac_prefs != NULL);
727@@ -229,6 +230,34 @@ void prefs_account_read_config(PrefsAccount *ac_prefs, const gchar *label)
728 ac_prefs->use_apop_auth = TRUE;
729 }
730
731+ if (master_password_active()) {
732+ if ((ac_prefs->passwd != NULL) &&
733+ !mpes_string_prefix(ac_prefs->passwd)) {
734+ /* TODO: Perhaps some prompt? */
735+ debug_print(
736+ "%s -> converting passwd cleartext to encrypted\n",
737+ label);
738+ tmp_str = ac_prefs->passwd;
739+ ac_prefs->passwd = encrypt_with_master_password(ac_prefs->passwd);
740+ cleanse_buffer(tmp_str, strlen(tmp_str));
741+ g_free(tmp_str);
742+ }
743+
744+ if ((ac_prefs->smtp_passwd != NULL) &&
745+ !mpes_string_prefix(ac_prefs->smtp_passwd)) {
746+ /* TODO: Perhaps some prompt? */
747+ debug_print(
748+ "%s -> converting smtp_passwd from cleartext to encrypted\n",
749+ label);
750+ tmp_str = ac_prefs->smtp_passwd;
751+ ac_prefs->smtp_passwd = encrypt_with_master_password(
752+ ac_prefs->smtp_passwd);
753+ cleanse_buffer(tmp_str, strlen(tmp_str));
754+ g_free(tmp_str);
755+ }
756+
757+ }
758+
759 custom_header_read_config(ac_prefs);
760 }
761
762diff --git a/libsylph/prefs_common.c b/libsylph/prefs_common.c
763index 8ed74a3..192964e 100644
764--- a/libsylph/prefs_common.c
765+++ b/libsylph/prefs_common.c
766@@ -406,6 +406,8 @@ static PrefParam param[] = {
767 P_BOOL},
768 {"gpg_signature_popup", "FALSE", &prefs_common.gpg_signature_popup,
769 P_BOOL},
770+ {"gpg_signature_popup_mode", "1", &prefs_common.gpg_signature_popup_mode,
771+ P_INT},
772 {"store_passphrase", "FALSE", &prefs_common.store_passphrase, P_BOOL},
773 {"store_passphrase_timeout", "0",
774 &prefs_common.store_passphrase_timeout, P_INT},
775@@ -416,6 +418,17 @@ static PrefParam param[] = {
776 {"show_gpg_warning", "TRUE", &prefs_common.gpg_warning, P_BOOL},
777 #endif
778
779+ /* Master password */
780+ {"use_master_password", "FALSE", &prefs_common.use_master_password,
781+ P_BOOL},
782+ {"master_password_hash", NULL, &prefs_common.master_password_hash,
783+ P_STRING},
784+ {"encrypted_password_min_length", "32",
785+ &prefs_common.encrypted_password_min_length, P_INT},
786+ {"auto_unload_master_password", "FALSE",
787+ &prefs_common.auto_unload_master_password,
788+ P_BOOL},
789+
790 /* Interface */
791 {"separate_folder", "FALSE", &prefs_common.sep_folder, P_BOOL},
792 {"separate_message", "FALSE", &prefs_common.sep_msg, P_BOOL},
793diff --git a/libsylph/prefs_common.h b/libsylph/prefs_common.h
794index ba9ddb5..6f9c364 100644
795--- a/libsylph/prefs_common.h
796+++ b/libsylph/prefs_common.h
797@@ -243,11 +243,18 @@ struct _PrefsCommon
798 /* Privacy */
799 gboolean auto_check_signatures;
800 gboolean gpg_signature_popup;
801+ gint gpg_signature_popup_mode;
802 gboolean store_passphrase;
803 gint store_passphrase_timeout;
804 gboolean passphrase_grab;
805 gboolean gpg_warning;
806
807+ /* Master password */
808+ gboolean use_master_password;
809+ gchar *master_password_hash;
810+ guint encrypted_password_min_length;
811+ gboolean auto_unload_master_password;
812+
813 /* Interface */
814 gboolean sep_folder;
815 gboolean sep_msg;
816diff --git a/libsylph/ssl.c b/libsylph/ssl.c
817index 8413925..e782b0e 100644
818--- a/libsylph/ssl.c
819+++ b/libsylph/ssl.c
820@@ -32,6 +32,13 @@
821 #include "ssl.h"
822 #include "ssl_hostname_validation.h"
823
824+#define SALT_SIZE 16
825+#define CIPHER EVP_aes_256_cfb()
826+#define KEY_HASH EVP_sha256()
827+#define DIGEST_HASH EVP_sha256()
828+#define PBKDF2_DIGEST_SIZE 64
829+#define PBKDF2_ITERATIONS 100000
830+
831 static SSL_CTX *ssl_ctx_SSLv23 = NULL;
832 static SSL_CTX *ssl_ctx_TLSv1 = NULL;
833
834@@ -402,4 +409,468 @@ void ssl_set_verify_func(SSLVerifyFunc func)
835 verify_ui_func = func;
836 }
837
838+/* master password related functions */
839+static gint secure_derive_key(guchar *key,
840+ gint length_key,
841+ const gchar *passphrase,
842+ const guchar *salt) {
843+
844+ guint length_buffer, length_hash;
845+ guchar *buffer, *ptr_hash;
846+
847+ EVP_MD_CTX *mdctx;
848+
849+ OPENSSL_cleanse(key, length_key);
850+
851+ length_buffer = SALT_SIZE + strlen(passphrase);
852+ buffer = OPENSSL_malloc(length_buffer);
853+ OPENSSL_cleanse(buffer, length_buffer);
854+
855+ memcpy(buffer, salt, SALT_SIZE);
856+ memcpy(buffer + SALT_SIZE, passphrase, strlen(passphrase));
857+
858+ mdctx = EVP_MD_CTX_create();
859+ EVP_DigestInit_ex(mdctx, KEY_HASH, NULL);
860+ EVP_DigestUpdate(mdctx, buffer, length_buffer);
861+ OPENSSL_cleanse(buffer, length_buffer);
862+
863+ ptr_hash = OPENSSL_malloc(EVP_MD_size(KEY_HASH));
864+ EVP_DigestFinal_ex(mdctx, ptr_hash, &length_hash);
865+
866+ memcpy(key,
867+ ptr_hash,
868+ (length_hash > length_key) ? length_key : length_hash);
869+
870+ OPENSSL_cleanse(ptr_hash, length_hash);
871+ OPENSSL_free(ptr_hash);
872+ OPENSSL_free(buffer);
873+ EVP_MD_CTX_destroy(mdctx);
874+
875+ return SSL_RC_OK;
876+
877+}
878+
879+gint encrypt_data(gchar **encrypted,
880+ gint *length_encrypted,
881+ const gchar *data,
882+ const gchar *passphrase,
883+ gint length_data,
884+ guint min_data_length,
885+ gboolean rnd_salt) {
886+
887+ gint crypt_buffer_cnt, rc;
888+ guint key_size, length_hash;
889+ guint length_cleartext, length_ciphertext, length_total;
890+ guchar salt[SALT_SIZE];
891+ guchar *ciphertext_buffer, *total_buffer;
892+ /* sensitive buffers and counters */
893+ guint length_data_payload, length_padding;
894+ gchar str_data_size[3];
895+ guchar *data_payload_buffer, *hash_buffer, *padding_buffer, *key;
896+ guchar *cleartext_buffer;
897+
898+ EVP_CIPHER_CTX *ctx;
899+ EVP_MD_CTX *mdctx;
900+
901+ rc = SSL_RC_ERROR;
902+
903+ if (length_data < 1) {
904+ return -1;
905+ }
906+ if (rnd_salt) {
907+ if (RAND_bytes(salt, SALT_SIZE) != 1) {
908+ debug_print("Random problems...\n");
909+ goto cleanup;
910+ }
911+ } else {
912+ strncpy((gchar *)salt, "FOR TESTING ONLY", SALT_SIZE);
913+ }
914+
915+ key_size = EVP_CIPHER_key_length(CIPHER);
916+ key = OPENSSL_malloc(key_size);
917+ OPENSSL_cleanse(key, key_size);
918+ if (secure_derive_key(key,
919+ key_size,
920+ passphrase,
921+ salt) != SSL_RC_OK) {
922+ OPENSSL_cleanse(key, key_size);
923+ debug_print("Could not generate secure key\n");
924+ goto cleanup;
925+ }
926+
927+ /* prepare the data-buffer */
928+ length_padding = 0;
929+ if (length_data < min_data_length) {
930+ g_snprintf(str_data_size, 3, "%02d", length_data);
931+ length_padding = min_data_length - length_data;
932+ padding_buffer = OPENSSL_malloc(length_padding);
933+ OPENSSL_cleanse(padding_buffer, length_padding);
934+ if (RAND_bytes(padding_buffer, length_padding) != 1) {
935+ debug_print("Random problems...\n");
936+ goto cleanup;
937+ }
938+ } else {
939+ g_snprintf(str_data_size, 3, "-1");
940+ }
941+
942+ length_data_payload = 2 + length_data + length_padding;
943+ data_payload_buffer = OPENSSL_malloc(length_data_payload);
944+ OPENSSL_cleanse(data_payload_buffer, length_data_payload);
945+
946+ memcpy(data_payload_buffer, str_data_size, 2);
947+ memcpy(data_payload_buffer + 2, data, length_data);
948+ if (length_padding > 0) {
949+ memcpy(data_payload_buffer + (2 + length_data),
950+ padding_buffer,
951+ length_padding);
952+ }
953+
954+ mdctx = EVP_MD_CTX_create();
955+ EVP_DigestInit_ex(mdctx, DIGEST_HASH, NULL);
956+ EVP_DigestUpdate(mdctx, data_payload_buffer, length_data_payload);
957+
958+ length_hash = EVP_MD_size(DIGEST_HASH);
959+ hash_buffer = OPENSSL_malloc(length_hash);
960+ OPENSSL_cleanse(hash_buffer, length_hash);
961+ EVP_DigestFinal_ex(mdctx, hash_buffer, NULL);
962+
963+ length_cleartext = length_hash + length_data_payload;
964+
965+ cleartext_buffer = OPENSSL_malloc(length_cleartext);
966+ OPENSSL_cleanse(cleartext_buffer, length_cleartext);
967+
968+ /* assemble cleartext-buffer */
969+ memcpy(cleartext_buffer, hash_buffer, length_hash);
970+ memcpy(cleartext_buffer + length_hash,
971+ data_payload_buffer,
972+ length_data_payload);
973+ OPENSSL_cleanse(data_payload_buffer, length_data_payload); /* sensitive */
974+
975+ /* encryption */
976+ if (!(ctx = EVP_CIPHER_CTX_new())) {
977+ debug_print("New ctx failed\n");
978+ goto cleanup;
979+ }
980+
981+ length_ciphertext = 0;
982+ if (EVP_EncryptInit_ex(ctx, CIPHER, NULL, key, salt) != 1) {
983+ debug_print("EVP_EncryptInit_ex failed\n");
984+ goto cleanup;
985+ }
986+
987+ ciphertext_buffer = OPENSSL_malloc(length_cleartext);
988+
989+ crypt_buffer_cnt = 0;
990+ while(1) {
991+ if (EVP_EncryptUpdate(ctx,
992+ ciphertext_buffer + length_ciphertext,
993+ &crypt_buffer_cnt,
994+ cleartext_buffer + length_ciphertext,
995+ 1) != 1) { /* one byte at a time */
996+ debug_print("EVP_EncryptUpdate failed\n");
997+ goto cleanup;
998+ }
999+
1000+ if (crypt_buffer_cnt != 1) { /* paranoia */
1001+ debug_print("The sizes of enc and dec text do not correspond\n");
1002+ goto cleanup;
1003+ }
1004+
1005+ ++length_ciphertext;
1006+
1007+ if (length_ciphertext >= length_cleartext) {
1008+ break;
1009+ }
1010+ }
1011+ /* No padding required for the CFB mode */
1012+
1013+ OPENSSL_cleanse(cleartext_buffer, length_cleartext); /* sensitive */
1014+ length_total = SALT_SIZE + length_ciphertext;
1015+ total_buffer = OPENSSL_malloc(length_total);
1016+
1017+ memcpy(total_buffer, salt, SALT_SIZE);
1018+ memcpy(total_buffer + SALT_SIZE, ciphertext_buffer, length_ciphertext);
1019+
1020+ *encrypted = g_base64_encode(total_buffer, length_total);
1021+ *length_encrypted = strlen(*encrypted);
1022+
1023+ rc = SSL_RC_OK;
1024+
1025+cleanup:
1026+ /* key */
1027+ OPENSSL_cleanse(key, key_size);
1028+ OPENSSL_free(key);
1029+ /* cleartext buffer */
1030+ OPENSSL_cleanse(cleartext_buffer, length_cleartext);
1031+ OPENSSL_free(cleartext_buffer);
1032+ /* payload buffer */
1033+ OPENSSL_cleanse(data_payload_buffer, length_data_payload);
1034+ OPENSSL_free(data_payload_buffer);
1035+ /* hash */
1036+ OPENSSL_cleanse(hash_buffer, length_hash);
1037+ OPENSSL_free(hash_buffer);
1038+ /* padding */
1039+ if (length_padding > 0) {
1040+ OPENSSL_cleanse(padding_buffer, length_padding);
1041+ OPENSSL_free(padding_buffer);
1042+ }
1043+
1044+ OPENSSL_cleanse(str_data_size, 3); /* paranoia */
1045+
1046+ /* ciphertext buffer */
1047+ OPENSSL_free(ciphertext_buffer);
1048+
1049+ /* total buffer */
1050+ OPENSSL_free(total_buffer);
1051+
1052+ length_data_payload = 0;
1053+ length_padding = 0;
1054+
1055+ EVP_CIPHER_CTX_free(ctx);
1056+ EVP_MD_CTX_destroy(mdctx);
1057+
1058+ return rc;
1059+
1060+}
1061+
1062+gint decrypt_data(gchar **decrypted,
1063+ const gchar *data,
1064+ const gchar *passphrase,
1065+ gint length_data) {
1066+
1067+ gint rc; /* return code */
1068+ gint decrypt_buffer_cnt, length_ciphertext, length_decrypted;
1069+ guint key_size, length_hash, length_cleartext;
1070+ gsize length_total;
1071+ guchar salt[SALT_SIZE];
1072+ guchar *ciphertext_buffer, *total_buffer;
1073+ /* sensitive buffers and counters */
1074+ gint data_size;
1075+ guint length_data_payload;
1076+ gchar str_data_size[3];
1077+ guchar *data_payload_buffer, *hash_buffer, *key;
1078+ guchar *cleartext_buffer;
1079+
1080+ EVP_CIPHER_CTX *ctx;
1081+ EVP_MD_CTX *mdctx;
1082+
1083+ rc = -1;
1084+
1085+ if (length_data < 1) {
1086+ return -1;
1087+ }
1088+
1089+ total_buffer = g_base64_decode(data, &length_total);
1090+ length_ciphertext = length_total - SALT_SIZE;
1091+ ciphertext_buffer = OPENSSL_malloc(length_ciphertext);
1092+ OPENSSL_cleanse(ciphertext_buffer, length_ciphertext);
1093+
1094+ memcpy(salt, total_buffer, SALT_SIZE);
1095+ memcpy(ciphertext_buffer, total_buffer + SALT_SIZE, length_ciphertext);
1096+
1097+ key_size = EVP_CIPHER_key_length(CIPHER);
1098+
1099+ /* decryption */
1100+ if(!(ctx = EVP_CIPHER_CTX_new())) {
1101+ debug_print("New ctx failed\n");
1102+ goto cleanup;
1103+ }
1104+
1105+ key = OPENSSL_malloc(key_size);
1106+ OPENSSL_cleanse(key, key_size);
1107+ if (secure_derive_key(key,
1108+ key_size,
1109+ passphrase,
1110+ salt) != 0) {
1111+ OPENSSL_cleanse(key, key_size);
1112+ debug_print("Could not generate secure key\n");
1113+ goto cleanup;
1114+ }
1115+
1116+ if (EVP_DecryptInit_ex(ctx, CIPHER, NULL, key, salt) != 1) {
1117+ debug_print("EVP_DecryptInit_ex failed\n");
1118+ goto cleanup;
1119+ }
1120+ OPENSSL_cleanse(key, key_size); /* highly sensitive */
1121+ cleartext_buffer = OPENSSL_malloc(length_ciphertext);
1122+ OPENSSL_cleanse(cleartext_buffer, length_ciphertext);
1123+
1124+ length_cleartext = 0;
1125+ decrypt_buffer_cnt = 0;
1126+
1127+ while(1) {
1128+ if (EVP_DecryptUpdate(ctx,
1129+ cleartext_buffer + length_cleartext,
1130+ &decrypt_buffer_cnt,
1131+ ciphertext_buffer + length_cleartext,
1132+ 1) != 1) { /* one byte at a time */
1133+ debug_print("EVP_EncryptUpdate failed\n");
1134+ goto cleanup;
1135+ }
1136+
1137+ if (decrypt_buffer_cnt != 1) { /* paranoia */
1138+ debug_print("The sizes of enc and dec text do not correspond");
1139+ goto cleanup;
1140+ }
1141+
1142+ ++length_cleartext;
1143+
1144+ if (length_cleartext >= length_ciphertext) {
1145+ break;
1146+ }
1147+ }
1148+
1149+
1150+ length_hash = EVP_MD_size(DIGEST_HASH);
1151+ hash_buffer = OPENSSL_malloc(length_hash);
1152+ length_data_payload = length_cleartext - length_hash;
1153+ data_payload_buffer = OPENSSL_malloc(length_data_payload);
1154+ OPENSSL_cleanse(data_payload_buffer, length_data_payload);
1155+
1156+ memcpy(data_payload_buffer,
1157+ cleartext_buffer + length_hash,
1158+ length_data_payload);
1159+
1160+ mdctx = EVP_MD_CTX_create();
1161+ EVP_DigestInit_ex(mdctx, DIGEST_HASH, NULL);
1162+ EVP_DigestUpdate(mdctx, data_payload_buffer, length_data_payload);
1163+ EVP_DigestFinal_ex(mdctx, hash_buffer, &length_hash);
1164+
1165+ if (strncmp((const gchar*) hash_buffer,
1166+ (const gchar*) cleartext_buffer,
1167+ length_hash) != 0) {
1168+ debug_print("Invalid hash\n");
1169+ rc = SSL_RC_WRONG_HASH_OR_KEY;
1170+ goto cleanup;
1171+ }
1172+
1173+ memcpy(str_data_size, cleartext_buffer + length_hash, 2);
1174+ data_size = atoi(str_data_size);
1175+
1176+ if (data_size < 0) {
1177+ length_decrypted = length_data_payload - 2;
1178+ } else {
1179+ length_decrypted = data_size;
1180+ }
1181+ *decrypted = OPENSSL_malloc(length_decrypted);
1182+ memcpy(*decrypted, data_payload_buffer + 2, length_decrypted);
1183+
1184+ rc = SSL_RC_OK;
1185+
1186+cleanup:
1187+
1188+ /* key */
1189+ OPENSSL_cleanse(key, key_size);
1190+ OPENSSL_free(key);
1191+ /* payload buffer */
1192+ OPENSSL_cleanse(data_payload_buffer, length_data_payload);
1193+ OPENSSL_free(data_payload_buffer);
1194+ /* hash */
1195+ OPENSSL_cleanse(hash_buffer, length_hash);
1196+ OPENSSL_free(hash_buffer);
1197+ /* ciphertext */
1198+ OPENSSL_free(ciphertext_buffer);
1199+ OPENSSL_free(total_buffer);
1200+
1201+ EVP_CIPHER_CTX_free(ctx);
1202+ EVP_MD_CTX_destroy(mdctx);
1203+
1204+ return rc;
1205+
1206+}
1207+
1208+gint generate_password_hash(gchar **password_hash,
1209+ const gchar *password,
1210+ const guchar *salt) {
1211+ /*
1212+ * Hashes 'password' using PKCS5_PBKDF2_HMAC with SHA512 and 'salt',
1213+ * and assigns a string to 'password_hash' with the following format:
1214+ * pbkdf2_sha512$iterations$base64(salt)$base64(password_hash)
1215+ */
1216+ guchar lsalt[SALT_SIZE];
1217+ gchar *PBKDF2_digest, *salt_b64, *digest_b64;
1218+
1219+ if (salt == NULL) {
1220+ if (RAND_bytes(lsalt, SALT_SIZE) != 1) {
1221+ debug_print("Random problems...\n");
1222+ return SSL_RC_ERROR;
1223+ }
1224+ } else {
1225+ memcpy(lsalt, salt, SALT_SIZE);
1226+ }
1227+
1228+ PBKDF2_digest = OPENSSL_malloc(PBKDF2_DIGEST_SIZE);
1229+ OPENSSL_cleanse(PBKDF2_digest, PBKDF2_DIGEST_SIZE);
1230+
1231+ PKCS5_PBKDF2_HMAC(password,
1232+ strlen(password),
1233+ lsalt,
1234+ SALT_SIZE,
1235+ PBKDF2_ITERATIONS,
1236+ EVP_sha512(),
1237+ PBKDF2_DIGEST_SIZE,
1238+ (guchar *) PBKDF2_digest);
1239+ digest_b64 = g_base64_encode((guchar *) PBKDF2_digest, PBKDF2_DIGEST_SIZE);
1240+ OPENSSL_cleanse(PBKDF2_digest, PBKDF2_DIGEST_SIZE);
1241+ OPENSSL_free(PBKDF2_digest);
1242+
1243+ salt_b64 = g_base64_encode(lsalt, SALT_SIZE);
1244+
1245+ *password_hash = g_strdup_printf("pbkdf2_sha512$%d$%s$%s",
1246+ PBKDF2_ITERATIONS,
1247+ salt_b64,
1248+ digest_b64);
1249+
1250+ OPENSSL_cleanse(digest_b64, strlen(digest_b64));
1251+ OPENSSL_free(digest_b64);
1252+
1253+ OPENSSL_cleanse(salt_b64, strlen(salt_b64));
1254+ OPENSSL_free(salt_b64);
1255+
1256+ return SSL_RC_OK;
1257+
1258+}
1259+
1260+gint check_password(const gchar *password, const gchar *password_hash) {
1261+
1262+ gint token_counter, rc;
1263+ guchar *salt;
1264+ gchar **tokens, *new_hash;
1265+ gsize salt_length;
1266+
1267+ rc = SSL_RC_ERROR;
1268+ tokens = g_strsplit(password_hash,
1269+ "$",
1270+ -1);
1271+ token_counter = 0;
1272+ while (*(tokens + token_counter) != NULL) {
1273+ ++token_counter;
1274+ }
1275+
1276+ if (token_counter != 4) {
1277+ debug_print("Invalid password hash...\n");
1278+ goto cleanup;
1279+ }
1280+
1281+ salt = g_base64_decode(*(tokens + 2), &salt_length);
1282+ if (salt_length != SALT_SIZE) {
1283+ debug_print("Salt size does not match\n");
1284+ goto cleanup;
1285+ }
1286+
1287+ if (generate_password_hash(&new_hash, password, salt) != SSL_RC_OK) {
1288+ debug_print("Password hash generation failed\n");
1289+ goto cleanup;
1290+ }
1291+
1292+ rc = g_strcmp0(password_hash, new_hash);
1293+
1294+cleanup:
1295+ g_free(salt);
1296+ g_free(new_hash);
1297+ g_strfreev(tokens);
1298+ return rc;
1299+
1300+}
1301+
1302 #endif /* USE_SSL */
1303diff --git a/libsylph/ssl.h b/libsylph/ssl.h
1304index a9f690d..4c0ccd1 100644
1305--- a/libsylph/ssl.h
1306+++ b/libsylph/ssl.h
1307@@ -32,9 +32,15 @@
1308 #include <openssl/pem.h>
1309 #include <openssl/ssl.h>
1310 #include <openssl/err.h>
1311+#include <openssl/evp.h>
1312+#include <openssl/rand.h>
1313
1314 #include "socket.h"
1315
1316+#define SSL_RC_OK 0
1317+#define SSL_RC_ERROR -1
1318+#define SSL_RC_WRONG_HASH_OR_KEY 1
1319+
1320 typedef enum {
1321 SSL_METHOD_SSLv23,
1322 SSL_METHOD_TLSv1
1323@@ -60,6 +66,26 @@ void ssl_done_socket (SockInfo *sockinfo);
1324
1325 void ssl_set_verify_func (SSLVerifyFunc func);
1326
1327+/* master password related code */
1328+gint encrypt_data(gchar **encrypted,
1329+ gint *length_encrypted,
1330+ const gchar *data,
1331+ const gchar *passphrase,
1332+ gint length_data,
1333+ guint min_data_length,
1334+ gboolean rnd_salt);
1335+
1336+gint decrypt_data(gchar **decrypted,
1337+ const gchar *data,
1338+ const gchar *passphrase,
1339+ gint length_data);
1340+
1341+gint generate_password_hash(gchar **password_hash,
1342+ const gchar *password,
1343+ const guchar *salt);
1344+
1345+gint check_password(const gchar *password, const gchar *password_hash);
1346+/* ---------------------------- */
1347 #endif /* USE_SSL */
1348
1349 #endif /* __SSL_H__ */
1350diff --git a/libsylph/utils.c b/libsylph/utils.c
1351index aabce06..3bbcbcf 100644
1352--- a/libsylph/utils.c
1353+++ b/libsylph/utils.c
1354@@ -3306,10 +3306,10 @@ gint canonicalize_file(const gchar *src, const gchar *dest)
1355 }
1356 }
1357
1358- if (last_linebreak == TRUE) {
1359- if (fputs("\r\n", dest_fp) == EOF)
1360- err = TRUE;
1361- }
1362+ /* if (last_linebreak == TRUE) { */
1363+ /* if (fputs("\r\n", dest_fp) == EOF) */
1364+ /* err = TRUE; */
1365+ /* } */
1366
1367 if (ferror(src_fp)) {
1368 FILE_OP_ERROR(src, "fgets");
1369@@ -4598,6 +4598,36 @@ gchar *input_query_password(const gchar *server, const gchar *user)
1370 return NULL;
1371 }
1372
1373+static QueryMasterPasswordFunc query_master_password_func = NULL;
1374+
1375+void set_input_query_master_password_func(QueryMasterPasswordFunc func)
1376+{
1377+ query_master_password_func = func;
1378+}
1379+
1380+gchar *input_query_master_password(void)
1381+{
1382+ if (query_master_password_func)
1383+ return query_master_password_func();
1384+ else
1385+ return NULL;
1386+}
1387+
1388+static SetNewPasswordFunc set_new_password_func = NULL;
1389+
1390+void set_input_set_new_password_func(SetNewPasswordFunc func)
1391+{
1392+ set_new_password_func = func;
1393+}
1394+
1395+gchar *input_set_new_password(guint max_attempts)
1396+{
1397+ if (set_new_password_func)
1398+ return set_new_password_func(max_attempts);
1399+ else
1400+ return NULL;
1401+}
1402+
1403 /* logging */
1404
1405 static FILE *log_fp = NULL;
1406diff --git a/libsylph/utils.h b/libsylph/utils.h
1407index 9ac65cf..ede55ff 100644
1408--- a/libsylph/utils.h
1409+++ b/libsylph/utils.h
1410@@ -202,6 +202,8 @@ typedef void (*ProgressFunc) (gint cur,
1411 gint total);
1412 typedef gchar * (*QueryPasswordFunc) (const gchar *server,
1413 const gchar *user);
1414+typedef gchar * (*QueryMasterPasswordFunc) (void);
1415+typedef gchar * (*SetNewPasswordFunc) (guint max_attempts);
1416 typedef void (*LogFunc) (const gchar *str);
1417 typedef void (*LogFlushFunc) (void);
1418
1419@@ -560,9 +562,12 @@ void progress_show (gint cur,
1420
1421 /* user input */
1422 void set_input_query_password_func (QueryPasswordFunc func);
1423-
1424 gchar *input_query_password (const gchar *server,
1425 const gchar *user);
1426+void set_input_query_master_password_func(QueryMasterPasswordFunc func);
1427+gchar *input_query_master_password(void);
1428+void set_input_set_new_password_func(SetNewPasswordFunc func);
1429+gchar *input_set_new_password(guint max_attempts);
1430
1431 /* logging */
1432 void set_log_file (const gchar *filename);
1433diff --git a/src/inputdialog.c b/src/inputdialog.c
1434index a601085..cdfb997 100644
1435--- a/src/inputdialog.c
1436+++ b/src/inputdialog.c
1437@@ -44,6 +44,7 @@
1438 #include "filesel.h"
1439 #include "prefs_common.h"
1440 #include "gtkutils.h"
1441+#include "masterpassword.h"
1442 #include "utils.h"
1443
1444 #define DIALOG_WIDTH 420
1445@@ -156,6 +157,57 @@ gchar *input_dialog_query_password(const gchar *server, const gchar *user)
1446 return pass;
1447 }
1448
1449+gchar *input_dialog_query_master_password(void)
1450+{
1451+ gchar *mp_input;
1452+
1453+ mp_input = input_dialog_with_invisible(_("Input password"),
1454+ _("Master password"),
1455+ NULL);
1456+
1457+ if (prefs_common.use_master_password &&
1458+ prefs_common.auto_unload_master_password)
1459+ {
1460+ g_timeout_add(1000 * 30, unload_master_password, NULL);
1461+ debug_print("Master password to be unloaded in 30 seconds\n");
1462+ }
1463+
1464+ return mp_input;
1465+}
1466+
1467+gchar *input_dialog_set_new_password(guint max_attempts)
1468+{
1469+ guint cnt;
1470+ gchar *pass1, *pass2;
1471+
1472+ if (max_attempts < 1)
1473+ return NULL;
1474+
1475+ for (cnt = 0; cnt < max_attempts; ++cnt) {
1476+ pass1 = input_dialog_with_invisible(
1477+ _("Input password"),
1478+ _("New password"),
1479+ NULL);
1480+ pass2 = input_dialog_with_invisible(
1481+ _("Input password"),
1482+ _("Confirm new password"),
1483+ NULL);
1484+
1485+ if (pass1 != NULL && pass2 != NULL && strcmp(pass1, pass2) == 0) {
1486+ cleanse_buffer(pass2, strlen(pass2));
1487+ g_free(pass2);
1488+ break;
1489+ }
1490+ cleanse_buffer(pass1, strlen(pass1));
1491+ cleanse_buffer(pass2, strlen(pass2));
1492+ g_free(pass2);
1493+ g_free(pass1);
1494+ pass1 = NULL;
1495+ }
1496+
1497+ return pass1;
1498+}
1499+
1500 gchar *input_dialog_with_filesel(const gchar *title, const gchar *message,
1501 const gchar *default_string,
1502 GtkFileChooserAction action)
1503diff --git a/src/inputdialog.h b/src/inputdialog.h
1504index 02bdda3..5364d1a 100644
1505--- a/src/inputdialog.h
1506+++ b/src/inputdialog.h
1507@@ -36,7 +36,8 @@ gchar *input_dialog_combo (const gchar *title,
1508 gboolean case_sensitive);
1509 gchar *input_dialog_query_password (const gchar *server,
1510 const gchar *user);
1511-
1512+gchar *input_dialog_query_master_password(void);
1513+gchar *input_dialog_set_new_password(guint max_attempts);
1514 gchar *input_dialog_with_filesel (const gchar *title,
1515 const gchar *message,
1516 const gchar *default_string,
1517diff --git a/src/main.c b/src/main.c
1518index 77d8192..97a4bd0 100644
1519--- a/src/main.c
1520+++ b/src/main.c
1521@@ -87,6 +87,7 @@
1522 #include "foldersel.h"
1523 #include "update_check.h"
1524 #include "colorlabel.h"
1525+#include "masterpassword.h"
1526
1527 #if USE_GPGME
1528 # include "rfc2015.h"
1529@@ -265,14 +266,54 @@ int main(int argc, char *argv[])
1530 set_ui_update_func(gtkut_events_flush);
1531 set_progress_func(main_window_progress_show);
1532 set_input_query_password_func(input_dialog_query_password);
1533+ set_input_set_new_password_func(input_dialog_set_new_password);
1534 #if USE_SSL
1535 ssl_init();
1536 ssl_set_verify_func(ssl_manager_verify_cert);
1537+ set_input_query_master_password_func(input_dialog_query_master_password);
1538 #endif
1539
1540 CHDIR_EXIT_IF_FAIL(get_home_dir(), 1);
1541
1542 prefs_common_read_config();
1543+ set_master_password(NULL);
1544+#if USE_SSL
1545+ if (prefs_common.use_master_password) {
1546+ if (prefs_common.master_password_hash != NULL) {
1547+ if (check_master_password_interactively(3) != MP_RC_OK) {
1548+ if (alertpanel(_("Master password"),
1549+ _("Invalid master password"),
1550+ GTK_STOCK_DISCARD,
1551+ GTK_STOCK_QUIT,
1552+ NULL) != G_ALERTDEFAULT) {
1553+ exit(1);
1554+ }
1555+ }
1556+ } else {
1557+ alertpanel_notice(
1558+ _("Master password enabled but not set. Setting one now"));
1559+ if (set_master_password_interactively(3) != MP_RC_OK) {
1560+ if (alertpanel(_("Master password"),
1561+ _("Unable to set master password"),
1562+ GTK_STOCK_DISCARD,
1563+ GTK_STOCK_QUIT,
1564+ NULL) != G_ALERTDEFAULT) {
1565+ exit(1);
1566+ }
1567+ }
1568+ }
1569+ }
1570+ /* security goal:
1571+ * if the master password is enabled and loaded on init,
1572+ * an attacker can potentially set use_master_password - disabled
1573+ * afterwards and then force Sylpheed to save account data - the result
1574+ * being passwords saved to accountrc in plain-text.
1575+ * possible solution:
1576+ * Do not allow the passwords to be stored in plain-text if Sylpheed
1577+ * was started with use_master_password - enabled.
1578+ */
1579+ master_password_enabled_on_init = prefs_common.use_master_password;
1580+#endif
1581 filter_set_addressbook_func(addressbook_has_address);
1582 filter_read_config();
1583 prefs_actions_read_config();
1584@@ -381,6 +422,13 @@ int main(int argc, char *argv[])
1585
1586 remote_command_exec();
1587
1588+#if USE_SSL
1589+ if (prefs_common.auto_unload_master_password && master_password_active()) {
1590+ debug_print("Auto unloading master password\n");
1591+ unload_master_password();
1592+ }
1593+#endif
1594+
1595 #if USE_UPDATE_CHECK
1596 if (prefs_common.auto_update_check)
1597 update_check(FALSE);
1598@@ -993,6 +1041,7 @@ void app_will_exit(gboolean force)
1599
1600 /* remove temporary files, close log file, socket cleanup */
1601 #if USE_SSL
1602+ unload_master_password();
1603 ssl_done();
1604 #endif
1605 syl_cleanup();
1606diff --git a/src/prefs_account_dialog.c b/src/prefs_account_dialog.c
1607index e9cba13..e215b8e 100644
1608--- a/src/prefs_account_dialog.c
1609+++ b/src/prefs_account_dialog.c
1610@@ -267,7 +267,7 @@ static PrefsUIData ui_data[] = {
1611 {"user_id", &basic.uid_entry,
1612 prefs_set_data_from_entry, prefs_set_entry},
1613 {"password", &basic.pass_entry,
1614- prefs_set_data_from_entry, prefs_set_entry},
1615+ prefs_set_data_from_epass_entry, prefs_set_entry},
1616
1617 /* Receive */
1618 {"use_apop_auth", &receive.use_apop_chkbtn,
1619@@ -313,7 +313,7 @@ static PrefsUIData ui_data[] = {
1620 {"smtp_user_id", &p_send.smtp_uid_entry,
1621 prefs_set_data_from_entry, prefs_set_entry},
1622 {"smtp_password", &p_send.smtp_pass_entry,
1623- prefs_set_data_from_entry, prefs_set_entry},
1624+ prefs_set_data_from_epass_entry, prefs_set_entry},
1625
1626 {"pop_before_smtp", &p_send.pop_bfr_smtp_chkbtn,
1627 prefs_set_data_from_toggle, prefs_set_toggle},
1628diff --git a/src/prefs_common_dialog.c b/src/prefs_common_dialog.c
1629index b46c9b7..119a056 100644
1630--- a/src/prefs_common_dialog.c
1631+++ b/src/prefs_common_dialog.c
1632@@ -205,6 +205,7 @@ static struct JunkMail {
1633 static struct Privacy {
1634 GtkWidget *checkbtn_auto_check_signatures;
1635 GtkWidget *checkbtn_gpg_signature_popup;
1636+ GtkWidget *radiobtn_gpg_signature_all;
1637 GtkWidget *checkbtn_store_passphrase;
1638 GtkWidget *spinbtn_store_passphrase;
1639 GtkObject *spinbtn_store_passphrase_adj;
1640@@ -215,6 +216,14 @@ static struct Privacy {
1641 } privacy;
1642 #endif
1643
1644+#if USE_SSL
1645+static struct MasterPassword {
1646+ GtkWidget *checkbtn_use_master_password;
1647+ GtkWidget *checkbtn_auto_unload_master_password;
1648+ GtkWidget *spinbtn_encrypted_password_min_length;
1649+} master_password;
1650+#endif
1651+
1652 static struct Interface {
1653 GtkWidget *checkbtn_always_show_msg;
1654 GtkWidget *checkbtn_always_mark_read;
1655@@ -314,6 +323,13 @@ static void prefs_common_attach_toolbtn_pos_set_radiobtn (PrefParam *pparam);
1656 static void prefs_common_online_mode_set_data_from_radiobtn(PrefParam *pparam);
1657 static void prefs_common_online_mode_set_radiobtn (PrefParam *pparam);
1658
1659+#if USE_GPGME
1660+static void prefs_common_gpg_signature_popup_mode_set_data_from_radiobtn(
1661+ PrefParam *pparam);
1662+static void prefs_common_gpg_signature_popup_mode_set_radiobtn(
1663+ PrefParam *pparam);
1664+#endif
1665+
1666 static PrefsUIData ui_data[] = {
1667 /* Receive */
1668 {"autochk_newmail", &receive.checkbtn_autochk,
1669@@ -540,6 +556,9 @@ static PrefsUIData ui_data[] = {
1670 prefs_set_data_from_toggle, prefs_set_toggle},
1671 {"gpg_signature_popup", &privacy.checkbtn_gpg_signature_popup,
1672 prefs_set_data_from_toggle, prefs_set_toggle},
1673+ {"gpg_signature_popup_mode", &privacy.radiobtn_gpg_signature_all,
1674+ prefs_common_gpg_signature_popup_mode_set_data_from_radiobtn,
1675+ prefs_common_gpg_signature_popup_mode_set_radiobtn},
1676 {"store_passphrase", &privacy.checkbtn_store_passphrase,
1677 prefs_set_data_from_toggle, prefs_set_toggle},
1678 {"store_passphrase_timeout", &privacy.spinbtn_store_passphrase,
1679@@ -552,6 +571,18 @@ static PrefsUIData ui_data[] = {
1680 prefs_set_data_from_toggle, prefs_set_toggle},
1681 #endif /* USE_GPGME */
1682
1683+#if USE_SSL
1684+ {"use_master_password", &master_password.checkbtn_use_master_password,
1685+ prefs_set_data_from_toggle, prefs_set_toggle},
1686+ {"auto_unload_master_password",
1687+ &master_password.checkbtn_auto_unload_master_password,
1688+ prefs_set_data_from_toggle, prefs_set_toggle},
1689+ {"encrypted_password_min_length",
1690+ &master_password.spinbtn_encrypted_password_min_length,
1691+ prefs_set_data_from_spinbtn,
1692+ prefs_set_spinbtn},
1693+#endif
1694+
1695 /* Interface */
1696 {"always_show_message_when_selected",
1697 &iface.checkbtn_always_show_msg,
1698@@ -682,6 +713,9 @@ static void prefs_junk_create (void);
1699 #if USE_GPGME
1700 static void prefs_privacy_create (void);
1701 #endif
1702+#if USE_SSL
1703+static void prefs_master_password_create (void);
1704+#endif
1705 static void prefs_details_create (void);
1706 static GtkWidget *prefs_other_create (void);
1707 static GtkWidget *prefs_extcmd_create (void);
1708@@ -841,6 +875,10 @@ static void prefs_common_create(void)
1709 #if USE_GPGME
1710 prefs_privacy_create();
1711 SET_NOTEBOOK_LABEL(dialog.notebook, _("Privacy"), page++);
1712+#endif
1713+#if USE_SSL
1714+ prefs_master_password_create();
1715+ SET_NOTEBOOK_LABEL(dialog.notebook, _("Master password"), page++);
1716 #endif
1717 prefs_details_create();
1718 SET_NOTEBOOK_LABEL(dialog.notebook, _("Details"), page++);
1719@@ -2478,10 +2516,14 @@ static void prefs_privacy_create(void)
1720 GtkWidget *vbox2;
1721 GtkWidget *vbox3;
1722 GtkWidget *hbox1;
1723+ GtkWidget *vbox_sign_popup_mode;
1724+ GtkWidget *hbox_sign_popup_mode;
1725 GtkWidget *hbox_spc;
1726 GtkWidget *label;
1727 GtkWidget *checkbtn_auto_check_signatures;
1728 GtkWidget *checkbtn_gpg_signature_popup;
1729+ GtkWidget *radiobtn_gpg_signature_all;
1730+ GtkWidget *radiobtn_gpg_signature_bad;
1731 GtkWidget *checkbtn_store_passphrase;
1732 GtkObject *spinbtn_store_passphrase_adj;
1733 GtkWidget *spinbtn_store_passphrase;
1734@@ -2505,6 +2547,56 @@ static void prefs_privacy_create(void)
1735 PACK_CHECK_BUTTON (vbox2, checkbtn_gpg_signature_popup,
1736 _("Show signature check result in a popup window"));
1737
1738+ vbox_sign_popup_mode = gtk_vbox_new (FALSE, VSPACING_NARROW);
1739+ gtk_widget_show (vbox_sign_popup_mode);
1740+ gtk_box_pack_start (GTK_BOX (vbox2), vbox_sign_popup_mode, FALSE, FALSE, 0);
1741+
1742+ hbox_sign_popup_mode = gtk_hbox_new (FALSE, 8);
1743+ gtk_widget_show (hbox_sign_popup_mode);
1744+ gtk_box_pack_start (GTK_BOX (vbox_sign_popup_mode),
1745+ hbox_sign_popup_mode,
1746+ FALSE,
1747+ FALSE,
1748+ 0);
1749+
1750+ hbox_spc = gtk_hbox_new (FALSE, 0);
1751+ gtk_widget_show (hbox_spc);
1752+ gtk_box_pack_start (GTK_BOX (hbox_sign_popup_mode),
1753+ hbox_spc,
1754+ FALSE,
1755+ FALSE,
1756+ 0);
1757+ gtk_widget_set_size_request (hbox_spc, 12, -1);
1758+
1759+ radiobtn_gpg_signature_all = gtk_radio_button_new_with_label(
1760+ NULL,
1761+ _("All signatures"));
1762+ gtk_widget_show(radiobtn_gpg_signature_all);
1763+ gtk_box_pack_start(GTK_BOX (hbox_sign_popup_mode),
1764+ radiobtn_gpg_signature_all,
1765+ FALSE,
1766+ FALSE,
1767+ 0);
1768+ g_object_set_data(G_OBJECT (radiobtn_gpg_signature_all),
1769+ MENU_VAL_ID,
1770+ GINT_TO_POINTER (1));
1771+
1772+ radiobtn_gpg_signature_bad = gtk_radio_button_new_with_label_from_widget(
1773+ GTK_RADIO_BUTTON (radiobtn_gpg_signature_all),
1774+ _("Bad signatures only"));
1775+ gtk_widget_show(radiobtn_gpg_signature_bad);
1776+ gtk_box_pack_start(GTK_BOX (hbox_sign_popup_mode),
1777+ radiobtn_gpg_signature_bad,
1778+ FALSE,
1779+ FALSE,
1780+ 0);
1781+ g_object_set_data(G_OBJECT (radiobtn_gpg_signature_bad),
1782+ MENU_VAL_ID,
1783+ GINT_TO_POINTER (0));
1784+
1785+ SET_TOGGLE_SENSITIVITY (checkbtn_gpg_signature_popup,
1786+ hbox_sign_popup_mode);
1787+
1788 PACK_CHECK_BUTTON (vbox2, checkbtn_store_passphrase,
1789 _("Store passphrase in memory temporarily"));
1790
1791@@ -2572,7 +2664,8 @@ static void prefs_privacy_create(void)
1792 = checkbtn_auto_check_signatures;
1793 privacy.checkbtn_gpg_signature_popup
1794 = checkbtn_gpg_signature_popup;
1795- privacy.checkbtn_store_passphrase = checkbtn_store_passphrase;
1796+ privacy.radiobtn_gpg_signature_all = radiobtn_gpg_signature_all;
1797+ privacy.checkbtn_store_passphrase = checkbtn_store_passphrase;
1798 privacy.spinbtn_store_passphrase = spinbtn_store_passphrase;
1799 privacy.spinbtn_store_passphrase_adj = spinbtn_store_passphrase_adj;
1800 #ifndef G_OS_WIN32
1801@@ -2582,6 +2675,93 @@ static void prefs_privacy_create(void)
1802 }
1803 #endif /* USE_GPGME */
1804
1805+#if USE_SSL
1806+static void prefs_master_password_create(void)
1807+{
1808+ GtkWidget *vbox_main;
1809+ GtkWidget *vbox_master_password_options;
1810+ GtkWidget *vbox_master_password_suboptions;
1811+ GtkWidget *hbox1;
1812+ GtkWidget *hbox_spc;
1813+ GtkWidget *label;
1814+ GtkWidget *checkbtn_use_master_password;
1815+ GtkWidget *checkbtn_auto_unload_master_password;
1816+ GtkWidget *spinbtn_encrypted_password_min_length;
1817+ GtkObject *spinbtn_encrypted_password_min_length_adj;
1818+
1819+ vbox_main = gtk_vbox_new (FALSE, VSPACING);
1820+ gtk_widget_show (vbox_main);
1821+ gtk_container_add (GTK_CONTAINER (dialog.notebook), vbox_main);
1822+ gtk_container_set_border_width (GTK_CONTAINER (vbox_main), VBOX_BORDER);
1823+
1824+ vbox_master_password_options = gtk_vbox_new (FALSE, 0);
1825+ gtk_widget_show (vbox_master_password_options);
1826+ gtk_box_pack_start (
1827+ GTK_BOX (vbox_main), vbox_master_password_options, FALSE, FALSE, 0);
1828+
1829+ PACK_CHECK_BUTTON (vbox_master_password_options,
1830+ checkbtn_use_master_password,
1831+ _("Use master password"));
1832+
1833+ vbox_master_password_suboptions = gtk_vbox_new (FALSE, VSPACING_NARROW);
1834+ gtk_widget_show (vbox_master_password_suboptions);
1835+ gtk_box_pack_start (GTK_BOX (vbox_master_password_options),
1836+ vbox_master_password_suboptions,
1837+ FALSE,
1838+ FALSE,
1839+ 0);
1840+
1841+ PACK_CHECK_BUTTON (vbox_master_password_suboptions,
1842+ checkbtn_auto_unload_master_password,
1843+ _("Automatically unload master password "
1844+ "after session initialization"));
1845+
1846+ hbox1 = gtk_hbox_new (FALSE, 8);
1847+ gtk_widget_show (hbox1);
1848+ gtk_box_pack_start (GTK_BOX (vbox_master_password_suboptions),
1849+ hbox1,
1850+ FALSE,
1851+ FALSE,
1852+ 0);
1853+
1854+ hbox_spc = gtk_hbox_new (FALSE, 0);
1855+ gtk_widget_show (hbox_spc);
1856+ gtk_box_pack_start (GTK_BOX (hbox1), hbox_spc, FALSE, FALSE, 0);
1857+ gtk_widget_set_size_request (hbox_spc, 12, -1);
1858+
1859+ label = gtk_label_new (_("Min. password length"));
1860+ gtk_widget_show (label);
1861+ gtk_box_pack_start (
1862+ GTK_BOX (hbox1), label, FALSE, FALSE, 0);
1863+
1864+ spinbtn_encrypted_password_min_length_adj = gtk_adjustment_new (
1865+ 32, 0, 99, 1, 5, 0);
1866+ spinbtn_encrypted_password_min_length = gtk_spin_button_new(
1867+ GTK_ADJUSTMENT (spinbtn_encrypted_password_min_length_adj), 1, 0);
1868+ gtk_widget_show (spinbtn_encrypted_password_min_length);
1869+ gtk_box_pack_start (GTK_BOX (hbox1),
1870+ spinbtn_encrypted_password_min_length,
1871+ FALSE,
1872+ FALSE,
1873+ 0);
1874+ gtk_spin_button_set_numeric (
1875+ GTK_SPIN_BUTTON (spinbtn_encrypted_password_min_length), TRUE);
1876+ gtk_widget_set_size_request (spinbtn_encrypted_password_min_length,
1877+ 64,
1878+ -1);
1879+
1880+ SET_TOGGLE_SENSITIVITY (checkbtn_use_master_password,
1881+ vbox_master_password_suboptions);
1882+
1883+ master_password.checkbtn_use_master_password
1884+ = checkbtn_use_master_password;
1885+ master_password.checkbtn_auto_unload_master_password
1886+ = checkbtn_auto_unload_master_password;
1887+ master_password.spinbtn_encrypted_password_min_length
1888+ = spinbtn_encrypted_password_min_length;
1889+}
1890+#endif /* USE_SSL */
1891+
1892 static void prefs_details_create(void)
1893 {
1894 GtkWidget *vbox1;
1895@@ -4719,6 +4899,60 @@ static void prefs_common_online_mode_set_radiobtn(PrefParam *pparam)
1896 }
1897 }
1898
1899+#if USE_GPGME
1900+static void prefs_common_gpg_signature_popup_mode_set_data_from_radiobtn(
1901+ PrefParam *pparam)
1902+{
1903+ PrefsUIData *ui_data;
1904+ GtkRadioButton *radiobtn;
1905+ GSList *group;
1906+
1907+ ui_data = (PrefsUIData *)pparam->ui_data;
1908+ g_return_if_fail(ui_data != NULL);
1909+ g_return_if_fail(*ui_data->widget != NULL);
1910+
1911+ radiobtn = GTK_RADIO_BUTTON(*ui_data->widget);
1912+ group = gtk_radio_button_get_group(radiobtn);
1913+ while (group != NULL) {
1914+ GtkToggleButton *btn = GTK_TOGGLE_BUTTON(group->data);
1915+
1916+ if (gtk_toggle_button_get_active(btn)) {
1917+ prefs_common.gpg_signature_popup_mode =
1918+ GPOINTER_TO_INT(g_object_get_data(G_OBJECT(btn), MENU_VAL_ID));
1919+ break;
1920+ }
1921+ group = group->next;
1922+ }
1923+}
1924+
1925+static void prefs_common_gpg_signature_popup_mode_set_radiobtn(
1926+ PrefParam *pparam)
1927+{
1928+ PrefsUIData *ui_data;
1929+ GtkRadioButton *radiobtn;
1930+ GSList *group;
1931+
1932+ ui_data = (PrefsUIData *)pparam->ui_data;
1933+ g_return_if_fail(ui_data != NULL);
1934+ g_return_if_fail(*ui_data->widget != NULL);
1935+
1936+ radiobtn = GTK_RADIO_BUTTON(*ui_data->widget);
1937+ group = gtk_radio_button_get_group(radiobtn);
1938+ while (group != NULL) {
1939+ GtkToggleButton *btn = GTK_TOGGLE_BUTTON(group->data);
1940+ gint data;
1941+
1942+ data = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(btn),
1943+ MENU_VAL_ID));
1944+ if (data == prefs_common.gpg_signature_popup_mode) {
1945+ gtk_toggle_button_set_active(btn, TRUE);
1946+ break;
1947+ }
1948+ group = group->next;
1949+ }
1950+}
1951+#endif
1952+
1953 static void prefs_common_dispitem_clicked(void)
1954 {
1955 prefs_summary_column_open(FOLDER_ITEM_IS_SENT_FOLDER
1956diff --git a/src/prefs_ui.c b/src/prefs_ui.c
1957index a3a8f74..2ab1d45 100644
1958--- a/src/prefs_ui.c
1959+++ b/src/prefs_ui.c
1960@@ -31,11 +31,13 @@
1961 #include <errno.h>
1962
1963 #include "prefs.h"
1964+#include "prefs_common.h"
1965 #include "prefs_ui.h"
1966 #include "menu.h"
1967 #include "codeconv.h"
1968 #include "utils.h"
1969 #include "gtkutils.h"
1970+#include "masterpassword.h"
1971
1972 typedef enum
1973 {
1974@@ -268,6 +270,65 @@ void prefs_set_data_from_entry(PrefParam *pparam)
1975 }
1976 }
1977
1978+void prefs_set_data_from_epass_entry(PrefParam *pparam)
1979+{
1980+#if USE_SSL
1981+ PrefsUIData *ui_data;
1982+ gchar **str;
1983+ const gchar *entry_str;
1984+
1985+ /* This is where decrypted passwords are encrypted and stored again */
1986+
1987+ /* master_password == NULL for any of the following reasons:
1988+ * - use_master_password is not enabled (we just save without encrypting)
1989+ * - master_password is auto unloaded (prompt for the master password)
1990+ * - undefined reason (refure to store the password)
1991+ */
1992+ if (!prefs_common.use_master_password) {
1993+ /* check if use_master_password was enabled when Sylpheed started */
1994+ if (!master_password_enabled_on_init) {
1995+ prefs_set_data_from_entry(pparam);
1996+ }
1997+ return;
1998+ } else if (master_password == NULL) {
1999+ if (!prefs_common.auto_unload_master_password) {
2000+ debug_print("Master password enabled, but not loaded for no "
2001+ "apparent reason. Not storing\n");
2002+ return;
2003+ } else {
2004+ if (check_master_password_interactively(3) != MP_RC_OK) {
2005+ debug_print("Failed to reload the master password\n");
2006+ return;
2007+ }
2008+ }
2009+ }
2010+
2011+ ui_data = (PrefsUIData *)pparam->ui_data;
2012+ g_return_if_fail(ui_data != NULL);
2013+ g_return_if_fail(*ui_data->widget != NULL);
2014+
2015+ entry_str = gtk_entry_get_text(GTK_ENTRY(*ui_data->widget));
2016+
2017+ switch (pparam->type) {
2018+ case P_STRING:
2019+ str = (gchar **)pparam->data;
2020+ g_free(*str);
2021+ if ((entry_str != NULL) && (!mpes_string_prefix(entry_str))) {
2022+ debug_print("Encrypting GTK password entry\n");
2023+ *str = encrypt_with_master_password(entry_str);
2024+ } else {
2025+ *str = entry_str[0] ? g_strdup(entry_str) : NULL;
2026+ }
2027+ break;
2028+ default:
2029+ g_warning("Invalid PrefType for GtkEntry widget: %d\n",
2030+ pparam->type);
2031+ }
2032+#else
2033+ prefs_set_data_from_entry(pparam);
2034+#endif
2035+}
2036+
2037 void prefs_set_entry(PrefParam *pparam)
2038 {
2039 PrefsUIData *ui_data;
2040diff --git a/src/prefs_ui.h b/src/prefs_ui.h
2041index cfdf353..f7bd903 100644
2042--- a/src/prefs_ui.h
2043+++ b/src/prefs_ui.h
2044@@ -164,6 +164,7 @@ void prefs_set_data_from_dialog (PrefParam *param);
2045 void prefs_set_dialog_to_default(PrefParam *param);
2046
2047 void prefs_set_data_from_entry (PrefParam *pparam);
2048+void prefs_set_data_from_epass_entry(PrefParam *pparam);
2049 void prefs_set_entry (PrefParam *pparam);
2050 void prefs_set_data_from_text (PrefParam *pparam);
2051 void prefs_set_text (PrefParam *pparam);
2052diff --git a/src/rfc2015.c b/src/rfc2015.c
2053index ebfa96c..48825e2 100644
2054--- a/src/rfc2015.c
2055+++ b/src/rfc2015.c
2056@@ -210,8 +210,12 @@ static void check_signature(MimeInfo *mimeinfo, MimeInfo *partinfo, FILE *fp)
2057 gchar *tmp_file;
2058 gint n_exclude_chars = 0;
2059
2060- if (prefs_common.gpg_signature_popup)
2061+ if (prefs_common.gpg_signature_popup &&
2062+ prefs_common.gpg_signature_popup_mode == 1)
2063+ {
2064+ /* FIXME: Perhaps some macro instead of 1 */
2065 statuswindow = gpgmegtk_sig_status_create();
2066+ }
2067
2068 err = gpgme_new(&ctx);
2069 if (err) {
2070@@ -309,8 +313,21 @@ leave:
2071 result = _("Error verifying the signature");
2072 }
2073 debug_print("verification status: %s\n", result);
2074- if (prefs_common.gpg_signature_popup)
2075+ if (prefs_common.gpg_signature_popup &&
2076+ prefs_common.gpg_signature_popup_mode == 1)
2077+ {
2078+ /* FIXME: Perhaps some macro instead of 1 */
2079+ gpgmegtk_sig_status_update(statuswindow, ctx);
2080+ } else if (prefs_common.gpg_signature_popup &&
2081+ verifyresult->signatures->status != GPG_ERR_NO_DATA &&
2082+ verifyresult->signatures->status != GPG_ERR_NO_ERROR)
2083+ {
2084+ /* prefs_common.gpg_signature_popup_mode == 0 is useless as long as
2085+ * there are only two modes (0 and 1)
2086+ */
2087+ statuswindow = gpgmegtk_sig_status_create();
2088 gpgmegtk_sig_status_update(statuswindow, ctx);
2089+ }
2090
2091 g_free (partinfo->sigstatus);
2092 partinfo->sigstatus = g_strdup (result);
2093@@ -319,8 +336,11 @@ leave:
2094 gpgme_data_release(text);
2095 if (ctx)
2096 gpgme_release(ctx);
2097- if (prefs_common.gpg_signature_popup)
2098+ if (prefs_common.gpg_signature_popup) {
2099+ /* gpgmegtk_sig_status_destroy handles NULL params so no complicated
2100+ check is needed */
2101 gpgmegtk_sig_status_destroy(statuswindow);
2102+ }
2103 }
2104
2105 /*
2106@@ -427,7 +447,11 @@ static gpgme_data_t pgp_decrypt(MsgInfo *msginfo, MimeInfo *partinfo, FILE *fp)
2107 debug_print("verification status: %s\n", result);
2108 debug_print("full status: %s\n",
2109 msginfo->encinfo->sigstatus_full);
2110- if (prefs_common.gpg_signature_popup) {
2111+ if (prefs_common.gpg_signature_popup &&
2112+ ((prefs_common.gpg_signature_popup_mode == 1) ||
2113+ (verifyresult->signatures->status != GPG_ERR_NO_DATA &&
2114+ verifyresult->signatures->status != GPG_ERR_NO_ERROR)))
2115+ {
2116 GpgmegtkSigStatus statuswindow;
2117 statuswindow = gpgmegtk_sig_status_create();
2118 gpgmegtk_sig_status_update(statuswindow, ctx);
2119diff --git a/src/send_message.c b/src/send_message.c
2120index a8c2c7a..537c3c8 100644
2121--- a/src/send_message.c
2122+++ b/src/send_message.c
2123@@ -58,6 +58,7 @@
2124 #include "inc.h"
2125 #include "mainwindow.h"
2126 #include "summaryview.h"
2127+#include "masterpassword.h"
2128
2129 #define SMTP_PORT 25
2130 #if USE_SSL
2131@@ -648,13 +649,13 @@ static gint send_message_smtp(PrefsAccount *ac_prefs, GSList *to_list, FILE *fp)
2132
2133 if (ac_prefs->smtp_userid) {
2134 smtp_session->user = g_strdup(ac_prefs->smtp_userid);
2135- if (ac_prefs->smtp_passwd)
2136- smtp_session->pass =
2137- g_strdup(ac_prefs->smtp_passwd);
2138- else if (ac_prefs->tmp_smtp_pass)
2139+ if (ac_prefs->smtp_passwd) {
2140+ smtp_session->pass = decrypt_with_master_password(
2141+ ac_prefs->smtp_passwd);
2142+ } else if (ac_prefs->tmp_smtp_pass) {
2143 smtp_session->pass =
2144 g_strdup(ac_prefs->tmp_smtp_pass);
2145- else {
2146+ } else {
2147 smtp_session->pass =
2148 input_query_password
2149 (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 @@
1diff -rupN sylpheed-3.7.0/libsylph/utils.c sylpheed-3.7.0.new/libsylph/utils.c
2--- sylpheed-3.7.0/libsylph/utils.c 2017-11-30 03:34:14.000000000 +0100
3+++ sylpheed-3.7.0.new/libsylph/utils.c 2018-02-08 08:39:06.586381290 +0100
4@@ -3306,10 +3306,10 @@ gint canonicalize_file(const gchar *src,
5 }
6 }
7
8- if (last_linebreak == TRUE) {
9- if (fputs("\r\n", dest_fp) == EOF)
10- err = TRUE;
11- }
12+ /* if (last_linebreak == TRUE) { */
13+ /* if (fputs("\r\n", dest_fp) == EOF) */
14+ /* err = TRUE; */
15+ /* } */
16
17 if (ferror(src_fp)) {
18 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 @@
1diff --git a/README.md b/README.md
2new file mode 100644
3index 0000000..1cf6d21
4--- /dev/null
5+++ b/README.md
6@@ -0,0 +1,219 @@
7+## Implemented features and fixes not present in the official Sylpheed release
8+
9+- (fix)
10+ PGP signature not verified properly when the message has no newline
11+ at the end. https://sylpheed.sraoss.jp/redmine/issues/288
12+
13+- (feature)
14+ Make it possible to select "Show signature check result in a popup window"
15+ only for bad signatures.
16+
17+- (feature)
18+ Support for encrypting and storing encrypted passwords using a master password.
19+ Read bellow for more details!
20+
21+
22+## Master password
23+
24+The master password feature is developed by Simeon Simeonov (sgs)
25+and is currently in an experimental state.
26+
27+
28+### Motivation
29+
30+Currently Sylpheed is storing passwords in plain-text. One can always refrain
31+from storing passwords and let Sylpheed prompt for them, but the more accounts
32+one has, the more annoying this becomes.
33+
34+The goal is to have the passwords stored in a secure way and let Sylpheed only
35+prompt for the master password.
36+
37+
38+### Security goals
39+
40+- attacker (A) should not be able to derive the password from the digest.
41+
42+- A should not be able to derive the master password even if she has
43+ read and write access to the storage.
44+
45+- A should not be able to determine the length of the encrypted password
46+ even if she has read and write access to the storage.
47+
48+- A should not be able to craft an edited password without obtaining the
49+ master password.
50+
51+- A should not be able to compromise the master password or force Sylpheed
52+ to store decrypted passwords even if she has access to a running Sylpheed.
53+
54+- a warning / prompt should be given if a user accidentally types in a "wrong"
55+ master password before decryption is initiated.
56+
57+
58+### Usage in Sylpheed
59+
60+- backup your Sylpheed profile (often $HOME/.sylpheed-2.0)!
61+
62+- start Sylpheed and open "Configuration" -> "Common preferences..."!
63+
64+- select the "Master password" tab, enable "Use master password" and
65+ apply the changes!
66+
67+- restart Sylpheed (exit and then start Sylpheed again)!
68+
69+- you will be asked to type and verify a new master password.
70+
71+- set your new passwords from the "Configuration" -> "Edit accounts..."!
72+
73+- select "Automatically unload master password after session initialization"
74+ for increased security and decreased convenience.
75+
76+Note:
77+Sylpheed will automatically convert existing stored passwords, but it will not
78+touch your backups. You will have to remove all remnants of plain-text
79+passwords manually.
80+
81+
82+### Choice of cryptographic primitives
83+
84+The primary concern when selecting cryptographic primitives was portability.
85+The desire was to go for primitives that are both strong and available in all
86+supported production distributions of OpenSSL and LibreSSL.
87+
88+
89+#### Cipher
90+
91+When it comes to implementation, there are several advantages in using stream
92+cipher or a block cipher that behaves like a stream cipher when used in a
93+certain mode of operation. One is avoiding to deal with padding.
94+
95+AES-256 operating in CFB was selected for these reasons.
96+ChaCha20 should be considered as a replacement in the future.
97+
98+
99+#### Hash-function
100+
101+Hash-functions are used for:
102+- key derivation
103+- plain-text digest
104+
105+Those operations do not have to use the same hash-function.
106+(See the "Encryption & decryption scheme" section for more details!)
107+
108+Key-derivation:
109+Since AES-256 uses a 256 bits key, we need a hash-function with at least
110+the same digest size or bigger.
111+Since the digest (which is the key itself) is considered confidential and is
112+stored only in memory for only a limited amount of time, SHA-256 is considered
113+sufficiently strong for that purpose.
114+
115+Size + plain-text + padding digest:
116+Since the digest is created of both the plain-text and the plain-text size,
117+as well as being encrypted, SHA-256 is considered sufficiently strong.
118+SHA-512 may increase security at the price of adding additional 32 bytes
119+to the encrypted password digest.
120+
121+Stronger hash-functions like SHA-3 or BLAKE2b can be considered as a
122+replacement in the future.
123+
124+
125+#### Master password digest
126+
127+In order to be able to decide whether the user typed a "wrong" master password,
128+before attempting to decrypt, Sylpheed stores a digest of the master password
129+in 'master_password_hash' in sylpheedrc.
130+100000 iterations of PBKDF2_HMAC with SHA-512 and 16 bytes salt is used.
131+Note that this digest is useless as a key and even if a plain-text that
132+produces the same digest is found, it will most probably be useless as a
133+master-password.
134+
135+
136+### Encryption & decryption scheme
137+
138+
139+#### Encryption
140+
141+
142+Input:
143+
144+- plain-text password to be encrypted (P)
145+
146+- plain-text master-password used for key derivation (M)
147+
148+- integer minimum password length (0 < L < 100)
149+
150+
151+Output:
152+
153+- an encrypted password digest (base64) (B)
154+
155+
156+Operation:
157+
158+- generate 16 bytes of random data to be used as a salt (S)
159+
160+- derive the key (K): K = SHA_256(S + M)
161+
162+- produce a 2 byte string (N) indicating the length of P
163+
164+- generate random padding and set N:
165+ - if the length of P < L, produce L - P bytes of random data (R), N = "%02d"
166+ - if the length of P >= L, N = "-1"
167+
168+- produce a hash digest (H): H = SHA_256(N + P + R (if the length of P < L))
169+
170+- encrypt (E): E = AES_256_CFB_ENCRYPT(H + N + P + R (if the length of P < L), K)
171+
172+- B = mpes1:BASE64_ENCODE(S + E)
173+
174+example:
175+mpes1:vo7lsIpD7i6byBA6+vlUoF4OVDfEe+aYRRk4FRtfJ2gMY8M43Kj6WfdfgbViIOl83bI4XEc96okhPW5Mla813aAR1gbPjDg0xmCyIbWOiUv/dg==
176+
177+
178+#### Decryption
179+
180+
181+Input:
182+
183+- encrypted password digest (base64) (B)
184+
185+- plain-text master-password used for key derivation (M)
186+
187+
188+Output:
189+
190+- plain-text password (P)
191+
192+
193+Operation:
194+
195+- remove the prefix (mpes1:) and base64-decode the rest of the digest: B = BASE64_DECODE(B)
196+
197+- fetch the first 16 bytes for the salt: S = B[0 : 15]
198+
199+- derive the key (K): K = SHA_256(S + M)
200+
201+- decrypt the rest of B (D): D = AES_256_CFB_DECRYPT(B[16 :], K)
202+
203+- extract the first 16 bytes for the hash digest (H): H = D[0 : 15]
204+
205+- in order to detect data-inconsistency, assert H == SHA_256(D[16 :])
206+
207+- extract the next 2 bytes for the length of P (N): N = D[16 : 17]
208+
209+- fetch the plain-text:
210+ - if N == "-1" the password is the remaining bytes of D: P = D[18 :]
211+ - if N != "-1", extract the next N-bytes from D: P = D[18 : (18 + N)]
212+
213+
214+### Limitations
215+
216+- currently only the 'password' and 'smtp_password' keys in accountrc
217+ are encrypted.
218+ A mechanism that allows for any key and even folders to be encrypted
219+ should be considered in the future.
220+
221+- currently it is not possible to select alternative ciphers, hash-functions
222+ and modes of operation (without editing the source code).
223+
224+- currently it is not possible to change your master password without having to
225+ (re)set your passwords manually.
226diff --git a/libsylph/Makefile.am b/libsylph/Makefile.am
227index 8f2b76b..40d26a9 100644
228--- a/libsylph/Makefile.am
229+++ b/libsylph/Makefile.am
230@@ -19,6 +19,7 @@ libsylph_0_la_SOURCES = \
231 folder.c \
232 html.c \
233 imap.c \
234+ masterpassword.c \
235 mbox.c \
236 md5.c \
237 md5_hmac.c \
238@@ -62,6 +63,7 @@ libsylph_0include_HEADERS = \
239 folder.h \
240 html.h \
241 imap.h \
242+ masterpassword.h \
243 mbox.h \
244 md5.h \
245 md5_hmac.h \
246diff --git a/libsylph/Makefile.in b/libsylph/Makefile.in
247index 6e3c999..ac8fefd 100644
248--- a/libsylph/Makefile.in
249+++ b/libsylph/Makefile.in
250@@ -129,8 +129,8 @@ libsylph_0_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \
251 $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
252 am_libsylph_0_la_OBJECTS = account.lo base64.lo codeconv.lo \
253 customheader.lo displayheader.lo filter.lo folder.lo html.lo \
254- imap.lo mbox.lo md5.lo md5_hmac.lo mh.lo news.lo nntp.lo \
255- pop.lo prefs.lo prefs_account.lo prefs_common.lo procheader.lo \
256+ imap.lo masterpassword.lo mbox.lo md5.lo md5_hmac.lo mh.lo news.lo \
257+ nntp.lo pop.lo prefs.lo prefs_account.lo prefs_common.lo procheader.lo \
258 procmime.lo procmsg.lo quoted-printable.lo recv.lo session.lo \
259 smtp.lo socket.lo socks.lo ssl.lo ssl_hostname_validation.lo \
260 stringtable.lo sylmain.lo unmime.lo utils.lo uuencode.lo \
261@@ -402,6 +402,7 @@ libsylph_0_la_SOURCES = \
262 folder.c \
263 html.c \
264 imap.c \
265+ masterpassword.c \
266 mbox.c \
267 md5.c \
268 md5_hmac.c \
269@@ -445,6 +446,7 @@ libsylph_0include_HEADERS = \
270 folder.h \
271 html.h \
272 imap.h \
273+ masterpassword.h \
274 mbox.h \
275 md5.h \
276 md5_hmac.h \
277@@ -579,6 +581,7 @@ distclean-compile:
278 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/folder.Plo@am__quote@
279 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/html.Plo@am__quote@
280 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap.Plo@am__quote@
281+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/masterpassword.Plo@am__quote@
282 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mbox.Plo@am__quote@
283 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/md5.Plo@am__quote@
284 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/md5_hmac.Plo@am__quote@
285diff --git a/libsylph/defs.h b/libsylph/defs.h
286index 9e3f82b..67325bf 100644
287--- a/libsylph/defs.h
288+++ b/libsylph/defs.h
289@@ -58,6 +58,9 @@
290 #define DISPLAY_HEADER_RC "dispheaderrc"
291 #define MENU_RC "menurc"
292 #define ACTIONS_RC "actionsrc"
293+#ifdef USE_SSL
294+#define SECURE_RC "securerc"
295+#endif
296 #define COMMAND_HISTORY "command_history"
297 #define TEMPLATE_DIR "templates"
298 #define TMP_DIR "tmp"
299diff --git a/libsylph/imap.c b/libsylph/imap.c
300index aa0d737..e1c7cfd 100644
301--- a/libsylph/imap.c
302+++ b/libsylph/imap.c
303@@ -52,6 +52,7 @@
304 #include "utils.h"
305 #include "prefs_common.h"
306 #include "virtual.h"
307+#include "masterpassword.h"
308
309 #define IMAP4_PORT 143
310 #if USE_SSL
311@@ -705,9 +706,13 @@ static gint imap_session_connect(IMAPSession *session)
312 account = (PrefsAccount *)(SESSION(session)->data);
313
314 log_message(_("creating IMAP4 connection to %s:%d ...\n"),
315- SESSION(session)->server, SESSION(session)->port);
316-
317- pass = account->passwd;
318+ SESSION(session)->server, SESSION(session)->port);
319+ if (master_password_active()) {
320+ pass = decrypt_with_master_password(account->passwd);
321+ /* a new string is allocated. To be removed ... */
322+ } else {
323+ pass = account->passwd;
324+ }
325 if (!pass)
326 pass = account->tmp_pass;
327 if (!pass) {
328@@ -770,8 +775,11 @@ static gint imap_session_connect(IMAPSession *session)
329 #endif
330
331 if (!session->authenticated &&
332- imap_auth(session, account->userid, pass, account->imap_auth_type)
333- != IMAP_SUCCESS) {
334+ imap_auth(session, account->userid, pass, account->imap_auth_type)
335+ != IMAP_SUCCESS) {
336+ if (master_password_active()) {
337+ g_free(pass); /* remove the decrypted password */
338+ }
339 if (account->tmp_pass) {
340 g_free(account->tmp_pass);
341 account->tmp_pass = NULL;
342@@ -780,6 +788,10 @@ static gint imap_session_connect(IMAPSession *session)
343 return IMAP_AUTHFAIL;
344 }
345
346+ if (master_password_active()) {
347+ g_free(pass); /* remove the decrypted password */
348+ }
349+
350 return IMAP_SUCCESS;
351 }
352
353diff --git a/libsylph/masterpassword.c b/libsylph/masterpassword.c
354new file mode 100644
355index 0000000..3d7af94
356--- /dev/null
357+++ b/libsylph/masterpassword.c
358@@ -0,0 +1,216 @@
359+/*
360+ * LibSylph -- E-Mail client library
361+ * Copyright (C) 1999-2018 Hiroyuki Yamamoto
362+ *
363+ * This library is free software; you can redistribute it and/or
364+ * modify it under the terms of the GNU Lesser General Public
365+ * License as published by the Free Software Foundation; either
366+ * version 2.1 of the License, or (at your option) any later version.
367+ *
368+ * This library is distributed in the hope that it will be useful,
369+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
370+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
371+ * Lesser General Public License for more details.
372+ *
373+ * You should have received a copy of the GNU Lesser General Public
374+ * License along with this library; if not, write to the Free Software
375+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
376+ */
377+
378+#ifdef HAVE_CONFIG_H
379+#include "config.h"
380+#endif
381+
382+#include <glib/gi18n.h>
383+
384+#include "prefs_common.h"
385+#include "ssl.h"
386+#include "utils.h"
387+#include "masterpassword.h"
388+
389+
390+gchar *master_password;
391+gboolean master_password_enabled_on_init;
392+
393+void set_master_password(const char *password) {
394+ master_password = password;
395+}
396+
397+gchar *get_master_password(void) {
398+ return master_password;
399+}
400+
401+void cleanse_buffer(void *buf, size_t len) {
402+#if USE_SSL
403+ OPENSSL_cleanse(buf, len);
404+#else
405+ memset(buf, 0, len); /* better than nothing */
406+#endif
407+}
408+
409+void unload_master_password(void) {
410+
411+ debug_print("Unloading master password\n");
412+ if (master_password == NULL) {
413+ /* not loaded / already unloaded */
414+ return;
415+ }
416+ cleanse_buffer(master_password, strlen(master_password));
417+ g_free(master_password);
418+ master_password = NULL;
419+ debug_print("Master password unloaded\n");
420+
421+}
422+
423+gint mpes_string_prefix(const gchar *str) {
424+
425+ /* this function will be expanded in time as the format changes */
426+ if (g_str_has_prefix(str, "mpes1:"))
427+ return 6;
428+ return 0;
429+
430+}
431+
432+gboolean master_password_active(void) {
433+
434+#if USE_SSL
435+ return ((master_password != NULL) &&
436+ prefs_common.use_master_password);
437+#else
438+ return FALSE;
439+#endif
440+
441+}
442+
443+gchar *decrypt_with_master_password(const gchar *str) {
444+
445+#if USE_SSL
446+ gchar *new_str;
447+ gint str_prefix;
448+
449+ if ((!str) || (!prefs_common.use_master_password))
450+ return g_strdup(str);
451+
452+ if (master_password == NULL) {
453+ /* we have empty or auto unloaded master password */
454+ if ((!prefs_common.auto_unload_master_password) ||
455+ (check_master_password_interactively(3) != MP_RC_OK)) {
456+ return g_strdup(str);
457+ }
458+ debug_print("Reloaded master password\n");
459+ }
460+
461+ str_prefix = mpes_string_prefix(str);
462+ if (!str_prefix)
463+ return g_strdup(str);
464+
465+ if (decrypt_data(&new_str,
466+ str + str_prefix,
467+ master_password,
468+ strlen(str) + 1 - str_prefix) != MP_RC_OK) {
469+ OPENSSL_cleanse(new_str, strlen(new_str));
470+ g_free(new_str);
471+ return g_strdup(str);
472+ }
473+
474+ return new_str;
475+#else
476+ return g_strdup(str);
477+#endif
478+
479+}
480+
481+gchar *encrypt_with_master_password(const gchar *str) {
482+
483+#if USE_SSL
484+ gchar *new_str, *mpes1_str;
485+ gint length_encrypted;
486+
487+ if ((!str) || (!master_password_active()))
488+ return NULL;
489+
490+ /*
491+ * unlike the decrypt function, here it is up to the caller
492+ * to make sure that auto unloaded master password is handled properly
493+ */
494+
495+ if (encrypt_data(&new_str,
496+ &length_encrypted,
497+ str,
498+ master_password,
499+ strlen(str) + 1,
500+ prefs_common.encrypted_password_min_length,
501+ TRUE) != MP_RC_OK) {
502+ g_free(new_str);
503+ return NULL;
504+ }
505+
506+ mpes1_str = g_strdup_printf("mpes1:%s", new_str);
507+ g_free(new_str);
508+
509+ return mpes1_str;
510+#else
511+ return NULL;
512+#endif
513+
514+}
515+
516+#if USE_SSL
517+gint set_master_password_interactively(guint max_attempts) {
518+
519+ if (master_password == NULL)
520+ master_password = input_set_new_password(max_attempts);
521+
522+ if (master_password == NULL)
523+ return 1;
524+
525+ if (generate_password_hash(
526+ &prefs_common.master_password_hash,
527+ master_password,
528+ NULL) != MP_RC_OK) {
529+ /* should not really happen unless buggy code / library */
530+ g_free(prefs_common.master_password_hash);
531+ prefs_common.master_password_hash = NULL;
532+ debug_print(_("Could not generate master password hash"));
533+ return 1;
534+ }
535+
536+ prefs_common_write_config();
537+ return 0;
538+
539+}
540+
541+gint check_master_password_interactively(guint max_attempts) {
542+
543+ guint cnt;
544+
545+ g_return_val_if_fail(max_attempts > 0, 1);
546+ g_return_val_if_fail(prefs_common.master_password_hash != NULL, 1);
547+
548+ if (master_password != NULL) {
549+ /* password already cached */
550+ debug_print("Master password already cached\n");
551+ return check_password(master_password,
552+ prefs_common.master_password_hash);
553+ }
554+
555+ for (cnt = 0; cnt < max_attempts; ++cnt) {
556+ master_password = input_query_master_password();
557+ if (master_password == NULL) {
558+ /* input canceled or query_master_password_func not set */
559+ continue;
560+ }
561+ if (check_password(master_password,
562+ prefs_common.master_password_hash) == MP_RC_OK) {
563+ return MP_RC_OK; /* match */
564+ }
565+ debug_print(_("Wrong master password entered (%d)\n"), cnt);
566+ OPENSSL_cleanse(master_password, strlen(master_password));
567+ g_free(master_password);
568+ master_password = NULL;
569+ }
570+
571+ return 1; /* no match */
572+
573+}
574+#endif /* USE_SSL */
575diff --git a/libsylph/masterpassword.h b/libsylph/masterpassword.h
576new file mode 100644
577index 0000000..7254643
578--- /dev/null
579+++ b/libsylph/masterpassword.h
580@@ -0,0 +1,47 @@
581+/*
582+ * LibSylph -- E-Mail client library
583+ * Copyright (C) 1999-2018 Hiroyuki Yamamoto
584+ *
585+ * This library is free software; you can redistribute it and/or
586+ * modify it under the terms of the GNU Lesser General Public
587+ * License as published by the Free Software Foundation; either
588+ * version 2.1 of the License, or (at your option) any later version.
589+ *
590+ * This library is distributed in the hope that it will be useful,
591+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
592+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
593+ * Lesser General Public License for more details.
594+ *
595+ * You should have received a copy of the GNU Lesser General Public
596+ * License along with this library; if not, write to the Free Software
597+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
598+ */
599+
600+#ifndef __MASTERPASSWORD_H__
601+#define __MASTERPASSWORD_H__
602+
603+#ifdef HAVE_CONFIG_H
604+#include "config.h"
605+#endif
606+
607+#define MP_RC_OK 0
608+#define MP_RC_WRONG_HASH_OR_KEY 1
609+#define MP_RC_INVALID_FORMAT 2 /* invalid digest format */
610+
611+extern gchar *master_password;
612+extern gboolean master_password_enabled_on_init; /* m.p. enabled on init? */
613+void set_master_password(const char *password);
614+gchar *get_master_password(void);
615+void cleanse_buffer(void *buf, size_t len);
616+void unload_master_password(void);
617+gint mpes_string_prefix(const gchar *str);
618+gboolean master_password_active(void);
619+gchar *decrypt_with_master_password(const gchar *str);
620+gchar *encrypt_with_master_password(const gchar *str);
621+
622+#if USE_SSL
623+gint set_master_password_interactively(guint max_attempts);
624+gint check_master_password_interactively(guint max_attempts);
625+#endif /* USE_SSL */
626+
627+#endif /* __MASTERPASSWORD_H__ */
628diff --git a/libsylph/news.c b/libsylph/news.c
629index ffff9f9..36d3b10 100644
630--- a/libsylph/news.c
631+++ b/libsylph/news.c
632@@ -44,6 +44,7 @@
633 #include "utils.h"
634 #include "prefs_common.h"
635 #include "prefs_account.h"
636+#include "masterpassword.h"
637 #if USE_SSL
638 # include "ssl.h"
639 #endif
640@@ -249,10 +250,11 @@ static Session *news_session_new_for_folder(Folder *folder)
641 ac = folder->account;
642 if (ac->use_nntp_auth && ac->userid && ac->userid[0]) {
643 userid = ac->userid;
644- if (ac->passwd && ac->passwd[0])
645- passwd = g_strdup(ac->passwd);
646- else
647+ if (ac->passwd && ac->passwd[0]) {
648+ passwd = decrypt_with_master_password(ac->passwd);
649+ } else {
650 passwd = input_query_password(ac->nntp_server, userid);
651+ }
652 }
653
654 if (ac->use_socks && ac->use_socks_for_recv && ac->proxy_host) {
655diff --git a/libsylph/pop.c b/libsylph/pop.c
656index 8cb7f5c..85f3ed9 100644
657--- a/libsylph/pop.c
658+++ b/libsylph/pop.c
659@@ -39,6 +39,7 @@
660 #include "prefs_account.h"
661 #include "utils.h"
662 #include "recv.h"
663+#include "masterpassword.h"
664
665 gint pop3_greeting_recv (Pop3Session *session,
666 const gchar *msg);
667@@ -437,8 +438,9 @@ Session *pop3_session_new(PrefsAccount *account)
668 session->error_msg = NULL;
669
670 session->user = g_strdup(account->userid);
671- session->pass = account->passwd ? g_strdup(account->passwd) :
672- account->tmp_pass ? g_strdup(account->tmp_pass) : NULL;
673+ session->pass = account->passwd ? decrypt_with_master_password(
674+ account->passwd) : account->tmp_pass ? g_strdup(
675+ account->tmp_pass) : NULL;
676
677 SESSION(session)->server = g_strdup(account->recv_server);
678
679diff --git a/libsylph/prefs_account.c b/libsylph/prefs_account.c
680index 1aecba9..54cbc89 100644
681--- a/libsylph/prefs_account.c
682+++ b/libsylph/prefs_account.c
683@@ -34,6 +34,7 @@
684 #include "customheader.h"
685 #include "account.h"
686 #include "utils.h"
687+#include "masterpassword.h"
688
689 static PrefsAccount tmp_ac_prefs;
690
691@@ -205,7 +206,7 @@ PrefsAccount *prefs_account_new(void)
692 void prefs_account_read_config(PrefsAccount *ac_prefs, const gchar *label)
693 {
694 const gchar *p = label;
695- gchar *rcpath;
696+ gchar *rcpath, *tmp_str;
697 gint id;
698
699 g_return_if_fail(ac_prefs != NULL);
700@@ -229,6 +230,34 @@ void prefs_account_read_config(PrefsAccount *ac_prefs, const gchar *label)
701 ac_prefs->use_apop_auth = TRUE;
702 }
703
704+ if (master_password_active()) {
705+ if ((ac_prefs->passwd != NULL) &&
706+ !mpes_string_prefix(ac_prefs->passwd)) {
707+ /* TODO: Perhaps some prompt? */
708+ debug_print(
709+ "%s -> converting passwd cleartext to encrypted\n",
710+ label);
711+ tmp_str = ac_prefs->passwd;
712+ ac_prefs->passwd = encrypt_with_master_password(ac_prefs->passwd);
713+ cleanse_buffer(tmp_str, strlen(tmp_str));
714+ g_free(tmp_str);
715+ }
716+
717+ if ((ac_prefs->smtp_passwd != NULL) &&
718+ !mpes_string_prefix(ac_prefs->smtp_passwd)) {
719+ /* TODO: Perhaps some prompt? */
720+ debug_print(
721+ "%s -> converting smtp_passwd from cleartext to encrypted\n",
722+ label);
723+ tmp_str = ac_prefs->smtp_passwd;
724+ ac_prefs->smtp_passwd = encrypt_with_master_password(
725+ ac_prefs->smtp_passwd);
726+ cleanse_buffer(tmp_str, strlen(tmp_str));
727+ g_free(tmp_str);
728+ }
729+
730+ }
731+
732 custom_header_read_config(ac_prefs);
733 }
734
735diff --git a/libsylph/prefs_common.c b/libsylph/prefs_common.c
736index 91814fd..192964e 100644
737--- a/libsylph/prefs_common.c
738+++ b/libsylph/prefs_common.c
739@@ -418,6 +418,17 @@ static PrefParam param[] = {
740 {"show_gpg_warning", "TRUE", &prefs_common.gpg_warning, P_BOOL},
741 #endif
742
743+ /* Master password */
744+ {"use_master_password", "FALSE", &prefs_common.use_master_password,
745+ P_BOOL},
746+ {"master_password_hash", NULL, &prefs_common.master_password_hash,
747+ P_STRING},
748+ {"encrypted_password_min_length", "32",
749+ &prefs_common.encrypted_password_min_length, P_INT},
750+ {"auto_unload_master_password", "FALSE",
751+ &prefs_common.auto_unload_master_password,
752+ P_BOOL},
753+
754 /* Interface */
755 {"separate_folder", "FALSE", &prefs_common.sep_folder, P_BOOL},
756 {"separate_message", "FALSE", &prefs_common.sep_msg, P_BOOL},
757diff --git a/libsylph/prefs_common.h b/libsylph/prefs_common.h
758index 910f370..6f9c364 100644
759--- a/libsylph/prefs_common.h
760+++ b/libsylph/prefs_common.h
761@@ -249,6 +249,12 @@ struct _PrefsCommon
762 gboolean passphrase_grab;
763 gboolean gpg_warning;
764
765+ /* Master password */
766+ gboolean use_master_password;
767+ gchar *master_password_hash;
768+ guint encrypted_password_min_length;
769+ gboolean auto_unload_master_password;
770+
771 /* Interface */
772 gboolean sep_folder;
773 gboolean sep_msg;
774diff --git a/libsylph/ssl.c b/libsylph/ssl.c
775index 8413925..e782b0e 100644
776--- a/libsylph/ssl.c
777+++ b/libsylph/ssl.c
778@@ -32,6 +32,13 @@
779 #include "ssl.h"
780 #include "ssl_hostname_validation.h"
781
782+#define SALT_SIZE 16
783+#define CIPHER EVP_aes_256_cfb()
784+#define KEY_HASH EVP_sha256()
785+#define DIGEST_HASH EVP_sha256()
786+#define PBKDF2_DIGEST_SIZE 64
787+#define PBKDF2_ITERATIONS 100000
788+
789 static SSL_CTX *ssl_ctx_SSLv23 = NULL;
790 static SSL_CTX *ssl_ctx_TLSv1 = NULL;
791
792@@ -402,4 +409,468 @@ void ssl_set_verify_func(SSLVerifyFunc func)
793 verify_ui_func = func;
794 }
795
796+/* master password related functions */
797+static gint secure_derive_key(guchar *key,
798+ gint length_key,
799+ const gchar *passphrase,
800+ const guchar *salt) {
801+
802+ guint length_buffer, length_hash;
803+ guchar *buffer, *ptr_hash;
804+
805+ EVP_MD_CTX *mdctx;
806+
807+ OPENSSL_cleanse(key, length_key);
808+
809+ length_buffer = SALT_SIZE + strlen(passphrase);
810+ buffer = OPENSSL_malloc(length_buffer);
811+ OPENSSL_cleanse(buffer, length_buffer);
812+
813+ memcpy(buffer, salt, SALT_SIZE);
814+ memcpy(buffer + SALT_SIZE, passphrase, strlen(passphrase));
815+
816+ mdctx = EVP_MD_CTX_create();
817+ EVP_DigestInit_ex(mdctx, KEY_HASH, NULL);
818+ EVP_DigestUpdate(mdctx, buffer, length_buffer);
819+ OPENSSL_cleanse(buffer, length_buffer);
820+
821+ ptr_hash = OPENSSL_malloc(EVP_MD_size(KEY_HASH));
822+ EVP_DigestFinal_ex(mdctx, ptr_hash, &length_hash);
823+
824+ memcpy(key,
825+ ptr_hash,
826+ (length_hash > length_key) ? length_key : length_hash);
827+
828+ OPENSSL_cleanse(ptr_hash, length_hash);
829+ OPENSSL_free(ptr_hash);
830+ OPENSSL_free(buffer);
831+ EVP_MD_CTX_destroy(mdctx);
832+
833+ return SSL_RC_OK;
834+
835+}
836+
837+gint encrypt_data(gchar **encrypted,
838+ gint *length_encrypted,
839+ const gchar *data,
840+ const gchar *passphrase,
841+ gint length_data,
842+ guint min_data_length,
843+ gboolean rnd_salt) {
844+
845+ gint crypt_buffer_cnt, rc;
846+ guint key_size, length_hash;
847+ guint length_cleartext, length_ciphertext, length_total;
848+ guchar salt[SALT_SIZE];
849+ guchar *ciphertext_buffer, *total_buffer;
850+ /* sensitive buffers and counters */
851+ guint length_data_payload, length_padding;
852+ gchar str_data_size[3];
853+ guchar *data_payload_buffer, *hash_buffer, *padding_buffer, *key;
854+ guchar *cleartext_buffer;
855+
856+ EVP_CIPHER_CTX *ctx;
857+ EVP_MD_CTX *mdctx;
858+
859+ rc = SSL_RC_ERROR;
860+
861+ if (length_data < 1) {
862+ return -1;
863+ }
864+ if (rnd_salt) {
865+ if (RAND_bytes(salt, SALT_SIZE) != 1) {
866+ debug_print("Random problems...\n");
867+ goto cleanup;
868+ }
869+ } else {
870+ strncpy((gchar *)salt, "FOR TESTING ONLY", SALT_SIZE);
871+ }
872+
873+ key_size = EVP_CIPHER_key_length(CIPHER);
874+ key = OPENSSL_malloc(key_size);
875+ OPENSSL_cleanse(key, key_size);
876+ if (secure_derive_key(key,
877+ key_size,
878+ passphrase,
879+ salt) != SSL_RC_OK) {
880+ OPENSSL_cleanse(key, key_size);
881+ debug_print("Could not generate secure key\n");
882+ goto cleanup;
883+ }
884+
885+ /* prepare the data-buffer */
886+ length_padding = 0;
887+ if (length_data < min_data_length) {
888+ g_snprintf(str_data_size, 3, "%02d", length_data);
889+ length_padding = min_data_length - length_data;
890+ padding_buffer = OPENSSL_malloc(length_padding);
891+ OPENSSL_cleanse(padding_buffer, length_padding);
892+ if (RAND_bytes(padding_buffer, length_padding) != 1) {
893+ debug_print("Random problems...\n");
894+ goto cleanup;
895+ }
896+ } else {
897+ g_snprintf(str_data_size, 3, "-1");
898+ }
899+
900+ length_data_payload = 2 + length_data + length_padding;
901+ data_payload_buffer = OPENSSL_malloc(length_data_payload);
902+ OPENSSL_cleanse(data_payload_buffer, length_data_payload);
903+
904+ memcpy(data_payload_buffer, str_data_size, 2);
905+ memcpy(data_payload_buffer + 2, data, length_data);
906+ if (length_padding > 0) {
907+ memcpy(data_payload_buffer + (2 + length_data),
908+ padding_buffer,
909+ length_padding);
910+ }
911+
912+ mdctx = EVP_MD_CTX_create();
913+ EVP_DigestInit_ex(mdctx, DIGEST_HASH, NULL);
914+ EVP_DigestUpdate(mdctx, data_payload_buffer, length_data_payload);
915+
916+ length_hash = EVP_MD_size(DIGEST_HASH);
917+ hash_buffer = OPENSSL_malloc(length_hash);
918+ OPENSSL_cleanse(hash_buffer, length_hash);
919+ EVP_DigestFinal_ex(mdctx, hash_buffer, NULL);
920+
921+ length_cleartext = length_hash + length_data_payload;
922+
923+ cleartext_buffer = OPENSSL_malloc(length_cleartext);
924+ OPENSSL_cleanse(cleartext_buffer, length_cleartext);
925+
926+ /* assemble cleartext-buffer */
927+ memcpy(cleartext_buffer, hash_buffer, length_hash);
928+ memcpy(cleartext_buffer + length_hash,
929+ data_payload_buffer,
930+ length_data_payload);
931+ OPENSSL_cleanse(data_payload_buffer, length_data_payload); /* sensitive */
932+
933+ /* encryption */
934+ if (!(ctx = EVP_CIPHER_CTX_new())) {
935+ debug_print("New ctx failed\n");
936+ goto cleanup;
937+ }
938+
939+ length_ciphertext = 0;
940+ if (EVP_EncryptInit_ex(ctx, CIPHER, NULL, key, salt) != 1) {
941+ debug_print("EVP_EncryptInit_ex failed\n");
942+ goto cleanup;
943+ }
944+
945+ ciphertext_buffer = OPENSSL_malloc(length_cleartext);
946+
947+ crypt_buffer_cnt = 0;
948+ while(1) {
949+ if (EVP_EncryptUpdate(ctx,
950+ ciphertext_buffer + length_ciphertext,
951+ &crypt_buffer_cnt,
952+ cleartext_buffer + length_ciphertext,
953+ 1) != 1) { /* one byte at a time */
954+ debug_print("EVP_EncryptUpdate failed\n");
955+ goto cleanup;
956+ }
957+
958+ if (crypt_buffer_cnt != 1) { /* paranoia */
959+ debug_print("The sizes of enc and dec text do not correspond\n");
960+ goto cleanup;
961+ }
962+
963+ ++length_ciphertext;
964+
965+ if (length_ciphertext >= length_cleartext) {
966+ break;
967+ }
968+ }
969+ /* No padding required for the CFB mode */
970+
971+ OPENSSL_cleanse(cleartext_buffer, length_cleartext); /* sensitive */
972+ length_total = SALT_SIZE + length_ciphertext;
973+ total_buffer = OPENSSL_malloc(length_total);
974+
975+ memcpy(total_buffer, salt, SALT_SIZE);
976+ memcpy(total_buffer + SALT_SIZE, ciphertext_buffer, length_ciphertext);
977+
978+ *encrypted = g_base64_encode(total_buffer, length_total);
979+ *length_encrypted = strlen(*encrypted);
980+
981+ rc = SSL_RC_OK;
982+
983+cleanup:
984+ /* key */
985+ OPENSSL_cleanse(key, key_size);
986+ OPENSSL_free(key);
987+ /* cleartext buffer */
988+ OPENSSL_cleanse(cleartext_buffer, length_cleartext);
989+ OPENSSL_free(cleartext_buffer);
990+ /* payload buffer */
991+ OPENSSL_cleanse(data_payload_buffer, length_data_payload);
992+ OPENSSL_free(data_payload_buffer);
993+ /* hash */
994+ OPENSSL_cleanse(hash_buffer, length_hash);
995+ OPENSSL_free(hash_buffer);
996+ /* padding */
997+ if (length_padding > 0) {
998+ OPENSSL_cleanse(padding_buffer, length_padding);
999+ OPENSSL_free(padding_buffer);
1000+ }
1001+
1002+ OPENSSL_cleanse(str_data_size, 3); /* paranoia */
1003+
1004+ /* ciphertext buffer */
1005+ OPENSSL_free(ciphertext_buffer);
1006+
1007+ /* total buffer */
1008+ OPENSSL_free(total_buffer);
1009+
1010+ length_data_payload = 0;
1011+ length_padding = 0;
1012+
1013+ EVP_CIPHER_CTX_free(ctx);
1014+ EVP_MD_CTX_destroy(mdctx);
1015+
1016+ return rc;
1017+
1018+}
1019+
1020+gint decrypt_data(gchar **decrypted,
1021+ const gchar *data,
1022+ const gchar *passphrase,
1023+ gint length_data) {
1024+
1025+ gint rc; /* return code */
1026+ gint decrypt_buffer_cnt, length_ciphertext, length_decrypted;
1027+ guint key_size, length_hash, length_cleartext;
1028+ gsize length_total;
1029+ guchar salt[SALT_SIZE];
1030+ guchar *ciphertext_buffer, *total_buffer;
1031+ /* sensitive buffers and counters */
1032+ gint data_size;
1033+ guint length_data_payload;
1034+ gchar str_data_size[3];
1035+ guchar *data_payload_buffer, *hash_buffer, *key;
1036+ guchar *cleartext_buffer;
1037+
1038+ EVP_CIPHER_CTX *ctx;
1039+ EVP_MD_CTX *mdctx;
1040+
1041+ rc = -1;
1042+
1043+ if (length_data < 1) {
1044+ return -1;
1045+ }
1046+
1047+ total_buffer = g_base64_decode(data, &length_total);
1048+ length_ciphertext = length_total - SALT_SIZE;
1049+ ciphertext_buffer = OPENSSL_malloc(length_ciphertext);
1050+ OPENSSL_cleanse(ciphertext_buffer, length_ciphertext);
1051+
1052+ memcpy(salt, total_buffer, SALT_SIZE);
1053+ memcpy(ciphertext_buffer, total_buffer + SALT_SIZE, length_ciphertext);
1054+
1055+ key_size = EVP_CIPHER_key_length(CIPHER);
1056+
1057+ /* decryption */
1058+ if(!(ctx = EVP_CIPHER_CTX_new())) {
1059+ debug_print("New ctx failed\n");
1060+ goto cleanup;
1061+ }
1062+
1063+ key = OPENSSL_malloc(key_size);
1064+ OPENSSL_cleanse(key, key_size);
1065+ if (secure_derive_key(key,
1066+ key_size,
1067+ passphrase,
1068+ salt) != 0) {
1069+ OPENSSL_cleanse(key, key_size);
1070+ debug_print("Could not generate secure key\n");
1071+ goto cleanup;
1072+ }
1073+
1074+ if (EVP_DecryptInit_ex(ctx, CIPHER, NULL, key, salt) != 1) {
1075+ debug_print("EVP_DecryptInit_ex failed\n");
1076+ goto cleanup;
1077+ }
1078+ OPENSSL_cleanse(key, key_size); /* highly sensitive */
1079+ cleartext_buffer = OPENSSL_malloc(length_ciphertext);
1080+ OPENSSL_cleanse(cleartext_buffer, length_ciphertext);
1081+
1082+ length_cleartext = 0;
1083+ decrypt_buffer_cnt = 0;
1084+
1085+ while(1) {
1086+ if (EVP_DecryptUpdate(ctx,
1087+ cleartext_buffer + length_cleartext,
1088+ &decrypt_buffer_cnt,
1089+ ciphertext_buffer + length_cleartext,
1090+ 1) != 1) { /* one byte at a time */
1091+ debug_print("EVP_EncryptUpdate failed\n");
1092+ goto cleanup;
1093+ }
1094+
1095+ if (decrypt_buffer_cnt != 1) { /* paranoia */
1096+ debug_print("The sizes of enc and dec text do not correspond");
1097+ goto cleanup;
1098+ }
1099+
1100+ ++length_cleartext;
1101+
1102+ if (length_cleartext >= length_ciphertext) {
1103+ break;
1104+ }
1105+ }
1106+
1107+
1108+ length_hash = EVP_MD_size(DIGEST_HASH);
1109+ hash_buffer = OPENSSL_malloc(length_hash);
1110+ length_data_payload = length_cleartext - length_hash;
1111+ data_payload_buffer = OPENSSL_malloc(length_data_payload);
1112+ OPENSSL_cleanse(data_payload_buffer, length_data_payload);
1113+
1114+ memcpy(data_payload_buffer,
1115+ cleartext_buffer + length_hash,
1116+ length_data_payload);
1117+
1118+ mdctx = EVP_MD_CTX_create();
1119+ EVP_DigestInit_ex(mdctx, DIGEST_HASH, NULL);
1120+ EVP_DigestUpdate(mdctx, data_payload_buffer, length_data_payload);
1121+ EVP_DigestFinal_ex(mdctx, hash_buffer, &length_hash);
1122+
1123+ if (strncmp((const gchar*) hash_buffer,
1124+ (const gchar*) cleartext_buffer,
1125+ length_hash) != 0) {
1126+ debug_print("Invalid hash\n");
1127+ rc = SSL_RC_WRONG_HASH_OR_KEY;
1128+ goto cleanup;
1129+ }
1130+
1131+ memcpy(str_data_size, cleartext_buffer + length_hash, 2);
1132+ data_size = atoi(str_data_size);
1133+
1134+ if (data_size < 0) {
1135+ length_decrypted = length_data_payload - 2;
1136+ } else {
1137+ length_decrypted = data_size;
1138+ }
1139+ *decrypted = OPENSSL_malloc(length_decrypted);
1140+ memcpy(*decrypted, data_payload_buffer + 2, length_decrypted);
1141+
1142+ rc = SSL_RC_OK;
1143+
1144+cleanup:
1145+
1146+ /* key */
1147+ OPENSSL_cleanse(key, key_size);
1148+ OPENSSL_free(key);
1149+ /* payload buffer */
1150+ OPENSSL_cleanse(data_payload_buffer, length_data_payload);
1151+ OPENSSL_free(data_payload_buffer);
1152+ /* hash */
1153+ OPENSSL_cleanse(hash_buffer, length_hash);
1154+ OPENSSL_free(hash_buffer);
1155+ /* ciphertext */
1156+ OPENSSL_free(ciphertext_buffer);
1157+ OPENSSL_free(total_buffer);
1158+
1159+ EVP_CIPHER_CTX_free(ctx);
1160+ EVP_MD_CTX_destroy(mdctx);
1161+
1162+ return rc;
1163+
1164+}
1165+
1166+gint generate_password_hash(gchar **password_hash,
1167+ const gchar *password,
1168+ const guchar *salt) {
1169+ /*
1170+ * Hashes 'password' using PKCS5_PBKDF2_HMAC with SHA512 and 'salt',
1171+ * and assigns a string to 'password_hash' with the following format:
1172+ * pbkdf2_sha512$iterations$base64(salt)$base64(password_hash)
1173+ */
1174+ guchar lsalt[SALT_SIZE];
1175+ gchar *PBKDF2_digest, *salt_b64, *digest_b64;
1176+
1177+ if (salt == NULL) {
1178+ if (RAND_bytes(lsalt, SALT_SIZE) != 1) {
1179+ debug_print("Random problems...\n");
1180+ return SSL_RC_ERROR;
1181+ }
1182+ } else {
1183+ memcpy(lsalt, salt, SALT_SIZE);
1184+ }
1185+
1186+ PBKDF2_digest = OPENSSL_malloc(PBKDF2_DIGEST_SIZE);
1187+ OPENSSL_cleanse(PBKDF2_digest, PBKDF2_DIGEST_SIZE);
1188+
1189+ PKCS5_PBKDF2_HMAC(password,
1190+ strlen(password),
1191+ lsalt,
1192+ SALT_SIZE,
1193+ PBKDF2_ITERATIONS,
1194+ EVP_sha512(),
1195+ PBKDF2_DIGEST_SIZE,
1196+ (guchar *) PBKDF2_digest);
1197+ digest_b64 = g_base64_encode((guchar *) PBKDF2_digest, PBKDF2_DIGEST_SIZE);
1198+ OPENSSL_cleanse(PBKDF2_digest, PBKDF2_DIGEST_SIZE);
1199+ OPENSSL_free(PBKDF2_digest);
1200+
1201+ salt_b64 = g_base64_encode(lsalt, SALT_SIZE);
1202+
1203+ *password_hash = g_strdup_printf("pbkdf2_sha512$%d$%s$%s",
1204+ PBKDF2_ITERATIONS,
1205+ salt_b64,
1206+ digest_b64);
1207+
1208+ OPENSSL_cleanse(digest_b64, strlen(digest_b64));
1209+ OPENSSL_free(digest_b64);
1210+
1211+ OPENSSL_cleanse(salt_b64, strlen(salt_b64));
1212+ OPENSSL_free(salt_b64);
1213+
1214+ return SSL_RC_OK;
1215+
1216+}
1217+
1218+gint check_password(const gchar *password, const gchar *password_hash) {
1219+
1220+ gint token_counter, rc;
1221+ guchar *salt;
1222+ gchar **tokens, *new_hash;
1223+ gsize salt_length;
1224+
1225+ rc = SSL_RC_ERROR;
1226+ tokens = g_strsplit(password_hash,
1227+ "$",
1228+ -1);
1229+ token_counter = 0;
1230+ while (*(tokens + token_counter) != NULL) {
1231+ ++token_counter;
1232+ }
1233+
1234+ if (token_counter != 4) {
1235+ debug_print("Invalid password hash...\n");
1236+ goto cleanup;
1237+ }
1238+
1239+ salt = g_base64_decode(*(tokens + 2), &salt_length);
1240+ if (salt_length != SALT_SIZE) {
1241+ debug_print("Salt size does not match\n");
1242+ goto cleanup;
1243+ }
1244+
1245+ if (generate_password_hash(&new_hash, password, salt) != SSL_RC_OK) {
1246+ debug_print("Password hash generation failed\n");
1247+ goto cleanup;
1248+ }
1249+
1250+ rc = g_strcmp0(password_hash, new_hash);
1251+
1252+cleanup:
1253+ g_free(salt);
1254+ g_free(new_hash);
1255+ g_strfreev(tokens);
1256+ return rc;
1257+
1258+}
1259+
1260 #endif /* USE_SSL */
1261diff --git a/libsylph/ssl.h b/libsylph/ssl.h
1262index a9f690d..4c0ccd1 100644
1263--- a/libsylph/ssl.h
1264+++ b/libsylph/ssl.h
1265@@ -32,9 +32,15 @@
1266 #include <openssl/pem.h>
1267 #include <openssl/ssl.h>
1268 #include <openssl/err.h>
1269+#include <openssl/evp.h>
1270+#include <openssl/rand.h>
1271
1272 #include "socket.h"
1273
1274+#define SSL_RC_OK 0
1275+#define SSL_RC_ERROR -1
1276+#define SSL_RC_WRONG_HASH_OR_KEY 1
1277+
1278 typedef enum {
1279 SSL_METHOD_SSLv23,
1280 SSL_METHOD_TLSv1
1281@@ -60,6 +66,26 @@ void ssl_done_socket (SockInfo *sockinfo);
1282
1283 void ssl_set_verify_func (SSLVerifyFunc func);
1284
1285+/* master password related code */
1286+gint encrypt_data(gchar **encrypted,
1287+ gint *length_encrypted,
1288+ const gchar *data,
1289+ const gchar *passphrase,
1290+ gint length_data,
1291+ guint min_data_length,
1292+ gboolean rnd_salt);
1293+
1294+gint decrypt_data(gchar **decrypted,
1295+ const gchar *data,
1296+ const gchar *passphrase,
1297+ gint length_data);
1298+
1299+gint generate_password_hash(gchar **password_hash,
1300+ const gchar *password,
1301+ const guchar *salt);
1302+
1303+gint check_password(const gchar *password, const gchar *password_hash);
1304+/* ---------------------------- */
1305 #endif /* USE_SSL */
1306
1307 #endif /* __SSL_H__ */
1308diff --git a/libsylph/utils.c b/libsylph/utils.c
1309index 6ecf328..3bbcbcf 100644
1310--- a/libsylph/utils.c
1311+++ b/libsylph/utils.c
1312@@ -4598,6 +4598,36 @@ gchar *input_query_password(const gchar *server, const gchar *user)
1313 return NULL;
1314 }
1315
1316+static QueryMasterPasswordFunc query_master_password_func = NULL;
1317+
1318+void set_input_query_master_password_func(QueryMasterPasswordFunc func)
1319+{
1320+ query_master_password_func = func;
1321+}
1322+
1323+gchar *input_query_master_password(void)
1324+{
1325+ if (query_master_password_func)
1326+ return query_master_password_func();
1327+ else
1328+ return NULL;
1329+}
1330+
1331+static SetNewPasswordFunc set_new_password_func = NULL;
1332+
1333+void set_input_set_new_password_func(SetNewPasswordFunc func)
1334+{
1335+ set_new_password_func = func;
1336+}
1337+
1338+gchar *input_set_new_password(guint max_attempts)
1339+{
1340+ if (set_new_password_func)
1341+ return set_new_password_func(max_attempts);
1342+ else
1343+ return NULL;
1344+}
1345+
1346 /* logging */
1347
1348 static FILE *log_fp = NULL;
1349diff --git a/libsylph/utils.h b/libsylph/utils.h
1350index 9ac65cf..ede55ff 100644
1351--- a/libsylph/utils.h
1352+++ b/libsylph/utils.h
1353@@ -202,6 +202,8 @@ typedef void (*ProgressFunc) (gint cur,
1354 gint total);
1355 typedef gchar * (*QueryPasswordFunc) (const gchar *server,
1356 const gchar *user);
1357+typedef gchar * (*QueryMasterPasswordFunc) (void);
1358+typedef gchar * (*SetNewPasswordFunc) (guint max_attempts);
1359 typedef void (*LogFunc) (const gchar *str);
1360 typedef void (*LogFlushFunc) (void);
1361
1362@@ -560,9 +562,12 @@ void progress_show (gint cur,
1363
1364 /* user input */
1365 void set_input_query_password_func (QueryPasswordFunc func);
1366-
1367 gchar *input_query_password (const gchar *server,
1368 const gchar *user);
1369+void set_input_query_master_password_func(QueryMasterPasswordFunc func);
1370+gchar *input_query_master_password(void);
1371+void set_input_set_new_password_func(SetNewPasswordFunc func);
1372+gchar *input_set_new_password(guint max_attempts);
1373
1374 /* logging */
1375 void set_log_file (const gchar *filename);
1376diff --git a/src/inputdialog.c b/src/inputdialog.c
1377index a601085..cdfb997 100644
1378--- a/src/inputdialog.c
1379+++ b/src/inputdialog.c
1380@@ -44,6 +44,7 @@
1381 #include "filesel.h"
1382 #include "prefs_common.h"
1383 #include "gtkutils.h"
1384+#include "masterpassword.h"
1385 #include "utils.h"
1386
1387 #define DIALOG_WIDTH 420
1388@@ -156,6 +157,57 @@ gchar *input_dialog_query_password(const gchar *server, const gchar *user)
1389 return pass;
1390 }
1391
1392+gchar *input_dialog_query_master_password(void)
1393+{
1394+ gchar *mp_input;
1395+
1396+ mp_input = input_dialog_with_invisible(_("Input password"),
1397+ _("Master password"),
1398+ NULL);
1399+
1400+ if (prefs_common.use_master_password &&
1401+ prefs_common.auto_unload_master_password)
1402+ {
1403+ g_timeout_add(1000 * 30, unload_master_password, NULL);
1404+ debug_print("Master password to be unloaded in 30 seconds\n");
1405+ }
1406+
1407+ return mp_input;
1408+}
1409+
1410+gchar *input_dialog_set_new_password(guint max_attempts)
1411+{
1412+ guint cnt;
1413+ gchar *pass1, *pass2;
1414+
1415+ if (max_attempts < 1)
1416+ return NULL;
1417+
1418+ for (cnt = 0; cnt < max_attempts; ++cnt) {
1419+ pass1 = input_dialog_with_invisible(
1420+ _("Input password"),
1421+ _("New password"),
1422+ NULL);
1423+ pass2 = input_dialog_with_invisible(
1424+ _("Input password"),
1425+ _("Confirm new password"),
1426+ NULL);
1427+
1428+ if (pass1 != NULL && pass2 != NULL && strcmp(pass1, pass2) == 0) {
1429+ cleanse_buffer(pass2, strlen(pass2));
1430+ g_free(pass2);
1431+ break;
1432+ }
1433+ cleanse_buffer(pass1, strlen(pass1));
1434+ cleanse_buffer(pass2, strlen(pass2));
1435+ g_free(pass2);
1436+ g_free(pass1);
1437+ pass1 = NULL;
1438+ }
1439+
1440+ return pass1;
1441+}
1442+
1443 gchar *input_dialog_with_filesel(const gchar *title, const gchar *message,
1444 const gchar *default_string,
1445 GtkFileChooserAction action)
1446diff --git a/src/inputdialog.h b/src/inputdialog.h
1447index 02bdda3..5364d1a 100644
1448--- a/src/inputdialog.h
1449+++ b/src/inputdialog.h
1450@@ -36,7 +36,8 @@ gchar *input_dialog_combo (const gchar *title,
1451 gboolean case_sensitive);
1452 gchar *input_dialog_query_password (const gchar *server,
1453 const gchar *user);
1454-
1455+gchar *input_dialog_query_master_password(void);
1456+gchar *input_dialog_set_new_password(guint max_attempts);
1457 gchar *input_dialog_with_filesel (const gchar *title,
1458 const gchar *message,
1459 const gchar *default_string,
1460diff --git a/src/main.c b/src/main.c
1461index 77d8192..97a4bd0 100644
1462--- a/src/main.c
1463+++ b/src/main.c
1464@@ -87,6 +87,7 @@
1465 #include "foldersel.h"
1466 #include "update_check.h"
1467 #include "colorlabel.h"
1468+#include "masterpassword.h"
1469
1470 #if USE_GPGME
1471 # include "rfc2015.h"
1472@@ -265,14 +266,54 @@ int main(int argc, char *argv[])
1473 set_ui_update_func(gtkut_events_flush);
1474 set_progress_func(main_window_progress_show);
1475 set_input_query_password_func(input_dialog_query_password);
1476+ set_input_set_new_password_func(input_dialog_set_new_password);
1477 #if USE_SSL
1478 ssl_init();
1479 ssl_set_verify_func(ssl_manager_verify_cert);
1480+ set_input_query_master_password_func(input_dialog_query_master_password);
1481 #endif
1482
1483 CHDIR_EXIT_IF_FAIL(get_home_dir(), 1);
1484
1485 prefs_common_read_config();
1486+ set_master_password(NULL);
1487+#if USE_SSL
1488+ if (prefs_common.use_master_password) {
1489+ if (prefs_common.master_password_hash != NULL) {
1490+ if (check_master_password_interactively(3) != MP_RC_OK) {
1491+ if (alertpanel(_("Master password"),
1492+ _("Invalid master password"),
1493+ GTK_STOCK_DISCARD,
1494+ GTK_STOCK_QUIT,
1495+ NULL) != G_ALERTDEFAULT) {
1496+ exit(1);
1497+ }
1498+ }
1499+ } else {
1500+ alertpanel_notice(
1501+ _("Master password enabled but not set. Setting one now"));
1502+ if (set_master_password_interactively(3) != MP_RC_OK) {
1503+ if (alertpanel(_("Master password"),
1504+ _("Unable to set master password"),
1505+ GTK_STOCK_DISCARD,
1506+ GTK_STOCK_QUIT,
1507+ NULL) != G_ALERTDEFAULT) {
1508+ exit(1);
1509+ }
1510+ }
1511+ }
1512+ }
1513+ /* security goal:
1514+ * if the master password is enabled and loaded on init,
1515+ * an attacker can potentially set use_master_password - disabled
1516+ * afterwards and then force Sylpheed to save account data - the result
1517+ * being passwords saved to accountrc in plain-text.
1518+ * possible solution:
1519+ * Do not allow the passwords to be stored in plain-text if Sylpheed
1520+ * was started with use_master_password - enabled.
1521+ */
1522+ master_password_enabled_on_init = prefs_common.use_master_password;
1523+#endif
1524 filter_set_addressbook_func(addressbook_has_address);
1525 filter_read_config();
1526 prefs_actions_read_config();
1527@@ -381,6 +422,13 @@ int main(int argc, char *argv[])
1528
1529 remote_command_exec();
1530
1531+#if USE_SSL
1532+ if (prefs_common.auto_unload_master_password && master_password_active()) {
1533+ debug_print("Auto unloading master password\n");
1534+ unload_master_password();
1535+ }
1536+#endif
1537+
1538 #if USE_UPDATE_CHECK
1539 if (prefs_common.auto_update_check)
1540 update_check(FALSE);
1541@@ -993,6 +1041,7 @@ void app_will_exit(gboolean force)
1542
1543 /* remove temporary files, close log file, socket cleanup */
1544 #if USE_SSL
1545+ unload_master_password();
1546 ssl_done();
1547 #endif
1548 syl_cleanup();
1549diff --git a/src/prefs_account_dialog.c b/src/prefs_account_dialog.c
1550index e9cba13..e215b8e 100644
1551--- a/src/prefs_account_dialog.c
1552+++ b/src/prefs_account_dialog.c
1553@@ -267,7 +267,7 @@ static PrefsUIData ui_data[] = {
1554 {"user_id", &basic.uid_entry,
1555 prefs_set_data_from_entry, prefs_set_entry},
1556 {"password", &basic.pass_entry,
1557- prefs_set_data_from_entry, prefs_set_entry},
1558+ prefs_set_data_from_epass_entry, prefs_set_entry},
1559
1560 /* Receive */
1561 {"use_apop_auth", &receive.use_apop_chkbtn,
1562@@ -313,7 +313,7 @@ static PrefsUIData ui_data[] = {
1563 {"smtp_user_id", &p_send.smtp_uid_entry,
1564 prefs_set_data_from_entry, prefs_set_entry},
1565 {"smtp_password", &p_send.smtp_pass_entry,
1566- prefs_set_data_from_entry, prefs_set_entry},
1567+ prefs_set_data_from_epass_entry, prefs_set_entry},
1568
1569 {"pop_before_smtp", &p_send.pop_bfr_smtp_chkbtn,
1570 prefs_set_data_from_toggle, prefs_set_toggle},
1571diff --git a/src/prefs_common_dialog.c b/src/prefs_common_dialog.c
1572index fa4be4e..119a056 100644
1573--- a/src/prefs_common_dialog.c
1574+++ b/src/prefs_common_dialog.c
1575@@ -216,6 +216,14 @@ static struct Privacy {
1576 } privacy;
1577 #endif
1578
1579+#if USE_SSL
1580+static struct MasterPassword {
1581+ GtkWidget *checkbtn_use_master_password;
1582+ GtkWidget *checkbtn_auto_unload_master_password;
1583+ GtkWidget *spinbtn_encrypted_password_min_length;
1584+} master_password;
1585+#endif
1586+
1587 static struct Interface {
1588 GtkWidget *checkbtn_always_show_msg;
1589 GtkWidget *checkbtn_always_mark_read;
1590@@ -563,6 +571,18 @@ static PrefsUIData ui_data[] = {
1591 prefs_set_data_from_toggle, prefs_set_toggle},
1592 #endif /* USE_GPGME */
1593
1594+#if USE_SSL
1595+ {"use_master_password", &master_password.checkbtn_use_master_password,
1596+ prefs_set_data_from_toggle, prefs_set_toggle},
1597+ {"auto_unload_master_password",
1598+ &master_password.checkbtn_auto_unload_master_password,
1599+ prefs_set_data_from_toggle, prefs_set_toggle},
1600+ {"encrypted_password_min_length",
1601+ &master_password.spinbtn_encrypted_password_min_length,
1602+ prefs_set_data_from_spinbtn,
1603+ prefs_set_spinbtn},
1604+#endif
1605+
1606 /* Interface */
1607 {"always_show_message_when_selected",
1608 &iface.checkbtn_always_show_msg,
1609@@ -693,6 +713,9 @@ static void prefs_junk_create (void);
1610 #if USE_GPGME
1611 static void prefs_privacy_create (void);
1612 #endif
1613+#if USE_SSL
1614+static void prefs_master_password_create (void);
1615+#endif
1616 static void prefs_details_create (void);
1617 static GtkWidget *prefs_other_create (void);
1618 static GtkWidget *prefs_extcmd_create (void);
1619@@ -852,6 +875,10 @@ static void prefs_common_create(void)
1620 #if USE_GPGME
1621 prefs_privacy_create();
1622 SET_NOTEBOOK_LABEL(dialog.notebook, _("Privacy"), page++);
1623+#endif
1624+#if USE_SSL
1625+ prefs_master_password_create();
1626+ SET_NOTEBOOK_LABEL(dialog.notebook, _("Master password"), page++);
1627 #endif
1628 prefs_details_create();
1629 SET_NOTEBOOK_LABEL(dialog.notebook, _("Details"), page++);
1630@@ -2648,6 +2675,93 @@ static void prefs_privacy_create(void)
1631 }
1632 #endif /* USE_GPGME */
1633
1634+#if USE_SSL
1635+static void prefs_master_password_create(void)
1636+{
1637+ GtkWidget *vbox_main;
1638+ GtkWidget *vbox_master_password_options;
1639+ GtkWidget *vbox_master_password_suboptions;
1640+ GtkWidget *hbox1;
1641+ GtkWidget *hbox_spc;
1642+ GtkWidget *label;
1643+ GtkWidget *checkbtn_use_master_password;
1644+ GtkWidget *checkbtn_auto_unload_master_password;
1645+ GtkWidget *spinbtn_encrypted_password_min_length;
1646+ GtkObject *spinbtn_encrypted_password_min_length_adj;
1647+
1648+ vbox_main = gtk_vbox_new (FALSE, VSPACING);
1649+ gtk_widget_show (vbox_main);
1650+ gtk_container_add (GTK_CONTAINER (dialog.notebook), vbox_main);
1651+ gtk_container_set_border_width (GTK_CONTAINER (vbox_main), VBOX_BORDER);
1652+
1653+ vbox_master_password_options = gtk_vbox_new (FALSE, 0);
1654+ gtk_widget_show (vbox_master_password_options);
1655+ gtk_box_pack_start (
1656+ GTK_BOX (vbox_main), vbox_master_password_options, FALSE, FALSE, 0);
1657+
1658+ PACK_CHECK_BUTTON (vbox_master_password_options,
1659+ checkbtn_use_master_password,
1660+ _("Use master password"));
1661+
1662+ vbox_master_password_suboptions = gtk_vbox_new (FALSE, VSPACING_NARROW);
1663+ gtk_widget_show (vbox_master_password_suboptions);
1664+ gtk_box_pack_start (GTK_BOX (vbox_master_password_options),
1665+ vbox_master_password_suboptions,
1666+ FALSE,
1667+ FALSE,
1668+ 0);
1669+
1670+ PACK_CHECK_BUTTON (vbox_master_password_suboptions,
1671+ checkbtn_auto_unload_master_password,
1672+ _("Automatically unload master password "
1673+ "after session initialization"));
1674+
1675+ hbox1 = gtk_hbox_new (FALSE, 8);
1676+ gtk_widget_show (hbox1);
1677+ gtk_box_pack_start (GTK_BOX (vbox_master_password_suboptions),
1678+ hbox1,
1679+ FALSE,
1680+ FALSE,
1681+ 0);
1682+
1683+ hbox_spc = gtk_hbox_new (FALSE, 0);
1684+ gtk_widget_show (hbox_spc);
1685+ gtk_box_pack_start (GTK_BOX (hbox1), hbox_spc, FALSE, FALSE, 0);
1686+ gtk_widget_set_size_request (hbox_spc, 12, -1);
1687+
1688+ label = gtk_label_new (_("Min. password length"));
1689+ gtk_widget_show (label);
1690+ gtk_box_pack_start (
1691+ GTK_BOX (hbox1), label, FALSE, FALSE, 0);
1692+
1693+ spinbtn_encrypted_password_min_length_adj = gtk_adjustment_new (
1694+ 32, 0, 99, 1, 5, 0);
1695+ spinbtn_encrypted_password_min_length = gtk_spin_button_new(
1696+ GTK_ADJUSTMENT (spinbtn_encrypted_password_min_length_adj), 1, 0);
1697+ gtk_widget_show (spinbtn_encrypted_password_min_length);
1698+ gtk_box_pack_start (GTK_BOX (hbox1),
1699+ spinbtn_encrypted_password_min_length,
1700+ FALSE,
1701+ FALSE,
1702+ 0);
1703+ gtk_spin_button_set_numeric (
1704+ GTK_SPIN_BUTTON (spinbtn_encrypted_password_min_length), TRUE);
1705+ gtk_widget_set_size_request (spinbtn_encrypted_password_min_length,
1706+ 64,
1707+ -1);
1708+
1709+ SET_TOGGLE_SENSITIVITY (checkbtn_use_master_password,
1710+ vbox_master_password_suboptions);
1711+
1712+ master_password.checkbtn_use_master_password
1713+ = checkbtn_use_master_password;
1714+ master_password.checkbtn_auto_unload_master_password
1715+ = checkbtn_auto_unload_master_password;
1716+ master_password.spinbtn_encrypted_password_min_length
1717+ = spinbtn_encrypted_password_min_length;
1718+}
1719+#endif /* USE_SSL */
1720+
1721 static void prefs_details_create(void)
1722 {
1723 GtkWidget *vbox1;
1724diff --git a/src/prefs_ui.c b/src/prefs_ui.c
1725index a3a8f74..2ab1d45 100644
1726--- a/src/prefs_ui.c
1727+++ b/src/prefs_ui.c
1728@@ -31,11 +31,13 @@
1729 #include <errno.h>
1730
1731 #include "prefs.h"
1732+#include "prefs_common.h"
1733 #include "prefs_ui.h"
1734 #include "menu.h"
1735 #include "codeconv.h"
1736 #include "utils.h"
1737 #include "gtkutils.h"
1738+#include "masterpassword.h"
1739
1740 typedef enum
1741 {
1742@@ -268,6 +270,65 @@ void prefs_set_data_from_entry(PrefParam *pparam)
1743 }
1744 }
1745
1746+void prefs_set_data_from_epass_entry(PrefParam *pparam)
1747+{
1748+#if USE_SSL
1749+ PrefsUIData *ui_data;
1750+ gchar **str;
1751+ const gchar *entry_str;
1752+
1753+ /* This is where decrypted passwords are encrypted and stored again */
1754+
1755+ /* master_password == NULL for any of the following reasons:
1756+ * - use_master_password is not enabled (we just save without encrypting)
1757+ * - master_password is auto unloaded (prompt for the master password)
1758+ * - undefined reason (refure to store the password)
1759+ */
1760+ if (!prefs_common.use_master_password) {
1761+ /* check if use_master_password was enabled when Sylpheed started */
1762+ if (!master_password_enabled_on_init) {
1763+ prefs_set_data_from_entry(pparam);
1764+ }
1765+ return;
1766+ } else if (master_password == NULL) {
1767+ if (!prefs_common.auto_unload_master_password) {
1768+ debug_print("Master password enabled, but not loaded for no "
1769+ "apparent reason. Not storing\n");
1770+ return;
1771+ } else {
1772+ if (check_master_password_interactively(3) != MP_RC_OK) {
1773+ debug_print("Failed to reload the master password\n");
1774+ return;
1775+ }
1776+ }
1777+ }
1778+
1779+ ui_data = (PrefsUIData *)pparam->ui_data;
1780+ g_return_if_fail(ui_data != NULL);
1781+ g_return_if_fail(*ui_data->widget != NULL);
1782+
1783+ entry_str = gtk_entry_get_text(GTK_ENTRY(*ui_data->widget));
1784+
1785+ switch (pparam->type) {
1786+ case P_STRING:
1787+ str = (gchar **)pparam->data;
1788+ g_free(*str);
1789+ if ((entry_str != NULL) && (!mpes_string_prefix(entry_str))) {
1790+ debug_print("Encrypting GTK password entry\n");
1791+ *str = encrypt_with_master_password(entry_str);
1792+ } else {
1793+ *str = entry_str[0] ? g_strdup(entry_str) : NULL;
1794+ }
1795+ break;
1796+ default:
1797+ g_warning("Invalid PrefType for GtkEntry widget: %d\n",
1798+ pparam->type);
1799+ }
1800+#else
1801+ prefs_set_data_from_entry(pparam);
1802+#endif
1803+}
1804+
1805 void prefs_set_entry(PrefParam *pparam)
1806 {
1807 PrefsUIData *ui_data;
1808diff --git a/src/prefs_ui.h b/src/prefs_ui.h
1809index cfdf353..f7bd903 100644
1810--- a/src/prefs_ui.h
1811+++ b/src/prefs_ui.h
1812@@ -164,6 +164,7 @@ void prefs_set_data_from_dialog (PrefParam *param);
1813 void prefs_set_dialog_to_default(PrefParam *param);
1814
1815 void prefs_set_data_from_entry (PrefParam *pparam);
1816+void prefs_set_data_from_epass_entry(PrefParam *pparam);
1817 void prefs_set_entry (PrefParam *pparam);
1818 void prefs_set_data_from_text (PrefParam *pparam);
1819 void prefs_set_text (PrefParam *pparam);
1820diff --git a/src/send_message.c b/src/send_message.c
1821index a8c2c7a..537c3c8 100644
1822--- a/src/send_message.c
1823+++ b/src/send_message.c
1824@@ -58,6 +58,7 @@
1825 #include "inc.h"
1826 #include "mainwindow.h"
1827 #include "summaryview.h"
1828+#include "masterpassword.h"
1829
1830 #define SMTP_PORT 25
1831 #if USE_SSL
1832@@ -648,13 +649,13 @@ static gint send_message_smtp(PrefsAccount *ac_prefs, GSList *to_list, FILE *fp)
1833
1834 if (ac_prefs->smtp_userid) {
1835 smtp_session->user = g_strdup(ac_prefs->smtp_userid);
1836- if (ac_prefs->smtp_passwd)
1837- smtp_session->pass =
1838- g_strdup(ac_prefs->smtp_passwd);
1839- else if (ac_prefs->tmp_smtp_pass)
1840+ if (ac_prefs->smtp_passwd) {
1841+ smtp_session->pass = decrypt_with_master_password(
1842+ ac_prefs->smtp_passwd);
1843+ } else if (ac_prefs->tmp_smtp_pass) {
1844 smtp_session->pass =
1845 g_strdup(ac_prefs->tmp_smtp_pass);
1846- else {
1847+ } else {
1848 smtp_session->pass =
1849 input_query_password
1850 (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 @@
1diff -x .git -rupN sylpheed-3.7.0/libsylph/prefs_common.c sylpheed-3.7.0.new/libsylph/prefs_common.c
2--- sylpheed-3.7.0/libsylph/prefs_common.c 2017-11-24 05:17:23.000000000 +0100
3+++ sylpheed-3.7.0.new/libsylph/prefs_common.c 2018-01-31 10:52:57.094885505 +0100
4@@ -406,6 +406,8 @@ static PrefParam param[] = {
5 P_BOOL},
6 {"gpg_signature_popup", "FALSE", &prefs_common.gpg_signature_popup,
7 P_BOOL},
8+ {"gpg_signature_popup_mode", "1", &prefs_common.gpg_signature_popup_mode,
9+ P_INT},
10 {"store_passphrase", "FALSE", &prefs_common.store_passphrase, P_BOOL},
11 {"store_passphrase_timeout", "0",
12 &prefs_common.store_passphrase_timeout, P_INT},
13diff -x .git -rupN sylpheed-3.7.0/libsylph/prefs_common.h sylpheed-3.7.0.new/libsylph/prefs_common.h
14--- sylpheed-3.7.0/libsylph/prefs_common.h 2017-11-24 05:16:18.000000000 +0100
15+++ sylpheed-3.7.0.new/libsylph/prefs_common.h 2018-01-31 10:52:57.052886536 +0100
16@@ -243,6 +243,7 @@ struct _PrefsCommon
17 /* Privacy */
18 gboolean auto_check_signatures;
19 gboolean gpg_signature_popup;
20+ gint gpg_signature_popup_mode;
21 gboolean store_passphrase;
22 gint store_passphrase_timeout;
23 gboolean passphrase_grab;
24diff -x .git -rupN sylpheed-3.7.0/src/prefs_common_dialog.c sylpheed-3.7.0.new/src/prefs_common_dialog.c
25--- sylpheed-3.7.0/src/prefs_common_dialog.c 2014-11-05 08:28:13.000000000 +0100
26+++ sylpheed-3.7.0.new/src/prefs_common_dialog.c 2018-01-31 10:52:57.184883295 +0100
27@@ -205,6 +205,7 @@ static struct JunkMail {
28 static struct Privacy {
29 GtkWidget *checkbtn_auto_check_signatures;
30 GtkWidget *checkbtn_gpg_signature_popup;
31+ GtkWidget *radiobtn_gpg_signature_all;
32 GtkWidget *checkbtn_store_passphrase;
33 GtkWidget *spinbtn_store_passphrase;
34 GtkObject *spinbtn_store_passphrase_adj;
35@@ -314,6 +315,13 @@ static void prefs_common_attach_toolbtn_
36 static void prefs_common_online_mode_set_data_from_radiobtn(PrefParam *pparam);
37 static void prefs_common_online_mode_set_radiobtn (PrefParam *pparam);
38
39+#if USE_GPGME
40+static void prefs_common_gpg_signature_popup_mode_set_data_from_radiobtn(
41+ PrefParam *pparam);
42+static void prefs_common_gpg_signature_popup_mode_set_radiobtn(
43+ PrefParam *pparam);
44+#endif
45+
46 static PrefsUIData ui_data[] = {
47 /* Receive */
48 {"autochk_newmail", &receive.checkbtn_autochk,
49@@ -540,6 +548,9 @@ static PrefsUIData ui_data[] = {
50 prefs_set_data_from_toggle, prefs_set_toggle},
51 {"gpg_signature_popup", &privacy.checkbtn_gpg_signature_popup,
52 prefs_set_data_from_toggle, prefs_set_toggle},
53+ {"gpg_signature_popup_mode", &privacy.radiobtn_gpg_signature_all,
54+ prefs_common_gpg_signature_popup_mode_set_data_from_radiobtn,
55+ prefs_common_gpg_signature_popup_mode_set_radiobtn},
56 {"store_passphrase", &privacy.checkbtn_store_passphrase,
57 prefs_set_data_from_toggle, prefs_set_toggle},
58 {"store_passphrase_timeout", &privacy.spinbtn_store_passphrase,
59@@ -2478,10 +2489,14 @@ static void prefs_privacy_create(void)
60 GtkWidget *vbox2;
61 GtkWidget *vbox3;
62 GtkWidget *hbox1;
63+ GtkWidget *vbox_sign_popup_mode;
64+ GtkWidget *hbox_sign_popup_mode;
65 GtkWidget *hbox_spc;
66 GtkWidget *label;
67 GtkWidget *checkbtn_auto_check_signatures;
68 GtkWidget *checkbtn_gpg_signature_popup;
69+ GtkWidget *radiobtn_gpg_signature_all;
70+ GtkWidget *radiobtn_gpg_signature_bad;
71 GtkWidget *checkbtn_store_passphrase;
72 GtkObject *spinbtn_store_passphrase_adj;
73 GtkWidget *spinbtn_store_passphrase;
74@@ -2505,6 +2520,56 @@ static void prefs_privacy_create(void)
75 PACK_CHECK_BUTTON (vbox2, checkbtn_gpg_signature_popup,
76 _("Show signature check result in a popup window"));
77
78+ vbox_sign_popup_mode = gtk_vbox_new (FALSE, VSPACING_NARROW);
79+ gtk_widget_show (vbox_sign_popup_mode);
80+ gtk_box_pack_start (GTK_BOX (vbox2), vbox_sign_popup_mode, FALSE, FALSE, 0);
81+
82+ hbox_sign_popup_mode = gtk_hbox_new (FALSE, 8);
83+ gtk_widget_show (hbox_sign_popup_mode);
84+ gtk_box_pack_start (GTK_BOX (vbox_sign_popup_mode),
85+ hbox_sign_popup_mode,
86+ FALSE,
87+ FALSE,
88+ 0);
89+
90+ hbox_spc = gtk_hbox_new (FALSE, 0);
91+ gtk_widget_show (hbox_spc);
92+ gtk_box_pack_start (GTK_BOX (hbox_sign_popup_mode),
93+ hbox_spc,
94+ FALSE,
95+ FALSE,
96+ 0);
97+ gtk_widget_set_size_request (hbox_spc, 12, -1);
98+
99+ radiobtn_gpg_signature_all = gtk_radio_button_new_with_label(
100+ NULL,
101+ _("All signatures"));
102+ gtk_widget_show(radiobtn_gpg_signature_all);
103+ gtk_box_pack_start(GTK_BOX (hbox_sign_popup_mode),
104+ radiobtn_gpg_signature_all,
105+ FALSE,
106+ FALSE,
107+ 0);
108+ g_object_set_data(G_OBJECT (radiobtn_gpg_signature_all),
109+ MENU_VAL_ID,
110+ GINT_TO_POINTER (1));
111+
112+ radiobtn_gpg_signature_bad = gtk_radio_button_new_with_label_from_widget(
113+ GTK_RADIO_BUTTON (radiobtn_gpg_signature_all),
114+ _("Bad signatures only"));
115+ gtk_widget_show(radiobtn_gpg_signature_bad);
116+ gtk_box_pack_start(GTK_BOX (hbox_sign_popup_mode),
117+ radiobtn_gpg_signature_bad,
118+ FALSE,
119+ FALSE,
120+ 0);
121+ g_object_set_data(G_OBJECT (radiobtn_gpg_signature_bad),
122+ MENU_VAL_ID,
123+ GINT_TO_POINTER (0));
124+
125+ SET_TOGGLE_SENSITIVITY (checkbtn_gpg_signature_popup,
126+ hbox_sign_popup_mode);
127+
128 PACK_CHECK_BUTTON (vbox2, checkbtn_store_passphrase,
129 _("Store passphrase in memory temporarily"));
130
131@@ -2572,7 +2637,8 @@ static void prefs_privacy_create(void)
132 = checkbtn_auto_check_signatures;
133 privacy.checkbtn_gpg_signature_popup
134 = checkbtn_gpg_signature_popup;
135- privacy.checkbtn_store_passphrase = checkbtn_store_passphrase;
136+ privacy.radiobtn_gpg_signature_all = radiobtn_gpg_signature_all;
137+ privacy.checkbtn_store_passphrase = checkbtn_store_passphrase;
138 privacy.spinbtn_store_passphrase = spinbtn_store_passphrase;
139 privacy.spinbtn_store_passphrase_adj = spinbtn_store_passphrase_adj;
140 #ifndef G_OS_WIN32
141@@ -4719,6 +4785,60 @@ static void prefs_common_online_mode_set
142 }
143 }
144
145+#if USE_GPGME
146+static void prefs_common_gpg_signature_popup_mode_set_data_from_radiobtn(
147+ PrefParam *pparam)
148+{
149+ PrefsUIData *ui_data;
150+ GtkRadioButton *radiobtn;
151+ GSList *group;
152+
153+ ui_data = (PrefsUIData *)pparam->ui_data;
154+ g_return_if_fail(ui_data != NULL);
155+ g_return_if_fail(*ui_data->widget != NULL);
156+
157+ radiobtn = GTK_RADIO_BUTTON(*ui_data->widget);
158+ group = gtk_radio_button_get_group(radiobtn);
159+ while (group != NULL) {
160+ GtkToggleButton *btn = GTK_TOGGLE_BUTTON(group->data);
161+
162+ if (gtk_toggle_button_get_active(btn)) {
163+ prefs_common.gpg_signature_popup_mode =
164+ GPOINTER_TO_INT(g_object_get_data(G_OBJECT(btn), MENU_VAL_ID));
165+ break;
166+ }
167+ group = group->next;
168+ }
169+}
170+
171+static void prefs_common_gpg_signature_popup_mode_set_radiobtn(
172+ PrefParam *pparam)
173+{
174+ PrefsUIData *ui_data;
175+ GtkRadioButton *radiobtn;
176+ GSList *group;
177+
178+ ui_data = (PrefsUIData *)pparam->ui_data;
179+ g_return_if_fail(ui_data != NULL);
180+ g_return_if_fail(*ui_data->widget != NULL);
181+
182+ radiobtn = GTK_RADIO_BUTTON(*ui_data->widget);
183+ group = gtk_radio_button_get_group(radiobtn);
184+ while (group != NULL) {
185+ GtkToggleButton *btn = GTK_TOGGLE_BUTTON(group->data);
186+ gint data;
187+
188+ data = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(btn),
189+ MENU_VAL_ID));
190+ if (data == prefs_common.gpg_signature_popup_mode) {
191+ gtk_toggle_button_set_active(btn, TRUE);
192+ break;
193+ }
194+ group = group->next;
195+ }
196+}
197+#endif
198+
199 static void prefs_common_dispitem_clicked(void)
200 {
201 prefs_summary_column_open(FOLDER_ITEM_IS_SENT_FOLDER
202diff -x .git -rupN sylpheed-3.7.0/src/rfc2015.c sylpheed-3.7.0.new/src/rfc2015.c
203--- sylpheed-3.7.0/src/rfc2015.c 2014-03-26 06:55:35.000000000 +0100
204+++ sylpheed-3.7.0.new/src/rfc2015.c 2018-01-31 11:10:44.946662147 +0100
205@@ -210,8 +210,12 @@ static void check_signature(MimeInfo *mi
206 gchar *tmp_file;
207 gint n_exclude_chars = 0;
208
209- if (prefs_common.gpg_signature_popup)
210+ if (prefs_common.gpg_signature_popup &&
211+ prefs_common.gpg_signature_popup_mode == 1)
212+ {
213+ /* FIXME: Perhaps some macro instead of 1 */
214 statuswindow = gpgmegtk_sig_status_create();
215+ }
216
217 err = gpgme_new(&ctx);
218 if (err) {
219@@ -309,8 +313,21 @@ leave:
220 result = _("Error verifying the signature");
221 }
222 debug_print("verification status: %s\n", result);
223- if (prefs_common.gpg_signature_popup)
224+ if (prefs_common.gpg_signature_popup &&
225+ prefs_common.gpg_signature_popup_mode == 1)
226+ {
227+ /* FIXME: Perhaps some macro instead of 1 */
228+ gpgmegtk_sig_status_update(statuswindow, ctx);
229+ } else if (prefs_common.gpg_signature_popup &&
230+ verifyresult->signatures->status != GPG_ERR_NO_DATA &&
231+ verifyresult->signatures->status != GPG_ERR_NO_ERROR)
232+ {
233+ /* prefs_common.gpg_signature_popup_mode == 0 is useless as long as
234+ * there are only two modes (0 and 1)
235+ */
236+ statuswindow = gpgmegtk_sig_status_create();
237 gpgmegtk_sig_status_update(statuswindow, ctx);
238+ }
239
240 g_free (partinfo->sigstatus);
241 partinfo->sigstatus = g_strdup (result);
242@@ -319,8 +336,11 @@ leave:
243 gpgme_data_release(text);
244 if (ctx)
245 gpgme_release(ctx);
246- if (prefs_common.gpg_signature_popup)
247+ if (prefs_common.gpg_signature_popup) {
248+ /* gpgmegtk_sig_status_destroy handles NULL params so no complicated
249+ check is needed */
250 gpgmegtk_sig_status_destroy(statuswindow);
251+ }
252 }
253
254 /*
255@@ -427,7 +447,11 @@ static gpgme_data_t pgp_decrypt(MsgInfo
256 debug_print("verification status: %s\n", result);
257 debug_print("full status: %s\n",
258 msginfo->encinfo->sigstatus_full);
259- if (prefs_common.gpg_signature_popup) {
260+ if (prefs_common.gpg_signature_popup &&
261+ ((prefs_common.gpg_signature_popup_mode == 1) ||
262+ (verifyresult->signatures->status != GPG_ERR_NO_DATA &&
263+ verifyresult->signatures->status != GPG_ERR_NO_ERROR)))
264+ {
265 GpgmegtkSigStatus statuswindow;
266 statuswindow = gpgmegtk_sig_status_create();
267 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 @@
1diff --git a/configure b/configure
2index 6984cce..cc2b770 100755
3--- a/configure
4+++ b/configure
5@@ -2581,7 +2581,7 @@ MINOR_VERSION=7
6 MICRO_VERSION=0
7 INTERFACE_AGE=0
8 BINARY_AGE=0
9-EXTRA_VERSION=
10+EXTRA_VERSION=-sgs
11 BUILD_REVISION=1185
12 VERSION=$MAJOR_VERSION.$MINOR_VERSION.$MICRO_VERSION$EXTRA_VERSION
13
14diff --git a/configure.ac b/configure.ac
15index 090177d..ae65343 100644
16--- a/configure.ac
17+++ b/configure.ac
18@@ -9,7 +9,7 @@ MINOR_VERSION=7
19 MICRO_VERSION=0
20 INTERFACE_AGE=0
21 BINARY_AGE=0
22-EXTRA_VERSION=
23+EXTRA_VERSION=-sgs
24 BUILD_REVISION=1185
25 VERSION=$MAJOR_VERSION.$MINOR_VERSION.$MICRO_VERSION$EXTRA_VERSION
26
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 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE pkgmetadata SYSTEM "http://www.gentoo.org/dtd/metadata.dtd">
3<pkgmetadata>
4 <maintainer type="person">
5 <email>blackmore@pichove.org</email>
6 </maintainer>
7 <use>
8 <flag name="oniguruma">Use <pkg>dev-libs/oniguruma</pkg> for regular expression</flag>
9 </use>
10</pkgmetadata>
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 @@
1# Copyright 2018 Simeon Simeonov
2# Distributed under the terms of the GNU General Public License v2
3
4EAPI="6"
5
6inherit eutils
7
8DESCRIPTION="A lightweight email client and newsreader (patched by sgs)"
9HOMEPAGE="http://sylpheed.sraoss.jp/"
10SRC_URI="http://${PN}.sraoss.jp/${PN}/v${PV%.*}/${P}.tar.bz2"
11
12LICENSE="GPL-2 LGPL-2.1"
13SLOT="0"
14KEYWORDS="amd64 x86"
15IUSE="crypt ipv6 ldap libressl nls oniguruma pda spell ssl xface"
16
17CDEPEND="net-libs/liblockfile
18 x11-libs/gtk+:2
19 crypt? ( app-crypt/gpgme )
20 ldap? ( net-nds/openldap )
21 nls? ( sys-devel/gettext )
22 oniguruma? ( dev-libs/oniguruma:= )
23 pda? ( app-pda/jpilot )
24 spell? (
25 app-text/gtkspell:2
26 dev-libs/dbus-glib
27 )
28 ssl? (
29 !libressl? ( dev-libs/openssl:0 )
30 libressl? ( dev-libs/libressl )
31 )"
32RDEPEND="${CDEPEND}
33 app-misc/mime-types
34 net-misc/curl"
35DEPEND="${CDEPEND}
36 virtual/pkgconfig
37 xface? ( media-libs/compface )"
38
39PATCHES=(
40 "${FILESDIR}/${P}-signature-box.patch"
41 "${FILESDIR}/${P}-PGP-signature-fix.patch"
42 "${FILESDIR}/${P}-master-password.patch"
43 "${FILESDIR}/${P}-version.patch"
44)
45
46DOCS="AUTHORS ChangeLog* NEW* PLUGIN* README* TODO*"
47
48src_configure() {
49 local htmldir="${EPREFIX}"/usr/share/doc/${PF}/html
50 econf \
51 $(use_enable crypt gpgme) \
52 $(use_enable ipv6) \
53 $(use_enable ldap) \
54 $(use_enable oniguruma) \
55 $(use_enable pda jpilot) \
56 $(use_enable spell gtkspell) \
57 $(use_enable ssl) \
58 $(use_enable xface compface) \
59 --with-plugindir="${EPREFIX}"/usr/$(get_libdir)/${PN}/plugins \
60 --with-manualdir="${htmldir}"/manual \
61 --with-faqdir="${htmldir}"/faq \
62 --disable-updatecheckplugin \
63 --disable-updatecheck
64}
65
66src_install() {
67 default
68
69 doicon *.png
70 domenu *.desktop
71
72 cd plugin/attachment_tool
73 emake DESTDIR="${D}" install-plugin
74 docinto plugin/attachment_tool
75 dodoc README
76}