#include #include #include #include #include #include "dns.h" #include "qrschedule.h" #include "qremote.h" #ifdef DEBUG #undef NDEBUG #define ASSERT(x) assert(x) #else #ifndef NDEBUG #define NDEBUG #endif #define ASSERT(x) do { } while (0); #endif /** * qsortdomain - callback for qsort() to sort MX entries by domain name * @a: first MX entry * @b: second MX entry * @returns: <0 if a1 if a>b */ static int qsortdomain(const void *a, const void *b) { const struct recip *ra = a; const struct recip *rb = b; return strcasecmp(ra->domain, rb->domain); } /** * cleanupmxlist - sort and normalize a list of MX entries * @ips: pointer to list * * This functions sorts the entries by MX priority and normalizes them. That * means the highest priority MX will all have prio 0, the next 1 and so on * regardless what the original prio values where. The order of IPs in one * priority level will not be changed so that load balancing by DNS round * robin is not disturbed. */ static void cleanupmxlist(struct ips **ips) { unsigned int o, n; struct ips *cur; sortmx(ips); n = 0; cur = *ips; o = cur->priority; do { cur->priority = n; cur = cur->next; if (!cur) return; if (cur->priority != o) { o = cur->priority; n++; } } while (1); } /** * findfirstunmatched - find first MX entry that is only in one list * @a: list to check * @a: list to compare to * @returns: first unmatched entry in @a or NULL if none found * * A entry is called unmatched if it has no matching entry in the * same priority class of the other list. */ static const struct ips * findfirstunmatched(const struct ips *a, const struct ips *b) { const struct ips *acur = a; /* the current item in a */ const struct ips *bcur = b; /* the first item of this prio in b */ const struct ips *tmp; /* the item in b currently checked */ while (acur) { tmp = bcur; do { int r = memcmp(&acur->addr, &tmp->addr, sizeof(acur->addr)); if (!r) { if (acur->next && (acur->priority != acur->next->priority)) { /* all entries of this prio match */ do { bcur = bcur->next; } while (bcur && (bcur->priority == acur->priority)); } acur = acur->next; break; } tmp = tmp->next; if (!tmp || (tmp->priority != bcur->priority)) return acur; } while (tmp); } return NULL; } /** * qsortmx - callback for qsort() to sort MX entries by IP list * @a: first entry to compare * @b: second entry to compare * @returns: -1 if a1 if a>b */ static int qsortmx(const void *a, const void *b) { const struct ips *am = (*((struct recip **)a))->mx; const struct ips *bm = (*((struct recip **)b))->mx; const struct ips *acur, *bcur; /* step one: if the number of elements differ the list with less entries is the smaller one */ acur = am; bcur = bm; while (acur && bcur) { int r = acur->priority - bcur->priority; if (r) return r; acur = acur->next; bcur = bcur->next; } if (acur || bcur) return ((unsigned long)acur) - ((unsigned long)bcur); /* now we know the lists have the same number of entries for all priorities */ /* check both: there might be duplicates */ acur = findfirstunmatched(am, bm); bcur = findfirstunmatched(bm, am); if (!acur && !bcur) return 0; return memcmp(&acur->addr, &bcur->addr, sizeof(acur->addr)); } /** * sortdomains - sort recipients by domain * @recips: list to sort * @num: number of entries in @recips * @returns: pointer to start of sorted list on success, NULL on error */ static struct recip * sortdomains(struct recip *recips, const int num) { int i, r; int domains = 0; struct recip **mxdom; struct recip *cur; /* pass 1: get domain pointer for every address */ for (i = num - 1; i >= 0; i--) { struct recip *current = recips + i; current->domain = strchr(current->address, '@'); ASSERT(current->domain != NULL); current->domain++; current->next = NULL; } /* pass 2: sort the recipients by domain */ qsort(recips, num, sizeof(*recips), qsortdomain); /* pass 3: MX lookups */ for (i = 0; i < num; i++) { struct recip *current = recips + i; unsigned int port; /* smtproute stores targetport here */ /* we only look up a domain once... */ if ((i > 0) && !strcasecmp(recips[i - 1].domain, current->domain)) continue; current->mx = smtproute(current->domain, strlen(current->domain), &port); if (current->mx == NULL) { switch (errno) { case 0: r = ask_dnsmx(current->domain, ¤t->mx); domains++; if (r) { switch (r) { case -1: current->msg = "Zout_of_memory\n"; break; case 1: current->msg = "Zhost_not_existent\n"; break; case 2: current->msg = "Ztemporary_dns_error\n"; break; case 3: current->msg = "Zhard_DNS_error\n"; break; } continue; } break; case ENOMEM: current->msg = "Zout_of_memory\n"; break; } } cleanupmxlist(¤t->mx); } /* if there is only one target domain we don't need any sorting */ if (domains == 1) return recips; /* pass 4: sort the domains by MX */ mxdom = malloc(sizeof(*mxdom) * domains); if (!mxdom) return recips; /* fallback to single domain delivery */ r = 0; for (i = 0; i < domains; i++) { mxdom[i] = recips + r; do { r++; } while ((r < num) && !recips[r].mx); } qsort(mxdom, domains, sizeof(void *), qsortmx); /* pass 5: remove superfluous MX entries and build up list */ /* 5a: build up a list for every domain */ for (i = num - 1; i > 0; i--) { if (!recips[i].mx) recips[i - 1].next = (recips + i); else recips[i - 1].next = NULL; } recips[num - 1].next = NULL; /* 5b: change the list order according to mxdom order */ cur = mxdom[0]; for (i = 0; i < domains; i++) { struct recip *tmp = cur; while (tmp->next && !tmp->next->mx) tmp = tmp->next; if (i < domains - 1) { tmp->next = mxdom[i + 1]; tmp = tmp->next; if (!qsortmx(&cur, &tmp)) { /* both domains deliver to the same MX: unify */ freeips(tmp->mx); tmp->mx = NULL; continue; } } else { tmp->next = NULL; } if (i < domains - 1) cur = mxdom[i + 1]; } cur = mxdom[0]; free(mxdom); return cur; } /** * rschedule - schedule recips for remote delivery * @recips: recipient entries * @num: number of recipients * @returns: 0 on success, -1 on error */ int rschedule(struct recip *recips, const int num) { struct recip *sorted, *cur; sorted = sortdomains(recips, num); if (sorted == NULL) return -1; cur = sorted; do { struct recip *tmp = cur->next; int r = 1; // number of recipients in this turn while (tmp && !tmp->mx) { tmp = tmp->next; r++; } rsend(cur, r); cur = tmp; } while (cur); return 0; }