/*-
 * Copyright 2017 Vsevolod Stakhov
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef RSPAMD_MILTER_INTERNAL_H
#define RSPAMD_MILTER_INTERNAL_H

#include "config.h"
#include "libutil/mem_pool.h"
#include "contrib/libev/ev.h"
#include "khash.h"
#include "libutil/str_util.h"
#include "libutil/libev_helper.h"

#ifdef __cplusplus
extern "C" {
#endif

enum rspamd_milter_state {
	st_len_1 = 0,
	st_len_2,
	st_len_3,
	st_len_4,
	st_read_cmd,
	st_read_data
};

struct rspamd_milter_parser {
	rspamd_fstring_t *buf;
	goffset pos;
	goffset cmd_start;
	gsize datalen;
	enum rspamd_milter_state state;
	char cur_cmd;
};

struct rspamd_milter_outbuf {
	rspamd_fstring_t *buf;
	goffset pos;
	struct rspamd_milter_outbuf *next, *prev;
};

enum rspamd_milter_io_state {
	RSPAMD_MILTER_READ_MORE,
	RSPAMD_MILTER_WRITE_REPLY,
	RSPAMD_MILTER_WANNA_DIE,
	RSPAMD_MILTER_WRITE_AND_DIE,
	RSPAMD_MILTER_PONG_AND_DIE,
};

KHASH_INIT(milter_headers_hash_t, char *, GArray *, true,
		   rspamd_strcase_hash, rspamd_strcase_equal);

struct rspamd_milter_private {
	struct rspamd_milter_parser parser;
	struct rspamd_io_ev ev;
	struct rspamd_milter_outbuf *out_chain;
	struct ev_loop *event_loop;
	rspamd_mempool_t *pool;
	khash_t(milter_headers_hash_t) * headers;
	int cur_hdr;
	rspamd_milter_finish fin_cb;
	rspamd_milter_error err_cb;
	void *ud;
	enum rspamd_milter_io_state state;
	int fd;
	gboolean discard_on_reject;
	gboolean quarantine_on_reject;
	gboolean no_action;
};

enum rspamd_milter_io_cmd {
	RSPAMD_MILTER_CMD_ABORT = 'A',   /* Abort */
	RSPAMD_MILTER_CMD_BODY = 'B',    /* Body chunk */
	RSPAMD_MILTER_CMD_CONNECT = 'C', /* Connection information */
	RSPAMD_MILTER_CMD_MACRO = 'D',   /* Define macro */
	RSPAMD_MILTER_CMD_BODYEOB = 'E', /* final body chunk (end of message) */
	RSPAMD_MILTER_CMD_HELO = 'H',    /* HELO/EHLO */
	RSPAMD_MILTER_CMD_QUIT_NC = 'K', /* QUIT but new connection follows */
	RSPAMD_MILTER_CMD_HEADER = 'L',  /* Header */
	RSPAMD_MILTER_CMD_MAIL = 'M',    /* MAIL from */
	RSPAMD_MILTER_CMD_EOH = 'N',     /* EOH */
	RSPAMD_MILTER_CMD_OPTNEG = 'O',  /* Option negotiation */
	RSPAMD_MILTER_CMD_QUIT = 'Q',    /* QUIT */
	RSPAMD_MILTER_CMD_RCPT = 'R',    /* RCPT to */
	RSPAMD_MILTER_CMD_DATA = 'T',    /* DATA */
	RSPAMD_MILTER_CMD_UNKNOWN = 'U'  /* Any unknown command */
};

/*
 * Protocol flags
 */
#define RSPAMD_MILTER_FLAG_NOUNKNOWN (1L << 8) /* filter does not want unknown cmd */
#define RSPAMD_MILTER_FLAG_NODATA (1L << 9)    /* filter does not want DATA */
#define RSPAMD_MILTER_FLAG_NR_HDR (1L << 7)    /* filter won't reply for header */
#define RSPAMD_MILTER_FLAG_SKIP (1L << 10)     /* MTA supports SMFIR_SKIP */
#define RSPAMD_MILTER_FLAG_RCPT_REJ (1L << 11) /* filter wants rejected RCPTs */
#define RSPAMD_MILTER_FLAG_NR_CONN (1L << 12)  /* filter won't reply for connect */
#define RSPAMD_MILTER_FLAG_NR_HELO (1L << 13)  /* filter won't reply for HELO */
#define RSPAMD_MILTER_FLAG_NR_MAIL (1L << 14)  /* filter won't reply for MAIL */
#define RSPAMD_MILTER_FLAG_NR_RCPT (1L << 15)  /* filter won't reply for RCPT */
#define RSPAMD_MILTER_FLAG_NR_DATA (1L << 16)  /* filter won't reply for DATA */
#define RSPAMD_MILTER_FLAG_NR_UNKN (1L << 17)  /* filter won't reply for UNKNOWN */
#define RSPAMD_MILTER_FLAG_NR_EOH (1L << 18)   /* filter won't reply for eoh */
#define RSPAMD_MILTER_FLAG_NR_BODY (1L << 19)  /* filter won't reply for body chunk */

/*
 * For now, we specify that we want to reply just after EOM
 */
#define RSPAMD_MILTER_FLAG_NOREPLY_MASK                        \
	(RSPAMD_MILTER_FLAG_NR_CONN | RSPAMD_MILTER_FLAG_NR_HELO | \
	 RSPAMD_MILTER_FLAG_NR_MAIL | RSPAMD_MILTER_FLAG_NR_RCPT | \
	 RSPAMD_MILTER_FLAG_NR_DATA | RSPAMD_MILTER_FLAG_NR_UNKN | \
	 RSPAMD_MILTER_FLAG_NR_HDR | RSPAMD_MILTER_FLAG_NR_EOH |   \
	 RSPAMD_MILTER_FLAG_NR_BODY)

/*
 * Options that the filter may send at initial handshake time, and message
 * modifications that the filter may request at the end of the message body.
 */
#define RSPAMD_MILTER_FLAG_ADDHDRS (1L << 0)    /* filter may add headers */
#define RSPAMD_MILTER_FLAG_CHGBODY (1L << 1)    /* filter may replace body */
#define RSPAMD_MILTER_FLAG_ADDRCPT (1L << 2)    /* filter may add recipients */
#define RSPAMD_MILTER_FLAG_DELRCPT (1L << 3)    /* filter may delete recipients */
#define RSPAMD_MILTER_FLAG_CHGHDRS (1L << 4)    /* filter may change/delete headers */
#define RSPAMD_MILTER_FLAG_QUARANTINE (1L << 5) /* filter may request quarantine */

#define RSPAMD_MILTER_ACTIONS_MASK                             \
	(RSPAMD_MILTER_FLAG_ADDHDRS | RSPAMD_MILTER_FLAG_ADDRCPT | \
	 RSPAMD_MILTER_FLAG_DELRCPT | RSPAMD_MILTER_FLAG_CHGHDRS | \
	 RSPAMD_MILTER_FLAG_CHGBODY | RSPAMD_MILTER_FLAG_QUARANTINE)

enum rspamd_milter_connect_proto {
	RSPAMD_MILTER_CONN_UNKNOWN = 'U',
	RSPAMD_MILTER_CONN_UNIX = 'L',
	RSPAMD_MILTER_CONN_INET = '4',
	RSPAMD_MILTER_CONN_INET6 = '6',
};

/*
 * Rspamd supports just version 6 of the protocol, failing all versions below
 * this one
 */
#define RSPAMD_MILTER_PROTO_VER 6

#define RSPAMD_MILTER_MESSAGE_CHUNK 65536

#define RSPAMD_MILTER_RCODE_REJECT "554"
#define RSPAMD_MILTER_RCODE_TEMPFAIL "451"
#define RSPAMD_MILTER_RCODE_LATER "452"
#define RSPAMD_MILTER_XCODE_REJECT "5.7.1"
#define RSPAMD_MILTER_XCODE_TEMPFAIL "4.7.1"
#define RSPAMD_MILTER_REJECT_MESSAGE "Spam message rejected"
#define RSPAMD_MILTER_QUARANTINE_MESSAGE "Spam message quarantined"
#define RSPAMD_MILTER_TEMPFAIL_MESSAGE "Try again later"
#define RSPAMD_MILTER_SPAM_HEADER "X-Spam"
#define RSPAMD_MILTER_DKIM_HEADER "DKIM-Signature"
#define RSPAMD_MILTER_ACTION_HEADER "X-Rspamd-Action"

#ifdef __cplusplus
}
#endif

#endif