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
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
//! # Milter
//!
//! Bindings for the libmilter (Sendmail mail filter) library.
//!
//! This is a work in progress etc.
//!
//! # Design
//!
//! As a Rust binding to an existing C API, there are some tradeoffs. This is a
//! safe convenience binding. Overall it tries to stay relatively close to the
//! underlying functions, and does not overly conceal what’s underneath. Keep
//! things possible as in C.
//!
//! A milter implementation is driven by the libmilter implementation itself.
//! Documentation will speak of the ‘milter library’ in those cases.
//!
//! # Globals
//!
//! A milter implementation is fundamentally a singleton within the process.
//! Only one invocation of `Milter::run` is allowed per process. Therefore,
//! globals are an acceptable and reasonable thing to have.
//!
//! # Safety
//!
//! In the callback code panicking must be avoided. As callback code is executed
//! through a foreign code layer, restrictions re FFI apply. Stack unwinding
//! through an FFI boundary is undefined behaviour.
//!
//! That is why `Result` is used pervasively.
//!
//! # Usage
//!
//! Intended for creation of main.rs binaries. Signals are handled by the milter
//! library, shut down with Ctrl-C.

#[macro_use]
extern crate bitflags;

extern crate milter_sys as sys;

#[doc(hidden)]
pub use sys::{SMFICTX, sfsistat};

// TODO correct?
#[allow(unused_imports)]
#[macro_use]
extern crate milter_callback;
#[doc(hidden)]
pub use milter_callback::*;

use libc::{c_char, c_uchar, c_ulong, size_t};
use std::cell::{Ref, RefCell, RefMut};
use std::ffi::{CStr, CString};
use std::ptr::{self, NonNull};
use std::time::Duration;
use std::convert::TryInto;

pub mod error;
pub use error::*;

bitflags! {
    /// Milter actions.
    ///
    /// Actions that the milter can take during processing need to be declared
    /// either with [`Milter::actions`] or during negotiation.
    ///
    /// [`Milter::actions`]: struct.Milter.html#method.actions
    #[derive(Default)]
    pub struct Actions: u64 {
        const SET_REQUESTED_MACROS = sys::SMFIF_SETSYMLIST;
        const CHANGE_SENDER = sys::SMFIF_CHGFROM;
        const ADD_RECIPIENT = sys::SMFIF_ADDRCPT;
        const ADD_RECIPIENT_EXT = sys::SMFIF_ADDRCPT_PAR;
        const DELETE_RECIPIENT = sys::SMFIF_DELRCPT;
        /// The milter may add headers.
        const ADD_HEADER = sys::SMFIF_ADDHDRS;
        const CHANGE_HEADER = sys::SMFIF_CHGHDRS;
        const CHANGE_BODY = sys::SMFIF_CHGBODY;
        const QUARANTINE = sys::SMFIF_QUARANTINE;
    }
}

bitflags! {
    /// Milter protocol options.
    ///
    /// These mean either what the MTA can do (MTA advertises capabilities) or
    /// what the milter wants to do (milter requests capabilities).
    #[derive(Default)]
    pub struct ProtocolOpts: u64 {
        /// Do not send connect info/do not call connect callback.
        const NO_CONNECT = sys::SMFIP_NOCONNECT;
        const NO_HELO = sys::SMFIP_NOHELO;
        const NO_MAIL = sys::SMFIP_NOMAIL;
        const NO_RCPT = sys::SMFIP_NORCPT;
        const NO_DATA = sys::SMFIP_NODATA;
        const NO_HEADER = sys::SMFIP_NOHDRS;
        const NO_EOH = sys::SMFIP_NOEOH;
        const NO_BODY = sys::SMFIP_NOBODY;
        const NO_UNKNOWN = sys::SMFIP_NOUNKNOWN;

        /// Advertise/allow `Status::Skip` return status.
        const SKIP = sys::SMFIP_SKIP;

        /// Send rejected RCPTs also.
        const REJECTED_RCPT = sys::SMFIP_RCPT_REJ;

        /// Answer with noreply in connect stage.
        const NOREPLY_CONNECT = sys::SMFIP_NR_CONN;
        const NOREPLY_HELO = sys::SMFIP_NR_HELO;
        const NOREPLY_MAIL = sys::SMFIP_NR_MAIL;
        const NOREPLY_RCPT = sys::SMFIP_NR_RCPT;
        const NOREPLY_DATA = sys::SMFIP_NR_DATA;
        const NOREPLY_HEADER = sys::SMFIP_NR_HDR;
        const NOREPLY_EOH = sys::SMFIP_NR_EOH;
        const NOREPLY_BODY = sys::SMFIP_NR_BODY;
        const NOREPLY_UNKNOWN = sys::SMFIP_NR_UNKN;

        const HEADER_LEADING_SPACE = sys::SMFIP_HDR_LEADSPC;

        const MAX_DATA_SIZE_256K = sys::SMFIP_MDS_256K;
        const MAX_DATA_SIZE_1M = sys::SMFIP_MDS_1M;
    }
}

/// Milter protocol stage.
pub enum Stage {
    /// Connect stage.
    Connect = 0,
    Helo = 1,
    Mail = 2,
    Rcpt = 3,
    Data = 4,
    Eoh = 6,
    Eom = 5,
}

/// Callback response status.
///
/// A response status is returned from milter callbacks.
///
/// `Status::AllOpts` is foreign here; it is used only during negotiation.
pub enum Status {
    /// Continue to the next stage. This is the default response.
    Continue = 0,

    /// Reject the entity processed in this stage.
    Reject = 1,

    /// Accept the entity being processed but discard the message.
    Discard = 2,
    Accept = 3,
    Tempfail = 4,
    Noreply = 7,
    Skip = 8,
    AllOpts = 10,
}

pub type NegotiateCallback = unsafe extern "C" fn(
    *mut sys::SMFICTX, c_ulong, c_ulong, c_ulong, c_ulong, *mut c_ulong, *mut c_ulong, *mut c_ulong, *mut c_ulong
) -> sys::sfsistat;
/// Alias for the connect callback function pointer type.
pub type ConnectCallback = unsafe extern "C" fn(*mut sys::SMFICTX, *mut c_char, *mut libc::sockaddr) -> sys::sfsistat;
pub type HeloCallback = unsafe extern "C" fn(*mut sys::SMFICTX, *mut c_char) -> sys::sfsistat;
pub type MailCallback = unsafe extern "C" fn(*mut sys::SMFICTX, *mut *mut c_char) -> sys::sfsistat;
pub type RcptCallback = unsafe extern "C" fn(*mut sys::SMFICTX, *mut *mut c_char) -> sys::sfsistat;
pub type DataCallback = unsafe extern "C" fn(*mut sys::SMFICTX) -> sys::sfsistat;
pub type HeaderCallback = unsafe extern "C" fn(*mut sys::SMFICTX, *mut c_char, *mut c_char) -> sys::sfsistat;
pub type EohCallback = unsafe extern "C" fn(*mut sys::SMFICTX) -> sys::sfsistat;
pub type BodyCallback = unsafe extern "C" fn(*mut sys::SMFICTX, *mut c_uchar, size_t) -> sys::sfsistat;
pub type EomCallback = unsafe extern "C" fn(*mut sys::SMFICTX) -> sys::sfsistat;
pub type AbortCallback = unsafe extern "C" fn(*mut sys::SMFICTX) -> sys::sfsistat;
pub type CloseCallback = unsafe extern "C" fn(*mut sys::SMFICTX) -> sys::sfsistat;
pub type UnknownCallback = unsafe extern "C" fn(*mut sys::SMFICTX, *const c_char) -> sys::sfsistat;

/// A milter object.
///
/// Note that only one milter should run per process.
///
/// # Example
///
/// The following shows the simplest possible, no-op milter.
///
/// ```
/// use milter::Milter;
///
/// let result = Milter::new("NoopMilter").run("unix:/run/noopmilter.sock");
/// ```
pub struct Milter {
    name: String,
    actions: Actions,
    timeout: Option<u64>,
    backlog: Option<u32>,
    negotiate_callback: Option<NegotiateCallback>,
    connect_callback: Option<ConnectCallback>,
    helo_callback: Option<HeloCallback>,
    mail_callback: Option<MailCallback>,
    rcpt_callback: Option<RcptCallback>,
    data_callback: Option<DataCallback>,
    header_callback: Option<HeaderCallback>,
    eoh_callback: Option<EohCallback>,
    body_callback: Option<BodyCallback>,
    eom_callback: Option<EomCallback>,
    abort_callback: Option<AbortCallback>,
    close_callback: Option<CloseCallback>,
    unknown_callback: Option<UnknownCallback>,
}

impl Milter {
    /// Creates a milter with the given name.
    pub fn new(name: &str) -> Self {
        Milter {
            name: name.to_owned(),
            actions: Default::default(),
            timeout: None,
            backlog: None,
            negotiate_callback: None,
            connect_callback: None,
            helo_callback: None,
            mail_callback: None,
            rcpt_callback: None,
            data_callback: None,
            header_callback: None,
            eoh_callback: None,
            body_callback: None,
            eom_callback: None,
            abort_callback: None,
            close_callback: None,
            unknown_callback: None,
        }
    }

    /// Registers the given negotiate stage callback.
    pub fn on_negotiate(&mut self, cb: NegotiateCallback) -> &mut Self {
        self.negotiate_callback = Some(cb);
        self
    }

    pub fn on_connect(&mut self, cb: ConnectCallback) -> &mut Self {
        self.connect_callback = Some(cb);
        self
    }

    pub fn on_helo(&mut self, cb: HeloCallback) -> &mut Self {
        self.helo_callback = Some(cb);
        self
    }

    pub fn on_mail(&mut self, cb: MailCallback) -> &mut Self {
        self.mail_callback = Some(cb);
        self
    }

    pub fn on_rcpt(&mut self, cb: RcptCallback) -> &mut Self {
        self.rcpt_callback = Some(cb);
        self
    }

    pub fn on_data(&mut self, cb: DataCallback) -> &mut Self {
        self.data_callback = Some(cb);
        self
    }

    pub fn on_header(&mut self, cb: HeaderCallback) -> &mut Self {
        self.header_callback = Some(cb);
        self
    }

    pub fn on_eoh(&mut self, cb: EohCallback) -> &mut Self {
        self.eoh_callback = Some(cb);
        self
    }

    /// Registers the body callback on this milter.
    pub fn on_body(&mut self, cb: BodyCallback) -> &mut Self {
        self.body_callback = Some(cb);
        self
    }

    pub fn on_eom(&mut self, cb: EomCallback) -> &mut Self {
        self.eom_callback = Some(cb);
        self
    }

    pub fn on_abort(&mut self, cb: AbortCallback) -> &mut Self {
        self.abort_callback = Some(cb);
        self
    }

    pub fn on_close(&mut self, cb: CloseCallback) -> &mut Self {
        self.close_callback = Some(cb);
        self
    }

    pub fn on_unknown(&mut self, cb: UnknownCallback) -> &mut Self {
        self.unknown_callback = Some(cb);
        self
    }

    pub fn actions(&mut self, actions: Actions) -> &mut Self {
        self.actions = actions;
        self
    }

    pub fn set_timeout(&mut self, duration: Duration) -> &mut Self {
        let secs = duration.as_secs();
        self.timeout = Some(secs);
        self
    }

    pub fn set_socket_backlog(&mut self, conns: u32) -> &mut Self {
        assert!(conns > 0);
        self.backlog = Some(conns);
        self
    }

    fn register(&self) -> std::result::Result<i32, &'static str> {
        let name: &str = &self.name;
        let name = CString::new(name).unwrap().into_raw();  // leak; mustn't be freed from C!

        let actions = &self.actions;

        let smfilter = sys::smfiDesc {
            xxfi_name: name,
            xxfi_version: sys::SMFI_VERSION,
            xxfi_flags: actions.bits,
            xxfi_connect: self.connect_callback,
            xxfi_helo: self.helo_callback,
            xxfi_envfrom: self.mail_callback,
            xxfi_envrcpt: self.rcpt_callback,
            xxfi_header: self.header_callback,
            xxfi_eoh: self.eoh_callback,
            xxfi_body: self.body_callback,
            xxfi_eom: self.eom_callback,
            xxfi_abort: self.abort_callback,
            xxfi_close: self.close_callback,
            xxfi_unknown: self.unknown_callback,
            xxfi_data: self.data_callback,
            xxfi_negotiate: self.negotiate_callback,
        };

        // TODO proper error handling
        let i = unsafe { sys::smfi_register(smfilter) };
        if i == sys::MI_FAILURE {
            Err("register failed")
        } else {
            Ok(i)
        }
    }

    /// Starts the milter.
    pub fn run(&self, socket: &str) -> std::result::Result<i32, &'static str> {
        let cs = CString::new(socket).unwrap();
        unsafe {
            let _ = sys::smfi_setconn(cs.into_raw());
        }

        // TODO proper error handling
        let _result = self.register();

        if let Some(timeout) = self.timeout {
            let _ = unsafe { sys::smfi_settimeout(timeout.try_into().unwrap()) };
        }
        if let Some(backlog) = self.backlog {
            let _ = unsafe { sys::smfi_setbacklog(backlog.try_into().unwrap()) };
        }

        println!("Starting main");
        let status = unsafe { sys::smfi_main() };
        println!("Exiting main");
        Ok(status)
    }
}

/// Milter context supplied to milter callback functions.
///
/// The context also provides access to connection or message data.
///
/// Type parameter defaults to the unit type for ease of use.
pub struct Context<T = ()> {
    base: ContextBase,

    /// A handle on managed user data.
    pub data: DataHandle<T>,
}

impl<T> Context<T> {
    /// Constructs a new context from the milter library-supplied raw context
    /// pointer.
    ///
    /// # Panics
    ///
    /// Panics if `ptr` is null.
    pub fn new(ptr: *mut sys::SMFICTX) -> Self {
        assert!(!ptr.is_null());

        Context {
            base: ContextBase::new(ptr),
            data: DataHandle::new(ptr),
        }
    }

    /// Requests the given macros to be made available in the given stage.
    ///
    /// Macro requirements should be handled during negotiation.
    pub fn set_requested_macros(&self, stage: Stage, macros: &str) -> Result<()> {
        self.base.set_requested_macros(stage, macros)
    }

    /// Sets the default SMTP (and ESMTP) error reply code and message.
    pub fn set_error_reply(&self, code: &str, ext_code: Option<&str>, lines: Vec<&str>) -> Result<()> {
        self.base.set_error_reply(code, ext_code, lines)
    }

    /// Requests exit from the milter library.
    ///
    /// This ultimately causes `Milter::run` to return.
    pub fn exit(&self) {
        self.base.exit()
    }
}

// TODO reconsider shared context trait

/// A trait for obtaining values.
pub trait MacroValue {
    /// Returns the macro value.
    fn macro_value(&self, name: &str) -> Result<Option<String>>;
}

impl<T> MacroValue for Context<T> {
    /// Returns the macro value for `name` if available.
    ///
    /// # Errors
    ///
    /// If conversion of arguments or return values at the boundary to the
    /// milter library fails, an error variant is returned.
    fn macro_value(&self, name: &str) -> Result<Option<String>> {
        self.base.macro_value(name)
    }
}
impl<T> MacroValue for ActionContext<T> {
    fn macro_value(&self, name: &str) -> Result<Option<String>> {
        self.base.macro_value(name)
    }
}

/// Context of the end of message, ie actions stage.
pub struct ActionContext<T = ()> {
    base: ContextBase,

    /// A handle on managed data in this context.
    pub data: DataHandle<T>,
}

impl<T> ActionContext<T> {
    /// Constructs a new action context from the milter library-supplied raw
    /// context pointer.
    ///
    /// # Panics
    ///
    /// Panics if `ptr` is null.
    pub fn new(ptr: *mut sys::SMFICTX) -> Self {
        assert!(!ptr.is_null());

        ActionContext {
            base: ContextBase::new(ptr),
            data: DataHandle::new(ptr),
        }
    }

    // TODO move to trait
    pub fn macro_value(&self, name: &str) -> Result<Option<String>> {
        self.base.macro_value(name)
    }

    pub fn set_error_reply(&self, code: &str, ext_code: Option<&str>, lines: Vec<&str>) -> Result<()> {
        self.base.set_error_reply(code, ext_code, lines)
    }

    pub fn exit(&self) {
        self.base.exit()
    }

    /// Adds a new header to this message.
    ///
    /// Enabled with flag `Actions::AddHeader`.
    pub fn add_header(&self, name: &str, value: &str) -> Result<()> {
        self.base.add_header(name, value)
    }

    /// Inserts a new header at `index`.
    pub fn insert_header(&self, index: usize, name: &str, value: &str) -> Result<()> {
        self.base.insert_header(index, name, value)
    }

    /// ‘Changes’, ie replaces, adds, or deletes the `index`-th occurrence of a
    /// header with the given `name`. Note that index is 1-based (starts at 1).
    ///
    /// * replaces the n’th occurrence of header name with value
    /// * if value is None, deletes the n’th occurrence of header name
    /// * if index is greater than the number of occurrences of header name,
    ///   appends a new header
    pub fn change_header(&self, name: &str, index: usize, value: Option<&str>) -> Result<()> {
        self.base.change_header(name, index, value)
    }

    /// Replaces the envelope sender (MAIL FROM).
    pub fn change_sender(&self, mail: &str, esmtp: Option<&str>) -> Result<()> {
        self.base.change_sender(mail, esmtp)
    }

    /// Adds an envelope recipient (RCPT TO).
    ///
    /// Enabled with two different flags:
    ///
    /// * `Actions::ADD_RECIPIENT`
    /// * `Actions::ADD_RECIPIENT_EXT` if a additional ESMTP arguments are given
    pub fn add_recipient(&self, rcpt: &str, esmtp: Option<&str>) -> Result<()> {
        self.base.add_recipient(rcpt, esmtp)
    }

    pub fn delete_recipient(&self, rcpt: &str) -> Result<()> {
        self.base.delete_recipient(rcpt)
    }

    /// Appends content to the new (replacement) message body.
    pub fn append_to_new_body(&self, content: &[u8]) -> Result<()> {
        self.base.append_to_new_body(content)
    }

    /// Signals to the milter library and the MTA that work is in progress;
    /// resets timeouts.
    pub fn keepalive(&self) -> Result<()> {
        self.base.keepalive()
    }

    /// Quarantine this message for the given reason.
    pub fn quarantine(&self, reason: &str) -> Result<()> {
        self.base.quarantine(reason)
    }
}

struct ContextBase {
    context_ptr: NonNull<sys::SMFICTX>,
}

impl ContextBase {
    fn new(ptr: *mut sys::SMFICTX) -> Self {
        ContextBase { context_ptr: NonNull::new(ptr).unwrap() }
    }

    fn macro_value(&self, name: &str) -> Result<Option<String>> {
        let name = to_c_string(name)?;
        unsafe {
            let value = sys::smfi_getsymval(self.context_ptr.as_ptr(), name.as_ptr() as _);

            Ok(if value.is_null() {
                None
            } else {
                Some(String::from(CStr::from_ptr(value).to_str().map_err(|_| Error::new(ErrorKind::ConversionError))?))
            })
        }
    }

    fn set_requested_macros(&self, stage: Stage, macros: &str) -> Result<()> {
        let macros = to_c_string(macros)?;

        to_result(unsafe {
            sys::smfi_setsymlist(self.context_ptr.as_ptr(), stage as _, macros.as_ptr() as _)
        })
    }

    fn set_error_reply(&self, code: &str, ext_code: Option<&str>, lines: Vec<&str>) -> Result<()> {
        let code = to_c_string(code)?;
        let ext_code = ext_code.map(|s| to_c_string(s)).transpose()?;

        let ls = lines.into_iter()
            .map(|line| to_c_string(line))
            .collect::<Result<Vec<CString>>>()?;

        let p = self.context_ptr.as_ptr();
        let c = code.as_ptr() as *mut _;
        let x = ext_code.as_ref().map_or(ptr::null_mut(), |cs| cs.as_ptr() as *mut _);

        macro_rules! reply {
            ($line:expr) => {
                unsafe { sys::smfi_setreply(p, c, x, $line as _) }
            };
            ($($line:expr),+) => {
                unsafe {
                    sys::smfi_setmlreply(
                        p, c, x, $($line.as_ptr() as *mut c_char),+, ptr::null_mut() as *mut c_char
                    )
                }
            }
        }

        to_result(match ls.len() {
            0 => reply!(ptr::null_mut()),
            1 => reply!(ls[0].as_ptr()),
            2 => reply!(ls[0], ls[1]),
            3 => reply!(ls[0], ls[1], ls[2]),
            4 => reply!(ls[0], ls[1], ls[2], ls[3]),
            5 => reply!(ls[0], ls[1], ls[2], ls[3], ls[4]),
            6 => reply!(ls[0], ls[1], ls[2], ls[3], ls[4], ls[5]),
            7 => reply!(ls[0], ls[1], ls[2], ls[3], ls[4], ls[5], ls[6]),
            8 => reply!(ls[0], ls[1], ls[2], ls[3], ls[4], ls[5], ls[6], ls[7]),
            9 => reply!(ls[0], ls[1], ls[2], ls[3], ls[4], ls[5], ls[6], ls[7], ls[8]),
            10 => reply!(ls[0], ls[1], ls[2], ls[3], ls[4], ls[5], ls[6], ls[7], ls[8], ls[9]),
            11 => reply!(ls[0], ls[1], ls[2], ls[3], ls[4], ls[5], ls[6], ls[7], ls[8], ls[9], ls[10]),
            12 => reply!(ls[0], ls[1], ls[2], ls[3], ls[4], ls[5], ls[6], ls[7], ls[8], ls[9], ls[10], ls[11]),
            13 => reply!(ls[0], ls[1], ls[2], ls[3], ls[4], ls[5], ls[6], ls[7], ls[8], ls[9], ls[10], ls[11], ls[12]),
            14 => reply!(ls[0], ls[1], ls[2], ls[3], ls[4], ls[5], ls[6], ls[7], ls[8], ls[9], ls[10], ls[11], ls[12], ls[13]),
            15 => reply!(ls[0], ls[1], ls[2], ls[3], ls[4], ls[5], ls[6], ls[7], ls[8], ls[9], ls[10], ls[11], ls[12], ls[13], ls[14]),
            16 => reply!(ls[0], ls[1], ls[2], ls[3], ls[4], ls[5], ls[6], ls[7], ls[8], ls[9], ls[10], ls[11], ls[12], ls[13], ls[14], ls[15]),
            17 => reply!(ls[0], ls[1], ls[2], ls[3], ls[4], ls[5], ls[6], ls[7], ls[8], ls[9], ls[10], ls[11], ls[12], ls[13], ls[14], ls[15], ls[16]),
            18 => reply!(ls[0], ls[1], ls[2], ls[3], ls[4], ls[5], ls[6], ls[7], ls[8], ls[9], ls[10], ls[11], ls[12], ls[13], ls[14], ls[15], ls[16], ls[17]),
            19 => reply!(ls[0], ls[1], ls[2], ls[3], ls[4], ls[5], ls[6], ls[7], ls[8], ls[9], ls[10], ls[11], ls[12], ls[13], ls[14], ls[15], ls[16], ls[17], ls[18]),
            20 => reply!(ls[0], ls[1], ls[2], ls[3], ls[4], ls[5], ls[6], ls[7], ls[8], ls[9], ls[10], ls[11], ls[12], ls[13], ls[14], ls[15], ls[16], ls[17], ls[18], ls[19]),
            21 => reply!(ls[0], ls[1], ls[2], ls[3], ls[4], ls[5], ls[6], ls[7], ls[8], ls[9], ls[10], ls[11], ls[12], ls[13], ls[14], ls[15], ls[16], ls[17], ls[18], ls[19], ls[20]),
            22 => reply!(ls[0], ls[1], ls[2], ls[3], ls[4], ls[5], ls[6], ls[7], ls[8], ls[9], ls[10], ls[11], ls[12], ls[13], ls[14], ls[15], ls[16], ls[17], ls[18], ls[19], ls[20], ls[21]),
            23 => reply!(ls[0], ls[1], ls[2], ls[3], ls[4], ls[5], ls[6], ls[7], ls[8], ls[9], ls[10], ls[11], ls[12], ls[13], ls[14], ls[15], ls[16], ls[17], ls[18], ls[19], ls[20], ls[21], ls[22]),
            24 => reply!(ls[0], ls[1], ls[2], ls[3], ls[4], ls[5], ls[6], ls[7], ls[8], ls[9], ls[10], ls[11], ls[12], ls[13], ls[14], ls[15], ls[16], ls[17], ls[18], ls[19], ls[20], ls[21], ls[22], ls[23]),
            25 => reply!(ls[0], ls[1], ls[2], ls[3], ls[4], ls[5], ls[6], ls[7], ls[8], ls[9], ls[10], ls[11], ls[12], ls[13], ls[14], ls[15], ls[16], ls[17], ls[18], ls[19], ls[20], ls[21], ls[22], ls[23], ls[24]),
            26 => reply!(ls[0], ls[1], ls[2], ls[3], ls[4], ls[5], ls[6], ls[7], ls[8], ls[9], ls[10], ls[11], ls[12], ls[13], ls[14], ls[15], ls[16], ls[17], ls[18], ls[19], ls[20], ls[21], ls[22], ls[23], ls[24], ls[25]),
            27 => reply!(ls[0], ls[1], ls[2], ls[3], ls[4], ls[5], ls[6], ls[7], ls[8], ls[9], ls[10], ls[11], ls[12], ls[13], ls[14], ls[15], ls[16], ls[17], ls[18], ls[19], ls[20], ls[21], ls[22], ls[23], ls[24], ls[25], ls[26]),
            28 => reply!(ls[0], ls[1], ls[2], ls[3], ls[4], ls[5], ls[6], ls[7], ls[8], ls[9], ls[10], ls[11], ls[12], ls[13], ls[14], ls[15], ls[16], ls[17], ls[18], ls[19], ls[20], ls[21], ls[22], ls[23], ls[24], ls[25], ls[26], ls[27]),
            29 => reply!(ls[0], ls[1], ls[2], ls[3], ls[4], ls[5], ls[6], ls[7], ls[8], ls[9], ls[10], ls[11], ls[12], ls[13], ls[14], ls[15], ls[16], ls[17], ls[18], ls[19], ls[20], ls[21], ls[22], ls[23], ls[24], ls[25], ls[26], ls[27], ls[28]),
            30 => reply!(ls[0], ls[1], ls[2], ls[3], ls[4], ls[5], ls[6], ls[7], ls[8], ls[9], ls[10], ls[11], ls[12], ls[13], ls[14], ls[15], ls[16], ls[17], ls[18], ls[19], ls[20], ls[21], ls[22], ls[23], ls[24], ls[25], ls[26], ls[27], ls[28], ls[29]),
            31 => reply!(ls[0], ls[1], ls[2], ls[3], ls[4], ls[5], ls[6], ls[7], ls[8], ls[9], ls[10], ls[11], ls[12], ls[13], ls[14], ls[15], ls[16], ls[17], ls[18], ls[19], ls[20], ls[21], ls[22], ls[23], ls[24], ls[25], ls[26], ls[27], ls[28], ls[29], ls[30]),
            32 => reply!(ls[0], ls[1], ls[2], ls[3], ls[4], ls[5], ls[6], ls[7], ls[8], ls[9], ls[10], ls[11], ls[12], ls[13], ls[14], ls[15], ls[16], ls[17], ls[18], ls[19], ls[20], ls[21], ls[22], ls[23], ls[24], ls[25], ls[26], ls[27], ls[28], ls[29], ls[30], ls[31]),
            _ => panic!("more than 32 lines"),
        })
    }

    fn exit(&self) {
        let _ = unsafe { sys::smfi_stop() };
    }

    fn change_sender(&self, mail: &str, esmtp: Option<&str>) -> Result<()> {
        let mail = to_c_string(mail)?;
        let esmtp = esmtp.map(|s| to_c_string(s)).transpose()?;

        let e = esmtp.as_ref().map_or(ptr::null_mut(), |cs| cs.as_ptr() as _);

        to_result(unsafe {
            sys::smfi_chgfrom(self.context_ptr.as_ptr(), mail.as_ptr() as _, e)
        })
    }

    fn add_recipient(&self, rcpt: &str, esmtp: Option<&str>) -> Result<()> {
        let rcpt = to_c_string(rcpt)?;
        to_result(match esmtp {
            None => {
                unsafe { sys::smfi_addrcpt(self.context_ptr.as_ptr(), rcpt.as_ptr() as _) }
            }
            Some(esmtp) => {
                let ext = to_c_string(esmtp)?;
                unsafe { sys::smfi_addrcpt_par(self.context_ptr.as_ptr(), rcpt.as_ptr() as _, ext.as_ptr() as _) }
            }
        })
    }

    fn delete_recipient(&self, rcpt: &str) -> Result<()> {
        let rcpt = to_c_string(rcpt)?;
        to_result(unsafe { sys::smfi_delrcpt(self.context_ptr.as_ptr(), rcpt.as_ptr() as _) })
    }

    fn add_header(&self, name: &str, value: &str) -> Result<()> {
        let name = to_c_string(name)?;
        let value = to_c_string(value)?;

        unsafe {
            to_result(sys::smfi_addheader(self.context_ptr.as_ptr(), name.as_ptr() as _, value.as_ptr() as _))
        }
    }

    fn insert_header(&self, index: usize, name: &str, value: &str) -> Result<()> {
        let name = to_c_string(name)?;
        let value = to_c_string(value)?;
        let index = index.try_into().unwrap();  // TODO proper conversion

        unsafe {
            to_result(sys::smfi_insheader(
                self.context_ptr.as_ptr(),
                index,
                name.as_ptr() as _,
                value.as_ptr() as _,
            ))
        }
    }

    fn change_header(&self, name: &str, index: usize, value: Option<&str>) -> Result<()> {
        let name = to_c_string(name)?;
        let value = value.map(|s| to_c_string(s)).transpose()?;
        let index = index.try_into().unwrap();  // TODO proper conversion

        let v = value.as_ref().map_or(ptr::null_mut(), |cs| cs.as_ptr() as _);

        to_result(unsafe {
            sys::smfi_chgheader(self.context_ptr.as_ptr(), name.as_ptr() as _, index, v)
        })
    }

    fn append_to_new_body(&self, content: &[u8]) -> Result<()> {
        to_result(unsafe {
            sys::smfi_replacebody(self.context_ptr.as_ptr(), content.as_ptr() as _, content.len() as _)
        })
    }

    fn quarantine(&self, reason: &str) -> Result<()> {
        let s = to_c_string(reason)?;
        let r = unsafe { sys::smfi_quarantine(self.context_ptr.as_ptr(), s.as_ptr() as _) };
        to_result(r)
    }

    fn keepalive(&self) -> Result<()> {
        let status = unsafe { sys::smfi_progress(self.context_ptr.as_ptr()) };
        match status {
            sys::MI_SUCCESS => Ok(()),
            _ => Err(Error::new(ErrorKind::MilterError)),
        }
    }
}

/// A handle on user data managed in the callback context.
///
/// This serves as an accessor to ‘private data’, that is connection-specific
/// and message-specific user data. Care must be taken to manage the data’s
/// lifecycle; specifically, the managed data is not automatically cleaned up
/// but must be reacquired (and dropped) at some appropriate time like
/// connection close or abort.
///
/// # Data lifecycle
///
/// Every path in the callbacks must have a final call to `DataHandle::take`.
pub struct DataHandle<T> {
    context_ptr: NonNull<sys::SMFICTX>,
    data_ptr: RefCell<Option<NonNull<T>>>,
}

impl<T> DataHandle<T> {
    fn new(ptr: *mut sys::SMFICTX) -> Self {
        assert!(!ptr.is_null());

        let data_ptr = unsafe { sys::smfi_getpriv(ptr) as _ };

        DataHandle {
            context_ptr: NonNull::new(ptr).unwrap(),
            data_ptr: RefCell::new(NonNull::new(data_ptr)),
        }
    }

    /// Hands over data into the context, returning current managed data if
    /// present.
    pub fn replace(&self, data: T) -> Option<T> {
        self.replace_data(Box::into_raw(Box::new(data)))
    }

    /// Retakes ownership of the managed data if present and removes it from the
    /// context.
    pub fn take(&self) -> Option<T> {
        self.replace_data(ptr::null_mut())
    }

    fn replace_data(&self, ptr: *mut T) -> Option<T> {
        let _ = unsafe { sys::smfi_setpriv(self.context_ptr.as_ptr(), ptr as _) };

        self.data_ptr
            .replace(NonNull::new(ptr))
            .map(|t| unsafe { *Box::from_raw(t.as_ptr()) })
    }

    /// Returns a reference to the managed data if present.
    ///
    /// It is the responsibility of the user only to call this when appropriate:
    /// eg, when the data is already borrowed mutably, `Err` is returned.
    ///
    /// # Example
    ///
    /// ```ignore
    /// if let Some(data) = context.data.borrow()? {
    ///     println!("{}", data);
    /// }
    /// ```
    pub fn borrow(&self) -> Result<Option<Ref<T>>> {
        let data_ref = self.data_ptr.try_borrow()
            .map_err(|_| Error::new(ErrorKind::DataAccessError))?;

        Ok(if data_ref.is_none() {
            None
        } else {
            Some(Ref::map(data_ref, |t| unsafe { t.as_ref().unwrap().as_ref() }))
        })
    }

    /// Returns a mutable reference to the managed data if present.
    ///
    /// It is the responsibility of the user only to call this when appropriate:
    /// only one exclusive mutable borrow is allowed at any time.
    pub fn borrow_mut(&self) -> Result<Option<RefMut<T>>> {
        let data_ref = self.data_ptr.try_borrow_mut()
            .map_err(|_| Error::new(ErrorKind::DataAccessError))?;

        Ok(if data_ref.is_none() {
            None
        } else {
            Some(RefMut::map(data_ref, |t| unsafe { t.as_mut().unwrap().as_mut() }))
        })
    }
}

/// Returns the runtime version of the milter library.
///
/// # Example
///
/// ```
/// let (major, minor, patch) = milter::version();
/// println!("Milter {}.{}.{}", major, minor, patch);
/// ```
pub fn version() -> (u32, u32, u32) {
    let (mut major, mut minor, mut patch) = (0, 0, 0);

    let _ = unsafe { sys::smfi_version(&mut major, &mut minor, &mut patch) };

    (major, minor, patch)
}

/// Sets the trace debug level of the milter library to the given value.
///
/// The value is unspecified, but will fall somewhere between zero (no debug
/// log) and six (max debug log volume).
pub fn set_internal_debug_level(level: i32) {
    let _ = unsafe { sys::smfi_setdbg(level) };
}

fn to_c_string(s: &str) -> Result<CString> {
    CString::new(s).map_err(|_| Error::new(ErrorKind::ConversionError))
}

fn to_result(status: sys::sfsistat) -> Result<()> {
    match status {
        sys::MI_SUCCESS => Ok(()),
        _ => Err(Error::new(ErrorKind::MilterError)),
    }
}