2 * Duplicate Mail Checker
4 * (c) 2006 Martin Mares <mj@ucw.cz>
22 static uns max_age = 86400;
24 static char *local_user;
25 static char *local_domain;
28 unsigned char digest[20];
34 static struct item *db_map;
35 static uns db_map_size;
42 struct passwd *pw = getpwuid(getuid());
44 die("Sorry, but you don't exist!");
45 db_name = xmalloc(strlen(pw->pw_dir) + 10);
46 sprintf(db_name, "%s/.mdup.db", pw->pw_dir);
48 verb(2, "Opening database %s", db_name);
49 db_fd = open(db_name, O_RDWR | O_CREAT, 0600);
51 die("Cannot open database %s: %m", db_name);
52 if (flock(db_fd, LOCK_EX) < 0)
53 die("Cannot flock %s: %m", db_name);
56 if (fstat(db_fd, &st) < 0)
57 die("Cannot stat %s: %m", db_name);
58 if (st.st_size % sizeof(struct item))
59 die("Database %s is inconsistent: Size is not an integer number of records");
60 db_items = st.st_size / sizeof(struct item);
61 verb(2, "Mapping %d items", db_items);
63 db_map_size = sizeof(struct item) * (db_items+16);
64 db_map = mmap(NULL, db_map_size, PROT_READ | PROT_WRITE, MAP_SHARED, db_fd, 0);
65 if (db_map == MAP_FAILED)
66 die("Mmap on %s failed: %m", db_name);
72 munmap(db_map, db_map_size);
73 flock(db_fd, LOCK_UN);
78 db_lookup(struct item *new)
80 struct item *free = NULL;
82 for (uns i=0; i<db_items; i++)
84 struct item *t = &db_map[i];
85 if (t->timestamp <= now && now - t->timestamp > max_age)
90 else if (!memcmp(t->digest, new->digest, 20))
92 verb(2, "Found at item %d, age %d sec", i, now - t->timestamp);
100 if (sizeof(struct item) * db_items >= db_map_size)
101 die("Internal error: map window too small");
102 free = &db_map[db_items++];
103 if (ftruncate(db_fd, sizeof(struct item) * db_items) < 0)
104 die("Cannot enlarge %s: %m", db_name);
106 verb(2, "Creating new entry");
108 free->timestamp = now;
112 #define MAX_HDR_LEN 1024
131 else if (*c == '\\' && c[1])
138 parse_header_line(char *line, uns cnt, struct item *item)
142 if (cnt >= MAX_HDR_LEN)
144 verb(2, "HDR: <too long>");
149 verb(2, "HDR: %s", line);
150 if (strncasecmp(line, "Message-ID: ", 12))
152 char *c = skip_cfws(line+12);
156 char lhs[MAX_HDR_LEN], *l = lhs;
157 if (*c == '\"') // LHS is no-fold-quote
164 else if (*c == '\\' && c[1])
174 else // LHS is dot-atom-text
176 while (*c && *c != '@')
181 if (*c++ != '@') // "@" is mandatory
184 char rhs[MAX_HDR_LEN], *r = rhs;
185 if (*c == '[') // RHS is no-fold-literal
191 else if (*c == '\\' && c[1])
201 else // RHS is dot-atom-text
203 while (*c && *c != '>')
212 verb(1, "Parsed Message-ID <%s@%s>", lhs, rhs);
214 if (local_domain && local_user)
216 uns lul = strlen(local_user);
217 if (!strcasecmp(rhs, local_domain) &&
218 !strncasecmp(lhs, local_user, lul) &&
219 !strncasecmp(lhs+lul, "+md-", 4))
221 verb(1, "Detected local Message-ID");
229 sha1_update(&ctx, lhs, l-lhs);
230 sha1_update(&ctx, rhs, r-rhs);
231 sha1_final(&ctx, item->digest);
235 fprintf(stderr, "Digest: ");
236 for (uns i=0; i<20; i++)
237 fprintf(stderr, "%02x", item->digest[i]);
238 fprintf(stderr, "\n");
243 parse_header(struct item *item)
245 char buf[MAX_HDR_LEN+1];
255 verb(1, "Incomplete header");
262 if (cnt == last_nl) // End of header
264 parse_header_line(buf, cnt, item);
265 return item->timestamp;
269 else if (c == ' ' || c == '\t' || !c)
273 verb(1, "Misplaced whitespace at the beginning of header");
276 if (cnt < MAX_HDR_LEN)
283 parse_header_line(buf, cnt, item);
286 if (cnt < MAX_HDR_LEN)
295 fprintf(stderr, "Usage: mdup [<options>]\n\
298 -a <age>\t\tRecords older than <age> hours are ignored (default: 24)\n\
299 -d <db>\t\t\tUse <db> as a Message-ID database (default: ~/.mdup.db; beware of NFS)\n\
300 -l <user>@<domain>\tDetect looped back messages by their Message-ID\n\
301 -v\t\t\tIncrease verbosity\n\
303 MailDups " STR(VERSION) ", (c) " STR(YEAR) " Martin Mares <mj@ucw.cz>\n\
304 It can be freely distributed and used according to the GNU GPL v2.\n\
310 main(int argc, char **argv)
313 while ((c = getopt(argc, argv, "a:d:l:v")) >= 0)
317 max_age = atol(optarg) * 3600;
324 char *c = strchr(optarg, '@');
344 int ok = parse_header(&msg);
352 int ret = db_lookup(&msg);
354 puts(ret ? "DUP" : "OK");