1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
//! Low-level FFI bindings to libmilter.

#![allow(non_camel_case_types)]

use libc::{c_char, c_int, c_uchar, c_uint, c_ulong, c_void, size_t};

pub const SMFI_VERSION: c_int = 0x1000001;
pub const SMFI_PROT_VERSION: c_int = 6;

pub const MILTER_LEN_BYTES: size_t = 4;
pub const MILTER_OPTLEN: size_t = MILTER_LEN_BYTES * 3;
pub const MILTER_CHUNK_SIZE: size_t = 65535;
pub const MILTER_MAX_DATA_SIZE: size_t = 65535;

pub const SMFI_V1_ACTS: c_ulong = 0xf;
pub const SMFI_V2_ACTS: c_ulong = 0x3f;
pub const SMFI_CURR_ACTS: c_ulong = 0x1ff;

pub const SMFIA_UNKNOWN: c_uchar = b'U';
pub const SMFIA_UNIX: c_uchar = b'L';
pub const SMFIA_INET: c_uchar = b'4';
pub const SMFIA_INET6: c_uchar = b'6';

pub const SMFIC_ABORT: c_uchar = b'A';
pub const SMFIC_BODY: c_uchar = b'B';
pub const SMFIC_CONNECT: c_uchar = b'C';
pub const SMFIC_MACRO: c_uchar = b'D';
pub const SMFIC_BODYEOB: c_uchar = b'E';
pub const SMFIC_HELO: c_uchar = b'H';
pub const SMFIC_QUIT_NC: c_uchar = b'K';
pub const SMFIC_HEADER: c_uchar = b'L';
pub const SMFIC_MAIL: c_uchar = b'M';
pub const SMFIC_EOH: c_uchar = b'N';
pub const SMFIC_OPTNEG: c_uchar = b'O';
pub const SMFIC_QUIT: c_uchar = b'Q';
pub const SMFIC_RCPT: c_uchar = b'R';
pub const SMFIC_DATA: c_uchar = b'T';
pub const SMFIC_UNKNOWN: c_uchar = b'U';

pub const SMFIR_ADDRCPT: c_uchar = b'+';
pub const SMFIR_DELRCPT: c_uchar = b'-';
pub const SMFIR_ADDRCPT_PAR: c_uchar = b'2';
pub const SMFIR_SHUTDOWN: c_uchar = b'4';
pub const SMFIR_ACCEPT: c_uchar = b'a';
pub const SMFIR_REPLBODY: c_uchar = b'b';
pub const SMFIR_CONTINUE: c_uchar = b'c';
pub const SMFIR_DISCARD: c_uchar = b'd';
pub const SMFIR_CHGFROM: c_uchar = b'e';
pub const SMFIR_CONN_FAIL: c_uchar = b'f';
pub const SMFIR_ADDHEADER: c_uchar = b'h';
pub const SMFIR_INSHEADER: c_uchar = b'i';
pub const SMFIR_SETSYMLIST: c_uchar = b'l';
pub const SMFIR_CHGHEADER: c_uchar = b'm';
pub const SMFIR_PROGRESS: c_uchar = b'p';
pub const SMFIR_QUARANTINE: c_uchar = b'q';
pub const SMFIR_REJECT: c_uchar = b'r';
pub const SMFIR_SKIP: c_uchar = b's';
pub const SMFIR_TEMPFAIL: c_uchar = b't';
pub const SMFIR_REPLYCODE: c_uchar = b'y';

pub const SMFIP_NOCONNECT: c_ulong = 0x1;
pub const SMFIP_NOHELO: c_ulong = 0x2;
pub const SMFIP_NOMAIL: c_ulong = 0x4;
pub const SMFIP_NORCPT: c_ulong = 0x8;
pub const SMFIP_NOBODY: c_ulong = 0x10;
pub const SMFIP_NOHDRS: c_ulong = 0x20;
pub const SMFIP_NOEOH: c_ulong = 0x40;
pub const SMFIP_NR_HDR: c_ulong = 0x80;
pub const SMFIP_NOHREPL: c_ulong = SMFIP_NR_HDR;
pub const SMFIP_NOUNKNOWN: c_ulong = 0x100;
pub const SMFIP_NODATA: c_ulong = 0x200;
pub const SMFIP_SKIP: c_ulong = 0x400;
pub const SMFIP_RCPT_REJ: c_ulong = 0x800;
pub const SMFIP_NR_CONN: c_ulong = 0x1000;
pub const SMFIP_NR_HELO: c_ulong = 0x2000;
pub const SMFIP_NR_MAIL: c_ulong = 0x4000;
pub const SMFIP_NR_RCPT: c_ulong = 0x8000;
pub const SMFIP_NR_DATA: c_ulong = 0x10000;
pub const SMFIP_NR_UNKN: c_ulong = 0x20000;
pub const SMFIP_NR_EOH: c_ulong = 0x40000;
pub const SMFIP_NR_BODY: c_ulong = 0x80000;
pub const SMFIP_HDR_LEADSPC: c_ulong = 0x100000;
pub const SMFIP_MDS_256K: c_ulong = 0x10000000;
pub const SMFIP_MDS_1M: c_ulong = 0x20000000;

pub const SMFI_V1_PROT: c_ulong = 0x3f;
pub const SMFI_V2_PROT: c_ulong = 0x7f;
pub const SMFI_CURR_PROT: c_ulong = 0x1fffff;
pub const SMFI_INTERNAL: c_ulong = 0x70000000;

pub const MI_SUCCESS: c_int = 0;
pub const MI_FAILURE: c_int = -1;
pub const MI_CONTINUE: c_int = 1;

pub const SMFIF_NONE: c_ulong = 0x0;
pub const SMFIF_ADDHDRS: c_ulong = 0x1;
pub const SMFIF_CHGBODY: c_ulong = 0x2;
pub const SMFIF_MODBODY: c_ulong = SMFIF_CHGBODY;
pub const SMFIF_ADDRCPT: c_ulong = 0x4;
pub const SMFIF_DELRCPT: c_ulong = 0x8;
pub const SMFIF_CHGHDRS: c_ulong = 0x10;
pub const SMFIF_QUARANTINE: c_ulong = 0x20;
pub const SMFIF_CHGFROM: c_ulong = 0x40;
pub const SMFIF_ADDRCPT_PAR: c_ulong = 0x80;
pub const SMFIF_SETSYMLIST: c_ulong = 0x100;

pub const SMFIM_NOMACROS: c_int = -1;
pub const SMFIM_FIRST: c_int = 0;
pub const SMFIM_CONNECT: c_int = 0;
pub const SMFIM_HELO: c_int = 1;
pub const SMFIM_ENVFROM: c_int = 2;
pub const SMFIM_ENVRCPT: c_int = 3;
pub const SMFIM_DATA: c_int = 4;
pub const SMFIM_EOM: c_int = 5;
pub const SMFIM_EOH: c_int = 6;
pub const SMFIM_LAST: c_int = 6;

pub const SMFIS_CONTINUE: c_int = 0;
pub const SMFIS_REJECT: c_int = 1;
pub const SMFIS_DISCARD: c_int = 2;
pub const SMFIS_ACCEPT: c_int = 3;
pub const SMFIS_TEMPFAIL: c_int = 4;
pub const SMFIS_NOREPLY: c_int = 7;
pub const SMFIS_SKIP: c_int = 8;
pub const SMFIS_ALL_OPTS: c_int = 10;

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct smfi_str {
    _private: [u8; 0],
}

pub type SMFICTX = smfi_str;
pub type SMFICTX_PTR = *mut smfi_str;
pub type smfiDesc_str = smfiDesc;
pub type smfiDesc_ptr = *mut smfiDesc;
pub type sfsistat = c_int;

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct smfiDesc {
    pub xxfi_name: *mut c_char,
    pub xxfi_version: c_int,
    pub xxfi_flags: c_ulong,
    pub xxfi_connect: Option<unsafe extern "C" fn(ctx: *mut SMFICTX, hostname: *mut c_char, hostaddr: *mut libc::sockaddr) -> sfsistat>,
    pub xxfi_helo: Option<unsafe extern "C" fn(ctx: *mut SMFICTX, helohost: *mut c_char) -> sfsistat>,
    pub xxfi_envfrom: Option<unsafe extern "C" fn(ctx: *mut SMFICTX, argv: *mut *mut c_char) -> sfsistat>,
    pub xxfi_envrcpt: Option<unsafe extern "C" fn(ctx: *mut SMFICTX, argv: *mut *mut c_char) -> sfsistat>,
    pub xxfi_header: Option<unsafe extern "C" fn(ctx: *mut SMFICTX, headerf: *mut c_char, headerv: *mut c_char) -> sfsistat>,
    pub xxfi_eoh: Option<unsafe extern "C" fn(ctx: *mut SMFICTX) -> sfsistat>,
    pub xxfi_body: Option<unsafe extern "C" fn(ctx: *mut SMFICTX, bodyp: *mut c_uchar, bodylen: size_t) -> sfsistat>,
    pub xxfi_eom: Option<unsafe extern "C" fn(ctx: *mut SMFICTX) -> sfsistat>,
    pub xxfi_abort: Option<unsafe extern "C" fn(ctx: *mut SMFICTX) -> sfsistat>,
    pub xxfi_close: Option<unsafe extern "C" fn(ctx: *mut SMFICTX) -> sfsistat>,
    pub xxfi_unknown: Option<unsafe extern "C" fn(ctx: *mut SMFICTX, arg: *const c_char) -> sfsistat>,
    pub xxfi_data: Option<unsafe extern "C" fn(ctx: *mut SMFICTX) -> sfsistat>,
    pub xxfi_negotiate: Option<
        unsafe extern "C" fn(
            ctx: *mut SMFICTX,
            f0: c_ulong,
            f1: c_ulong,
            f2: c_ulong,
            f3: c_ulong,
            pf0: *mut c_ulong,
            pf1: *mut c_ulong,
            pf2: *mut c_ulong,
            pf3: *mut c_ulong,
        ) -> sfsistat,
    >,
}

extern "C" {
    pub fn smfi_opensocket(rmsocket: c_int) -> c_int;
    pub fn smfi_register(descr: smfiDesc) -> c_int;
    pub fn smfi_main() -> c_int;
    pub fn smfi_setbacklog(obacklog: c_int) -> c_int;
    pub fn smfi_setdbg(level: c_int) -> c_int;
    pub fn smfi_settimeout(otimeout: c_int) -> c_int;
    pub fn smfi_setconn(oconn: *mut c_char) -> c_int;
    pub fn smfi_stop() -> c_int;
    pub fn smfi_setmaxdatasize(sz: size_t) -> size_t;
    pub fn smfi_version(pmajor: *mut c_uint, pminor: *mut c_uint, ppl: *mut c_uint) -> c_int;
    pub fn smfi_getsymval(ctx: *mut SMFICTX, symname: *mut c_char) -> *mut c_char;
    pub fn smfi_setreply(ctx: *mut SMFICTX, rcode: *mut c_char, xcode: *mut c_char, message: *mut c_char) -> c_int;
    pub fn smfi_setmlreply(ctx: *mut SMFICTX, rcode: *const c_char, xcode: *const c_char, ...) -> c_int;
    pub fn smfi_addheader(ctx: *mut SMFICTX, headerf: *mut c_char, headerv: *mut c_char) -> c_int;
    pub fn smfi_chgheader(ctx: *mut SMFICTX, headerf: *mut c_char, index: c_int, headerv: *mut c_char) -> c_int;
    pub fn smfi_insheader(ctx: *mut SMFICTX, index: c_int, headerf: *mut c_char, headerv: *mut c_char) -> c_int;
    pub fn smfi_chgfrom(ctx: *mut SMFICTX, mail: *mut c_char, args: *mut c_char) -> c_int;
    pub fn smfi_addrcpt(ctx: *mut SMFICTX, rcpt: *mut c_char) -> c_int;
    pub fn smfi_addrcpt_par(ctx: *mut SMFICTX, rcpt: *mut c_char, args: *mut c_char) -> c_int;
    pub fn smfi_delrcpt(ctx: *mut SMFICTX, rcpt: *mut c_char) -> c_int;
    pub fn smfi_progress(ctx: *mut SMFICTX) -> c_int;
    pub fn smfi_replacebody(ctx: *mut SMFICTX, bodyp: *mut c_uchar, bodylen: c_int) -> c_int;
    pub fn smfi_quarantine(ctx: *mut SMFICTX, reason: *mut c_char) -> c_int;
    pub fn smfi_setpriv(ctx: *mut SMFICTX, privatedata: *mut c_void) -> c_int;
    pub fn smfi_getpriv(ctx: *mut SMFICTX) -> *mut c_void;
    pub fn smfi_setsymlist(ctx: *mut SMFICTX, stage: c_int, macros: *mut c_char) -> c_int;
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn all_current_protocol_opts() {
        let protocol_opts = SMFIP_NOCONNECT
            | SMFIP_NOHELO
            | SMFIP_NOMAIL
            | SMFIP_NORCPT
            | SMFIP_NOBODY
            | SMFIP_NOHDRS
            | SMFIP_NOEOH
            | SMFIP_NR_HDR
            | SMFIP_NOUNKNOWN
            | SMFIP_NODATA
            | SMFIP_SKIP
            | SMFIP_RCPT_REJ
            | SMFIP_NR_CONN
            | SMFIP_NR_HELO
            | SMFIP_NR_MAIL
            | SMFIP_NR_RCPT
            | SMFIP_NR_DATA
            | SMFIP_NR_UNKN
            | SMFIP_NR_EOH
            | SMFIP_NR_BODY
            | SMFIP_HDR_LEADSPC;

        assert_eq!(protocol_opts, SMFI_CURR_PROT);
    }

    #[test]
    fn smfi_version_returns_version() {
        let (mut a, mut b, mut c) = (0, 0, 0);

        let result = unsafe { smfi_version(&mut a, &mut b, &mut c) };

        assert_eq!(result, MI_SUCCESS);
        assert_eq!((a, b, c), (1, 0, 1));
    }

    #[test]
    fn register_milter() {
        let desc = smfiDesc {
            xxfi_name: "test\0".as_ptr() as _,
            xxfi_version: SMFI_VERSION,
            xxfi_flags: 0,
            xxfi_connect: None,
            xxfi_helo: None,
            xxfi_envfrom: None,
            xxfi_envrcpt: None,
            xxfi_header: None,
            xxfi_eoh: None,
            xxfi_body: None,
            xxfi_eom: None,
            xxfi_abort: None,
            xxfi_close: None,
            xxfi_unknown: None,
            xxfi_data: None,
            xxfi_negotiate: None,
        };

        let result = unsafe { smfi_register(desc) };

        assert_eq!(result, MI_SUCCESS);
    }
}