Commit 78e57f7e 78e57f7ec9df1547dd5e176a97fa1f8954b51c09 by Sergey Poznyakoff

Fix the "UNIX mailbox first message symptom".

UFMS, or "UNIX mailbox first message symptom", is a long-standing
bug that existed in all previous versions of Mailutils:  under
certain circumstances, a fragment of the message headers would get
prepended to the body of the very first message in a UNIX mailbox.
See the detailed description in testsuite/ufms.c.

Along with fixing this bug, this change also ensures a proper restoring
of UIDs from UNIX mailboxes.

* libproto/mbox/mbox.c (_msg_body_setup): Call mu_body_clear_modified
after constructing the body.
(new_message): Ditto for mu_message_clear_modified.
(mbox_reset): Rewrite.  Drop all cached messages and rescan entire
mailbox to avoid the "1st message symptom".
(mbox_expunge0): Change the call to mbox_reset.
* libproto/mbox/mboxscan.c (IS_X_UID, IS_X_IMAPBASE): New macros.
(mbox_scan_internal): Change handling of min_uid.
Attempt to get UID and imapbase from the corresponding message headers.
(mbox_scan0): Reflect the above changes.

* testsuite/.gitignore: Add ufms.
* testsuite/Makefile.am (noinst_PROGRAMS): Add ufms.
(TESTSUITE_AT): Add ufms.at.
* testsuite/testsuite.at: Include ufms.at.
* testsuite/ufms.at: New test case.
* testsuite/ufms.c: New test program.

* imap4d/testsuite/imap4d/expunge.exp: Fix UIDNEXT expectation.
1 parent 78d43b05
...@@ -63,7 +63,7 @@ imap4d_test "SELECT mbox1" \ ...@@ -63,7 +63,7 @@ imap4d_test "SELECT mbox1" \
63 "1 EXISTS"\ 63 "1 EXISTS"\
64 "0 RECENT"\ 64 "0 RECENT"\
65 -re {OK \[UIDVALIDITY [0-9]+\] UID valididy status}\ 65 -re {OK \[UIDVALIDITY [0-9]+\] UID valididy status}\
66 "OK \[UIDNEXT 2\] Predicted next uid"\ 66 "OK \[UIDNEXT 6\] Predicted next uid"\
67 "OK \[UNSEEN 1\] first unseen messsage"\ 67 "OK \[UNSEEN 1\] first unseen messsage"\
68 "FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)"\ 68 "FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)"\
69 "OK \[PERMANENTFLAGS (\\Answered \\Deleted \\Seen)\] Permanent flags" \ 69 "OK \[PERMANENTFLAGS (\\Answered \\Deleted \\Seen)\] Permanent flags" \
......
...@@ -349,6 +349,7 @@ _msg_body_setup (mu_message_t msg, mbox_message_t mum) ...@@ -349,6 +349,7 @@ _msg_body_setup (mu_message_t msg, mbox_message_t mum)
349 mu_body_set_stream (body, stream, msg); 349 mu_body_set_stream (body, stream, msg);
350 mu_body_set_size (body, mbox_body_size, msg); 350 mu_body_set_size (body, mbox_body_size, msg);
351 mu_body_set_lines (body, mbox_body_lines, msg); 351 mu_body_set_lines (body, mbox_body_lines, msg);
352 mu_body_clear_modified (body);
352 mu_message_set_body (msg, body, mum); 353 mu_message_set_body (msg, body, mum);
353 } 354 }
354 return status; 355 return status;
...@@ -571,6 +572,8 @@ new_message (mu_mailbox_t mailbox, mbox_message_t mum, mu_message_t *pmsg) ...@@ -571,6 +572,8 @@ new_message (mu_mailbox_t mailbox, mbox_message_t mum, mu_message_t *pmsg)
571 mum->message = msg; 572 mum->message = msg;
572 mu_message_set_mailbox (msg, mailbox, mum); 573 mu_message_set_mailbox (msg, mailbox, mum);
573 574
575 mu_message_clear_modified (msg);
576
574 *pmsg = msg; 577 *pmsg = msg;
575 578
576 return 0; 579 return 0;
...@@ -1149,37 +1152,18 @@ mbox_append_message (mu_mailbox_t mailbox, mu_message_t msg) ...@@ -1149,37 +1152,18 @@ mbox_append_message (mu_mailbox_t mailbox, mu_message_t msg)
1149 1152
1150 1153
1151 static void 1154 static void
1152 mbox_reset (mu_mailbox_t mailbox, size_t dirty, int remove_deleted) 1155 mbox_reset (mu_mailbox_t mailbox)
1153 { 1156 {
1154 mbox_data_t mud = mailbox->data; 1157 mbox_data_t mud = mailbox->data;
1155 size_t i; 1158 size_t i;
1156 size_t dlast;
1157 1159
1158 mu_monitor_wrlock (mailbox->monitor); 1160 mu_monitor_wrlock (mailbox->monitor);
1159 for (i = dirty, dlast = mud->messages_count - 1; i <= dlast; i++) 1161 for (i = 0; i < mud->messages_count; i++)
1160 { 1162 {
1161 /* Clear all the references */ 1163 /* Clear all the references */
1162 mbox_message_t mum = mud->umessages[i]; 1164 mbox_message_t mum = mud->umessages[i];
1163 if (remove_deleted && ATTRIBUTE_IS_DELETED (mum->attr_flags)) 1165 mu_message_destroy (&mum->message, mum);
1164 {
1165 if ((i + 1) <= dlast)
1166 {
1167 /* Move all the pointers up. So the message pointer
1168 part of mum will be at the right position. */
1169 memmove (mud->umessages + i, mud->umessages + i + 1,
1170 (dlast - i) * sizeof (mum));
1171 memset (mum, 0, sizeof (*mum));
1172 /* We are not free()ing the useless mum, but instead
1173 we put it back in the pool, to be reused. */
1174 mud->umessages[dlast] = mum;
1175 dlast--;
1176 /* Set mum to the new value after the memmove so it
1177 gets cleared to. */
1178 mum = mud->umessages[i];
1179 }
1180 else
1181 memset (mum, 0, sizeof (*mum)); 1166 memset (mum, 0, sizeof (*mum));
1182 }
1183 mum->envel_from = mum->envel_from_end = 0; 1167 mum->envel_from = mum->envel_from_end = 0;
1184 mum->body = mum->body_end = 0; 1168 mum->body = mum->body_end = 0;
1185 mum->header_lines = mum->body_lines = 0; 1169 mum->header_lines = mum->body_lines = 0;
...@@ -1187,7 +1171,7 @@ mbox_reset (mu_mailbox_t mailbox, size_t dirty, int remove_deleted) ...@@ -1187,7 +1171,7 @@ mbox_reset (mu_mailbox_t mailbox, size_t dirty, int remove_deleted)
1187 mu_monitor_unlock (mailbox->monitor); 1171 mu_monitor_unlock (mailbox->monitor);
1188 /* This resets the messages_count, the last argument 0 means 1172 /* This resets the messages_count, the last argument 0 means
1189 not to send event notification. */ 1173 not to send event notification. */
1190 mbox_scan0 (mailbox, dirty, NULL, 0); 1174 mbox_scan0 (mailbox, 1, NULL, 0);
1191 } 1175 }
1192 1176
1193 static int 1177 static int
...@@ -1434,7 +1418,7 @@ mbox_expunge0 (mu_mailbox_t mailbox, int remove_deleted) ...@@ -1434,7 +1418,7 @@ mbox_expunge0 (mu_mailbox_t mailbox, int remove_deleted)
1434 mu_stream_destroy (&tempstr); 1418 mu_stream_destroy (&tempstr);
1435 1419
1436 if (status == 0) 1420 if (status == 0)
1437 mbox_reset (mailbox, dirty, remove_deleted); 1421 mbox_reset (mailbox);
1438 } 1422 }
1439 if (mailbox->locker) 1423 if (mailbox->locker)
1440 mu_locker_unlock (mailbox->locker); 1424 mu_locker_unlock (mailbox->locker);
......
...@@ -34,6 +34,8 @@ ...@@ -34,6 +34,8 @@
34 34
35 #include <stdlib.h> 35 #include <stdlib.h>
36 #include <mbox0.h> 36 #include <mbox0.h>
37 #include <mailutils/cctype.h>
38 #include <mailutils/cstr.h>
37 39
38 /* Parsing. 40 /* Parsing.
39 The approach is to detect the "From " as start of a new message, give the 41 The approach is to detect the "From " as start of a new message, give the
...@@ -279,6 +281,26 @@ do \ ...@@ -279,6 +281,26 @@ do \
279 && (buf[5] == 'S' || buf[5] == 's') \ 281 && (buf[5] == 'S' || buf[5] == 's') \
280 && (buf[6] == ':' || buf[6] == ' ' || buf[6] == '\t')) 282 && (buf[6] == ':' || buf[6] == ' ' || buf[6] == '\t'))
281 283
284 #define IS_X_UID(buf) ( \
285 (buf[0] == 'X' || buf[0] == 'x') \
286 && buf[1] == '-' \
287 && (buf[2] == 'U' || buf[2] == 'u') \
288 && (buf[3] == 'I' || buf[3] == 'i') \
289 && (buf[4] == 'D' || buf[4] == 'd') \
290 && (buf[5] == ':' || buf[5] == ' ' || buf[5] == '\t'))
291 #define IS_X_IMAPBASE(buf) ( \
292 (buf[0] == 'X' || buf[0] == 'x') \
293 && buf[1] == '-' \
294 && (buf[2] == 'I' || buf[2] == 'i') \
295 && (buf[3] == 'M' || buf[3] == 'm') \
296 && (buf[4] == 'A' || buf[4] == 'a') \
297 && (buf[5] == 'P' || buf[5] == 'p') \
298 && (buf[6] == 'B' || buf[6] == 'b') \
299 && (buf[7] == 'A' || buf[7] == 'a') \
300 && (buf[8] == 'S' || buf[8] == 's') \
301 && (buf[9] == 'E' || buf[9] == 'e') \
302 && (buf[10] == ':' || buf[10] == ' ' || buf[10] == '\t'))
303
282 #define MBOX_SCAN_NOTIFY 0x1 304 #define MBOX_SCAN_NOTIFY 0x1
283 #define MBOX_SCAN_ONEMSG 0x2 305 #define MBOX_SCAN_ONEMSG 0x2
284 306
...@@ -298,7 +320,7 @@ mbox_scan_internal (mu_mailbox_t mailbox, mbox_message_t mum, ...@@ -298,7 +320,7 @@ mbox_scan_internal (mu_mailbox_t mailbox, mbox_message_t mum,
298 int newline; 320 int newline;
299 size_t n = 0; 321 size_t n = 0;
300 mu_stream_t stream; 322 mu_stream_t stream;
301 size_t min_uid = 0; 323 size_t min_uid = 1;
302 int zn, isfrom = 0; 324 int zn, isfrom = 0;
303 char *temp; 325 char *temp;
304 326
...@@ -345,9 +367,9 @@ mbox_scan_internal (mu_mailbox_t mailbox, mbox_message_t mum, ...@@ -345,9 +367,9 @@ mbox_scan_internal (mu_mailbox_t mailbox, mbox_message_t mum,
345 mum->body_end = total - n - newline; 367 mum->body_end = total - n - newline;
346 mum->body_lines = --lines - newline; 368 mum->body_lines = --lines - newline;
347 369
348 if (mum->uid <= min_uid) 370 if (mum->uid < min_uid)
349 { 371 {
350 mum->uid = ++min_uid; 372 mum->uid = min_uid++;
351 /* Note that modification for when expunging. */ 373 /* Note that modification for when expunging. */
352 mum->attr_flags |= MU_ATTRIBUTE_MODIFIED; 374 mum->attr_flags |= MU_ATTRIBUTE_MODIFIED;
353 } 375 }
...@@ -370,13 +392,32 @@ mbox_scan_internal (mu_mailbox_t mailbox, mbox_message_t mum, ...@@ -370,13 +392,32 @@ mbox_scan_internal (mu_mailbox_t mailbox, mbox_message_t mum,
370 mum->attr_flags = 0; 392 mum->attr_flags = 0;
371 lines = 0; 393 lines = 0;
372 } 394 }
373 else if (ISSTATUS(buf)) 395 else if (ISSTATUS (buf))
374 { 396 {
375 ATTRIBUTE_SET(buf, mum, 'r', 'R', MU_ATTRIBUTE_READ); 397 ATTRIBUTE_SET(buf, mum, 'r', 'R', MU_ATTRIBUTE_READ);
376 ATTRIBUTE_SET(buf, mum, 'o', 'O', MU_ATTRIBUTE_SEEN); 398 ATTRIBUTE_SET(buf, mum, 'o', 'O', MU_ATTRIBUTE_SEEN);
377 ATTRIBUTE_SET(buf, mum, 'a', 'A', MU_ATTRIBUTE_ANSWERED); 399 ATTRIBUTE_SET(buf, mum, 'a', 'A', MU_ATTRIBUTE_ANSWERED);
378 ATTRIBUTE_SET(buf, mum, 'd', 'D', MU_ATTRIBUTE_DELETED); 400 ATTRIBUTE_SET(buf, mum, 'd', 'D', MU_ATTRIBUTE_DELETED);
379 } 401 }
402 else if (IS_X_UID (buf))
403 {
404 char *p;
405 unsigned long n = strtoul (buf + 6, &p, 10);
406 if (*p == 0 || mu_isspace (*p))
407 mum->uid = min_uid = n;
408 }
409 else if (mud->messages_count == 1 && IS_X_IMAPBASE (buf))
410 {
411 char *p;
412 unsigned long n = strtoul (buf + 11, &p, 10);
413 if (mu_isspace (*p))
414 mud->uidvalidity = n;
415 n = strtoul (mu_str_skip_cset (p, " \t"), &p, 10);
416 if (*p == 0 || mu_isspace (*p))
417 mud->uidnext = n;
418 else
419 mud->uidvalidity = 0;
420 }
380 } 421 }
381 422
382 /* Body. */ 423 /* Body. */
...@@ -410,9 +451,9 @@ mbox_scan_internal (mu_mailbox_t mailbox, mbox_message_t mum, ...@@ -410,9 +451,9 @@ mbox_scan_internal (mu_mailbox_t mailbox, mbox_message_t mum,
410 mum->body_end = total - newline; 451 mum->body_end = total - newline;
411 mum->body_lines = lines - newline; 452 mum->body_lines = lines - newline;
412 453
413 if (mum->uid <= min_uid) 454 if (mum->uid < min_uid)
414 { 455 {
415 mum->uid = ++min_uid; 456 mum->uid = min_uid++;
416 /* Note that modification for when expunging. */ 457 /* Note that modification for when expunging. */
417 mum->attr_flags |= MU_ATTRIBUTE_MODIFIED; 458 mum->attr_flags |= MU_ATTRIBUTE_MODIFIED;
418 } 459 }
...@@ -498,7 +539,7 @@ mbox_scan0 (mu_mailbox_t mailbox, size_t msgno, size_t *pcount, int do_notif) ...@@ -498,7 +539,7 @@ mbox_scan0 (mu_mailbox_t mailbox, size_t msgno, size_t *pcount, int do_notif)
498 } 539 }
499 } 540 }
500 541
501 if (mud->messages_count > 0 && min_uid >= mud->uidnext) 542 if (mud->messages_count > 0 && min_uid > mud->uidnext)
502 { 543 {
503 mum = mud->umessages[0]; 544 mum = mud->umessages[0];
504 mud->uidnext = min_uid + 1; 545 mud->uidnext = min_uid + 1;
......
...@@ -7,3 +7,4 @@ testsuite.log ...@@ -7,3 +7,4 @@ testsuite.log
7 mbdel 7 mbdel
8 mimetest 8 mimetest
9 smtpsend 9 smtpsend
10 ufms
......
...@@ -42,7 +42,8 @@ INCLUDES = @MU_LIB_COMMON_INCLUDES@ ...@@ -42,7 +42,8 @@ INCLUDES = @MU_LIB_COMMON_INCLUDES@
42 noinst_PROGRAMS = \ 42 noinst_PROGRAMS = \
43 mbdel\ 43 mbdel\
44 mimetest\ 44 mimetest\
45 smtpsend 45 smtpsend\
46 ufms
46 47
47 LDADD = \ 48 LDADD = \
48 ${MU_LIB_MBOX}\ 49 ${MU_LIB_MBOX}\
...@@ -68,6 +69,7 @@ smtpsend_LDADD = \ ...@@ -68,6 +69,7 @@ smtpsend_LDADD = \
68 TESTSUITE_AT = \ 69 TESTSUITE_AT = \
69 mbdel.at\ 70 mbdel.at\
70 mime.at\ 71 mime.at\
72 ufms.at\
71 testsuite.at 73 testsuite.at
72 74
73 TESTSUITE = $(srcdir)/testsuite 75 TESTSUITE = $(srcdir)/testsuite
......
...@@ -20,3 +20,4 @@ AT_INIT ...@@ -20,3 +20,4 @@ AT_INIT
20 20
21 m4_include([mime.at]) 21 m4_include([mime.at])
22 m4_include([mbdel.at]) 22 m4_include([mbdel.at])
23 m4_include([ufms.at])
......
1 # This file is part of GNU Mailutils. -*- Autotest -*-
2 # Copyright (C) 2010 Free Software Foundation, Inc.
3 #
4 # GNU Mailutils is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License as
6 # published by the Free Software Foundation; either version 3, or (at
7 # your option) any later version.
8 #
9 # GNU Mailutils is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>.
16
17 # Check for "UNIX mbox 1st message symptom" bug. See the description in
18 # ufms.c, for a detailed description.
19
20 AT_SETUP([UNIX mbox 1st message symptom])
21 AT_KEYWORDS([mailbox mbox mbox-ufms ufms])
22 AT_CHECK([
23 MUT_MBCOPY($abs_top_srcdir/testsuite/spool/mbox1)
24 ufms mbox:mbox1 || exit $?
25 sed 's/^\(X-IMAPbase\): \([[0-9][0-9]*]\) \(.*\)/\1: UIDVALIDITY \3/' mbox1
26 ],
27 [0],
28 [From foobar@nonexistent.net Fri Dec 28 22:18:09 2001
29 Received: (from foobar@nonexistent.net)
30 by nonexistent.net id fBSKI8N04906
31 for bar@dontmailme.org; Fri, 28 Dec 2001 22:18:08 +0200
32 Date: Fri, 28 Dec 2001 22:18:08 +0200
33 From: Foo Bar <foobar@nonexistent.net>
34 Message-Id: <200112282018.fBSKI8N04906@nonexistent.net>
35 To: Bar <bar@dontmailme.org>
36 Subject: Jabberwocky
37 X-IMAPbase: UIDVALIDITY 6
38 Status: R
39 X-UID: 1
40
41 `Twas brillig, and the slithy toves
42 Did gyre and gimble in the wabe;
43 All mimsy were the borogoves,
44 And the mome raths outgrabe.
45
46 `Beware the Jabberwock, my son!
47 The jaws that bite, the claws that catch!
48 Beware the Jujub bird, and shun
49 The frumious Bandersnatch!'
50
51 He took his vorpal sword in hand:
52 Long time the manxome foe he sought --
53 So rested he by the Tumtum gree,
54 And stood awhile in thought.
55
56 And as in uffish thought he stood,
57 The Jabberwock, with eyes of flame,
58 Came whiffling through the tulgey wook,
59 And burbled as it came!
60
61 One, two! One, two! And through and through
62 The vorpal blade went snicker-snack!
63 He left it dead, and with its head
64 He went galumphing back.
65
66 `And has thou slain the Jabberwock?
67 Come to my arms, my beamish boy!
68 O frabjous day! Calloh! Callay!
69 He chortled in his joy.
70
71 `Twas brillig, and the slithy toves
72 Did gyre and gimble in the wabe;
73 All mimsy were the borogoves,
74 And the mome raths outgrabe.
75
76
77 ])
78
79 AT_CLEANUP
80
1 /* ufms.c - a test of "UNIX mailbox first message symptom"
2 Copyright (C) 2010 Free Software Foundation, Inc.
3
4 GNU Mailutils is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 3, or (at your option)
7 any later version.
8
9 GNU Mailutils is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. */
16
17 #ifdef HAVE_CONFIG_H
18 # include <config.h>
19 #endif
20 #include <unistd.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <mailutils/mailutils.h>
24
25 /* UFMS, or "UNIX mailbox first message symptom", is a bug that
26 existed in all versions of Mailutils up to 2.2.1 and 2.9.90
27 as of 2010-11-07.
28
29 Due to improperly designed expunge routine, the body of the very
30 first message in a UNIX mailbox would get corrupted under certain
31 circumstances. The following three lines would appear at the top
32 of the body:
33
34 -IMAPbase: X Y
35 Status: S
36 X-UID: Z
37
38 where (X, Y, Z are decimal numbers and S is a status string).
39
40 Prerequisites for triggering the bug:
41
42 1. Mailbox contains more than one message.
43 2. The first message does not carry X-IMAPbase and X-UID headers.
44
45 Recipe for triggering the bug:
46
47 1. Delete some messages.
48 2. Expunge the mailbox.
49 3. Modify attributes of the first message.
50 4. Flush your changes.
51 */
52
53
54 int
55 main (int argc, char **argv)
56 {
57 mu_mailbox_t mbox;
58 size_t i, count;
59 mu_message_t msg;
60 mu_attribute_t attr;
61
62 if (argc != 2)
63 {
64 fprintf (stderr, "usage: %s MBOX\n", argv[0]);
65 return 1;
66 }
67
68 mu_registrar_record (mu_mbox_record);
69
70 /* Open the mailbox */
71 MU_ASSERT (mu_mailbox_create (&mbox, argv[1]));
72 MU_ASSERT (mu_mailbox_open (mbox, MU_STREAM_RDWR));
73 mu_mailbox_messages_count (mbox, &count);
74 if (count < 2)
75 {
76 fprintf (stderr,
77 "%s: input mailbox should contain at least two messages\n",
78 argv[0]);
79 exit (1);
80 }
81 /* Delete all records except the first and expunge the mailbox. */
82 for (i = 2; i <= count; i++)
83 {
84 MU_ASSERT (mu_mailbox_get_message (mbox, i, &msg));
85 MU_ASSERT (mu_message_get_attribute (msg, &attr));
86 MU_ASSERT (mu_attribute_set_deleted (attr));
87 }
88 mu_mailbox_expunge (mbox);
89
90 /* Modify attributes on the remaining message and synch */
91 MU_ASSERT (mu_mailbox_get_message (mbox, 1, &msg));
92 MU_ASSERT (mu_message_get_attribute (msg, &attr));
93 MU_ASSERT (mu_attribute_set_read (attr));
94 mu_mailbox_sync (mbox);
95
96 /* Cleanup & exit */
97 mu_mailbox_close (mbox);
98 mu_mailbox_destroy (&mbox);
99 return 0;
100 }