2 * Duplicate Mail Checker
4 * (c) 2006 Martin Mares <mj@ucw.cz>
21 const char progname[] = "mdup";
24 static uns max_age = 86400;
26 static char *local_user;
27 static char *local_domain;
30 unsigned char digest[20];
36 static struct item *db_map;
37 static uns db_map_size;
44 struct passwd *pw = getpwuid(getuid());
46 die("Sorry, but you don't exist!");
47 db_name = xmalloc(strlen(pw->pw_dir) + 10);
48 sprintf(db_name, "%s/.mdup.db", pw->pw_dir);
50 verb(2, "Opening database %s", db_name);
51 db_fd = open(db_name, O_RDWR | O_CREAT, 0600);
53 die("Cannot open database %s: %m", db_name);
54 if (flock(db_fd, LOCK_EX) < 0)
55 die("Cannot flock %s: %m", db_name);
58 if (fstat(db_fd, &st) < 0)
59 die("Cannot stat %s: %m", db_name);
60 if (st.st_size % sizeof(struct item))
61 die("Database %s is inconsistent: Size is not an integer number of records");
62 db_items = st.st_size / sizeof(struct item);
63 verb(2, "Mapping %d items", db_items);
65 db_map_size = sizeof(struct item) * (db_items+16);
66 db_map = mmap(NULL, db_map_size, PROT_READ | PROT_WRITE, MAP_SHARED, db_fd, 0);
67 if (db_map == MAP_FAILED)
68 die("Mmap on %s failed: %m", db_name);
74 munmap(db_map, db_map_size);
75 flock(db_fd, LOCK_UN);
80 db_lookup(struct item *new)
82 struct item *free = NULL;
84 for (uns i=0; i<db_items; i++)
86 struct item *t = &db_map[i];
87 if (t->timestamp <= now && now - t->timestamp > max_age)
92 else if (!memcmp(t->digest, new->digest, 20))
94 verb(2, "Found at item %d, age %d sec", i, now - t->timestamp);
102 if (sizeof(struct item) * db_items >= db_map_size)
103 die("Internal error: map window too small");
104 free = &db_map[db_items++];
105 if (ftruncate(db_fd, sizeof(struct item) * db_items) < 0)
106 die("Cannot enlarge %s: %m", db_name);
108 verb(2, "Creating new entry");
110 free->timestamp = now;
114 #define MAX_HDR_LEN 1024
133 else if (*c == '\\' && c[1])
140 parse_header_line(char *line, uns cnt, struct item *item)
144 if (cnt >= MAX_HDR_LEN)
146 verb(2, "HDR: <too long>");
151 verb(2, "HDR: %s", line);
152 if (strncasecmp(line, "Message-ID: ", 12))
154 char *c = skip_cfws(line+12);
158 char lhs[MAX_HDR_LEN], *l = lhs;
159 if (*c == '\"') // LHS is no-fold-quote
166 else if (*c == '\\' && c[1])
176 else // LHS is dot-atom-text
178 while (*c && *c != '@')
183 if (*c++ != '@') // "@" is mandatory
186 char rhs[MAX_HDR_LEN], *r = rhs;
187 if (*c == '[') // RHS is no-fold-literal
193 else if (*c == '\\' && c[1])
203 else // RHS is dot-atom-text
205 while (*c && *c != '>')
214 verb(1, "Parsed Message-ID <%s@%s>", lhs, rhs);
216 if (local_domain && local_user)
218 uns lul = strlen(local_user);
219 if (!strcasecmp(rhs, local_domain) &&
220 !strncasecmp(lhs, local_user, lul) &&
221 !strncasecmp(lhs+lul, "+md-", 4))
223 verb(1, "Detected local Message-ID");
231 sha1_update(&ctx, (unsigned char *) lhs, l-lhs);
232 sha1_update(&ctx, (unsigned char *) rhs, r-rhs);
233 sha1_final(&ctx, item->digest);
237 fprintf(stderr, "Digest: ");
238 for (uns i=0; i<20; i++)
239 fprintf(stderr, "%02x", item->digest[i]);
240 fprintf(stderr, "\n");
245 parse_header(struct item *item)
247 char buf[MAX_HDR_LEN+1];
257 verb(1, "Incomplete header");
264 if (cnt == last_nl) // End of header
266 parse_header_line(buf, cnt, item);
267 return item->timestamp;
271 else if (c == ' ' || c == '\t' || !c)
275 verb(1, "Misplaced whitespace at the beginning of header");
278 if (cnt < MAX_HDR_LEN)
285 parse_header_line(buf, cnt, item);
288 if (cnt < MAX_HDR_LEN)
297 fprintf(stderr, "Usage: mdup [<options>]\n\
300 -a <age>\t\tRecords older than <age> hours are ignored (default: 24)\n\
301 -d <db>\t\t\tUse <db> as a Message-ID database (default: ~/.mdup.db; beware of NFS)\n\
302 -l <user>@<domain>\tDetect looped back messages by their Message-ID\n\
303 -v\t\t\tIncrease verbosity\n\
305 MailDups " STR(VERSION) ", (c) " STR(YEAR) " Martin Mares <mj@ucw.cz>\n\
306 It can be freely distributed and used according to the GNU GPL v2.\n\
312 main(int argc, char **argv)
315 while ((c = getopt(argc, argv, "a:d:l:v")) >= 0)
319 max_age = atol(optarg) * 3600;
326 char *c = strchr(optarg, '@');
346 int ok = parse_header(&msg);
354 int ret = db_lookup(&msg);
356 puts(ret ? "DUP" : "OK");