aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--Makefile7
-rw-r--r--defconfig4
-rw-r--r--dspam-exec.c340
-rw-r--r--plugin.c185
-rw-r--r--plugin.h18
5 files changed, 201 insertions, 353 deletions
diff --git a/Makefile b/Makefile
index 717c58d..691835f 100644
--- a/Makefile
+++ b/Makefile
@@ -13,7 +13,7 @@ CFLAGS += -I$(DOVECOT)/src/imap/
# per-backend configuration
ifeq ("$(BACKEND)", "dspam-exec")
-CFLAGS += -DCONFIG_PLUGIN_WANT_SIGNATURE=1
+CFLAGS += -DBACKEND_WANT_SIGNATURE=1
# can take a while, check more often
CFLAGS += -DCOPY_CHECK_INTERVAL=10
endif
@@ -38,8 +38,11 @@ ALL = antispam
all: verify_config $(ALL)
+%.o: %.c .config
+ $(CC) -c $(CFLAGS) -o $@ $<
+
antispam: $(objs)
- $(CC) $(CFLAGS) $(INCLUDES) $(objs) -o $@.so $(LDFLAGS)
+ $(CC) $(CFLAGS) $(objs) -o $@.so $(LDFLAGS)
clean:
rm -f *.so *.o *~
diff --git a/defconfig b/defconfig
index c9ac677..b1cd848 100644
--- a/defconfig
+++ b/defconfig
@@ -21,5 +21,5 @@ BACKEND=dspam-exec
# enable debugging to syslog or stderr
-DEBUG=stderr
-#DEBUG=syslog
+#DEBUG=stderr
+DEBUG=syslog
diff --git a/dspam-exec.c b/dspam-exec.c
index 71560f9..b413209 100644
--- a/dspam-exec.c
+++ b/dspam-exec.c
@@ -1,5 +1,5 @@
/*
- * dspam plugin for dovecot
+ * dspam backend for dovecot antispam plugin
*
* Copyright (C) 2004-2007 Johannes Berg <johannes@sipsolutions.net>
* 2006 Frank Cusack
@@ -16,52 +16,30 @@
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
- *
- * based on the original framework http://www.dovecot.org/patches/1.0/copy_plugin.c
- *
- * Please see http://johannes.sipsolutions.net/wiki/Projects/dovecot-dspam-integration
- * for more information on this code.
- *
- * Install the plugin in the usual dovecot module location.
*/
-#include <unistd.h>
-#include <sys/wait.h>
#include <stdlib.h>
-#include <stdio.h>
-#include <errno.h>
+#include <sys/wait.h>
#include <fcntl.h>
-#ifdef DEBUG
-#include <syslog.h>
-#endif
#include "lib.h"
-#include "mempool.h"
-#include "str.h"
-#include "strfuncs.h"
-#include "commands.h"
-#include "imap-search.h"
-#include "lib-storage/mail-storage.h"
-#include "mail-storage.h"
-#include "client.h"
-#define DEFAULT_SIGHDR "X-DSPAM-Signature"
-#define DEFAULT_DSPAM "/usr/bin/dspam"
-#define MAXSIGLEN 100
+#include "plugin.h"
-static int call_dspam(const char *signature, int is_spam)
+static const char *dspam_binary = "/usr/bin/dspam";
+
+static bool call_dspam(pool_t pool, const char *signature, bool is_spam)
{
pid_t pid;
- int s;
- char class_arg[16 + 2];
- char sign_arg[MAXSIGLEN + 2];
+ const char *class_arg;
+ const char *sign_arg;
int pipes[2];
- s = snprintf(sign_arg, 101, "--signature=%s", signature);
- if (s > MAXSIGLEN || s <= 0)
- return -1;
-
- snprintf(class_arg, 17, "--class=%s", is_spam ? "spam" : "innocent");
+ sign_arg = t_strconcat("--signature=", signature, NULL);
+ if (is_spam)
+ class_arg = t_strconcat("--class=", "spam", NULL);
+ else
+ class_arg = t_strconcat("--class=", "innocent", NULL);
pipe(pipes); /* for dspam stderr */
@@ -129,298 +107,42 @@ static int call_dspam(const char *signature, int is_spam)
}
close(fd);
-#ifdef DEBUG
- syslog(LOG_INFO, DSPAM " --source=error --stdout %s %s",
- class_arg, sign_arg);
-#endif
- execl(DSPAM, DSPAM, "--source=error", "--stdout", class_arg,
+ debug("antispam: %s --source=error --stdout %s %s",
+ dspam_binary, class_arg, sign_arg);
+ execl(dspam_binary, dspam_binary,
+ "--source=error", "--stdout", class_arg,
sign_arg, NULL);
exit(127); /* fall through if dspam can't be found */
return -1; /* never executed */
}
}
-struct dspam_signature_list {
- struct dspam_signature_list *next;
- char *sig;
-};
-typedef struct dspam_signature_list *siglist_t;
-
-static siglist_t list_append(pool_t pool, siglist_t * list)
+bool backend(pool_t pool, bool spam, struct strlist *sigs)
{
- siglist_t l = *list;
- siglist_t p = NULL;
- siglist_t n;
-
- while (l != NULL) {
- p = l;
- l = l->next;
- }
- n = p_malloc(pool, sizeof(struct dspam_signature_list));
- n->next = NULL;
- n->sig = NULL;
- if (p == NULL) {
- *list = n;
- } else {
- p->next = n;
- }
- return n;
-}
-
-static int
-fetch_and_copy_reclassified(struct mailbox_transaction_context *t,
- struct mailbox *srcbox,
- struct mail_search_arg *search_args,
- int is_spam, int *enh_error)
-{
- struct mail_search_context *search_ctx;
- struct mailbox_transaction_context *src_trans;
- struct mail_keywords *keywords;
- const char *const *keywords_list;
- struct mail *mail;
int ret;
- const char *signature;
- struct dspam_signature_list *siglist = NULL;
- pool_t listpool = pool_alloconly_create("dspam-siglist-pool", 1024);
-
- *enh_error = 0;
-
- src_trans = mailbox_transaction_begin(srcbox, 0);
- search_ctx = mailbox_search_init(src_trans, NULL, search_args, NULL);
-
- mail = mail_alloc(src_trans, MAIL_FETCH_STREAM_HEADER |
- MAIL_FETCH_STREAM_BODY, NULL);
- ret = 1;
- while (mailbox_search_next(search_ctx, mail) > 0 && ret > 0) {
- if (mail->expunged) {
- ret = 0;
- break;
- }
-
- signature = mail_get_first_header(mail, SIGHEADERLINE);
- if (is_empty_str(signature)) {
- ret = -1;
- *enh_error = -2;
- break;
- }
- list_append(listpool, &siglist)->sig =
- p_strdup(listpool, signature);
-
- keywords_list = mail_get_keywords(mail);
- keywords = strarray_length(keywords_list) == 0 ? NULL :
- mailbox_keywords_create(t, keywords_list);
- if (mailbox_copy(t, mail, mail_get_flags(mail),
- keywords, NULL) < 0)
- ret = -1;
- mailbox_keywords_free(t, &keywords);
- }
- mail_free(&mail);
-
- if (mailbox_search_deinit(&search_ctx) < 0)
- ret = -1;
-
/* got all signatures now, walk them passing to dspam */
- while (siglist) {
- if ((*enh_error = call_dspam(siglist->sig, is_spam))) {
- ret = -1;
- break;
- }
- siglist = siglist->next;
- }
-
- pool_unref(listpool);
-
- if (*enh_error) {
- mailbox_transaction_rollback(&src_trans);
- } else {
- if (mailbox_transaction_commit(&src_trans, 0) < 0)
- ret = -1;
- }
-
- return ret;
-}
-
-static bool cmd_append_spam_plugin(struct client_command_context *cmd)
-{
- const char *mailbox;
- struct mail_storage *storage;
- struct mailbox *box;
-
- /* <mailbox> */
- if (!client_read_string_args(cmd, 1, &mailbox))
- return FALSE;
-
- storage = client_find_storage(cmd, &mailbox);
- if (storage == NULL)
- return FALSE;
- /* TODO: is this really the best way to handle this? maybe more logic could be provided */
- box =
- mailbox_open(storage, mailbox, NULL,
- MAILBOX_OPEN_FAST | MAILBOX_OPEN_KEEP_RECENT);
- if (box != NULL) {
-
- if (mailbox_equals(box, storage, "SPAM")) {
- mailbox_close(&box);
- return cmd_sync(cmd, 0, 0,
- "NO Cannot APPEND to SPAM box, sorry.");
- }
-
- mailbox_close(&box);
+ while (sigs) {
+ ret = call_dspam(pool, sigs->str, spam);
+ if (ret)
+ return FALSE;
+ sigs = sigs->next;
}
- return cmd_append(cmd);
+ return TRUE;
}
-static bool cmd_copy_spam_plugin(struct client_command_context *cmd)
+void backend_init(pool_t pool)
{
- struct client *client = cmd->client;
- struct mail_storage *storage;
- struct mailbox *destbox;
- struct mailbox_transaction_context *t;
- struct mail_search_arg *search_arg;
- const char *messageset, *mailbox;
- enum mailbox_sync_flags sync_flags = 0;
- int ret;
- int spam_folder = 0;
- int enh_error = 0, is_spam;
-#ifdef IGNORE_TRASH_NAME
- int is_trash;
- int trash_folder = 0;
-#endif
- struct mailbox *box;
-
- /* <message set> <mailbox> */
- if (!client_read_string_args(cmd, 2, &messageset, &mailbox))
- return FALSE;
-
- if (!client_verify_open_mailbox(cmd))
- return TRUE;
-
- storage = client_find_storage(cmd, &mailbox);
- if (storage == NULL)
- return FALSE;
- box =
- mailbox_open(storage, mailbox, NULL,
- MAILBOX_OPEN_FAST | MAILBOX_OPEN_KEEP_RECENT);
- if (!box) {
- client_send_storage_error(cmd, storage);
- return TRUE;
- }
-
- is_spam = mailbox_equals(box, storage, "SPAM");
- spam_folder = is_spam
- || mailbox_equals(cmd->client->mailbox, storage, "SPAM");
-#ifdef IGNORE_TRASH_NAME
- is_trash = mailbox_equals(box, storage, IGNORE_TRASH_NAME);
- trash_folder = is_trash
- || mailbox_equals(cmd->client->mailbox, storage, IGNORE_TRASH_NAME);
-#endif
-
- mailbox_close(&box);
-
- /* only act on spam */
- if (!spam_folder)
- return cmd_copy(cmd);
-#ifdef IGNORE_TRASH_NAME
- /*
- * Ignore any mail going into or out of trash;
- * this means users can circumvent re-classification
- * by moving into trash and then out again...
- * All in all, it may be a better idea to not use
- * a Trash folder at all :)
- */
- if (trash_folder)
- return cmd_copy(cmd);
-#endif
-
- /* otherwise, do (almost) everything the copy would have done */
- /* open the destination mailbox */
- if (!client_verify_mailbox_name(cmd, mailbox, TRUE, FALSE))
- return TRUE;
+ char *tmp;
- search_arg = imap_search_get_arg(cmd, messageset, cmd->uid);
- if (search_arg == NULL)
- return TRUE;
-
- storage = client_find_storage(cmd, &mailbox);
- if (storage == NULL)
- return TRUE;
-
- if (mailbox_equals(client->mailbox, storage, mailbox))
- destbox = client->mailbox;
- else {
- destbox = mailbox_open(storage, mailbox, NULL,
- MAILBOX_OPEN_FAST |
- MAILBOX_OPEN_KEEP_RECENT);
- if (destbox == NULL) {
- client_send_storage_error(cmd, storage);
- return TRUE;
- }
+ tmp = getenv("ANTISPAM_DSPAM_BINARY");
+ if (tmp) {
+ dspam_binary = tmp;
+ debug("dspam binary set to %s\n", tmp);
}
-
- t = mailbox_transaction_begin(destbox,
- MAILBOX_TRANSACTION_FLAG_EXTERNAL);
- ret =
- fetch_and_copy_reclassified(t, client->mailbox, search_arg, is_spam,
- &enh_error);
-
- if (ret <= 0)
- mailbox_transaction_rollback(&t);
- else {
- if (mailbox_transaction_commit(&t, 0) < 0)
- ret = -1;
- }
-
- if (destbox != client->mailbox) {
- sync_flags |= MAILBOX_SYNC_FLAG_FAST;
- mailbox_close(&destbox);
- }
-
- if (ret > 0)
- return cmd_sync(cmd, sync_flags, 0, "OK Copy completed.");
- else if (ret == 0) {
- /* some messages were expunged, sync them */
- return cmd_sync(cmd, 0, 0,
- "NO Some of the requested messages no longer exist.");
- } else {
- switch (enh_error) {
- case -2:
- return cmd_sync(cmd, 0, 0,
- "NO Some messages did not have "
- SIGHEADERLINE " header line");
- break;
- case -3:
- return cmd_sync(cmd, 0, 0, "NO Failed to call dspam");
- break;
- case 0:
- client_send_storage_error(cmd, storage);
- return TRUE;
- break;
- default:
- return cmd_sync(cmd, 0, 0, "NO dspam failed");
- break;
- }
- }
-
- return TRUE;
-}
-
-void dspam_init(void)
-{
- command_unregister("COPY");
- command_unregister("APPEND");
- command_unregister("UID COPY");
- /*
- * i_strdup() here is a kludge to avoid crashing in commands_deinit()
- * since modules are unloaded before it's called, this "COPY" string
- * would otherwise point to nonexisting memory.
- */
- command_register(i_strdup("COPY"), cmd_copy_spam_plugin);
- command_register(i_strdup("UID COPY"), cmd_copy_spam_plugin);
- command_register(i_strdup("APPEND"), cmd_append_spam_plugin);
}
-void dspam_deinit(void)
+void backend_exit(void)
{
}
diff --git a/plugin.c b/plugin.c
index 3f7eb97..944c446 100644
--- a/plugin.c
+++ b/plugin.c
@@ -25,43 +25,53 @@
* Install the plugin in the usual dovecot module location.
*/
-#include <unistd.h>
-#include <sys/wait.h>
#include <stdlib.h>
-#include <stdio.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <time.h>
-#ifdef DEBUG
-#include <syslog.h>
-#endif
/* dovecot headers we need */
#include "lib.h"
-#include "mempool.h"
-#include "str.h"
-#include "strfuncs.h"
-#include "commands.h"
-#include "imap-search.h"
-#include "lib-storage/mail-storage.h"
-#include "mail-storage.h"
#include "client.h"
#include "ostream.h"
+#include "imap-search.h"
/* internal stuff we need */
#include "plugin.h"
-static struct signature *list_add(pool_t pool, struct signature *list)
+static pool_t global_pool;
+static char **trash_folders = NULL;
+static char *default_spam_folders[] = {
+ "SPAM",
+ NULL
+};
+static char **spam_folders = default_spam_folders;
+#ifdef BACKEND_WANT_SIGNATURE
+static char *signature_hdr = "X-DSPAM-Signature";
+#endif
+
+static struct strlist *list_add(pool_t pool, struct strlist *list)
{
- struct signature *n;
+ struct strlist *n;
- n = p_malloc(pool, sizeof(struct signature));
+ n = p_malloc(pool, sizeof(struct strlist));
n->next = list;
- n->sig = NULL;
+ n->str = NULL;
return n;
}
+static bool mailbox_in_list(struct mail_storage *storage, struct mailbox *box,
+ char **list)
+{
+ if (!list)
+ return FALSE;
+
+ while (*list) {
+ if (mailbox_equals(box, storage, *list))
+ return TRUE;
+ list++;
+ }
+
+ return FALSE;
+}
static void client_send_sendalive_if_needed(struct client *client)
{
@@ -79,9 +89,16 @@ static void client_send_sendalive_if_needed(struct client *client)
}
}
+#define GENERIC_ERROR -1
+#define SIGNATURE_MISSING -2
+#define BACKEND_FAILURE -3
+
+/* mostly copied from cmd-copy.c (notes added where changed) */
+/* MODIFIED: prototype to include "src_spam" */
static int fetch_and_copy(struct client *client,
struct mailbox_transaction_context *t,
- struct mail_search_arg *search_args)
+ struct mail_search_arg *search_args,
+ bool src_spam)
{
struct mail_search_context *search_ctx;
struct mailbox_transaction_context *src_trans;
@@ -90,6 +107,14 @@ static int fetch_and_copy(struct client *client,
struct mail *mail;
unsigned int copy_count = 0;
int ret;
+ /* MODIFIED: new variables */
+ pool_t pool = pool_alloconly_create("antispam-copy-pool", 1024);
+#ifdef BACKEND_WANT_SIGNATURE
+ const char *signature;
+ struct strlist *siglist = NULL;
+#else
+#error Not implemented
+#endif
src_trans = mailbox_transaction_begin(client->mailbox, 0);
search_ctx = mailbox_search_init(src_trans, NULL, search_args, NULL);
@@ -106,6 +131,19 @@ static int fetch_and_copy(struct client *client,
if ((++copy_count % COPY_CHECK_INTERVAL) == 0)
client_send_sendalive_if_needed(client);
+ /* MODIFIED: keep track of mail as we copy */
+#ifdef BACKEND_WANT_SIGNATURE
+ signature = mail_get_first_header(mail, signature_hdr);
+ if (is_empty_str(signature)) {
+ ret = SIGNATURE_MISSING;
+ break;
+ }
+ siglist = list_add(pool, siglist);
+ siglist->str = p_strdup(pool, signature);
+#else
+#error Not implemented
+#endif
+
keywords_list = mail_get_keywords(mail);
keywords = strarray_length(keywords_list) == 0 ? NULL :
mailbox_keywords_create(t, keywords_list);
@@ -122,10 +160,25 @@ static int fetch_and_copy(struct client *client,
if (mailbox_transaction_commit(&src_trans, 0) < 0)
ret = -1;
+ /* MODIFIED: pass to backend */
+#ifdef BACKEND_WANT_SIGNATURE
+ /* got all signatures now, pass them to backend if no errors */
+ if (ret == 0) {
+ ret = backend(pool, src_spam, siglist);
+ if (ret)
+ ret = BACKEND_FAILURE;
+ }
+#else
+#error Not implemented
+#endif
+ /* MODIFIED: kill pool */
+ pool_unref(pool);
+
return ret;
}
+/* mostly copied from cmd-copy.c (notes added where changed) */
static bool cmd_copy_antispam(struct client_command_context *cmd)
{
struct client *client = cmd->client;
@@ -136,6 +189,8 @@ static bool cmd_copy_antispam(struct client_command_context *cmd)
const char *messageset, *mailbox;
enum mailbox_sync_flags sync_flags = 0;
int ret;
+ /* MODIFIED: added variables */
+ bool dst_spam, src_spam;
/* <message set> <mailbox> */
if (!client_read_string_args(cmd, 2, &messageset, &mailbox))
@@ -156,9 +211,11 @@ static bool cmd_copy_antispam(struct client_command_context *cmd)
if (storage == NULL)
return TRUE;
- if (mailbox_equals(client->mailbox, storage, mailbox))
+ if (mailbox_equals(client->mailbox, storage, mailbox)) {
destbox = client->mailbox;
- else {
+ /* MODIFIED: don't try to reclassify on copy within folder */
+ return cmd_copy(cmd);
+ } else {
destbox = mailbox_open(storage, mailbox, NULL,
MAILBOX_OPEN_SAVEONLY |
MAILBOX_OPEN_FAST |
@@ -169,9 +226,29 @@ static bool cmd_copy_antispam(struct client_command_context *cmd)
}
}
+ /* MODIFIED: Trash detection */
+ if (mailbox_in_list(storage, client->mailbox, trash_folders) ||
+ mailbox_in_list(storage, destbox, trash_folders)) {
+ mailbox_close(&destbox);
+ return cmd_copy(cmd);
+ }
+
+ /* MODIFIED: from/to-SPAM detection */
+ src_spam = mailbox_in_list(storage, client->mailbox, spam_folders);
+ dst_spam = mailbox_in_list(storage, destbox, spam_folders);
+ /*
+ * "both spam" can happen with multiple spam folders,
+ * "none spam" is the common case where spam folders are not involved
+ */
+ if ((src_spam && dst_spam) ||
+ (!src_spam && !dst_spam)) {
+ mailbox_close(&destbox);
+ return cmd_copy(cmd);
+ }
+
t = mailbox_transaction_begin(destbox,
MAILBOX_TRANSACTION_FLAG_EXTERNAL);
- ret = fetch_and_copy(client, t, search_arg);
+ ret = fetch_and_copy(client, t, search_arg, src_spam);
if (ret <= 0)
mailbox_transaction_rollback(&t);
@@ -209,17 +286,16 @@ static bool cmd_append_antispam(struct client_command_context *cmd)
return FALSE;
storage = client_find_storage(cmd, &mailbox);
- if (storage == NULL)
+ if (!storage)
return FALSE;
- box =
- mailbox_open(storage, mailbox, NULL,
- MAILBOX_OPEN_FAST | MAILBOX_OPEN_KEEP_RECENT);
- if (box != NULL) {
- if (mailbox_equals(box, storage, "SPAM")) {
+ box = mailbox_open(storage, mailbox, NULL,
+ MAILBOX_OPEN_FAST | MAILBOX_OPEN_KEEP_RECENT);
+ if (box) {
+ if (mailbox_in_list(storage, box, spam_folders)) {
mailbox_close(&box);
return cmd_sync(cmd, 0, 0,
- "NO Cannot APPEND to SPAM box, sorry.");
+ "NO Cannot APPEND to spam folder.");
}
mailbox_close(&box);
@@ -229,8 +305,49 @@ static bool cmd_append_antispam(struct client_command_context *cmd)
}
-void dspam_init(void)
+void antispam_init(void)
{
+ char *tmp, **iter;
+
+ debug("antispam plugin intialising\n");
+
+ global_pool = pool_alloconly_create("antispam-pool", 1024);
+
+ tmp = getenv("ANTISPAM_TRASH");
+ if (tmp)
+ trash_folders = p_strsplit(global_pool, tmp, ";");
+
+ if (trash_folders) {
+ iter = trash_folders;
+ while (*iter) {
+ debug("antispam: \"%s\" is trash folder\n", *iter);
+ iter++;
+ }
+ } else
+ debug("antispam: no trash folders\n");
+
+ tmp = getenv("ANTISPAM_SPAM");
+ if (tmp)
+ spam_folders = p_strsplit(global_pool, tmp, ";");
+
+ if (spam_folders) {
+ iter = spam_folders;
+ while (*iter) {
+ debug("antispam: \"%s\" is spam folder\n", *iter);
+ iter++;
+ }
+ } else
+ debug("antispam: no spam folders\n");
+
+#ifdef BACKEND_WANT_SIGNATURE
+ tmp = getenv("ANTISPAM_SIGNATURE");
+ if (tmp)
+ signature_hdr = tmp;
+ debug("antispam: signature header line is \"%s\"\n", signature_hdr);
+#endif
+
+ backend_init(global_pool);
+
command_unregister("COPY");
command_unregister("APPEND");
command_unregister("UID COPY");
@@ -244,6 +361,8 @@ void dspam_init(void)
command_register(i_strdup("APPEND"), cmd_append_antispam);
}
-void dspam_deinit(void)
+void antispam_deinit(void)
{
+ backend_exit();
+ pool_unref(global_pool);
}
diff --git a/plugin.h b/plugin.h
index e55bd05..99126eb 100644
--- a/plugin.h
+++ b/plugin.h
@@ -2,14 +2,15 @@
#define _ANTISPAM_PLUGIN_H
#include <unistd.h>
+#include "lib.h"
#include "mempool.h"
-struct signature {
- struct signature *next;
- char *sig;
+struct strlist {
+ struct strlist *next;
+ const char *str;
};
-#ifdef CONFIG_PLUGIN_WANT_SIGNATURE
+#ifdef BACKEND_WANT_SIGNATURE
/*
* Call backend giving
* - pool: dovecot memory pool, will be freed afterwards
@@ -17,18 +18,21 @@ struct signature {
* - sigs: signatures, next == NULL terminates list
* -
*/
-int backend(pool_t pool, int spam, struct signature *sigs);
+bool backend(pool_t pool, bool spam, struct strlist *sigs);
#elif CONFIG_PLUGIN_WANT_MAIL
#error TODO: no support for pristine training yet
#else
#error BUILD SYSTEM ERROR
#endif
+void backend_init(pool_t pool);
+void backend_exit(void);
+
#ifdef CONFIG_DEBUG
void debug(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
#else
-static inline void
-debug(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)))
+static void debug(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
+static inline void debug(const char *fmt, ...)
{
}
#endif