• R/O
  • SSH
  • HTTPS

eirrepo: Commit


Commit MetaInfo

Revision188 (tree)
Time2019-01-25 01:47:11
Authorquiret

Log Message

- added sendmail milter protocol handler implementation, protocol version 2

Change Summary

Incremental Difference

--- sendmail_milter_lib/include/milter_lib.h (nonexistent)
+++ sendmail_milter_lib/include/milter_lib.h (revision 188)
@@ -0,0 +1,155 @@
1+// Sendmail milter library for creating a server that talks to SMTP agents which implement the Sendmail Milter Protocol.
2+// Protocol version: 2.
3+
4+#ifndef _SENDMAIL_MILTER_LIB_HEADER_
5+#define _SENDMAIL_MILTER_LIB_HEADER_
6+
7+#include <cstdint>
8+
9+#include <sdk/String.h>
10+#include <sdk/Map.h>
11+#include <sdk/Vector.h>
12+#include <sdk/UniChar.h>
13+
14+#include <tidesnet/tidesnet.h>
15+
16+// Milter protocol version: 2
17+static constexpr std::uint32_t MILTER_PROTOCOL_VERSION = 2;
18+static constexpr std::uint32_t MILTER_CHUNK_SIZE = 65535; // for body max transfer data size from SMTP agent.
19+
20+// Milter response implementations.
21+void milter_respond_add_recipient ( TidesNetwork::NetSocket *socket, const char *recipient, size_t len );
22+void milter_respond_del_recipient ( TidesNetwork::NetSocket *socket, const char *recipient, size_t len );
23+void milter_respond_accept ( TidesNetwork::NetSocket *socket );
24+void milter_respond_replace_body ( TidesNetwork::NetSocket *socket, const char *content, size_t len );
25+void milter_respond_continue ( TidesNetwork::NetSocket *socket );
26+void milter_respond_discard ( TidesNetwork::NetSocket *socket );
27+void milter_respond_add_header ( TidesNetwork::NetSocket *socket, const char *headerName, size_t headerLen, const char *valueName, size_t valueLen );
28+void milter_respond_change_header ( TidesNetwork::NetSocket *socket, std::uint32_t headerIndex, const char *headerName, size_t headerLen, const char *valueName, size_t valueLen );
29+void milter_respond_progress ( TidesNetwork::NetSocket *socket );
30+void milter_respond_quarantine ( TidesNetwork::NetSocket *socket, const char *reason, size_t len );
31+void milter_respond_reject ( TidesNetwork::NetSocket *socket );
32+void milter_respond_tempfail ( TidesNetwork::NetSocket *socket );
33+void milter_respond_replycode ( TidesNetwork::NetSocket *socket, char replyCode[3], const char *replyMsg, size_t replyMsgLen );
34+void milter_respond_optneg ( TidesNetwork::NetSocket *socket, std::uint32_t actions, std::uint32_t protocol );
35+
36+// Helpers.
37+template <typename charType>
38+using basicString = eir::String <charType, CRTHeapAllocator>;
39+
40+template <typename keyType, typename valueType, typename comparatorType = eir::MapDefaultComparator>
41+using basicMap = eir::Map <keyType, valueType, CRTHeapAllocator, comparatorType>;
42+
43+template <typename valueType>
44+using basicVector = eir::Vector <valueType, CRTHeapAllocator>;
45+
46+// Negotiation data for the milter protocol.
47+struct MilterNegotiationData
48+{
49+ // Set what we will send.
50+ bool allowAddHeaders = true;
51+ bool allowChangeBody = true;
52+ bool allowAddRcpt = true;
53+ bool allowDelRcpt = true;
54+ bool allowChangeHeaders = true;
55+ bool allowQuarantine = false;
56+
57+ // Exclude certain kind of messages
58+ bool noConnect = false;
59+ bool noHelo = false;
60+ bool noMail = false;
61+ bool noRcpt = false;
62+ bool noBody = false;
63+ bool noHeaders = false;
64+ bool noEndOfHeaders = false;
65+};
66+
67+// Interface for accepting milter commands.
68+struct MilterResponder
69+{
70+ virtual void OnAbort( TidesNetwork::NetSocket *socket )
71+ {
72+ return;
73+ }
74+ virtual bool OnBody( TidesNetwork::NetSocket *socket, const void *data, size_t dataLen )
75+ {
76+ return true;
77+ }
78+ virtual bool OnConnect( TidesNetwork::NetSocket *socket, basicString <char> hostName, char family, std::uint16_t port, basicString <char> address )
79+ {
80+ return true;
81+ }
82+ // Macros should be handled by the milter receiver itself.
83+ virtual bool OnEndOfBody( TidesNetwork::NetSocket *socket )
84+ {
85+ return true;
86+ }
87+ virtual bool OnHelo( TidesNetwork::NetSocket *socket, basicString <char> helo )
88+ {
89+ return true;
90+ }
91+ virtual bool OnMailHeader( TidesNetwork::NetSocket *socket, basicString <char> name, basicString <char> value )
92+ {
93+ return true;
94+ }
95+ virtual bool OnMailFrom( TidesNetwork::NetSocket *socket, basicVector <basicString <char>> senders )
96+ {
97+ return true;
98+ }
99+ virtual bool OnEndOfHeaders( TidesNetwork::NetSocket *socket )
100+ {
101+ return true;
102+ }
103+ virtual void OnOptionNegotiation( TidesNetwork::NetSocket *socket, const MilterNegotiationData& remoteData, MilterNegotiationData& negOut )
104+ {
105+ return;
106+ }
107+ virtual bool OnMailRecipients( TidesNetwork::NetSocket *socket, basicVector <basicString <char>> recipients )
108+ {
109+ return true;
110+ }
111+ virtual void OnQuit( TidesNetwork::NetSocket *socket )
112+ {
113+ return;
114+ }
115+ virtual void OnUnknownPacket( TidesNetwork::NetSocket *socket, std::int8_t cmdId, const void *data, size_t dataLen )
116+ {
117+ return;
118+ }
119+};
120+
121+// Milter protocol handler.
122+struct MilterProtocolHandler
123+{
124+ MilterProtocolHandler( MilterResponder *responder );
125+ MilterProtocolHandler( const MilterProtocolHandler& ) = delete;
126+ MilterProtocolHandler( MilterProtocolHandler&& ) = delete;
127+ ~MilterProtocolHandler( void );
128+
129+ MilterProtocolHandler& operator = ( const MilterProtocolHandler& ) = delete;
130+ MilterProtocolHandler& operator = ( MilterProtocolHandler&& ) = delete;
131+
132+ // Processes milter packets as long as socket is not closed.
133+ void Run( TidesNetwork::NetSocket *socket );
134+
135+private:
136+ MilterResponder *responder;
137+
138+ // Information about a given command.
139+ struct milter_cmd_info
140+ {
141+ struct caseSensitiveComparator
142+ {
143+ static AINLINE bool is_less_than( const basicString <char>& left, const basicString <char>& right )
144+ {
145+ return FixedStringCompare( left.GetConstString(), left.GetLength(), right.GetConstString(), right.GetLength(), true ) == eir::eCompResult::LEFT_LESS;
146+ }
147+ };
148+
149+ basicMap <basicString <char>, basicString <char>, caseSensitiveComparator> macroMap;
150+ };
151+
152+ basicMap <std::int8_t, milter_cmd_info> cmdInfoMap;
153+};
154+
155+#endif //_SENDMAIL_MILTER_LIB_HEADER_
\ No newline at end of file
--- sendmail_milter_lib/milter-protocol.txt (nonexistent)
+++ sendmail_milter_lib/milter-protocol.txt (revision 188)
@@ -0,0 +1,471 @@
1+$Id: milter-protocol.txt,v 1.6 2004/08/04 16:27:50 tvierling Exp $
2+_______________________________________
3+THE SENDMAIL MILTER PROTOCOL, VERSION 2
4+
5+**
6+
7+The Sendmail and "libmilter" implementations of the protocol described
8+herein are:
9+
10+ Copyright (c) 1999-2002 Sendmail, Inc. and its suppliers.
11+ All rights reserved.
12+
13+This document is:
14+
15+ Copyright (c) 2002-2003, Todd Vierling <tv@pobox.com> <tv@duh.org>
16+ All rights reserved.
17+
18+Permission is granted to copy or reproduce this document in its entirety
19+in any medium without charge, provided that the copy or reproduction is
20+without modification and includes the above copyright notice(s).
21+
22+________
23+OVERVIEW
24+
25+The date of this document is contained within the "Id" symbolic CVS/RCS
26+tag present at the top of this document.
27+
28+This document describes the Sendmail "milter" mail filtering and
29+MTA-level mail manipulation protocol, version 2, based on the publicly
30+available C-language source code to Sendmail, version 8.11.6.
31+
32+As of this writing, this protocol document is based on the
33+implementation of milter in Sendmail 8.11, but has been verified
34+compatible with Sendmail 8.12. Some Sendmail 8.12 extensions,
35+determined by flags sent with the SMFIC_OPTNEG command, are not yet
36+described here.
37+
38+Technical terms describing mail transport are used throughout. A reader
39+should have ample understanding of RFCs 821, 822, 2821, and their
40+successors, and (for Sendmail MTAs) a cursory understanding of Sendmail
41+configuration procedures.
42+
43+______
44+LEGEND
45+
46+All integers are assumed to be in network (big-endian) byte order.
47+Data items are aligned to a byte boundary, and are not forced to any
48+larger alignment.
49+
50+This document makes use of a mnemonic representation of data structures
51+as transmitted over a communications endpoint to and from a milter
52+program. A structure may be represented like the following:
53+
54+'W' SMFIC_HWORLD Hello world packet
55+uint16 len Length of string
56+char str[len] Text value
57+
58+This structure contains a single byte with the ASCII representation 'W',
59+a 16-bit network byte order integer, and a character array with the
60+length given by the "len" integer. Character arrays described in this
61+fashion are an exact number of bytes, and are not assumed to be NUL
62+terminated.
63+
64+A special data type representation is used here to indicate strings and
65+arrays of strings using C-language semantics of NUL termination.
66+
67+char str[] String, NUL terminated
68+char array[][] Array of strings, NUL terminated
69+
70+Here, "str" is a NUL-terminated string, and subsequent data items are
71+assumed to be located immediately following the NUL byte. "array" is a
72+stream of NUL-terminated strings, located immediately following each
73+other in the stream, leading up to the end of the data structure
74+(determined by the data packet's size).
75+
76+____________________
77+LINK/PACKET PROTOCOL
78+
79+The MTA makes a connection to a milter by connecting to an IPC endpoint
80+(socket), via a stream-based protocol. TCPv4, TCPv6, and "Unix
81+filesystem" sockets can be used for connection to a milter.
82+(Configuration of Sendmail to make use of these different endpoint
83+addressing methods is not described here.)
84+
85+Data is transmitted in both directions using a structured packet
86+protocol. Each packets is comprised of:
87+
88+uint32 len Size of data to follow
89+char cmd Command/response code
90+char data[len-1] Code-specific data (may be empty)
91+
92+The connection can be closed at any time by either side. If closed by
93+the MTA, the milter program should release all state information for the
94+previously established connection. If closed by the milter program
95+without first sending an accept or reject action message, the MTA will
96+take the default action for any message in progress (configurable to
97+ignore the milter program, or reject with a 4xx or 5xx error).
98+
99+_____________________________
100+A TYPICAL MILTER CONVERSATION
101+
102+The MTA drives the milter conversation. The milter program sends
103+responses when (and only when) specified by the particular command code
104+sent by the MTA. It is an error for a milter either to send a response
105+packet when not requested, or fail to send a response packet when
106+requested. The MTA may have limits on the time allowed for a response
107+packet to be sent.
108+
109+The typical lifetime of a milter connection can be viewed as follows:
110+
111+MTA Milter
112+
113+SMFIC_OPTNEG
114+ SMFIC_OPTNEG
115+SMFIC_MACRO:'C'
116+SMFIC_CONNECT
117+ Accept/reject action
118+SMFIC_MACRO:'H'
119+SMFIC_HELO
120+ Accept/reject action
121+SMFIC_MACRO:'M'
122+SMFIC_MAIL
123+ Accept/reject action
124+SMFIC_MACRO:'R'
125+SMFIC_RCPT
126+ Accept/reject action
127+SMFIC_HEADER (multiple)
128+ Accept/reject action (per SMFIC_HEADER)
129+SMFIC_EOH
130+ Accept/reject action
131+SMFIC_BODY (multiple)
132+ Accept/reject action (per SMFIC_BODY)
133+SMFIC_BODYEOB
134+ Modification action (multiple, may be none)
135+ Accept/reject action
136+
137+ (Reset state to before SMFIC_MAIL and continue,
138+ unless connection is dropped by MTA)
139+
140+Several of these MTA/milter steps can be skipped if requested by the
141+SMFIC_OPTNEG response packet; see below.
142+
143+____________________
144+PROTOCOL NEGOTIATION
145+
146+Milters can perform several actions on a SMTP transaction. The following is
147+a bitmask of possible actions, which may be set by the milter in the
148+"actions" field of the SMFIC_OPTNEG response packet. (Any action which MAY
149+be performed by the milter MUST be included in this field.)
150+
151+0x01 SMFIF_ADDHDRS Add headers (SMFIR_ADDHEADER)
152+0x02 SMFIF_CHGBODY Change body chunks (SMFIR_REPLBODY)
153+0x04 SMFIF_ADDRCPT Add recipients (SMFIR_ADDRCPT)
154+0x08 SMFIF_DELRCPT Remove recipients (SMFIR_DELRCPT)
155+0x10 SMFIF_CHGHDRS Change or delete headers (SMFIR_CHGHEADER)
156+0x20 SMFIF_QUARANTINE Quarantine message (SMFIR_QUARANTINE)
157+
158+(XXX: SMFIF_DELRCPT has an impact on how address rewriting affects
159+addresses sent in the SMFIC_RCPT phase. This will be described in a
160+future revision of this document.)
161+
162+Protocol content can contain only selected parts of the SMTP
163+transaction. To mask out unwanted parts (saving on "over-the-wire" data
164+churn), the following can be set in the "protocol" field of the
165+SMFIC_OPTNEG response packet.
166+
167+0x01 SMFIP_NOCONNECT Skip SMFIC_CONNECT
168+0x02 SMFIP_NOHELO Skip SMFIC_HELO
169+0x04 SMFIP_NOMAIL Skip SMFIC_MAIL
170+0x08 SMFIP_NORCPT Skip SMFIC_RCPT
171+0x10 SMFIP_NOBODY Skip SMFIC_BODY
172+0x20 SMFIP_NOHDRS Skip SMFIC_HEADER
173+0x40 SMFIP_NOEOH Skip SMFIC_EOH
174+
175+For backwards-compatible milters, the milter should pay attention to the
176+"actions" and "protocol" fields of the SMFIC_OPTNEG packet, and mask out
177+any bits that are not part of the offered protocol content. The MTA may
178+reject the milter program if any action or protocol bit appears outside
179+the MTA's offered bitmask.
180+
181+_____________
182+COMMAND CODES
183+
184+The following are commands transmitted from the MTA to the milter
185+program. The data structures represented occupy the "cmd" and "data"
186+fields of the packets described above in LINK/PACKET PROTOCOL. (In
187+other words, the data structures below take up exactly "len" bytes,
188+including the "cmd" byte.)
189+
190+**
191+
192+'A' SMFIC_ABORT Abort current filter checks
193+ Expected response: NONE
194+
195+(Resets internal state of milter program to before SMFIC_HELO, but keeps
196+the connection open.)
197+
198+**
199+
200+'B' SMFIC_BODY Body chunk
201+ Expected response: Accept/reject action
202+
203+char buf[] Up to MILTER_CHUNK_SIZE (65535) bytes
204+
205+The buffer is not NUL-terminated.
206+
207+The body SHOULD be encoded with CRLF line endings, as if it was being
208+transmitted over SMTP. In practice existing MTAs and milter clients
209+will probably accept bare LFs, although at least some will convert CRLF
210+sequences to LFs.
211+
212+(These body chunks can be buffered by the milter for later replacement
213+via SMFIR_REPLBODY during the SMFIC_BODYEOB phase.)
214+
215+**
216+
217+'C' SMFIC_CONNECT SMTP connection information
218+ Expected response: Accept/reject action
219+
220+char hostname[] Hostname, NUL terminated
221+char family Protocol family (see below)
222+uint16 port Port number (SMFIA_INET or SMFIA_INET6 only)
223+char address[] IP address (ASCII) or unix socket path, NUL terminated
224+
225+(Sendmail invoked via the command line or via "-bs" will report the
226+connection as the "Unknown" protocol family.)
227+
228+Protocol families used with SMFIC_CONNECT in the "family" field:
229+
230+'U' SMFIA_UNKNOWN Unknown (NOTE: Omits "port" and "host" fields entirely)
231+'L' SMFIA_UNIX Unix (AF_UNIX/AF_LOCAL) socket ("port" is 0)
232+'4' SMFIA_INET TCPv4 connection
233+'6' SMFIA_INET6 TCPv6 connection
234+
235+**
236+
237+'D' SMFIC_MACRO Define macros
238+ Expected response: NONE
239+
240+char cmdcode Command for which these macros apply
241+char nameval[][] Array of NUL-terminated strings, alternating
242+ between name of macro and value of macro.
243+
244+SMFIC_MACRO appears as a packet just before the corresponding "cmdcode"
245+(here), which is the same identifier as the following command. The
246+names correspond to Sendmail macros, omitting the "$" identifier
247+character.
248+
249+Types of macros, and some commonly supplied macro names, used with
250+SMFIC_MACRO are as follows, organized by "cmdcode" value.
251+Implementations SHOULD NOT assume that any of these macros will be
252+present on a given connection. In particular, communications protocol
253+information may not be present on the "Unknown" protocol type.
254+
255+'C' SMFIC_CONNECT $_ $j ${daemon_name} ${if_name} ${if_addr}
256+
257+'H' SMFIC_HELO ${tls_version} ${cipher} ${cipher_bits}
258+ ${cert_subject} ${cert_issuer}
259+
260+'M' SMFIC_MAIL $i ${auth_type} ${auth_authen} ${auth_ssf}
261+ ${auth_author} ${mail_mailer} ${mail_host}
262+ ${mail_addr}
263+
264+'R' SMFIC_RCPT ${rcpt_mailer} ${rcpt_host} ${rcpt_addr}
265+
266+For future compatibility, implementations MUST allow SMFIC_MACRO at any
267+time, but the handling of unspecified command codes, or SMFIC_MACRO not
268+appearing before its specified command, is currently undefined.
269+
270+**
271+
272+'E' SMFIC_BODYEOB End of body marker
273+ Expected response: Zero or more modification
274+ actions, then accept/reject action
275+
276+**
277+
278+'H' SMFIC_HELO HELO/EHLO name
279+ Expected response: Accept/reject action
280+
281+char helo[] HELO string, NUL terminated
282+
283+**
284+
285+'L' SMFIC_HEADER Mail header
286+ Expected response: Accept/reject action
287+
288+char name[] Name of header, NUL terminated
289+char value[] Value of header, NUL terminated
290+
291+**
292+
293+'M' SMFIC_MAIL MAIL FROM: information
294+ Expected response: Accept/reject action
295+
296+char args[][] Array of strings, NUL terminated (address at index 0).
297+ args[0] is sender, with <> qualification.
298+ args[1] and beyond are ESMTP arguments, if any.
299+
300+**
301+
302+'N' SMFIC_EOH End of headers marker
303+ Expected response: Accept/reject action
304+
305+**
306+
307+'O' SMFIC_OPTNEG Option negotiation
308+ Expected response: SMFIC_OPTNEG packet
309+
310+uint32 version SMFI_VERSION (2)
311+uint32 actions Bitmask of allowed actions from SMFIF_*
312+uint32 protocol Bitmask of possible protocol content from SMFIP_*
313+
314+**
315+
316+'R' SMFIC_RCPT RCPT TO: information
317+ Expected response: Accept/reject action
318+
319+char args[][] Array of strings, NUL terminated (address at index 0).
320+ args[0] is recipient, with <> qualification.
321+ args[1] and beyond are ESMTP arguments, if any.
322+
323+**
324+
325+'Q' SMFIC_QUIT Quit milter communication
326+ Expected response: Close milter connection
327+
328+______________
329+RESPONSE CODES
330+
331+The following are commands transmitted from the milter program to the
332+MTA, in response to the appropriate type of command packet. The data
333+structures represented occupy the "cmd" and "data" fields of the packets
334+described above in LINK/PACKET PROTOCOL. (In other words, the data
335+structures below take up exactly "len" bytes, including the "cmd" byte.)
336+
337+**
338+
339+Response codes:
340+
341+'+' SMFIR_ADDRCPT Add recipient (modification action)
342+
343+char rcpt[] New recipient, NUL terminated
344+
345+**
346+
347+'-' SMFIR_DELRCPT Remove recipient (modification action)
348+
349+char rcpt[] Recipient to remove, NUL terminated
350+ (string must match the one in SMFIC_RCPT exactly)
351+
352+**
353+
354+'a' SMFIR_ACCEPT Accept message completely (accept/reject action)
355+
356+(This will skip to the end of the milter sequence, and recycle back to
357+the state before SMFIC_MAIL. The MTA may, instead, close the connection
358+at that point.)
359+
360+**
361+
362+'b' SMFIR_REPLBODY Replace body (modification action)
363+
364+char buf[] A portion of the body to be replaced
365+
366+The buffer is not NUL-terminated.
367+
368+As with SMFIC_BODY, the body SHOULD be encoded with CRLF line endings.
369+Sendmail will convert CRLFs to bare LFs as it receives SMFIR_REPLBODY
370+responses (even if the CR and LF are split across two responses); the
371+behavior of other MTAs has not been investigated.
372+
373+A milter that uses SMFIR_REPLBODY must replace the entire body, but
374+it may split the new replacement body across multiple SMFIR_REPLBODY
375+responses and it may make each response as small as it wants (and
376+they do not need to correspond one to one with SMFIC_BODY messages).
377+There is no explicit end of body marker; this role is filled by
378+whatever accept/reject response the milter finishes with.
379+
380+**
381+
382+'c' SMFIR_CONTINUE Accept and keep processing (accept/reject action)
383+
384+(If issued at the end of the milter conversation, functions the same as
385+SMFIR_ACCEPT.)
386+
387+**
388+
389+'d' SMFIR_DISCARD Set discard flag for entire message (accept/reject action)
390+
391+(Note that message processing MAY continue afterwards, but the mail will
392+not be delivered even if accepted with SMFIR_ACCEPT.)
393+
394+**
395+
396+'h' SMFIR_ADDHEADER Add header (modification action)
397+
398+char name[] Name of header, NUL terminated
399+char value[] Value of header, NUL terminated
400+
401+**
402+
403+'m' SMFIR_CHGHEADER Change header (modification action)
404+
405+uint32 index Index of the occurrence of this header
406+char name[] Name of header, NUL terminated
407+char value[] Value of header, NUL terminated
408+
409+(Note that the "index" above is per-name--i.e. a 3 in this field
410+indicates that the modification is to be applied to the third such
411+header matching the supplied "name" field. A zero length string for
412+"value", leaving only a single NUL byte, indicates that the header
413+should be deleted entirely.)
414+
415+**
416+
417+'p' SMFIR_PROGRESS Progress (asynchronous action)
418+
419+This is an asynchronous response which is sent to the MTA to reset the
420+communications timer during long operations. The MTA should consume
421+as many of these responses as are sent, waiting for the real response
422+for the issued command.
423+
424+**
425+
426+'q' SMFIR_QUARANTINE Quarantine message (modification action)
427+char reason[] Reason for quarantine, NUL terminated
428+
429+This quarantines the message into a holding pool defined by the MTA.
430+(First implemented in Sendmail in version 8.13; offered to the milter by
431+the SMFIF_QUARANTINE flag in "actions" of SMFIC_OPTNEG.)
432+
433+**
434+
435+'r' SMFIR_REJECT Reject command/recipient with a 5xx (accept/reject action)
436+
437+**
438+
439+'t' SMFIR_TEMPFAIL Reject command/recipient with a 4xx (accept/reject action)
440+
441+**
442+
443+'y' SMFIR_REPLYCODE Send specific Nxx reply message (accept/reject action)
444+
445+char smtpcode[3] Nxx code (ASCII), not NUL terminated
446+char space ' '
447+char text[] Text of reply message, NUL terminated
448+
449+('%' characters present in "text" must be doubled to prevent problems
450+with printf-style formatting that may be used by the MTA.)
451+
452+**
453+
454+'O' SMFIC_OPTNEG Option negotiation (in response to SMFIC_OPTNEG)
455+
456+uint32 version SMFI_VERSION (2)
457+uint32 actions Bitmask of requested actions from SMFIF_*
458+uint32 protocol Bitmask of undesired protocol content from SMFIP_*
459+
460+_______
461+CREDITS
462+
463+Sendmail, Inc. - for the Sendmail program itself
464+
465+The anti-spam community - for making e-mail a usable medium again
466+
467+The spam community - for convincing me that it's time to really do
468+somthing to quell the inflow of their crap
469+
470+___
471+EOF
--- sendmail_milter_lib/src/main.cpp (nonexistent)
+++ sendmail_milter_lib/src/main.cpp (revision 188)
@@ -0,0 +1,572 @@
1+#include "milter_lib.h"
2+
3+#include <sdk/Map.h>
4+
5+#include <sdk/MemoryUtils.stream.h>
6+
7+#pragma pack(push)
8+#pragma pack(1)
9+
10+struct milter_msg_header
11+{
12+ endian::big_endian <std::uint32_t> len;
13+ std::int8_t cmd;
14+};
15+
16+#pragma pack(pop)
17+
18+// Supported messages from the client.
19+static constexpr std::int8_t SMFIC_ABORT = 'A';
20+static constexpr std::int8_t SMFIC_BODY = 'B';
21+static constexpr std::int8_t SMFIC_CONNECT = 'C';
22+static constexpr std::int8_t SMFIC_MACRO = 'D';
23+static constexpr std::int8_t SMFIC_BODYEOB = 'E';
24+static constexpr std::int8_t SMFIC_HELO = 'H';
25+static constexpr std::int8_t SMFIC_HEADER = 'L';
26+static constexpr std::int8_t SMFIC_MAIL = 'M';
27+static constexpr std::int8_t SMFIC_EOH = 'N';
28+static constexpr std::int8_t SMFIC_OPTNEG = 'O';
29+static constexpr std::int8_t SMFIC_RCPT = 'R';
30+static constexpr std::int8_t SMFIC_QUIT = 'Q';
31+
32+// Known-supported responses to the client.
33+static constexpr std::int8_t SMFIR_ADDRCPT = '+';
34+static constexpr std::int8_t SMFIR_DELRCPT = '-';
35+static constexpr std::int8_t SMFIR_ACCEPT = 'a';
36+static constexpr std::int8_t SMFIR_REPLBODY = 'b';
37+static constexpr std::int8_t SMFIR_CONTINUE = 'c';
38+static constexpr std::int8_t SMFIR_DISCARD = 'd';
39+static constexpr std::int8_t SMFIR_ADDHEADER = 'h';
40+static constexpr std::int8_t SMFIR_CHGHEADER = 'm';
41+static constexpr std::int8_t SMFIR_PROGRESS = 'p';
42+static constexpr std::int8_t SMFIR_QUARANTINE = 'q';
43+static constexpr std::int8_t SMFIR_REJECT = 'r';
44+static constexpr std::int8_t SMFIR_TEMPFAIL = 't';
45+static constexpr std::int8_t SMFIR_REPLYCODE = 'y';
46+// SMFIC_OPTNEG
47+
48+// Known option-negotation bitfield impressions.
49+static constexpr std::uint32_t SMFIF_ADDHDRS = 0x00000001;
50+static constexpr std::uint32_t SMFIF_CHGBODY = 0x00000002;
51+static constexpr std::uint32_t SMFIF_ADDRCPT = 0x00000004;
52+static constexpr std::uint32_t SMFIF_DELRCPT = 0x00000008;
53+static constexpr std::uint32_t SMFIF_CHGHDRS = 0x00000010;
54+static constexpr std::uint32_t SMFIF_QUARANTINE = 0x00000020;
55+
56+static constexpr std::uint32_t SMFIP_NOCONNECT = 0x00000001;
57+static constexpr std::uint32_t SMFIP_NOHELO = 0x00000002;
58+static constexpr std::uint32_t SMFIP_NOMAIL = 0x00000004;
59+static constexpr std::uint32_t SMFIP_NORCPT = 0x00000008;
60+static constexpr std::uint32_t SMFIP_NOBODY = 0x00000010;
61+static constexpr std::uint32_t SMFIP_NOHDRS = 0x00000020;
62+static constexpr std::uint32_t SMFIP_NOEOH = 0x00000040;
63+
64+template <typename callbackType>
65+inline void milter_respond_template( TidesNetwork::NetSocket *socket, std::int8_t cmd, const callbackType& cb )
66+{
67+ BasicMemStream::basicMemStreamAllocMan <std::uint32_t> allocMan;
68+ BasicMemStream::basicMemoryBufferStream <std::uint32_t> stream( nullptr, 0, allocMan );
69+
70+ stream.Truncate( sizeof(milter_msg_header) );
71+ stream.Seek( sizeof(milter_msg_header) );
72+
73+ cb( stream );
74+
75+ milter_msg_header header;
76+ header.len = stream.Size() - sizeof(std::uint32_t);
77+ header.cmd = cmd;
78+
79+ stream.Seek( 0 );
80+ stream.WriteStruct( header );
81+
82+ socket->Write( stream.Data(), stream.Size() );
83+}
84+
85+// Implementations of all packets that we can send to clients.
86+void milter_respond_add_recipient( TidesNetwork::NetSocket *socket, const char *recipient, size_t len )
87+{
88+ milter_respond_template( socket, SMFIR_ADDRCPT,
89+ [&]( auto& stream )
90+ {
91+ stream.Write( recipient, len );
92+ stream.WriteInt8_BE( 0 );
93+ });
94+}
95+
96+void milter_respond_del_recipient( TidesNetwork::NetSocket *socket, const char *recipient, size_t len )
97+{
98+ milter_respond_template( socket, SMFIR_DELRCPT,
99+ [&]( auto& stream )
100+ {
101+ stream.Write( recipient, len );
102+ stream.WriteInt8_BE( 0 );
103+ });
104+}
105+
106+void milter_respond_accept( TidesNetwork::NetSocket *socket )
107+{
108+ milter_msg_header header;
109+ header.len = sizeof(header.cmd);
110+ header.cmd = SMFIR_ACCEPT;
111+
112+ socket->Write( &header, sizeof(header) );
113+}
114+
115+void milter_respond_replace_body( TidesNetwork::NetSocket *socket, const char *content, size_t len )
116+{
117+ milter_respond_template( socket, SMFIR_REPLBODY,
118+ [&]( auto& stream )
119+ {
120+ stream.Write( content, len );
121+ });
122+}
123+
124+void milter_respond_continue( TidesNetwork::NetSocket *socket )
125+{
126+ milter_msg_header header;
127+ header.len = sizeof(header.cmd);
128+ header.cmd = SMFIR_CONTINUE;
129+
130+ socket->Write( &header, sizeof(header) );
131+}
132+
133+void milter_respond_discard( TidesNetwork::NetSocket *socket )
134+{
135+ milter_msg_header header;
136+ header.len = sizeof(header.cmd);
137+ header.cmd = SMFIR_DISCARD;
138+
139+ socket->Write( &header, sizeof(header) );
140+}
141+
142+void milter_respond_add_header( TidesNetwork::NetSocket *socket, const char *headerName, size_t headerLen, const char *valueName, size_t valueLen )
143+{
144+ milter_respond_template( socket, SMFIR_ADDHEADER,
145+ [&]( auto& stream )
146+ {
147+ stream.Write( headerName, headerLen );
148+ stream.WriteInt8_BE( 0 );
149+ stream.Write( valueName, valueLen );
150+ stream.WriteInt8_BE( 0 );
151+ });
152+}
153+
154+void milter_respond_change_header( TidesNetwork::NetSocket *socket, std::uint32_t headerIndex, const char *headerName, size_t headerLen, const char *valueName, size_t valueLen )
155+{
156+ milter_respond_template( socket, SMFIR_CHGHEADER,
157+ [&]( auto& stream )
158+ {
159+ stream.WriteUInt32_BE( headerIndex );
160+ stream.Write( headerName, headerLen );
161+ stream.WriteInt8_BE( 0 );
162+ stream.Write( valueName, valueLen );
163+ stream.WriteInt8_BE( 0 );
164+ });
165+}
166+
167+void milter_respond_progress( TidesNetwork::NetSocket *socket )
168+{
169+ milter_msg_header header;
170+ header.len = sizeof(header.cmd);
171+ header.cmd = SMFIR_PROGRESS;
172+
173+ socket->Write( &header, sizeof(header) );
174+}
175+
176+void milter_respond_quarantine( TidesNetwork::NetSocket *socket, const char *reason, size_t len )
177+{
178+ milter_respond_template( socket, SMFIR_QUARANTINE,
179+ [&]( auto& stream )
180+ {
181+ stream.Write( reason, len );
182+ stream.WriteInt8_BE( 0 );
183+ });
184+}
185+
186+void milter_respond_reject( TidesNetwork::NetSocket *socket )
187+{
188+ milter_msg_header header;
189+ header.len = sizeof(header.cmd);
190+ header.cmd = SMFIR_REJECT;
191+
192+ socket->Write( &header, sizeof(header) );
193+}
194+
195+void milter_respond_tempfail( TidesNetwork::NetSocket *socket )
196+{
197+ milter_msg_header header;
198+ header.len = sizeof(header.cmd);
199+ header.cmd = SMFIR_TEMPFAIL;
200+
201+ socket->Write( &header, sizeof(header) );
202+}
203+
204+void milter_respond_replycode( TidesNetwork::NetSocket *socket, char replyCode[3], const char *replyMsg, size_t replyMsgLen )
205+{
206+ //TODO: remember to double any % to prevent issues with printf at SMTP agent.
207+
208+ milter_respond_template( socket, SMFIR_REPLYCODE,
209+ [&]( auto& stream )
210+ {
211+ stream.Write( replyCode, 3 );
212+ stream.WriteInt8_BE( ' ' );
213+ stream.Write( replyMsg, replyMsgLen );
214+ stream.WriteInt8_BE( 0 );
215+ });
216+}
217+
218+void milter_respond_optneg( TidesNetwork::NetSocket *socket, std::uint32_t actions, std::uint32_t protocol )
219+{
220+ milter_respond_template( socket, SMFIC_OPTNEG,
221+ [&]( auto& stream )
222+ {
223+ stream.WriteUInt32_BE( MILTER_PROTOCOL_VERSION );
224+ stream.WriteUInt32_BE( actions );
225+ stream.WriteUInt32_BE( protocol );
226+ });
227+}
228+
229+// Helpers.
230+template <typename seekType, typename allocManType, bool isConst, bool releaseOnDestroy>
231+static basicString <char> read_null_terminated_string( memoryBufferStream <seekType, allocManType, isConst, releaseOnDestroy>& stream )
232+{
233+ basicString <char> outStr;
234+
235+ while ( true )
236+ {
237+ char c;
238+
239+ bool couldRead = stream.Read( &c, sizeof(c) );
240+
241+ if ( !couldRead )
242+ {
243+ break;
244+ }
245+
246+ if ( c == 0 )
247+ {
248+ break;
249+ }
250+
251+ outStr += c;
252+ }
253+
254+ return outStr;
255+}
256+
257+// Implementation of the milter protocol handler.
258+MilterProtocolHandler::MilterProtocolHandler( MilterResponder *responder )
259+{
260+ this->responder = responder;
261+}
262+
263+MilterProtocolHandler::~MilterProtocolHandler( void )
264+{
265+ return;
266+}
267+
268+AINLINE void milter_respond_accept_or_reject( TidesNetwork::NetSocket *socket, bool acceptOrReject )
269+{
270+ if ( acceptOrReject )
271+ {
272+ milter_respond_accept( socket );
273+ }
274+ else
275+ {
276+ milter_respond_reject( socket );
277+ }
278+}
279+
280+void MilterProtocolHandler::Run( TidesNetwork::NetSocket *accSock )
281+{
282+ // Process milter stuff.
283+ // We choose a very simple message model.
284+ while ( accSock->IsClosed() == false )
285+ {
286+ milter_msg_header header;
287+ {
288+ size_t readCount = accSock->Read( &header, sizeof(header) );
289+
290+ if ( readCount == 0 )
291+ {
292+ break;
293+ }
294+ if ( readCount != sizeof(header) )
295+ {
296+ break;
297+ }
298+ }
299+
300+ std::uint32_t packetSize = header.len;
301+ std::int8_t packetCmd = header.cmd;
302+
303+ // Decrement the command byte that we already received.
304+ if ( packetSize < 1 )
305+ {
306+ throw eir_exception();
307+ }
308+
309+ packetSize -= 1;
310+
311+ // Read the data of the packet.
312+ void *packetData = CRTHeapAllocator::Allocate( nullptr, packetSize, 1 );
313+
314+ assert( packetData != nullptr );
315+
316+ try
317+ {
318+ // Do the read.
319+ {
320+ size_t readCount = accSock->Read( packetData, packetSize );
321+
322+ if ( readCount != packetSize )
323+ {
324+ throw eir_exception();
325+ }
326+ }
327+
328+ // Process the packet.
329+ nullBufAllocMan <std::uint32_t> man;
330+ memoryBufferStream <std::uint32_t, nullBufAllocMan <std::uint32_t>, true, false> stream( packetData, packetSize, man );
331+
332+ MilterResponder *responder = this->responder;
333+
334+ if ( packetCmd == SMFIC_ABORT )
335+ {
336+ // Reset our informations about commands.
337+ this->cmdInfoMap.Clear();
338+
339+ responder->OnAbort( accSock );
340+ }
341+ else if ( packetCmd == SMFIC_BODY )
342+ {
343+ bool doAccept = responder->OnBody( accSock, packetData, packetSize );
344+
345+ milter_respond_accept_or_reject( accSock, doAccept );
346+ }
347+ else if ( packetCmd == SMFIC_CONNECT )
348+ {
349+ // Read out the connection information for this SMTP request.
350+ // We expect that this information is delivered truthfully.
351+ basicString <char> hostName = read_null_terminated_string( stream );
352+ std::int8_t family = stream.ReadInt8_EX();
353+ std::uint16_t portNumber = stream.ReadUInt16_EX();
354+ basicString <char> address = read_null_terminated_string( stream );
355+
356+ bool doAccept = responder->OnConnect(
357+ accSock,
358+ std::move( hostName ), family, portNumber, std::move( address )
359+ );
360+
361+ milter_respond_accept_or_reject( accSock, doAccept );
362+ }
363+ else if ( packetCmd == SMFIC_MACRO )
364+ {
365+ // Fetch the macro details and register new values.
366+ std::int8_t cmdToRegisterTo = stream.ReadInt8_EX();
367+
368+ while ( stream.Tell() < stream.Size() )
369+ {
370+ basicString <char> macroName = read_null_terminated_string( stream );
371+ basicString <char> macroValue = read_null_terminated_string( stream );
372+
373+ if ( macroName.GetLength() > 0 )
374+ {
375+ if ( macroValue.GetLength() == 0 )
376+ {
377+ // Remove any registration.
378+ if ( auto *node = this->cmdInfoMap.Find( cmdToRegisterTo ) )
379+ {
380+ node->GetValue().macroMap.RemoveByKey( macroName );
381+ }
382+ }
383+ else
384+ {
385+ // Register a new macro.
386+ this->cmdInfoMap[ cmdToRegisterTo ].macroMap[ std::move( macroName ) ] = std::move( macroValue );
387+ }
388+ }
389+ }
390+
391+ // TODO: add methods that actually use the macros for easy string-item replacement with them.
392+ }
393+ else if ( packetCmd == SMFIC_BODYEOB )
394+ {
395+ bool doAccept = responder->OnEndOfBody( accSock );
396+
397+ milter_respond_accept_or_reject( accSock, doAccept );
398+ }
399+ else if ( packetCmd == SMFIC_HELO )
400+ {
401+ // Fetch the helo string.
402+ basicString <char> helo = read_null_terminated_string( stream );
403+
404+ bool doAccept = responder->OnHelo( accSock, std::move( helo ) );
405+
406+ milter_respond_accept_or_reject( accSock, doAccept );
407+ }
408+ else if ( packetCmd == SMFIC_HEADER )
409+ {
410+ basicString <char> headerName = read_null_terminated_string( stream );
411+ basicString <char> headerValue = read_null_terminated_string( stream );
412+
413+ bool doAccept = responder->OnMailHeader( accSock, std::move( headerName ), std::move( headerValue ) );
414+
415+ milter_respond_accept_or_reject( accSock, doAccept );
416+ }
417+ else if ( packetCmd == SMFIC_MAIL )
418+ {
419+ // Read all mail-from values.
420+ basicVector <basicString <char>> senders;
421+
422+ while ( stream.Tell() < stream.Size() )
423+ {
424+ basicString <char> fromItem = read_null_terminated_string( stream );
425+
426+ senders.AddToBack( std::move( fromItem ) );
427+ }
428+
429+ bool doAccept = responder->OnMailFrom( accSock, std::move( senders ) );
430+
431+ milter_respond_accept_or_reject( accSock, doAccept );
432+ }
433+ else if ( packetCmd == SMFIC_EOH )
434+ {
435+ bool doAccept = responder->OnEndOfHeaders( accSock );
436+
437+ milter_respond_accept_or_reject( accSock, doAccept );
438+ }
439+ else if ( packetCmd == SMFIC_OPTNEG )
440+ {
441+ std::uint32_t remoteVer = stream.ReadUInt32_BE_EX();
442+
443+ if ( remoteVer != MILTER_PROTOCOL_VERSION )
444+ {
445+ throw eir_exception();
446+ }
447+
448+ std::uint32_t actions = stream.ReadUInt32_BE_EX();
449+ std::uint32_t protocol = stream.ReadUInt32_BE_EX();
450+
451+ // Translate to friendly format.
452+ MilterNegotiationData remoteData;
453+ remoteData.allowAddHeaders = ( ( actions & SMFIF_ADDHDRS ) != 0 );
454+ remoteData.allowChangeBody = ( ( actions & SMFIF_CHGBODY ) != 0 );
455+ remoteData.allowAddRcpt = ( ( actions & SMFIF_ADDRCPT ) != 0 );
456+ remoteData.allowDelRcpt = ( ( actions & SMFIF_DELRCPT ) != 0 );
457+ remoteData.allowChangeHeaders = ( ( actions & SMFIF_CHGHDRS ) != 0 );
458+ remoteData.allowQuarantine = ( ( actions & SMFIF_QUARANTINE ) != 0 );
459+
460+ remoteData.noConnect = ( ( protocol & SMFIP_NOCONNECT ) != 0 );
461+ remoteData.noHelo = ( ( protocol & SMFIP_NOHELO ) != 0 );
462+ remoteData.noMail = ( ( protocol & SMFIP_NOMAIL ) != 0 );
463+ remoteData.noRcpt = ( ( protocol & SMFIP_NORCPT ) != 0 );
464+ remoteData.noBody = ( ( protocol & SMFIP_NOBODY ) != 0 );
465+ remoteData.noHeaders = ( ( protocol & SMFIP_NOHDRS ) != 0 );
466+ remoteData.noEndOfHeaders = ( ( protocol & SMFIP_NOEOH ) != 0 );
467+
468+ MilterNegotiationData ourNeg;
469+
470+ responder->OnOptionNegotiation( accSock, remoteData, ourNeg );
471+
472+ // Transform to network format.
473+ std::uint32_t neg_actions = 0;
474+
475+ if ( ourNeg.allowAddHeaders )
476+ {
477+ neg_actions |= SMFIF_ADDHDRS;
478+ }
479+ if ( ourNeg.allowChangeBody )
480+ {
481+ neg_actions |= SMFIF_CHGBODY;
482+ }
483+ if ( ourNeg.allowAddRcpt )
484+ {
485+ neg_actions |= SMFIF_ADDRCPT;
486+ }
487+ if ( ourNeg.allowDelRcpt )
488+ {
489+ neg_actions |= SMFIF_DELRCPT;
490+ }
491+ if ( ourNeg.allowChangeHeaders )
492+ {
493+ neg_actions |= SMFIF_CHGHDRS;
494+ }
495+ if ( ourNeg.allowQuarantine )
496+ {
497+ neg_actions |= SMFIF_QUARANTINE;
498+ }
499+
500+ std::uint32_t neg_protocol = 0;
501+
502+ if ( ourNeg.noConnect )
503+ {
504+ neg_protocol |= SMFIP_NOCONNECT;
505+ }
506+ if ( ourNeg.noHelo )
507+ {
508+ neg_protocol |= SMFIP_NOHELO;
509+ }
510+ if ( ourNeg.noMail )
511+ {
512+ neg_protocol |= SMFIP_NOMAIL;
513+ }
514+ if ( ourNeg.noRcpt )
515+ {
516+ neg_protocol |= SMFIP_NORCPT;
517+ }
518+ if ( ourNeg.noBody )
519+ {
520+ neg_protocol |= SMFIP_NOBODY;
521+ }
522+ if ( ourNeg.noHeaders )
523+ {
524+ neg_protocol |= SMFIP_NOHDRS;
525+ }
526+ if ( ourNeg.noEndOfHeaders )
527+ {
528+ neg_protocol |= SMFIP_NOEOH;
529+ }
530+
531+ milter_respond_optneg( accSock, neg_actions, neg_protocol );
532+ }
533+ else if ( packetCmd == SMFIC_RCPT )
534+ {
535+ // Read all the recipient stuff.
536+ basicVector <basicString <char>> recipients;
537+
538+ while ( stream.Tell() < stream.Size() )
539+ {
540+ basicString <char> item = read_null_terminated_string( stream );
541+
542+ recipients.AddToBack( std::move( item ) );
543+ }
544+
545+ bool doAccept = responder->OnMailRecipients( accSock, std::move( recipients ) );
546+
547+ milter_respond_accept_or_reject( accSock, doAccept );
548+ }
549+ else if ( packetCmd == SMFIC_QUIT )
550+ {
551+ // We are expected to just close ourselves.
552+
553+ responder->OnQuit( accSock );
554+
555+ accSock->Close();
556+ }
557+ else
558+ {
559+ responder->OnUnknownPacket( accSock, packetCmd, packetData, packetSize );
560+ }
561+ }
562+ catch( ... )
563+ {
564+ CRTHeapAllocator::Free( nullptr, packetData );
565+
566+ throw;
567+ }
568+
569+ // Do not forget to clean up.
570+ CRTHeapAllocator::Free( nullptr, packetData );
571+ }
572+}
\ No newline at end of file
Show on old repository browser