--- checkpassword-pam-0.99.orig/checkpassword-pam.c	2016-02-22 16:27:23.493286410 +0100
+++ checkpassword-pam-0.99/checkpassword-pam.c	2016-02-22 16:24:37.846440833 +0100
@@ -17,6 +17,8 @@
 
 #include "logging.h"
 #include "pam-support.h"
+#include <stdint.h>
+#include "dovecot-anvil.h"
 
 #include <errno.h>
 #include <getopt.h>
@@ -36,7 +38,10 @@
 int opt_open_session = 0;
 int opt_use_stdout = 0;
 
-static const char* short_options = "dehs:CSHV";
+#define ANVIL_URI "unix:///var/run/dovecot/anvil-auth-penalty"
+static const char *opt_anvil_uri = NULL;
+
+static const char* short_options = "dehs:CSHVp::";
 
 enum { OPT_STDOUT = 1 };
 static struct option long_options[] = {
@@ -48,6 +53,7 @@
     { "service", required_argument, NULL, 's' },
     { "session", no_argument, NULL, 'S' },
     { "stdout", no_argument, NULL, OPT_STDOUT },
+    { "penalty", optional_argument, NULL, 'p' },
     { "version", no_argument, NULL, 'V' },
     { NULL, 0, NULL, 0 }
 };
@@ -69,6 +75,7 @@
 "\t\t\t(by default use the contents of $PAM_SERVICE)\n"
 "  -S, --session\t\topen and immediately close PAM session. some\n"
 "\t\t\tPAM plugins need this to work, such as pam_mkhomedir\n"
+"  -p, --penalty[=URI]\tenable dovecot anvil penalty\n"
 "  -V, --version\t\tdisplay version information and exit\n";
 
 
@@ -140,6 +147,10 @@
 	    opt_open_session = 1;
 	    break;
 
+    case 'p':
+        opt_anvil_uri = (optarg) ? strdup(optarg) : ANVIL_URI;
+        break;
+
 	case 'V':
 	    puts(PACKAGE " " VERSION);
 	    exit(0);
@@ -207,11 +218,44 @@
     }
     debugging("Password read successfully");
 
+    struct {
+        int fd;
+        const char *ident;
+        int penalty;
+    } anvil = { .fd = -1 };
+    if (opt_anvil_uri != NULL) {
+        if ((anvil.ident = anvil_get_ident_qmail()) != NULL)
+            anvil.fd = anvil_connect(opt_anvil_uri);
+
+        if (anvil.fd != -1) {
+            anvil.penalty = anvil_penalty_get(anvil.fd, anvil.ident,
+                anvil_penalty_callback_sleep);
+            if (anvil.penalty == -1) {
+                fatal("Anvil: Error while fetching penalty: %s", strerror(errno));
+                close(anvil.fd);
+                anvil.fd = -1;
+            }
+        }
+    }
+
     /* save pointer to oldusername to free memory if username gets changed */
     oldusername = username;
 
     /* authenticate using PAM */
     exit_status = authenticate_using_pam(service_name, &username, password);
+
+    if (anvil.fd != -1) {
+        if (exit_status == 0 && anvil.penalty > 0)
+            (void)anvil_penalty_set(anvil.fd, anvil.ident, 0);
+        else if (exit_status != 0) {
+            uint32_t checksum = (password == NULL) ? 0 :
+                crc32_str_more(crc32_str(password), username);
+            (void)anvil_penalty_inc(anvil.fd, anvil.ident, checksum);
+        }
+        close(anvil.fd);
+        anvil.fd = -1;
+    }
+
     if (exit_status != 0)
 	goto out;
     if (oldusername != NULL && username != oldusername)
--- /dev/null	2016-01-21 01:36:34.269852985 +0100
+++ checkpassword-pam-0.99/dovecot-anvil.h	2016-02-22 14:10:36.590324558 +0100
@@ -0,0 +1,37 @@
+/*
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2, or (at
+   your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   Copyright (c) Manuel Mausz <manuel@mausz.at> 2016
+
+   Dovecot anvil support for checkpassword-pam
+*/
+
+#ifndef DOVECOT_ANVIL_H
+#define DOVECOT_ANVIL_H
+
+char *anvil_get_ident_qmail();
+
+int anvil_connect(const char *uri);
+
+typedef int anvil_penalty_callback_t(uint16_t penalty,
+   unsigned long last_penalty);
+int anvil_penalty_get(int sockfd, const char *ident,
+   anvil_penalty_callback_t *callback);
+void anvil_penalty_inc(int sockfd, const char *ident, uint32_t checksum);
+void anvil_penalty_set(int sockfd, const char *ident, uint16_t value);
+
+int anvil_penalty_callback_sleep(uint16_t penalty,
+   unsigned long last_penalty);
+
+uint32_t crc32_str(const char *str) __attribute__((pure));
+uint32_t crc32_str_more(uint32_t crc, const char *str) __attribute__((pure));
+
+#endif
--- /dev/null	2016-01-21 01:36:34.269852985 +0100
+++ checkpassword-pam-0.99/dovecot-anvil.c	2016-02-22 16:25:46.514620459 +0100
@@ -0,0 +1,302 @@
+/*
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2, or (at
+   your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   Copyright (c) Manuel Mausz <manuel@mausz.at> 2016
+
+   Dovecot anvil support for checkpassword-pam
+*/
+
+#include "config.h"
+#include "logging.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+#include <inttypes.h>
+#include <arpa/inet.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netdb.h>
+#include <errno.h>
+
+#include "dovecot-anvil.h"
+
+#define ANVIL_HANDSHAKE "VERSION\tanvil\t1\t0\n"
+#define ANVIL_PENALTY_INIT_SECS 2
+#define ANVIL_PENALTY_MAX_SECS 60
+
+#define V4MAPPREFIX "::ffff:"
+
+/* We don't want IPv6 hosts being able to flood our penalty
+ *    tracking with tons of different IPs. */
+#define PENALTY_IPV6_MASK_BITS 48
+
+char *anvil_get_ident_qmail()
+{
+    const char *remote_ip = getenv("TCPREMOTEIP");
+    if (!remote_ip) {
+        fatal("Anvil: IP address not set");
+        return NULL;
+    }
+
+    /* check for ipv4 mapped ipv6 address */
+    const char *tmp = getenv("PROTO");
+    char remote_ip6[INET6_ADDRSTRLEN];
+    if (tmp && strcmp(tmp, "TCP6") == 0) {
+        if (!strncmp(remote_ip, V4MAPPREFIX, strlen(V4MAPPREFIX)))
+            remote_ip += strlen(V4MAPPREFIX);
+        else {
+            struct in6_addr ip;
+            if (inet_pton(AF_INET6, remote_ip, &ip) <= 0) {
+                fatal("Anvil: Invalid IP address: %s", remote_ip);
+                return NULL;
+            }
+            memset(ip.s6_addr + PENALTY_IPV6_MASK_BITS/CHAR_BIT, 0,
+                    sizeof(ip.s6_addr) - PENALTY_IPV6_MASK_BITS/CHAR_BIT);
+            inet_ntop(AF_INET6, &ip, remote_ip6, INET6_ADDRSTRLEN);
+            remote_ip = remote_ip6;
+        }
+    }
+    return strdup(remote_ip);
+}
+
+static int net_connect_inet(const char *host, const char *port)
+{
+    struct addrinfo hints = {
+        .ai_family = AF_UNSPEC,
+        .ai_socktype = SOCK_STREAM
+    };
+    struct addrinfo *result;
+
+    int s = getaddrinfo(host, port, &hints, &result);
+    if (s != 0) {
+        fatal("Anvil: getaddrinfo: %s", gai_strerror(s));
+        return -1;
+    }
+
+    //FIXME: connect might hang
+    int sockfd = -1;
+    for (struct addrinfo *r = result; r != NULL; r=r->ai_next) {
+        sockfd = socket(r->ai_family, r->ai_socktype | SOCK_CLOEXEC,
+            r->ai_protocol);
+        if (sockfd == -1)
+            continue;
+        if (connect(sockfd, r->ai_addr, r->ai_addrlen) != -1)
+            break; /* success */
+        close(sockfd);
+        sockfd = -1;
+        break; /* we only try the first record */
+    }
+
+    freeaddrinfo(result);
+    return sockfd;
+}
+
+static int net_connect_unix(const char *path)
+{
+    int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+    if (sockfd < 0)
+        return -1;
+
+    struct sockaddr_un saddr;
+    saddr.sun_family = AF_UNIX;
+    strcpy(saddr.sun_path, path);
+    if (connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) != -1)
+        return sockfd;
+
+    close(sockfd);
+    return -1;
+}
+
+static int net_connect(const char *uri)
+{
+    int sockfd = -1;
+    if (strncmp(uri, "tcp://", strlen("tcp://")) == 0) {
+        char *path = strdup(uri + strlen("tcp://"));
+
+        char *portptr = rindex(path, ':');
+        if (portptr == NULL) {
+            fatal("Anvil: Invalid uri: port missing?");
+            free(path);
+            return -1;
+        }
+        *portptr = '\0';
+
+        char *host = path;
+        if (host[0] == '[' && *(portptr - 1) == ']') {
+            host++;
+            *(portptr - 1) = '\0';
+        }
+
+        sockfd = net_connect_inet(host, portptr + 1);
+        free(path);
+    }
+    else if (strncmp(uri, "unix://", strlen("unix://")) == 0)
+        sockfd = net_connect_unix(uri + strlen("unix://"));
+    else {
+        fatal("Anvil: Invalid uri: unknown syntax");
+        return -1;
+    }
+
+    if (sockfd < 0)
+        fatal("Anvil: Unable to connect to socket: %s", strerror(errno));
+    return sockfd;
+}
+
+int anvil_connect(const char *uri)
+{
+    debugging("Anvil: connecting to %s", uri);
+    int sockfd = net_connect(uri);
+    if (sockfd < 0)
+        return -1;
+    if (send(sockfd, ANVIL_HANDSHAKE, strlen(ANVIL_HANDSHAKE), 0)
+            != strlen(ANVIL_HANDSHAKE)) {
+        fatal("Anvil: Error while sending handshake: %s", strerror(errno));
+        close(sockfd);
+        return -1;
+    }
+    return sockfd;
+}
+
+int anvil_penalty_get(int sockfd, const char *ident,
+    anvil_penalty_callback_t *callback)
+{
+    char buf[100];
+    int len = sprintf(buf, "PENALTY-GET\t%s\n", ident);
+    if (send(sockfd, buf, len, 0) != len)
+        return -1;
+
+    if ((len = recv(sockfd, &buf, sizeof(buf), 0)) < 0)
+        return -1;
+    buf[len] = '\0';
+
+    uint16_t penalty = 0;
+    unsigned long last_penalty = 0;
+    if (sscanf(buf, "%" SCNu16 " %lu", &penalty, &last_penalty) != 2) {
+        fatal("Anvil: Invalid PENALTY-GET reply: %s", buf);
+        return -1;
+    }
+
+    debugging("Anvil: penalty for '%s' is %u", ident, penalty);
+    return callback(penalty, last_penalty);
+}
+
+void anvil_penalty_inc(int sockfd, const char *ident, uint32_t checksum)
+{
+    char buf[100];
+    debugging("Anvil: incrementing penalty for '%s'", ident);
+    int len = sprintf(buf, "PENALTY-INC\t%s\t%u\n", ident, checksum);
+    (void)send(sockfd, buf, len, 0);
+}
+
+void anvil_penalty_set(int sockfd, const char *ident, uint16_t value)
+{
+    char buf[100];
+    debugging("Anvil: setting penalty for '%s' to %" PRIu16, ident, value);
+    int len = sprintf(buf, "PENALTY-SET\t%s\t%u\n", ident, value);
+    (void)send(sockfd, buf, len, 0);
+}
+
+static unsigned int anvil_penalty_to_secs(uint16_t penalty)
+{
+    unsigned int i, secs = ANVIL_PENALTY_INIT_SECS;
+
+    for (i = 0; i < penalty; i++)
+        secs *= 2;
+    return secs < ANVIL_PENALTY_MAX_SECS ? secs : ANVIL_PENALTY_MAX_SECS;
+}
+
+int anvil_penalty_callback_sleep(uint16_t penalty, unsigned long last_penalty)
+{
+    if (penalty > 0) {
+        unsigned int secs = anvil_penalty_to_secs(penalty);
+        debugging("Anvil: sleeping %u secs", secs);
+        sleep(secs);
+    }
+    return penalty;
+}
+
+/* copied from dovecot/lib/crc32.c, MIT licence */
+static uint32_t crc32tab[256] = {
+    0x00000000,
+    0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F,
+    0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E,
+    0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
+    0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D,
+    0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0,
+    0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63,
+    0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
+    0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA,
+    0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75,
+    0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, 0xC8D75180,
+    0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
+    0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87,
+    0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106,
+    0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5,
+    0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818,
+    0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4,
+    0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B,
+    0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA,
+    0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
+    0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541,
+    0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC,
+    0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F,
+    0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086,
+    0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E,
+    0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81,
+    0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C,
+    0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
+    0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B,
+    0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2,
+    0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671,
+    0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
+    0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8,
+    0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767,
+    0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6,
+    0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
+    0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795,
+    0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28,
+    0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B,
+    0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A,
+    0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82,
+    0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D,
+    0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8,
+    0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
+    0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF,
+    0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE,
+    0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D,
+    0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0,
+    0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C,
+    0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693,
+    0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02,
+    0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
+};
+
+uint32_t crc32_str(const char *str)
+{
+    return crc32_str_more(0, str);
+}
+
+uint32_t crc32_str_more(uint32_t crc, const char *str)
+{
+    const uint8_t *p = (const uint8_t *)str;
+
+    crc ^= 0xffffffff;
+    for (; *p != '\0'; p++)
+        crc = (crc >> 8) ^ crc32tab[((crc ^ *p) & 0xff)];
+    crc ^= 0xffffffff;
+    return crc;
+}
--- checkpassword-pam-0.99.orig/configure.ac	2004-08-17 23:29:27.000000000 +0200
+++ checkpassword-pam-0.99/configure.ac	2016-02-21 20:20:33.718992326 +0100
@@ -15,6 +15,7 @@
 
 AC_PROG_MAKE_SET
 AC_PROG_CC
+AC_PROG_CC_STDC
 
 AC_C_CONST
 
--- checkpassword-pam-0.99.orig/Makefile.am	2003-08-06 23:29:00.000000000 +0200
+++ checkpassword-pam-0.99/Makefile.am	2016-02-19 18:33:19.046494897 +0100
@@ -7,7 +7,7 @@
 bin_PROGRAMS = checkpassword-pam
 
 checkpassword_pam_SOURCES = checkpassword-pam.c logging.h pam-support.h pam-support.c \
-                            getopt.h getopt.c getopt1.c
+                            getopt.h getopt.c getopt1.c dovecot-anvil.c dovecot-anvil.h
 
 EXTRA_DIST = $(man_MANS) interface.html \
              debian/changelog debian/control debian/copyright \
