├── .gitignore ├── .npmignore ├── lib └── milter.js ├── src ├── forward.h ├── milter.h ├── envelope.h ├── events.h ├── events.cc ├── envelope.cc └── milter.cc ├── binding.gyp ├── package.json ├── test.js ├── test-async.js ├── test-udp.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /build/ 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /build/ 3 | npm-debug.log 4 | test.js 5 | -------------------------------------------------------------------------------- /lib/milter.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../build/Release/milter'); 2 | -------------------------------------------------------------------------------- /src/forward.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ifndef FORWARD_H 4 | #define FORWARD_H 5 | 6 | typedef struct bindings bindings_t; 7 | typedef struct envelope envelope_t; 8 | class MilterEvent; 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "milter", 5 | "sources": [ "src/envelope.cc", "src/events.cc", "src/milter.cc" ], 6 | "include_dirs": [ "/usr/include/libmilter" ], 7 | "cflags": [ "-g -Wall" ], 8 | "cflags!": [ "-O3" ], 9 | "ldflags": [ "-L/usr/lib/libmilter" ], 10 | "libraries" : [ "-lmilter" ] 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "milter", 3 | "description": "bindings for writing postfix milter daemons", 4 | "version": "2.0.0", 5 | "author" : "jon shusta", 6 | "maintainers":[ 7 | { 8 | "name":"jons", 9 | "email":"jon@wroth.org" 10 | } 11 | ], 12 | "repository" : { 13 | "type" : "git", 14 | "url" : "http://github.com/jons/node-milter.git" 15 | }, 16 | "scripts":{ 17 | "test": "nodeunit test" 18 | }, 19 | "main" : "./lib/milter", 20 | "license" : "CC-BY-4.0", 21 | "devDependencies": { 22 | "nodeunit": "*" 23 | }, 24 | "engines": ["node =7.7.4"], 25 | "keywords": ["postfix", "milter"] 26 | } 27 | -------------------------------------------------------------------------------- /src/milter.h: -------------------------------------------------------------------------------- 1 | /** 2 | */ 3 | 4 | #ifndef MILTER_H 5 | #define MILTER_H 6 | 7 | #include 8 | #include 9 | #include 10 | #include "forward.h" 11 | 12 | using namespace node; 13 | using namespace v8; 14 | 15 | 16 | struct bindings 17 | { 18 | bindings () 19 | : first(NULL), last(NULL) 20 | { 21 | // TODO: deal with error case 22 | pthread_mutex_init(&lck_queue, NULL); 23 | } 24 | 25 | ~bindings () 26 | { 27 | // TODO: lock queue, clear it, unlock it 28 | // ensure no further events can be delivered 29 | 30 | pthread_mutex_destroy(&lck_queue); 31 | } 32 | 33 | uv_loop_t *loop; 34 | uv_work_t request; 35 | uv_async_t trigger; 36 | 37 | pthread_mutex_t lck_queue; 38 | MilterEvent *first, *last; 39 | 40 | struct 41 | { 42 | Persistent negotiate; 43 | Persistent connect; 44 | Persistent unknown; 45 | Persistent helo; 46 | Persistent envfrom; 47 | Persistent envrcpt; 48 | Persistent data; 49 | Persistent header; 50 | Persistent eoh; 51 | Persistent body; 52 | Persistent eom; 53 | Persistent abort; 54 | Persistent close; 55 | } fcall; 56 | 57 | int retval; 58 | }; 59 | 60 | 61 | struct envelope 62 | { 63 | envelope (bindings_t *local) 64 | : local(local) 65 | { } 66 | 67 | bindings_t *local; 68 | 69 | /** 70 | * this is an ObjectWrap'd Envelope 71 | * it lives from connect to close 72 | */ 73 | Persistent object; 74 | }; 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /src/envelope.h: -------------------------------------------------------------------------------- 1 | /** 2 | * mail session envelope 3 | * 4 | */ 5 | 6 | #ifndef MILTER_ENVELOPE_H 7 | #define MILTER_ENVELOPE_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include "libmilter/mfapi.h" 13 | #include "events.h" 14 | 15 | using namespace v8; 16 | using namespace node; 17 | 18 | 19 | /** 20 | */ 21 | class Envelope : public ObjectWrap 22 | { 23 | public: 24 | static void Init (Local exports); 25 | 26 | void SetCurrentEvent (MilterEvent *event); 27 | void SetMilterContext (SMFICTX *context); 28 | 29 | static Local PrivateInstance (Isolate *isolate); 30 | 31 | private: 32 | explicit Envelope (); 33 | ~Envelope (); 34 | 35 | static Persistent constructor; 36 | 37 | static void NewInstance (const FunctionCallbackInfo &args); 38 | 39 | static void New (const FunctionCallbackInfo &args); 40 | 41 | /** 42 | * the completion callback provided to each event callback, via the latter's 43 | * first argument. all js funcs have the signature: 44 | * function eventname (envelope, ...) 45 | * 46 | * and all of them continue a libmilter thread by calling 47 | * envelope.done(retval); 48 | * 49 | * where retval is an SMFIS_* filter status code. 50 | */ 51 | static void Done (const FunctionCallbackInfo &args); 52 | 53 | /** 54 | * during negotiate, just before continuation, the implementor needs to let 55 | * us know what to store in the pointers to config values f0-f3. 56 | */ 57 | static void Negotiate (const FunctionCallbackInfo &args); 58 | 59 | /** 60 | * allowed whenever. 61 | */ 62 | static void SMFI_GetSymbol (const FunctionCallbackInfo &args); 63 | 64 | /** 65 | * allowed during negotiate. 66 | */ 67 | static void SMFI_SetSymbolList (const FunctionCallbackInfo &args); 68 | 69 | /** 70 | * allowed in events other than connect (and negotiate, i suspect.) 71 | */ 72 | static void SMFI_SetReply (const FunctionCallbackInfo &args); 73 | static void SMFI_SetMultilineReply (const FunctionCallbackInfo &args); 74 | 75 | /** 76 | * these modifiers are allowed during EOM. 77 | */ 78 | static void SMFI_Progress (const FunctionCallbackInfo &args); 79 | static void SMFI_Quarantine (const FunctionCallbackInfo &args); 80 | 81 | static void SMFI_AddHeader (const FunctionCallbackInfo &args); 82 | static void SMFI_ChangeHeader (const FunctionCallbackInfo &args); 83 | static void SMFI_InsertHeader (const FunctionCallbackInfo &args); 84 | static void SMFI_ReplaceBody (const FunctionCallbackInfo &args); 85 | static void SMFI_AddRecipient (const FunctionCallbackInfo &args); 86 | static void SMFI_AddRecipientExtended (const FunctionCallbackInfo &args); 87 | static void SMFI_DelRecipient (const FunctionCallbackInfo &args); 88 | static void SMFI_ChangeFrom (const FunctionCallbackInfo &args); 89 | 90 | MilterEvent *current_event; 91 | SMFICTX *smfi_context; 92 | }; 93 | 94 | #endif 95 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | require('v8-profiler'); 2 | var crypto = require('crypto'); 3 | var milter = require('./build/Debug/milter'); 4 | 5 | process.on('uncaughtException', function (e) { console.log(e); }); 6 | 7 | 8 | /** 9 | */ 10 | function negotiate (envelope, f0, f1, f2, f3) 11 | { 12 | envelope.local.sid = crypto.randomBytes(2).toString('hex'); 13 | 14 | console.log("[" + envelope.local.sid + "] filter session created"); 15 | console.log("[" + envelope.local.sid + "] f0=" + f0); 16 | console.log("[" + envelope.local.sid + "] f1=" + f1); 17 | console.log("[" + envelope.local.sid + "] f2=" + f2); 18 | console.log("[" + envelope.local.sid + "] f3=" + f3); 19 | 20 | f2 = 0; 21 | f3 = 0; 22 | envelope.negotiate(f0, f1, f2, f3); 23 | envelope.done(milter.SMFIS_ALL_OPTS); 24 | } 25 | 26 | /** 27 | */ 28 | function connect (envelope, host, address) 29 | { 30 | envelope.local.ts_start = (new Date).getTime(); 31 | 32 | console.log("[" + envelope.local.sid + "] connection from " + host + " (" + address + ")"); 33 | envelope.done(milter.SMFIS_CONTINUE); 34 | } 35 | 36 | 37 | /** 38 | */ 39 | function helo (envelope, identity) 40 | { 41 | console.log("[" + envelope.local.sid + "] helo " + identity); 42 | envelope.done(milter.SMFIS_CONTINUE); 43 | } 44 | 45 | 46 | // 47 | function unknown (envelope) 48 | { envelope.done(milter.SMFIS_CONTINUE); } 49 | 50 | 51 | function mailfrom (envelope, addr) 52 | { 53 | console.log("[" + envelope.local.sid + "] from " + addr); 54 | envelope.done(milter.SMFIS_CONTINUE); 55 | } 56 | 57 | function rcptto (envelope, addr) 58 | { 59 | console.log("[" + envelope.local.sid + "] rcpt " + addr); 60 | envelope.done(milter.SMFIS_CONTINUE); 61 | } 62 | 63 | function mstart (envelope) { envelope.done(milter.SMFIS_CONTINUE); } 64 | function header (envelope) { envelope.done(milter.SMFIS_CONTINUE); } 65 | function eoh (envelope) { envelope.done(milter.SMFIS_CONTINUE); } 66 | function segment (envelope) { envelope.done(milter.SMFIS_CONTINUE); } 67 | function mfinish (envelope) { envelope.done(milter.SMFIS_CONTINUE); } 68 | function abort (envelope) { envelope.done(milter.SMFIS_CONTINUE); } 69 | 70 | 71 | /** 72 | */ 73 | function close (envelope) 74 | { 75 | var now = (new Date).getTime(); 76 | console.log("[" + envelope.local.sid + "] connection closed"); 77 | console.log("[" + envelope.local.sid + "] session lasted " + (now - envelope.local.ts_start) + " msec"); 78 | envelope.done(milter.SMFIS_CONTINUE); 79 | } 80 | 81 | 82 | /** main **********************************************************************/ 83 | 84 | var ok = milter.start( 85 | // setconn. see postconf(5) "smtpd_milters" for details 86 | "inet:12345", 87 | 88 | // register flags. see smfi_register for details 89 | milter.SMFIF_QUARANTINE | milter.SMFIF_ADDHDRS | milter.SMFIF_CHGFROM, 90 | 91 | negotiate, // milter-to-MTA negotiation 92 | connect, // connection event 93 | unknown, // unknown client command 94 | helo, // client "HELO" or "EHLO" i presume 95 | mailfrom, // client "MAIL FROM" 96 | rcptto, // client "RCPT TO" 97 | mstart, // client "DATA" 98 | header, // parser event 99 | eoh, // parser event 100 | segment, // buffer of data 101 | mfinish, // client "." (EOM) 102 | abort, // ? 103 | close); // connection event 104 | 105 | console.log("test.js milter.start:", ok); 106 | -------------------------------------------------------------------------------- /test-async.js: -------------------------------------------------------------------------------- 1 | require('v8-profiler'); 2 | var crypto = require('crypto'); 3 | var milter = require('./build/Debug/milter'); 4 | 5 | process.on('uncaughtException', function (e) { console.log(e); }); 6 | 7 | 8 | /** 9 | */ 10 | function negotiate (envelope, f0, f1, f2, f3) 11 | { 12 | envelope.local.sid = crypto.randomBytes(2).toString('hex'); 13 | 14 | console.log("[" + envelope.local.sid + "] filter session created"); 15 | console.log("[" + envelope.local.sid + "] f0=" + f0); 16 | console.log("[" + envelope.local.sid + "] f1=" + f1); 17 | console.log("[" + envelope.local.sid + "] f2=" + f2); 18 | console.log("[" + envelope.local.sid + "] f3=" + f3); 19 | 20 | f2 = 0; 21 | f3 = 0; 22 | envelope.negotiate(f0, f1, f2, f3); 23 | envelope.done(milter.SMFIS_ALL_OPTS); 24 | } 25 | 26 | /** 27 | */ 28 | function connect (envelope, host, address) 29 | { 30 | envelope.local.ts_start = (new Date).getTime(); 31 | 32 | console.log("[" + envelope.local.sid + "] connection from " + host + " (" + address + ")"); 33 | envelope.done(milter.SMFIS_CONTINUE); 34 | } 35 | 36 | 37 | /** 38 | */ 39 | function helo (envelope, identity) 40 | { 41 | console.log("[" + envelope.local.sid + "] helo " + identity); 42 | 43 | setImmediate(function (env) { 44 | env.done(milter.SMFIS_CONTINUE); 45 | }, envelope); 46 | } 47 | 48 | 49 | // 50 | function unknown (envelope) 51 | { envelope.done(milter.SMFIS_CONTINUE); } 52 | 53 | 54 | function mailfrom (envelope, address) 55 | { 56 | setTimeout(function (env, addr) { 57 | console.log("[" + env.local.sid + "] from " + addr); 58 | env.done(milter.SMFIS_CONTINUE); 59 | }, 500, envelope, address); 60 | } 61 | 62 | function rcptto (envelope, address) 63 | { 64 | setTimeout(function (env, addr) { 65 | console.log("[" + env.local.sid + "] rcpt " + addr); 66 | env.done(milter.SMFIS_CONTINUE); 67 | }, 2500, envelope, address); 68 | } 69 | 70 | function mstart (envelope) { envelope.done(milter.SMFIS_CONTINUE); } 71 | function header (envelope) { envelope.done(milter.SMFIS_CONTINUE); } 72 | function eoh (envelope) { envelope.done(milter.SMFIS_CONTINUE); } 73 | function segment (envelope) { envelope.done(milter.SMFIS_CONTINUE); } 74 | function mfinish (envelope) { envelope.done(milter.SMFIS_CONTINUE); } 75 | function abort (envelope) { envelope.done(milter.SMFIS_CONTINUE); } 76 | 77 | 78 | /** 79 | */ 80 | function close (envelope) 81 | { 82 | var now = (new Date).getTime(); 83 | console.log("[" + envelope.local.sid + "] connection closed"); 84 | console.log("[" + envelope.local.sid + "] session lasted " + (now - envelope.local.ts_start) + " msec"); 85 | envelope.done(milter.SMFIS_CONTINUE); 86 | } 87 | 88 | 89 | /** main **********************************************************************/ 90 | 91 | var ok = milter.start( 92 | // setconn. see postconf(5) "smtpd_milters" for details 93 | "inet:12345", 94 | 95 | // register flags. see smfi_register for details 96 | milter.SMFIF_QUARANTINE | milter.SMFIF_ADDHDRS | milter.SMFIF_CHGFROM, 97 | 98 | negotiate, // milter-to-MTA negotiation 99 | connect, // connection event 100 | unknown, // unknown client command 101 | helo, // client "HELO" or "EHLO" i presume 102 | mailfrom, // client "MAIL FROM" 103 | rcptto, // client "RCPT TO" 104 | mstart, // client "DATA" 105 | header, // parser event 106 | eoh, // parser event 107 | segment, // buffer of data 108 | mfinish, // client "." (EOM) 109 | abort, // ? 110 | close); // connection event 111 | 112 | console.log("test-async.js milter.start:", ok); 113 | -------------------------------------------------------------------------------- /test-udp.js: -------------------------------------------------------------------------------- 1 | require('v8-profiler'); 2 | var crypto = require('crypto'); 3 | var milter = require('./build/Debug/milter'); 4 | var dgram = require('dgram'); 5 | 6 | process.on('uncaughtException', function (e) { console.log(e); }); 7 | 8 | 9 | /** 10 | */ 11 | function negotiate (envelope, f0, f1, f2, f3) 12 | { 13 | envelope.local.sid = crypto.randomBytes(2).toString('hex'); 14 | 15 | console.log("[" + envelope.local.sid + "] filter session created"); 16 | console.log("[" + envelope.local.sid + "] f0=" + f0); 17 | console.log("[" + envelope.local.sid + "] f1=" + f1); 18 | console.log("[" + envelope.local.sid + "] f2=" + f2); 19 | console.log("[" + envelope.local.sid + "] f3=" + f3); 20 | 21 | f2 = 0; 22 | f3 = 0; 23 | envelope.negotiate(f0, f1, f2, f3); 24 | envelope.done(milter.SMFIS_ALL_OPTS); 25 | } 26 | 27 | function connect (envelope, host, address) 28 | { 29 | envelope.local.ts_start = (new Date).getTime(); 30 | 31 | console.log("[" + envelope.local.sid + "] connection from " + host + " (" + address + ")"); 32 | envelope.done(milter.SMFIS_CONTINUE); 33 | } 34 | 35 | function helo (envelope, identity) 36 | { 37 | console.log("[" + envelope.local.sid + "] helo " + identity); 38 | envelope.done(milter.SMFIS_CONTINUE); 39 | } 40 | 41 | function mailfrom (envelope, addr) 42 | { 43 | console.log("[" + envelope.local.sid + "] from " + addr); 44 | envelope.done(milter.SMFIS_CONTINUE); 45 | } 46 | 47 | function rcptto (envelope, addr) 48 | { 49 | console.log("[" + envelope.local.sid + "] rcpt " + addr); 50 | envelope.done(milter.SMFIS_CONTINUE); 51 | } 52 | 53 | function mfinish (envelope) 54 | { 55 | console.log("[" + envelope.local.sid + "] EOM"); 56 | var udp = dgram.createSocket('udp4'); 57 | var str = Buffer.from(JSON.stringify(envelope)); 58 | 59 | // workaround for issue #2 60 | // without setImmediate wrapping this udpclient.send call, no datagram 61 | // is ever sent, and the callback provided to send() is never called 62 | setImmediate(function (udpclient, datas) { 63 | console.log('tick'); 64 | udpclient.send( 65 | datas, 66 | 0, 67 | datas.length, 68 | 25828, 69 | '127.0.0.1', 70 | function (e) { 71 | console.log('sent'); 72 | udpclient.close(); 73 | if (e) 74 | console.log(e); 75 | }); 76 | 77 | envelope.done(milter.SMFIS_CONTINUE); 78 | 79 | }, udp, str); 80 | } 81 | 82 | // 83 | function unknown (envelope) { console.log("[" + envelope.local.sid + "] unknown"); envelope.done(milter.SMFIS_CONTINUE); } 84 | function mstart (envelope) { console.log("[" + envelope.local.sid + "] DATA"); envelope.done(milter.SMFIS_CONTINUE); } 85 | function header (envelope) { console.log("[" + envelope.local.sid + "] header"); envelope.done(milter.SMFIS_CONTINUE); } 86 | function eoh (envelope) { console.log("[" + envelope.local.sid + "] EOH"); envelope.done(milter.SMFIS_CONTINUE); } 87 | function segment (envelope) { console.log("[" + envelope.local.sid + "] segment"); envelope.done(milter.SMFIS_CONTINUE); } 88 | function abort (envelope) { console.log("[" + envelope.local.sid + "] abort"); envelope.done(milter.SMFIS_CONTINUE); } 89 | 90 | /** 91 | */ 92 | function close (envelope) 93 | { 94 | var now = (new Date).getTime(); 95 | console.log("[" + envelope.local.sid + "] connection closed"); 96 | console.log("[" + envelope.local.sid + "] session lasted " + (now - envelope.local.ts_start) + " msec"); 97 | envelope.done(milter.SMFIS_CONTINUE); 98 | } 99 | 100 | 101 | /** main **********************************************************************/ 102 | 103 | var ok = milter.start( 104 | // setconn. see postconf(5) "smtpd_milters" for details 105 | "inet:12345", 106 | 107 | // register flags. see smfi_register for details 108 | milter.SMFIF_QUARANTINE | milter.SMFIF_ADDHDRS | milter.SMFIF_CHGFROM, 109 | 110 | negotiate, // milter-to-MTA negotiation 111 | connect, // connection event 112 | unknown, // unknown client command 113 | helo, // client "HELO" or "EHLO" i presume 114 | mailfrom, // client "MAIL FROM" 115 | rcptto, // client "RCPT TO" 116 | mstart, // client "DATA" 117 | header, // parser event 118 | eoh, // parser event 119 | segment, // buffer of data 120 | mfinish, // client "." (EOM) 121 | abort, // ? 122 | close); // connection event 123 | 124 | console.log("test.js milter.start:", ok); 125 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-milter 2 | node.js bindings for postfix milters 3 | 4 | this addon produces libmilter callbacks in node.js v7.7.4 so that you don't have 5 | to be a C programmer to use postfix with libmilter. 6 | 7 | when its main function is called, libmilter creates a threaded daemon where each 8 | mail session has one unique thread in your program servicing it. this main is 9 | therefore sequestered away in a libuv worker to allow node.js to continue to 10 | function normally. 11 | 12 | your libmilter event callbacks are called from these threads with a context 13 | pointer to distinguish them from each other. you must return a code at the end 14 | of the event implementation, which tells postfix what to do next, continue, 15 | reject, tempfail, etc. 16 | 17 | this milter blocks itself during each of those events. it wraps the event into 18 | a localized context object and queues that, signals to libuv that it's time 19 | to do some node.js work, and goes to sleep until the pthread condition is met. 20 | thus multiple libmilter sessions may block on a single stage of the event, but, 21 | they are in their own threads so it's mostly ok. 22 | 23 | when libuv runs the async worker to service the queue on the other side, we are 24 | now back in node.js-land (a scope) and can make js callbacks, wrap event data in 25 | js objects, and so on. but it is required that the js callbacks return a 26 | decision for postfix immediately, so your js callbacks cannot be async (yet). 27 | 28 | 29 | QUICKSTART 30 | 31 | creating a milter daemon. 32 | 33 | milter.start( 34 | string smtpd_milter_description, 35 | number flags, 36 | function(env, f0, f1, f2, f3) negotiate, 37 | function(env,host,addr) connect, 38 | function(env,command) unknown, 39 | function(env,identity) helo, 40 | function(env,address) mailfrom, 41 | function(env,address) rcptto, 42 | function(env) data, 43 | function(env,name,value) header, 44 | function(env) eoh, 45 | function(env,buffer,length) segment, 46 | function(env) eom, 47 | function(env) abort, 48 | function(env) close, 49 | ); 50 | 51 | 52 | other controls are available when creating a milter. 53 | 54 | completing a callback. 55 | 56 | connect = function (env, host, addr) { 57 | /* ... */ 58 | env.done(decision); 59 | } 60 | 61 | 62 | the allowed filter decisions for all callbacks except negotiate. 63 | 64 | milter.SMFIS_CONTINUE 65 | milter.SMFIS_REJECT 66 | milter.SMFIS_DISCARD 67 | milter.SMFIS_ACCEPT 68 | milter.SMFIS_TEMPFAIL 69 | milter.SMFIS_NOREPLY 70 | milter.SMFIS_SKIP 71 | 72 | 73 | negotiate shall return one of these decisions instead. see env.negotiate() for 74 | use of the CONTINUE return code. 75 | 76 | milter.SMFIS_ALL_OPTS 77 | milter.SMFIS_CONTINUE 78 | milter.SMFIS_REJECT 79 | 80 | 81 | add a smtpd_milters line to your main.cf that matches your description in milter.start 82 | and go to town. the test cases included in the project should show you how. 83 | 84 | 85 | MILTER STUFF 86 | 87 | other envelope methods. 88 | access to message modifiers is allowed during the EOM event with these methods. 89 | 90 | env.addheader(name, value) 91 | env.chgheader(name, refcount, value) 92 | env.insheader(index, name, value) 93 | env.replacebody(newbody) 94 | env.addrcpt(recipient) 95 | env.addrcpt_par(recipient, args) 96 | env.delrcpt(recipient) 97 | env.chgfrom(envfrom, args) 98 | env.quarantine(reason) 99 | env.progress() 100 | 101 | 102 | changing the symbol list is allowed during negotiate. 103 | 104 | env.setsymlist(stage, macrolist) 105 | 106 | 107 | because pointers cannot be wrapped in node.js addons, an additional method in 108 | the addon implementation that has no analog in libmilter exists to facilitate 109 | changing these settings so that the pointers made available to the xxfi_negotiate 110 | callback can be changed after node.js returns control to libmilter. call this 111 | method BEFORE calling env.done(), also, the values changed by this function are 112 | ignored unless env.done() is called with SMFIS_CONTINUE. 113 | 114 | env.negotiate(f0, f1, f2, f3) 115 | 116 | 117 | retrieving a macro is allowed during any event. will return an empty string if 118 | the macro doesn't have a value. 119 | 120 | env.getsymval(symname) 121 | 122 | 123 | changing the server's smtp reply is allowed during any event other than connect. 124 | i believe this should also exclude negotiate, so i enforce that as well. 125 | 126 | env.setreply(rcode, xcode, message) 127 | env.setmlreply(rcode, xcode, lines) 128 | 129 | 130 | ANOMALIES 131 | 132 | libmilter uses globals and is not thread-safe. you cannot use multiprocessing 133 | features in node with this addon. 134 | 135 | 136 | the milter will register in postfix with the name "node-bindings", you already 137 | know it's a milter. the name "node-milter" is intended to give the project a 138 | sensible npm identity and some github presence, too. 139 | 140 | sources implicitly depend on pthreads yet don't use their #include files by name. 141 | - libmilter explicitly uses pthreads, and libuv is implicitly using them. the 142 | code compiles because of this happy coincidence. 143 | - you can't use this on any non-POSIX platforms yet. but then again, you can't 144 | use milters there, either. 145 | - this cannot be worked around by switching the relevant code over to libuv 146 | without also rewriting all of libmilter. i looked. it's not worth it. 147 | 148 | node.js buffers are created during the message data event (the client has already 149 | send command "DATA", and now a chunk of the message data has arrived) using the 150 | i-suspect-is-soon-to-be-deprecated method Buffer::Use(), which should have been 151 | named Buffer::New() like the others, by passing (const unsigned char *) as the 152 | expected (char *) which is probably stupid. it is unclear to me why there is no 153 | Buffer::New() that simply accepts (void *) like all the real POSIX C buffer- 154 | manipulating functions. whatever. 155 | 156 | some calls probably don't work unless wrapped in setImmediate(), but i don't 157 | know why yet. 158 | -------------------------------------------------------------------------------- /src/events.h: -------------------------------------------------------------------------------- 1 | /** 2 | * contextual information for events 3 | * it is apparently fashionable in node.js-speak to call these "batons" 4 | * 5 | * do you like 1980s C style standards for variable names? no? haha! 6 | * 7 | * do you like encapsulation? :) 8 | * 9 | * hahaha! 10 | */ 11 | 12 | #ifndef MILTER_EVENTS_H 13 | #define MILTER_EVENTS_H 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include "milter.h" 21 | 22 | using namespace v8; 23 | using namespace node; 24 | 25 | 26 | /** 27 | * base class. something-something inheritance 28 | */ 29 | class MilterEvent 30 | { 31 | public: 32 | /** 33 | * called by node worker to begin the main event work 34 | */ 35 | void Fire (Isolate *isolate, bindings_t *local); 36 | 37 | /** 38 | * just a mutex; control of the event 39 | */ 40 | bool Lock (); 41 | bool Unlock (); 42 | 43 | /** 44 | * called by libmilter worker to wait until event is ready 45 | */ 46 | bool Wait (); 47 | 48 | /** 49 | * allow an event callback to set a milter result flag 50 | * returns true if libmilter thread is signaled 51 | */ 52 | bool Done (Isolate *isolate, int retval); 53 | 54 | /** 55 | * allow an envelope function to ensure it is not being called at the wrong 56 | * milter stage. 57 | * most of them are allowed during EOM only. 58 | */ 59 | virtual bool IsNegotiate () const; 60 | virtual bool IsConnect() const; 61 | virtual bool IsEndMessage () const; 62 | 63 | // linklist junk 64 | void Append (MilterEvent *ev); 65 | void Detatch (); 66 | MilterEvent *Next () const; 67 | 68 | /** 69 | * 70 | */ 71 | void SetMilterContext (SMFICTX *context); 72 | 73 | // not sure if needed 74 | bool IsDone () const; 75 | int Result () const; 76 | 77 | virtual ~MilterEvent (); 78 | 79 | protected: 80 | explicit MilterEvent (envelope_t *env); 81 | 82 | /** 83 | * each event type must select its associated event callback from 84 | * 85 | */ 86 | virtual void FireWrapper (Isolate *isolate, bindings_t *local) = 0; 87 | virtual void Prefire (Isolate *isolate); 88 | virtual void Postfire (Isolate *isolate); 89 | 90 | /** 91 | * each event type must implement 92 | */ 93 | void DoFCall (Isolate *isolate, Persistent &pfunc, unsigned int argc, Local *argv); 94 | 95 | Local envelope; 96 | 97 | envelope_t *fi_envelope; 98 | SMFICTX *smfi_context; 99 | 100 | private: 101 | MilterEvent *ev_next; 102 | bool is_done; 103 | int fi_retval; 104 | 105 | pthread_mutex_t pt_lock; 106 | pthread_cond_t pt_ready; 107 | }; 108 | 109 | 110 | /** 111 | */ 112 | class MilterNegotiate : public MilterEvent 113 | { 114 | public: 115 | MilterNegotiate (envelope_t *env, 116 | unsigned long f0_, unsigned long f1_, unsigned long f2_, unsigned long f3_, 117 | unsigned long *pf0_, unsigned long *pf1_, unsigned long *pf2_, unsigned long *pf3_); 118 | void FireWrapper (Isolate *isolate, bindings_t *local); 119 | 120 | bool IsNegotiate () const; 121 | 122 | /** 123 | * change negotiation settings 124 | */ 125 | void Negotiate (unsigned long f0, unsigned long f1, unsigned long f2, unsigned long f3); 126 | 127 | protected: 128 | void Prefire (Isolate *isolate); 129 | 130 | private: 131 | unsigned long f0; 132 | unsigned long f1; 133 | unsigned long f2; 134 | unsigned long f3; 135 | unsigned long *pf0; 136 | unsigned long *pf1; 137 | unsigned long *pf2; 138 | unsigned long *pf3; 139 | }; 140 | 141 | 142 | /** 143 | * new connection established 144 | * 145 | * each connection is associated 1:1 with an envelope, created by this event, 146 | * which lives until either the close or abort event, that allows the node.js 147 | * script kiddie to associate each individual callback with the correct mail 148 | * session. 149 | */ 150 | class MilterConnect : public MilterEvent 151 | { 152 | public: 153 | MilterConnect (envelope_t *env, const char *host, sockaddr_in *sa); 154 | void FireWrapper (Isolate *isolate, bindings_t *local); 155 | 156 | bool IsConnect() const; 157 | 158 | const char *Host() const; 159 | const char *Address() const; 160 | 161 | protected: 162 | 163 | private: 164 | const char *sz_host; 165 | char sz_addr[INET6_ADDRSTRLEN+1]; 166 | }; 167 | 168 | 169 | /** 170 | */ 171 | class MilterUnknown : public MilterEvent 172 | { 173 | public: 174 | MilterUnknown (envelope_t *env, const char *command); 175 | void FireWrapper (Isolate *isolate, bindings_t *local); 176 | 177 | private: 178 | const char *sz_command; 179 | }; 180 | 181 | 182 | /** 183 | */ 184 | class MilterHELO : public MilterEvent 185 | { 186 | public: 187 | MilterHELO (envelope_t *env, const char *helo); 188 | void FireWrapper (Isolate *isolate, bindings_t *local); 189 | 190 | private: 191 | const char *sz_helo; 192 | }; 193 | 194 | 195 | /** 196 | */ 197 | class MilterMAILFROM : public MilterEvent 198 | { 199 | public: 200 | MilterMAILFROM (envelope_t *env, char **argv); 201 | void FireWrapper (Isolate *isolate, bindings_t *local); 202 | 203 | private: 204 | char **szpp_argv; 205 | }; 206 | 207 | 208 | /** 209 | */ 210 | class MilterRCPTTO : public MilterEvent 211 | { 212 | public: 213 | MilterRCPTTO (envelope_t *env, char **argv); 214 | void FireWrapper (Isolate *isolate, bindings_t *local); 215 | 216 | private: 217 | char **szpp_argv; 218 | }; 219 | 220 | 221 | /** 222 | */ 223 | class MilterDATA : public MilterEvent 224 | { 225 | public: 226 | MilterDATA (envelope_t *env); 227 | void FireWrapper (Isolate *isolate, bindings_t *local); 228 | }; 229 | 230 | 231 | /** 232 | */ 233 | class MilterHeader : public MilterEvent 234 | { 235 | public: 236 | MilterHeader (envelope_t *env, const char *name, const char *value); 237 | void FireWrapper (Isolate *isolate, bindings_t *local); 238 | 239 | private: 240 | const char *sz_name; 241 | const char *sz_value; 242 | }; 243 | 244 | 245 | /** 246 | */ 247 | class MilterEndHeaders : public MilterEvent 248 | { 249 | public: 250 | MilterEndHeaders (envelope_t *env); 251 | void FireWrapper (Isolate *isolate, bindings_t *local); 252 | }; 253 | 254 | 255 | /** 256 | */ 257 | class MilterMessageData : public MilterEvent 258 | { 259 | public: 260 | MilterMessageData (envelope_t *env, const unsigned char *buf, const size_t len); 261 | void FireWrapper (Isolate *isolate, bindings_t *local); 262 | 263 | private: 264 | const unsigned char *buf; 265 | const size_t len; 266 | }; 267 | 268 | 269 | /** 270 | */ 271 | class MilterEndMessage : public MilterEvent 272 | { 273 | public: 274 | MilterEndMessage (envelope_t *env); 275 | void FireWrapper (Isolate *isolate, bindings_t *local); 276 | 277 | bool IsEndMessage () const; 278 | }; 279 | 280 | 281 | /** 282 | */ 283 | class MilterAbort : public MilterEvent 284 | { 285 | public: 286 | MilterAbort (envelope_t *env); 287 | void FireWrapper (Isolate *isolate, bindings_t *local); 288 | }; 289 | 290 | 291 | /** 292 | */ 293 | class MilterClose : public MilterEvent 294 | { 295 | public: 296 | MilterClose (envelope_t *env); 297 | void FireWrapper (Isolate *isolate, bindings_t *local); 298 | protected: 299 | void Postfire (Isolate *isolate); 300 | }; 301 | 302 | 303 | #endif 304 | -------------------------------------------------------------------------------- /src/events.cc: -------------------------------------------------------------------------------- 1 | /** 2 | * contextual information for events 3 | * it is apparently fashionable in node.js-speak to call these "batons" 4 | * 5 | * do you like 1980s C style standards for variable names? no? haha! 6 | * 7 | * do you like encapsulation? :) 8 | * 9 | * hahaha! 10 | */ 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include "libmilter/mfapi.h" 19 | #include "milter.h" 20 | #include "events.h" 21 | #include "envelope.h" 22 | 23 | using namespace v8; 24 | using namespace node; 25 | 26 | 27 | MilterEvent::MilterEvent (envelope_t *env) 28 | : fi_envelope(env), 29 | ev_next(NULL), 30 | is_done(false), 31 | fi_retval(SMFIS_TEMPFAIL) // TODO: allow custom default 32 | { 33 | pthread_mutex_init(&this->pt_lock, NULL); 34 | pthread_cond_init(&this->pt_ready, NULL); 35 | } 36 | 37 | 38 | bool MilterEvent::Lock () { return 0 == pthread_mutex_lock(&this->pt_lock); } 39 | 40 | bool MilterEvent::Unlock () { return 0 == pthread_mutex_unlock(&this->pt_lock); } 41 | 42 | bool MilterEvent::Wait () { return 0 == pthread_cond_wait(&this->pt_ready, &this->pt_lock); } 43 | 44 | 45 | void MilterEvent::Append (MilterEvent *ev) 46 | { 47 | assert(this->ev_next == NULL); 48 | this->ev_next = ev; 49 | } 50 | 51 | void MilterEvent::Detatch () 52 | { 53 | this->ev_next = NULL; 54 | } 55 | 56 | MilterEvent *MilterEvent::Next () const { return this->ev_next; } 57 | 58 | void MilterEvent::SetMilterContext (SMFICTX *context) 59 | { 60 | assert(NULL != context); 61 | this->smfi_context = context; 62 | } 63 | 64 | bool MilterEvent::IsDone () const { return this->is_done; } 65 | 66 | int MilterEvent::Result () const { return this->fi_retval; } 67 | 68 | /*virtual*/ MilterEvent::~MilterEvent () 69 | { 70 | pthread_cond_destroy(&this->pt_ready); 71 | pthread_mutex_destroy(&this->pt_lock); 72 | } 73 | 74 | 75 | /** 76 | * the last call made by the script kiddie from a callback is: 77 | * envelope.done(retval) 78 | * that then returns here, via the envelope's pointer to the current event 79 | * 80 | * xxfi_close will always be a MilterClose which will cause Postfire() to 81 | * Reset() its persistent envelope handle 82 | */ 83 | bool MilterEvent::Done (Isolate *isolate, int retval) 84 | { 85 | this->Postfire(isolate); 86 | 87 | this->fi_retval = retval; 88 | this->is_done = (0 == pthread_cond_signal(&this->pt_ready)); 89 | 90 | // throw exception if the sleeping libmilter thread is not signaled 91 | if (!this->is_done) 92 | isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "Condition signal failed"))); 93 | 94 | // throw exception if event is not yielded back to pthread 95 | if (!this->Unlock()) 96 | isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "Event unlock failed"))); 97 | 98 | return this->is_done; 99 | } 100 | 101 | 102 | /** 103 | * caling envelope.whatever() outside of certain events is largely forbidden 104 | */ 105 | bool MilterEvent::IsNegotiate () const 106 | { 107 | return false; 108 | } 109 | bool MilterEvent::IsConnect () const 110 | { 111 | return false; 112 | } 113 | bool MilterEvent::IsEndMessage () const 114 | { 115 | return false; 116 | } 117 | 118 | 119 | 120 | /** 121 | * the default prefire event 122 | * 123 | * must be called during trigger_event 124 | * restore existing object for session 125 | */ 126 | void MilterEvent::Prefire (Isolate *isolate) 127 | { 128 | this->envelope = Local::New(isolate, this->fi_envelope->object); 129 | } 130 | 131 | /** 132 | * the default postfire event (do nothing) 133 | */ 134 | void MilterEvent::Postfire (Isolate *isolate) 135 | { 136 | // naaaah-thing 137 | } 138 | 139 | 140 | /** 141 | */ 142 | void MilterEvent::Fire (Isolate *isolate, bindings_t *local) 143 | { 144 | //HandleScope scope (isolate); 145 | this->Prefire(isolate); 146 | 147 | Envelope *env = ObjectWrap::Unwrap(this->envelope); 148 | env->SetCurrentEvent(this); 149 | env->SetMilterContext(this->smfi_context); 150 | 151 | this->FireWrapper(isolate, local); 152 | } 153 | 154 | 155 | /** 156 | * shared wrapper for each event implementation 157 | * XXX: maybe inline this for maximum gofast 158 | */ 159 | void MilterEvent::DoFCall (Isolate *isolate, Persistent &pfunc, unsigned int argc, Local *argv) 160 | { 161 | TryCatch tc(isolate); 162 | Local fcall = Local::New(isolate, pfunc); 163 | fcall->Call(isolate->GetCurrentContext()->Global(), argc, argv); 164 | if (tc.HasCaught()) 165 | FatalException(isolate, tc); 166 | } 167 | 168 | 169 | /** MilterNegotiate ***********************************************************/ 170 | 171 | MilterNegotiate::MilterNegotiate (envelope_t *env, 172 | unsigned long f0_, 173 | unsigned long f1_, 174 | unsigned long f2_, 175 | unsigned long f3_, 176 | unsigned long *pf0_, 177 | unsigned long *pf1_, 178 | unsigned long *pf2_, 179 | unsigned long *pf3_) 180 | : MilterEvent(env), f0(f0_), f1(f1_), f2(f2_), f3(f3_), pf0(pf0_), pf1(pf1_), pf2(pf2_), pf3(pf3_) 181 | { 182 | // 183 | } 184 | 185 | bool MilterNegotiate::IsNegotiate () const { return true; } 186 | 187 | void MilterNegotiate::Prefire (Isolate *isolate) 188 | { 189 | this->envelope = Envelope::PrivateInstance(isolate); 190 | this->fi_envelope->object.Reset(isolate, this->envelope); 191 | } 192 | 193 | /** 194 | */ 195 | void MilterNegotiate::FireWrapper (Isolate *isolate, bindings_t *local) 196 | { 197 | const unsigned argc = 5; 198 | Local argv[argc] = { 199 | this->envelope, 200 | Number::New(isolate, this->f0), 201 | Number::New(isolate, this->f1), 202 | Number::New(isolate, this->f2), 203 | Number::New(isolate, this->f3) 204 | }; 205 | this->DoFCall(isolate, local->fcall.negotiate, argc, argv); 206 | } 207 | 208 | 209 | /** 210 | */ 211 | void MilterNegotiate::Negotiate(unsigned long f0, unsigned long f1, unsigned long f2, unsigned long f3) 212 | { 213 | *pf0 = f0; 214 | *pf1 = f1; 215 | *pf2 = f2; 216 | *pf3 = f3; 217 | } 218 | 219 | 220 | /** MilterConnect *************************************************************/ 221 | 222 | MilterConnect::MilterConnect (envelope_t *env, const char *host, sockaddr_in *sa) 223 | : MilterEvent(env), sz_host(host) 224 | { 225 | inet_ntop(AF_INET, &((sockaddr_in *)sa)->sin_addr, sz_addr, sizeof sz_addr); 226 | } 227 | 228 | bool MilterConnect::IsConnect () const { return true; } 229 | 230 | /** 231 | */ 232 | void MilterConnect::FireWrapper (Isolate *isolate, bindings_t *local) 233 | { 234 | const unsigned argc = 3; 235 | Local argv[argc] = { 236 | this->envelope, 237 | String::NewFromUtf8(isolate, this->sz_host), 238 | String::NewFromUtf8(isolate, this->sz_addr) 239 | }; 240 | this->DoFCall(isolate, local->fcall.connect, argc, argv); 241 | } 242 | 243 | const char *MilterConnect::Host() const { return this->sz_host; } 244 | 245 | const char *MilterConnect::Address() const { return this->sz_addr; } 246 | 247 | 248 | /** MilterUnknown *************************************************************/ 249 | 250 | MilterUnknown::MilterUnknown (envelope_t *env, const char *command) 251 | : MilterEvent(env), sz_command(command) { } 252 | 253 | /** 254 | */ 255 | void MilterUnknown::FireWrapper (Isolate *isolate, bindings_t *local) 256 | { 257 | const unsigned argc = 2; 258 | Local argv[argc] = { 259 | this->envelope, 260 | String::NewFromUtf8(isolate, this->sz_command) 261 | }; 262 | this->DoFCall(isolate, local->fcall.unknown, argc, argv); 263 | } 264 | 265 | 266 | /** MilterHELO ***************************************************************/ 267 | 268 | MilterHELO::MilterHELO (envelope_t *env, const char *helo) 269 | : MilterEvent(env), sz_helo(helo) { } 270 | 271 | 272 | void MilterHELO::FireWrapper (Isolate *isolate, bindings_t *local) 273 | { 274 | const unsigned argc = 2; 275 | Local argv[argc] = { 276 | this->envelope, 277 | String::NewFromUtf8(isolate, sz_helo) 278 | }; 279 | this->DoFCall(isolate, local->fcall.helo, argc, argv); 280 | } 281 | 282 | 283 | /** MilterMAILFROM ***************************************************************/ 284 | 285 | MilterMAILFROM::MilterMAILFROM (envelope_t *env, char **argv) 286 | : MilterEvent(env), szpp_argv(argv) { } 287 | 288 | void MilterMAILFROM::FireWrapper (Isolate *isolate, bindings_t *local) 289 | { 290 | const unsigned argc = 2; 291 | Local argv[argc] = { 292 | this->envelope, 293 | String::NewFromUtf8(isolate, szpp_argv[0]) 294 | }; 295 | this->DoFCall(isolate, local->fcall.envfrom, argc, argv); 296 | } 297 | 298 | 299 | /** MilterRCPTTO ***************************************************************/ 300 | 301 | MilterRCPTTO::MilterRCPTTO (envelope_t *env, char **argv) 302 | : MilterEvent(env), szpp_argv(argv) { } 303 | 304 | void MilterRCPTTO::FireWrapper (Isolate *isolate, bindings_t *local) 305 | { 306 | const unsigned argc = 2; 307 | Local argv[argc] = { 308 | this->envelope, 309 | String::NewFromUtf8(isolate, szpp_argv[0]) 310 | }; 311 | this->DoFCall(isolate, local->fcall.envrcpt, argc, argv); 312 | } 313 | 314 | 315 | /** MilterDATA ***************************************************************/ 316 | 317 | MilterDATA::MilterDATA (envelope_t *env) : MilterEvent(env) { } 318 | 319 | void MilterDATA::FireWrapper (Isolate *isolate, bindings_t *local) 320 | { 321 | const unsigned argc = 1; 322 | Local argv[argc] = { 323 | this->envelope, 324 | }; 325 | this->DoFCall(isolate, local->fcall.data, argc, argv); 326 | } 327 | 328 | 329 | /** MilterHeader ***************************************************************/ 330 | 331 | MilterHeader::MilterHeader (envelope_t *env, const char *name, const char *value) 332 | : MilterEvent(env), sz_name(name), sz_value(value) { } 333 | 334 | void MilterHeader::FireWrapper (Isolate *isolate, bindings_t *local) 335 | { 336 | const unsigned argc = 3; 337 | Local argv[argc] = { 338 | this->envelope, 339 | String::NewFromUtf8(isolate, sz_name), 340 | String::NewFromUtf8(isolate, sz_value) 341 | }; 342 | this->DoFCall(isolate, local->fcall.header, argc, argv); 343 | } 344 | 345 | 346 | /** MilterEndHeaders ***************************************************************/ 347 | 348 | MilterEndHeaders::MilterEndHeaders (envelope_t *env) : MilterEvent(env) { } 349 | 350 | void MilterEndHeaders::FireWrapper (Isolate *isolate, bindings_t *local) 351 | { 352 | const unsigned argc = 1; 353 | Local argv[argc] = { 354 | this->envelope, 355 | }; 356 | this->DoFCall(isolate, local->fcall.eoh, argc, argv); 357 | } 358 | 359 | 360 | /** MilterMessageData ***************************************************************/ 361 | 362 | MilterMessageData::MilterMessageData (envelope_t *env, const unsigned char *buf, const size_t len) 363 | : MilterEvent(env), buf(buf), len(len) { } 364 | 365 | void MilterMessageData::FireWrapper (Isolate *isolate, bindings_t *local) 366 | { 367 | char *nodebuf = new char[len]; 368 | memcpy(nodebuf, buf, len); 369 | const unsigned argc = 3; 370 | Local argbuf = Buffer::New(isolate, nodebuf, len).ToLocalChecked(); 371 | Local argv[argc] = { 372 | this->envelope, 373 | argbuf, 374 | Number::New(isolate, len) 375 | }; 376 | this->DoFCall(isolate, local->fcall.body, argc, argv); 377 | } 378 | 379 | 380 | /** MilterEndMessage ***************************************************************/ 381 | 382 | MilterEndMessage::MilterEndMessage (envelope_t *env) : MilterEvent(env) { } 383 | 384 | bool MilterEndMessage::IsEndMessage () const { return true; } 385 | 386 | void MilterEndMessage::FireWrapper (Isolate *isolate, bindings_t *local) 387 | { 388 | const unsigned argc = 1; 389 | Local argv[argc] = { 390 | this->envelope, 391 | }; 392 | this->DoFCall(isolate, local->fcall.eom, argc, argv); 393 | } 394 | 395 | 396 | /** MilterAbort ***************************************************************/ 397 | 398 | MilterAbort::MilterAbort (envelope_t *env) : MilterEvent(env) { } 399 | 400 | void MilterAbort::FireWrapper (Isolate *isolate, bindings_t *local) 401 | { 402 | const unsigned argc = 1; 403 | Local argv[argc] = { 404 | this->envelope, 405 | }; 406 | this->DoFCall(isolate, local->fcall.abort, argc, argv); 407 | } 408 | 409 | 410 | /** MilterClose ***************************************************************/ 411 | 412 | MilterClose::MilterClose (envelope_t *env) : MilterEvent(env) { } 413 | 414 | void MilterClose::FireWrapper (Isolate *isolate, bindings_t *local) 415 | { 416 | const unsigned argc = 1; 417 | Local argv[argc] = { 418 | this->envelope, 419 | }; 420 | this->DoFCall(isolate, local->fcall.close, argc, argv); 421 | } 422 | 423 | 424 | /** 425 | */ 426 | void MilterClose::Postfire (Isolate *isolate) 427 | { 428 | this->fi_envelope->object.Reset(); 429 | } 430 | -------------------------------------------------------------------------------- /src/envelope.cc: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "envelope.h" 9 | 10 | using namespace v8; 11 | using namespace node; 12 | 13 | 14 | Persistent Envelope::constructor; 15 | 16 | 17 | /** 18 | */ 19 | Envelope::Envelope () 20 | { } 21 | 22 | /** 23 | */ 24 | Envelope::~Envelope () 25 | { } 26 | 27 | 28 | /** 29 | */ 30 | void Envelope::Init (Local exports) 31 | { 32 | Isolate *isolate = exports->GetIsolate(); 33 | Local tmpl = FunctionTemplate::New(isolate, New); 34 | tmpl->SetClassName(String::NewFromUtf8(isolate, "Envelope")); 35 | tmpl->InstanceTemplate()->SetInternalFieldCount(2); 36 | 37 | NODE_SET_PROTOTYPE_METHOD(tmpl, "done", Done); 38 | NODE_SET_PROTOTYPE_METHOD(tmpl, "negotiate", Negotiate); 39 | 40 | NODE_SET_PROTOTYPE_METHOD(tmpl, "getsymval", SMFI_GetSymbol); 41 | NODE_SET_PROTOTYPE_METHOD(tmpl, "setsymlist", SMFI_SetSymbolList); 42 | 43 | NODE_SET_PROTOTYPE_METHOD(tmpl, "setreply", SMFI_SetReply); 44 | NODE_SET_PROTOTYPE_METHOD(tmpl, "setmlreply", SMFI_SetMultilineReply); 45 | 46 | NODE_SET_PROTOTYPE_METHOD(tmpl, "progress", SMFI_Progress); 47 | NODE_SET_PROTOTYPE_METHOD(tmpl, "quarantine", SMFI_Quarantine); 48 | 49 | NODE_SET_PROTOTYPE_METHOD(tmpl, "addheader", SMFI_AddHeader); 50 | NODE_SET_PROTOTYPE_METHOD(tmpl, "chgheader", SMFI_ChangeHeader); 51 | NODE_SET_PROTOTYPE_METHOD(tmpl, "insheader", SMFI_InsertHeader); 52 | NODE_SET_PROTOTYPE_METHOD(tmpl, "replacebody", SMFI_ReplaceBody); 53 | NODE_SET_PROTOTYPE_METHOD(tmpl, "addrcpt", SMFI_AddRecipient); 54 | NODE_SET_PROTOTYPE_METHOD(tmpl, "addrcpt_par", SMFI_AddRecipientExtended); 55 | NODE_SET_PROTOTYPE_METHOD(tmpl, "delrcpt", SMFI_DelRecipient); 56 | NODE_SET_PROTOTYPE_METHOD(tmpl, "chgfrom", SMFI_ChangeFrom); 57 | 58 | constructor.Reset(isolate, tmpl->GetFunction()); 59 | // XXX: uncomment to let the programmer use 'new Envelope()'? 60 | //exports->Set(String::NewFromUtf8(Isolate, "Envelope"), tmpl->GetFunction()); 61 | } 62 | 63 | 64 | /** 65 | */ 66 | void Envelope::NewInstance (const FunctionCallbackInfo &args) 67 | { 68 | Isolate *isolate = args.GetIsolate(); 69 | Local argv[0] = { }; 70 | Local cons = Local::New(isolate, constructor); 71 | Local context = isolate->GetCurrentContext(); 72 | Local envelope = cons->NewInstance(context, 0, argv).ToLocalChecked(); 73 | args.GetReturnValue().Set(envelope); 74 | } 75 | 76 | 77 | /** 78 | */ 79 | Local Envelope::PrivateInstance (Isolate *isolate) 80 | { 81 | Local argv[0] = { }; 82 | Local cons = Local::New(isolate, constructor); 83 | Local context = isolate->GetCurrentContext(); 84 | Local envelope = cons->NewInstance(context, 0, argv).ToLocalChecked(); 85 | envelope->Set(String::NewFromUtf8(isolate, "local", String::kInternalizedString), Object::New(isolate)); 86 | return envelope; 87 | } 88 | 89 | 90 | /** 91 | */ 92 | void Envelope::New (const FunctionCallbackInfo &args) 93 | { 94 | Isolate *isolate = args.GetIsolate(); 95 | 96 | if (args.IsConstructCall()) 97 | { 98 | Envelope *envelope = new Envelope; 99 | envelope->Wrap(args.This()); 100 | args.GetReturnValue().Set(args.This()); 101 | } 102 | else 103 | { 104 | const unsigned int argc = 0; 105 | Local argv[argc] = { }; 106 | Local cons = Local::New(isolate, constructor); 107 | Local context = isolate->GetCurrentContext(); 108 | args.GetReturnValue().Set(cons->NewInstance(context, argc, argv).ToLocalChecked()); 109 | } 110 | } 111 | 112 | 113 | /** 114 | */ 115 | void Envelope::SetCurrentEvent (MilterEvent *event) 116 | { 117 | assert (event != NULL); 118 | this->current_event = event; 119 | } 120 | 121 | /** 122 | */ 123 | void Envelope::SetMilterContext (SMFICTX *context) 124 | { 125 | assert (context != NULL); 126 | this->smfi_context = context; 127 | } 128 | 129 | 130 | 131 | /** 132 | */ 133 | void Envelope::Done (const FunctionCallbackInfo &args) 134 | { 135 | Isolate *isolate = args.GetIsolate(); 136 | Envelope *envelope = ObjectWrap::Unwrap(args.Holder()); 137 | #ifdef DEBUG_COMMANDS 138 | fprintf(stderr, "Envelope::Done started\n"); 139 | #endif 140 | if (NULL == envelope) 141 | { 142 | isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "Done unwrap failed"))); 143 | return; 144 | } 145 | MilterEvent *event = envelope->current_event; 146 | if (NULL == event) 147 | { 148 | isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "Envelope done method called outside event context"))); 149 | return; 150 | } 151 | 152 | if (args.Length() < 1) 153 | { 154 | isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong number of arguments"))); 155 | return; 156 | } 157 | 158 | if (!args[0]->IsNumber()) 159 | { 160 | isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "First argument: expected number"))); 161 | return; 162 | } 163 | 164 | event->Done(isolate, args[0]->IntegerValue()); 165 | #ifdef DEBUG_COMMANDS 166 | fprintf(stderr, "Envelope::Done completed\n"); 167 | #endif 168 | } 169 | 170 | 171 | /** 172 | */ 173 | void Envelope::Negotiate (const FunctionCallbackInfo &args) 174 | { 175 | Isolate *isolate = args.GetIsolate(); 176 | Envelope *envelope = ObjectWrap::Unwrap(args.Holder()); 177 | 178 | if (NULL == envelope) 179 | { 180 | isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "Negotiate unwrap failed"))); 181 | return; 182 | } 183 | MilterEvent *event = envelope->current_event; 184 | 185 | if (NULL == event || !event->IsNegotiate()) 186 | { 187 | isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "Envelope negotiate method called outside event context"))); 188 | return; 189 | } 190 | 191 | 192 | if (args.Length() < 4) 193 | { 194 | isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong number of arguments"))); 195 | return; 196 | } 197 | 198 | for (int i = 0; i < 4; i++) 199 | if (!args[i]->IsNumber()) 200 | { 201 | isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Invalid argument: expected number"))); 202 | return; 203 | } 204 | 205 | unsigned long f0 = args[0]->Uint32Value(); 206 | unsigned long f1 = args[1]->Uint32Value(); 207 | unsigned long f2 = args[2]->Uint32Value(); 208 | unsigned long f3 = args[3]->Uint32Value(); 209 | 210 | MilterNegotiate *ev = (MilterNegotiate *)event; 211 | ev->Negotiate(f0, f1, f2, f3); 212 | } 213 | 214 | 215 | /** 216 | */ 217 | void Envelope::SMFI_GetSymbol (const FunctionCallbackInfo &args) 218 | { 219 | Isolate *isolate = args.GetIsolate(); 220 | Envelope *envelope = ObjectWrap::Unwrap(args.Holder()); 221 | 222 | if (NULL == envelope) 223 | { 224 | isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "SMFI_GetSymbol unwrap failed"))); 225 | return; 226 | } 227 | 228 | Local symname = args[0]->ToString(); 229 | char *c_symname = new char[symname->Utf8Length()+1]; 230 | symname->WriteUtf8(c_symname); 231 | 232 | char *c_symval = smfi_getsymval(envelope->smfi_context, c_symname); 233 | delete [] c_symname; 234 | 235 | args.GetReturnValue().Set(String::NewFromUtf8(isolate, c_symval ? c_symval : "")); 236 | 237 | // TODO: either segfault or memory leak here, probably 238 | if (c_symval) 239 | free(c_symval); 240 | } 241 | 242 | 243 | /** 244 | */ 245 | void Envelope::SMFI_SetSymbolList (const FunctionCallbackInfo &args) 246 | { 247 | Isolate *isolate = args.GetIsolate(); 248 | isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "Not yet implemented"))); 249 | 250 | // TODO: smfi_setsymlist 251 | } 252 | 253 | 254 | /** 255 | */ 256 | void Envelope::SMFI_SetReply (const FunctionCallbackInfo &args) 257 | { 258 | Isolate *isolate = args.GetIsolate(); 259 | Envelope *envelope = ObjectWrap::Unwrap(args.Holder()); 260 | 261 | if (NULL == envelope) 262 | { 263 | isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "SMFI_SetReply unwrap failed"))); 264 | return; 265 | } 266 | MilterEvent *event = envelope->current_event; 267 | if (NULL == event) 268 | { 269 | isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "Envelope method called out of event context"))); 270 | return; 271 | } 272 | if (event->IsNegotiate() /*|| event->IsConnect()*/) 273 | { 274 | isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "Envelope method called from prohibited event context"))); 275 | return; 276 | } 277 | 278 | 279 | if (args.Length() < 1) 280 | { 281 | isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong number of arguments"))); 282 | return; 283 | } 284 | 285 | if (!args[0]->IsString() && !args[0]->IsNumber()) 286 | { 287 | isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "First argument: expected number or string"))); 288 | return; 289 | } 290 | 291 | if (args.Length() > 1) 292 | { 293 | if (!args[1]->IsString() && !args[1]->IsNull() && !args[1]->IsUndefined()) 294 | { 295 | isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Second argument: expected string, null, or undefined"))); 296 | return; 297 | } 298 | if (args.Length() > 2) 299 | { 300 | if (!args[2]->IsString() && !args[2]->IsNull() && !args[1]->IsUndefined()) 301 | { 302 | isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Third argument: expected string, null, or undefined"))); 303 | return; 304 | } 305 | } 306 | } 307 | 308 | Local rcode = args[0]->ToString(); 309 | char *c_rcode = new char[rcode->Utf8Length()+1]; 310 | rcode->WriteUtf8(c_rcode); 311 | 312 | char *c_xcode = NULL; 313 | char *c_message = NULL; 314 | 315 | if (args.Length() > 1 && args[1]->IsString()) 316 | { 317 | Local xcode = args[1]->ToString(); 318 | c_xcode = new char[xcode->Utf8Length()+1]; 319 | xcode->WriteUtf8(c_xcode); 320 | } 321 | 322 | if (args.Length() > 2 && args[2]->IsString()) 323 | { 324 | Local message = args[2]->ToString(); 325 | c_message = new char[message->Utf8Length()+1]; 326 | message->WriteUtf8(c_message); 327 | } 328 | 329 | int r = smfi_setreply(envelope->smfi_context, c_rcode, c_xcode, c_message); 330 | 331 | delete [] c_rcode; 332 | if (c_xcode) 333 | delete [] c_xcode; 334 | if (c_message) 335 | delete [] c_message; 336 | 337 | args.GetReturnValue().Set(Number::New(isolate, r)); 338 | } 339 | 340 | 341 | /** 342 | */ 343 | void Envelope::SMFI_SetMultilineReply (const FunctionCallbackInfo &args) 344 | { 345 | Isolate *isolate = args.GetIsolate(); 346 | isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "Not yet implemented"))); 347 | 348 | // TODO: implement with variable arguments somehow. smfi_setmlreply(rcode,xcode,vaargs) 349 | } 350 | 351 | 352 | /** 353 | */ 354 | void Envelope::SMFI_Progress (const FunctionCallbackInfo &args) 355 | { 356 | Isolate *isolate = args.GetIsolate(); 357 | Envelope *envelope = ObjectWrap::Unwrap(args.Holder()); 358 | 359 | if (NULL == envelope) 360 | { 361 | isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "SMFI_Progress unwrap failed"))); 362 | return; 363 | } 364 | MilterEvent *event = envelope->current_event; 365 | if (NULL == event) 366 | { 367 | isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "Envelope method called outside event context"))); 368 | return; 369 | } 370 | 371 | if (!event->IsEndMessage()) 372 | { 373 | isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "Envelope method called from prohibited event context"))); 374 | return; 375 | } 376 | int r = smfi_progress(envelope->smfi_context); 377 | args.GetReturnValue().Set(Number::New(isolate, r)); 378 | } 379 | 380 | 381 | /** 382 | */ 383 | void Envelope::SMFI_AddHeader (const FunctionCallbackInfo &args) 384 | { 385 | Isolate *isolate = args.GetIsolate(); 386 | Envelope *envelope = ObjectWrap::Unwrap(args.Holder()); 387 | 388 | if (NULL == envelope) 389 | { 390 | isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "SMFI_AddHeader unwrap failed"))); 391 | return; 392 | } 393 | MilterEvent *event = envelope->current_event; 394 | if (NULL == event) 395 | { 396 | isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "Envelope method called outside event context"))); 397 | return; 398 | } 399 | if (!event->IsEndMessage()) 400 | { 401 | isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "Envelope method called from prohibited event context"))); 402 | return; 403 | } 404 | 405 | if (args.Length() < 2) 406 | { 407 | isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong number of arguments"))); 408 | return; 409 | } 410 | 411 | if (!args[0]->IsString()) 412 | { 413 | isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "First argument: expected string"))); 414 | return; 415 | } 416 | 417 | if (!args[1]->IsString()) 418 | { 419 | isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Second argument: expected string"))); 420 | return; 421 | } 422 | 423 | Local headerf = args[0]->ToString(); 424 | Local headerv = args[1]->ToString(); 425 | char *c_name = new char[headerf->Utf8Length()+1]; 426 | char *c_value = new char[headerv->Utf8Length()+1]; 427 | 428 | headerf->WriteUtf8(c_name); 429 | headerv->WriteUtf8(c_value); 430 | 431 | int r = smfi_addheader(envelope->smfi_context, c_name, c_value); 432 | 433 | delete [] c_name; 434 | delete [] c_value; 435 | 436 | args.GetReturnValue().Set(Number::New(isolate, r)); 437 | } 438 | 439 | 440 | /** 441 | */ 442 | void Envelope::SMFI_ChangeHeader (const FunctionCallbackInfo &args) 443 | { 444 | // TODO: smfi_chgheader(envelope->smfi_context, name, index, value) 445 | } 446 | 447 | 448 | /** 449 | */ 450 | void Envelope::SMFI_InsertHeader (const FunctionCallbackInfo &args) 451 | { 452 | // TODO: smfi_insheader(envelope->smfi_context, index, name, value) 453 | } 454 | 455 | 456 | /** 457 | * who would ever do this? implement later when actually needed 458 | */ 459 | void Envelope::SMFI_ReplaceBody (const FunctionCallbackInfo &args) 460 | { 461 | // TODO: smfi_replacebody 462 | } 463 | 464 | 465 | /** 466 | */ 467 | void Envelope::SMFI_AddRecipient (const FunctionCallbackInfo &args) 468 | { 469 | // TODO: smfi_addrcpt 470 | } 471 | 472 | 473 | /** 474 | */ 475 | void Envelope::SMFI_AddRecipientExtended (const FunctionCallbackInfo &args) 476 | { 477 | // TODO: smfi_addrcpt_par 478 | } 479 | 480 | 481 | /** 482 | */ 483 | void Envelope::SMFI_DelRecipient (const FunctionCallbackInfo &args) 484 | { 485 | // TODO: smfi_delrcpt 486 | } 487 | 488 | 489 | /** 490 | */ 491 | void Envelope::SMFI_Quarantine (const FunctionCallbackInfo &args) 492 | { 493 | Isolate *isolate = args.GetIsolate(); 494 | Envelope *envelope = ObjectWrap::Unwrap(args.Holder()); 495 | 496 | if (NULL == envelope) 497 | { 498 | isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "SMFI_Quarantine unwrap failed"))); 499 | return; 500 | } 501 | MilterEvent *event = envelope->current_event; 502 | if (NULL == event) 503 | { 504 | isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "Envelope method called outside event context"))); 505 | return; 506 | } 507 | if (!event->IsEndMessage()) 508 | { 509 | isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Envelope method called from prohibited event context"))); 510 | return; 511 | } 512 | 513 | 514 | if (args.Length() < 1) 515 | { 516 | isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong number of arguments"))); 517 | return; 518 | } 519 | if (!args[0]->IsString()) 520 | { 521 | isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "First argument: expected string"))); 522 | return; 523 | } 524 | 525 | Local reason = args[0]->ToString(); 526 | size_t len = reason->Utf8Length(); 527 | if (len < 1) 528 | { 529 | isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "First argument: expected non-empty string"))); 530 | return; 531 | } 532 | 533 | char *c_reason = new char[len+1]; 534 | reason->WriteUtf8(c_reason); 535 | int r = smfi_quarantine(envelope->smfi_context, c_reason); 536 | delete [] c_reason; 537 | 538 | args.GetReturnValue().Set(Number::New(isolate, r)); 539 | } 540 | 541 | 542 | /** 543 | */ 544 | void Envelope::SMFI_ChangeFrom (const FunctionCallbackInfo &args) 545 | { 546 | Isolate *isolate = args.GetIsolate(); 547 | Envelope *envelope = ObjectWrap::Unwrap(args.Holder()); 548 | 549 | if (NULL == envelope) 550 | { 551 | isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "SMFI_ChangeFrom unwrap failed"))); 552 | return; 553 | } 554 | MilterEvent *event = envelope->current_event; 555 | if (NULL == event) 556 | { 557 | isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "Envelope method called outside event context"))); 558 | return; 559 | } 560 | if (!event->IsEndMessage()) 561 | { 562 | isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "Envelope method called from prohibited event context"))); 563 | return; 564 | } 565 | 566 | if (args.Length() < 1) 567 | { 568 | isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong number of arguments"))); 569 | return; 570 | } 571 | 572 | if (!args[0]->IsString()) 573 | { 574 | isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "First argument: expected string"))); 575 | return; 576 | } 577 | if (args.Length() > 1 && !args[1]->IsString()) 578 | { 579 | isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Second argument: expected string"))); 580 | return; 581 | } 582 | 583 | 584 | Local address = args[0]->ToString(); 585 | char *c_address = new char[address->Utf8Length()+1]; 586 | char *c_esmtp_args = NULL; 587 | address->WriteUtf8(c_address); 588 | if (args.Length() > 1) 589 | { 590 | Local esmtp_args = args[1]->ToString(); 591 | c_esmtp_args = new char[esmtp_args->Utf8Length()+1]; 592 | esmtp_args->WriteUtf8(c_esmtp_args); 593 | } 594 | 595 | int r = smfi_chgfrom(envelope->smfi_context, c_address, c_esmtp_args); 596 | 597 | delete [] c_address; 598 | if (c_esmtp_args) 599 | delete [] c_esmtp_args; 600 | 601 | args.GetReturnValue().Set(Number::New(isolate, r)); 602 | } 603 | -------------------------------------------------------------------------------- /src/milter.cc: -------------------------------------------------------------------------------- 1 | /** 2 | * node bindings for milter 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "libmilter/mfapi.h" 11 | #include "forward.h" 12 | #include "events.h" 13 | #include "envelope.h" 14 | 15 | using namespace v8; 16 | using namespace node; 17 | 18 | 19 | /** globals *******************************************************************/ 20 | 21 | 22 | /** 23 | * the name the milter registers with to the MTA. 24 | */ 25 | static const char *g_name = "node-bindings"; 26 | 27 | 28 | /** 29 | * the libmilter implementation and other application globals. 30 | */ 31 | bindings_t app; 32 | 33 | 34 | /** demultiplexer *************************************************************/ 35 | 36 | 37 | /** 38 | * all queued events reach node.js from this point; this task is queued on the 39 | * libuv side, in a loop, via uv_async_send 40 | */ 41 | void trigger_event (uv_async_t *h) 42 | { 43 | bindings *local = (bindings *)h->data; 44 | Isolate *isolate = Isolate::GetCurrent(); 45 | Locker locker (isolate); 46 | HandleScope scope (isolate); 47 | MilterEvent *ev; 48 | 49 | #ifdef DEBUG_ASYNC 50 | fprintf(stderr, "trigger_event: begin scope\n"); 51 | #endif 52 | 53 | // grab the queue lock 54 | if (pthread_mutex_lock(&local->lck_queue)) 55 | { 56 | fprintf(stderr, "trigger_event: queue lock failed\n"); 57 | return; 58 | } 59 | 60 | // dequeue one event 61 | ev = local->first; 62 | if (NULL == ev) 63 | { 64 | pthread_mutex_unlock(&local->lck_queue); 65 | fprintf(stderr, "trigger_event: spurious wakeup\n"); 66 | return; 67 | } 68 | local->first = ev->Next(); 69 | if (NULL == local->first) 70 | local->last = NULL; 71 | ev->Detatch(); 72 | 73 | if (!ev->Lock()) 74 | { 75 | fprintf(stderr, "trigger_event: event lock failed\n"); 76 | 77 | if (pthread_mutex_unlock(&local->lck_queue)) 78 | { 79 | fprintf(stderr, "trigger_event: queue unlocked failed\n"); 80 | } 81 | return; 82 | } 83 | 84 | // XXX: for top speed, move queue unlock to this point (with or without event lock success) 85 | if (pthread_mutex_unlock(&local->lck_queue)) 86 | { 87 | fprintf(stderr, "trigger_event: queue unlock failed\n"); 88 | } 89 | 90 | // launch the appropriate node.js callback for the given event 91 | ev->Fire(isolate, local); 92 | } 93 | 94 | 95 | /** multiplexer ***************************************************************/ 96 | 97 | 98 | /** 99 | * queue an event from the libmilter side. 100 | */ 101 | int generate_event (SMFICTX *context, bindings_t *local, MilterEvent *event) 102 | { 103 | int retval; 104 | 105 | // lock the queue 106 | if (pthread_mutex_lock(&local->lck_queue)) 107 | { 108 | fprintf(stderr, "generate_event: queue lock failed\n"); 109 | return SMFIS_TEMPFAIL; 110 | } 111 | 112 | // lock the event 113 | if (!event->Lock()) 114 | { 115 | if (pthread_mutex_unlock(&local->lck_queue)) 116 | { 117 | fprintf(stderr, "generate_event: queue unlock failed\n"); 118 | // TODO: handle error? 119 | } 120 | 121 | fprintf(stderr, "generate_event: event lock failed\n"); 122 | return SMFIS_TEMPFAIL; 123 | } 124 | 125 | // enqueue the event while holding both locks 126 | if (NULL == local->last) 127 | { 128 | local->first = event; 129 | local->last = event; 130 | } 131 | else 132 | { 133 | local->last->Append(event); 134 | local->last = event; 135 | } 136 | 137 | // unlock the queue to allow other libmilter threads to append events to it 138 | if (pthread_mutex_unlock(&local->lck_queue)) 139 | { 140 | // TODO: handle error? 141 | fprintf(stderr, "generate_event: queue unlock failed\n"); 142 | } 143 | 144 | // give the event control of the libmilter context 145 | event->SetMilterContext(context); 146 | 147 | for (;;) 148 | { 149 | // let the node loop know we await a result 150 | #ifdef DEBUG_ASYNC 151 | fprintf(stderr, "generated_event: uv_async_send\n"); 152 | #endif 153 | uv_async_send(&local->trigger); 154 | 155 | // wait on the event's return condition 156 | event->Wait(); 157 | #ifdef DEBUG_ASYNC 158 | fprintf(stderr, "generate_event: wakeup!\n"); 159 | #endif 160 | // TODO: check error condition 161 | if (event->IsDone()) break; 162 | fprintf(stderr, "generate_event: incomplete task, spurious wakeup\n"); 163 | } 164 | // retrieve return code 165 | retval = event->Result(); 166 | 167 | if (!event->Unlock()) 168 | { 169 | // TODO: handle error? 170 | fprintf(stderr, "generate_event: event unlock failed\n"); 171 | } 172 | 173 | return retval; 174 | } 175 | 176 | 177 | /** callbacks provided to libmilter *******************************************/ 178 | 179 | 180 | /** 181 | * complicated MTA-to-milter bullshit nobody needs to fiddle with right now 182 | * 183 | * http://www-01.ibm.com/support/knowledgecenter/ssw_aix_71/com.ibm.aix.networkcomm/libmilter_xxfi_negotiate.htm 184 | */ 185 | sfsistat fi_negotiate (SMFICTX *context, 186 | unsigned long f0, 187 | unsigned long f1, 188 | unsigned long f2, 189 | unsigned long f3, 190 | unsigned long *pf0, 191 | unsigned long *pf1, 192 | unsigned long *pf2, 193 | unsigned long *pf3) 194 | { 195 | int retval = SMFIS_ALL_OPTS; 196 | 197 | // envelope initializer 198 | // links future events in this thread to the same envelope in node.js 199 | envelope_t *env = new envelope_t(&app); 200 | smfi_setpriv(context, env); 201 | 202 | MilterNegotiate *event = new MilterNegotiate(env, f0, f1, f2, f3, pf0, pf1, pf2, pf3); 203 | #ifdef DEBUG_MILTEREVENT 204 | fprintf(stderr, "negotiate %lu %lu %lu %lu\n", f0, f1, f2, f3); 205 | #endif 206 | retval = generate_event(context, &app, event); 207 | delete event; 208 | return retval; 209 | } 210 | 211 | 212 | /** 213 | * an incoming connection from the MTA has occurred. 214 | */ 215 | sfsistat fi_connect (SMFICTX *context, char *host, _SOCK_ADDR *sa) 216 | { 217 | envelope_t *env = (envelope_t *)smfi_getpriv(context); 218 | int retval; 219 | 220 | MilterConnect *event = new MilterConnect(env, host, (sockaddr_in *)sa); 221 | #ifdef DEBUG_MILTEREVENT 222 | fprintf(stderr, "connect \"%s\" \"%s\"\n", event->Host(), event->Address()); 223 | #endif 224 | retval = generate_event(context, env->local, event); 225 | delete event; 226 | return retval; 227 | } 228 | 229 | 230 | /** 231 | */ 232 | sfsistat fi_unknown (SMFICTX *context, const char *command) 233 | { 234 | envelope_t *env = (envelope_t *)smfi_getpriv(context); 235 | int retval; 236 | MilterUnknown *event = new MilterUnknown(env, command); 237 | #ifdef DEBUG_MILTEREVENT 238 | fprintf(stderr, "unknown \"%s\"\n", command); 239 | #endif 240 | retval = generate_event(context, env->local, event); 241 | delete event; 242 | return retval; 243 | } 244 | 245 | 246 | /** 247 | */ 248 | sfsistat fi_helo (SMFICTX *context, char *helo) 249 | { 250 | envelope_t *env = (envelope_t *)smfi_getpriv(context); 251 | int retval; 252 | MilterHELO *event = new MilterHELO(env, helo); 253 | #ifdef DEBUG_MILTEREVENT 254 | fprintf(stderr, "helo \"%s\"\n", helo); 255 | #endif 256 | retval = generate_event(context, env->local, event); 257 | delete event; 258 | return retval; 259 | } 260 | 261 | 262 | /** 263 | * client "MAIL FROM" command 264 | */ 265 | sfsistat fi_envfrom (SMFICTX *context, char **argv) 266 | { 267 | envelope_t *env = (envelope_t *)smfi_getpriv(context); 268 | int retval; 269 | MilterMAILFROM *event = new MilterMAILFROM(env, argv); 270 | #ifdef DEBUG_MILTEREVENT 271 | fprintf(stderr, "envfrom \"%s\"\n", argv[0]); // argv[0] is guaranteed 272 | #endif 273 | retval = generate_event(context, env->local, event); 274 | delete event; 275 | return retval; 276 | } 277 | 278 | 279 | /** 280 | * client "RCPT TO" command 281 | */ 282 | sfsistat fi_envrcpt (SMFICTX *context, char **argv) 283 | { 284 | envelope_t *env = (envelope_t *)smfi_getpriv(context); 285 | int retval; 286 | MilterRCPTTO *event = new MilterRCPTTO(env, argv); 287 | #ifdef DEBUG_MILTEREVENT 288 | fprintf(stderr, "envrcpt \"%s\"\n", argv[0]); // argv[0] is guaranteed 289 | #endif 290 | retval = generate_event(context, env->local, event); 291 | delete event; 292 | return retval; 293 | } 294 | 295 | 296 | /** 297 | * client "DATA" command 298 | */ 299 | sfsistat fi_data (SMFICTX *context) 300 | { 301 | envelope_t *env = (envelope_t *)smfi_getpriv(context); 302 | int retval; 303 | MilterDATA *event = new MilterDATA(env); 304 | #ifdef DEBUG_MILTEREVENT 305 | fprintf(stderr, "data\n"); 306 | #endif 307 | retval = generate_event(context, env->local, event); 308 | delete event; 309 | return retval; 310 | } 311 | 312 | 313 | /** 314 | * detected a valid message header 315 | */ 316 | sfsistat fi_header (SMFICTX *context, char *name, char *value) 317 | { 318 | envelope_t *env = (envelope_t *)smfi_getpriv(context); 319 | int retval; 320 | MilterHeader *event = new MilterHeader(env, name, value); 321 | #ifdef DEBUG_MILTEREVENT_HEADERS_TOO 322 | fprintf(stderr, "header \"%s\" \"%s\"\n", name, value); 323 | #endif 324 | retval = generate_event(context, env->local, event); 325 | delete event; 326 | return retval; 327 | } 328 | 329 | 330 | /** 331 | * detected CRLFCRLF in data stream 332 | */ 333 | sfsistat fi_eoh (SMFICTX *context) 334 | { 335 | envelope_t *env = (envelope_t *)smfi_getpriv(context); 336 | int retval; 337 | MilterEndHeaders *event = new MilterEndHeaders(env); 338 | #ifdef DEBUG_MILTEREVENT 339 | fprintf(stderr, "eoh\n"); 340 | #endif 341 | retval = generate_event(context, env->local, event); 342 | delete event; 343 | return retval; 344 | } 345 | 346 | 347 | /** 348 | * nota bene: segment is a byte buffer of unspecified encoding! don't 349 | * assume it isn't utf-8 just because smtp doesn't truly support that yet! 350 | */ 351 | sfsistat fi_body (SMFICTX *context, unsigned char *segment, size_t len) 352 | { 353 | envelope_t *env = (envelope_t *)smfi_getpriv(context); 354 | int retval; 355 | MilterMessageData *event = new MilterMessageData(env, segment, len); 356 | #ifdef DEBUG_MILTEREVENT 357 | fprintf(stderr, "message-data (%lu bytes)\n", len); 358 | #endif 359 | retval = generate_event(context, env->local, event); 360 | delete event; 361 | return retval; 362 | } 363 | 364 | 365 | /** 366 | * client "." command 367 | */ 368 | sfsistat fi_eom (SMFICTX *context) 369 | { 370 | envelope_t *env = (envelope_t *)smfi_getpriv(context); 371 | int retval; 372 | MilterEndMessage *event = new MilterEndMessage(env); 373 | #ifdef DEBUG_MILTEREVENT 374 | fprintf(stderr, "eom\n"); 375 | #endif 376 | retval = generate_event(context, env->local, event); 377 | delete event; 378 | return retval; 379 | } 380 | 381 | 382 | /** 383 | * probably triggered on client "RSET" or any other connection loss 384 | * 385 | * fi_close will still be called afterward, but fi_abort implies the client 386 | * definitely changed their mind about sending mail 387 | */ 388 | sfsistat fi_abort (SMFICTX *context) 389 | { 390 | envelope_t *env = (envelope_t *)smfi_getpriv(context); 391 | int retval; 392 | MilterAbort *event = new MilterAbort(env); 393 | #ifdef DEBUG_MILTEREVENT 394 | fprintf(stderr, "abort\n"); 395 | #endif 396 | retval = generate_event(context, env->local, event); 397 | delete event; 398 | return retval; 399 | } 400 | 401 | 402 | /** 403 | */ 404 | sfsistat fi_close (SMFICTX *context) 405 | { 406 | envelope_t *env = (envelope_t *)smfi_getpriv(context); 407 | int retval; 408 | MilterClose *event = new MilterClose(env); 409 | #ifdef DEBUG_MILTEREVENT 410 | fprintf(stderr, "close\n"); 411 | #endif 412 | retval = generate_event(context, env->local, event); 413 | delete event; 414 | 415 | // final teardown sequence 416 | delete env; 417 | smfi_setpriv(context, NULL); 418 | 419 | return retval; 420 | } 421 | 422 | 423 | /** 424 | * run async version of smfi_main, which is blocking 425 | */ 426 | void milter_worker (uv_work_t *request) 427 | { 428 | int r; 429 | r = smfi_main(); 430 | fprintf(stderr, "milter_worker finishing\n"); 431 | // TODO: any concurrency issues here? 432 | app.retval = r; 433 | } 434 | 435 | 436 | /** 437 | * cleanup after smfi_main finally stops. 438 | */ 439 | void milter_cleanup (uv_work_t *request, int status) 440 | { 441 | Isolate *isolate = Isolate::GetCurrent(); 442 | Locker locker (isolate); 443 | HandleScope scope (isolate); 444 | 445 | fprintf(stderr, "milter_cleanup started\n"); 446 | 447 | // immediately stop event delivery 448 | uv_close((uv_handle_t *)&(app.trigger), NULL); 449 | 450 | app.fcall.negotiate.Reset(); 451 | app.fcall.connect.Reset(); 452 | app.fcall.unknown.Reset(); 453 | app.fcall.helo.Reset(); 454 | app.fcall.envfrom.Reset(); 455 | app.fcall.envrcpt.Reset(); 456 | app.fcall.data.Reset(); 457 | app.fcall.header.Reset(); 458 | app.fcall.eoh.Reset(); 459 | app.fcall.body.Reset(); 460 | app.fcall.eom.Reset(); 461 | app.fcall.abort.Reset(); 462 | app.fcall.close.Reset(); 463 | 464 | // TODO: call a "end" callback provided during instantiation? unsure how to do this, probably with a persistent function 465 | } 466 | 467 | 468 | /** 469 | */ 470 | void milter_start (const FunctionCallbackInfo &args) 471 | { 472 | Isolate *isolate = Isolate::GetCurrent(); 473 | Locker locker (isolate); 474 | HandleScope scope(isolate); 475 | 476 | struct smfiDesc desc; 477 | 478 | desc.xxfi_name =(char *)g_name; 479 | desc.xxfi_version = SMFI_VERSION; 480 | desc.xxfi_flags = 0; 481 | desc.xxfi_negotiate = fi_negotiate; 482 | desc.xxfi_connect = fi_connect; 483 | desc.xxfi_unknown = fi_unknown; 484 | desc.xxfi_helo = fi_helo; 485 | desc.xxfi_envfrom = fi_envfrom; 486 | desc.xxfi_envrcpt = fi_envrcpt; 487 | desc.xxfi_data = fi_data; 488 | desc.xxfi_header = fi_header; 489 | desc.xxfi_eoh = fi_eoh; 490 | desc.xxfi_body = fi_body; 491 | desc.xxfi_eom = fi_eom; 492 | desc.xxfi_abort = fi_abort; 493 | desc.xxfi_close = fi_close; 494 | #if 0 495 | // signal handler is not implemented yet 496 | desc.xxfi_signal = fi_signal; 497 | #endif 498 | 499 | // connect libuv 500 | app.request.data = &app; 501 | app.trigger.data = &app; 502 | 503 | if (args.Length() < 14) 504 | { 505 | isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong number of arguments"))); 506 | return; 507 | } 508 | if (!args[0]->IsString()) 509 | { 510 | isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Invalid argument: expected string"))); 511 | return; 512 | } 513 | if (!args[1]->IsNumber()) 514 | { 515 | isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Invalid argument: expected number"))); 516 | return; 517 | } 518 | 519 | desc.xxfi_flags = args[1]->IntegerValue(); 520 | 521 | // ya dats lazy 522 | for (int i = 2; i < 15; i++) 523 | if (!args[i]->IsFunction()) 524 | { 525 | isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Invalid argument: expected function"))); 526 | return; 527 | } 528 | 529 | Local connstr = args[0]->ToString(); 530 | char *c_connstr = new char[connstr->Utf8Length()+1]; 531 | connstr->WriteUtf8(c_connstr); 532 | 533 | app.fcall.negotiate.Reset (isolate, Local::Cast(args[2])); 534 | app.fcall.connect.Reset (isolate, Local::Cast(args[3])); 535 | app.fcall.unknown.Reset (isolate, Local::Cast(args[4])); 536 | app.fcall.helo.Reset (isolate, Local::Cast(args[5])); 537 | app.fcall.envfrom.Reset (isolate, Local::Cast(args[6])); 538 | app.fcall.envrcpt.Reset (isolate, Local::Cast(args[7])); 539 | app.fcall.data.Reset (isolate, Local::Cast(args[8])); 540 | app.fcall.header.Reset (isolate, Local::Cast(args[9])); 541 | app.fcall.eoh.Reset (isolate, Local::Cast(args[10])); 542 | app.fcall.body.Reset (isolate, Local::Cast(args[11])); 543 | app.fcall.eom.Reset (isolate, Local::Cast(args[12])); 544 | app.fcall.abort.Reset (isolate, Local::Cast(args[13])); 545 | app.fcall.close.Reset (isolate, Local::Cast(args[14])); 546 | 547 | bool ok = false; 548 | if (MI_SUCCESS == smfi_register(desc)) 549 | { 550 | if (MI_SUCCESS == smfi_setconn(c_connstr)) 551 | { 552 | app.loop = uv_default_loop(); 553 | uv_async_init(app.loop, &app.trigger, trigger_event); 554 | uv_queue_work(app.loop, &app.request, milter_worker, milter_cleanup); 555 | ok = true; 556 | } 557 | } 558 | delete c_connstr; 559 | 560 | if (!ok) 561 | { 562 | app.fcall.negotiate.Reset(); 563 | app.fcall.connect.Reset(); 564 | app.fcall.unknown.Reset(); 565 | app.fcall.helo.Reset(); 566 | app.fcall.envfrom.Reset(); 567 | app.fcall.envrcpt.Reset(); 568 | app.fcall.data.Reset(); 569 | app.fcall.header.Reset(); 570 | app.fcall.eoh.Reset(); 571 | app.fcall.body.Reset(); 572 | app.fcall.eom.Reset(); 573 | app.fcall.abort.Reset(); 574 | app.fcall.close.Reset(); 575 | // TODO: throw exception? 576 | } 577 | 578 | args.GetReturnValue().Set(Boolean::New(isolate, ok)); 579 | } 580 | 581 | 582 | /** 583 | */ 584 | void milter_setbacklog (const FunctionCallbackInfo &args) 585 | { 586 | Isolate *isolate = Isolate::GetCurrent(); 587 | HandleScope scope(isolate); 588 | if (args.Length() < 1) 589 | { 590 | isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong number of arguments"))); 591 | return; 592 | } 593 | if (!args[0]->IsNumber()) 594 | { 595 | isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Invalid argument: expected number"))); 596 | return; 597 | } 598 | int n = args[0]->IntegerValue(); 599 | int r = smfi_setbacklog(n); 600 | args.GetReturnValue().Set(Number::New(isolate, r)); 601 | } 602 | 603 | 604 | /** 605 | */ 606 | void milter_setdbg (const FunctionCallbackInfo &args) 607 | { 608 | Isolate *isolate = Isolate::GetCurrent(); 609 | HandleScope scope(isolate); 610 | if (args.Length() < 1) 611 | { 612 | isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong number of arguments"))); 613 | return; 614 | } 615 | if (!args[0]->IsNumber()) 616 | { 617 | isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Invalid argument: expected number"))); 618 | return; 619 | } 620 | int f = args[0]->IntegerValue(); 621 | int r = smfi_setdbg(f); 622 | args.GetReturnValue().Set(Number::New(isolate, r)); 623 | } 624 | 625 | 626 | /** 627 | */ 628 | void milter_settimeout (const FunctionCallbackInfo &args) 629 | { 630 | Isolate *isolate = Isolate::GetCurrent(); 631 | HandleScope scope(isolate); 632 | if (args.Length() < 1) 633 | { 634 | isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong number of arguments"))); 635 | return; 636 | } 637 | if (!args[0]->IsNumber()) 638 | { 639 | isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Invalid argument: expected number"))); 640 | return; 641 | } 642 | int timeo = args[0]->IntegerValue(); 643 | int r = smfi_settimeout(timeo); 644 | args.GetReturnValue().Set(Number::New(isolate, r)); 645 | } 646 | 647 | 648 | /** 649 | */ 650 | void milter_stop (const FunctionCallbackInfo &args) 651 | { 652 | Isolate *isolate = Isolate::GetCurrent(); 653 | HandleScope scope(isolate); 654 | int r = smfi_stop(); 655 | args.GetReturnValue().Set(Number::New(isolate, r)); 656 | } 657 | 658 | 659 | /** 660 | */ 661 | void milter_version (const FunctionCallbackInfo &args) 662 | { 663 | Isolate *isolate = Isolate::GetCurrent(); 664 | HandleScope scope (isolate); 665 | unsigned int major, minor, patchlevel; 666 | 667 | smfi_version(&major, &minor, &patchlevel); 668 | 669 | Local r = Array::New(isolate); 670 | r->Set(0, Number::New(isolate, major)); 671 | r->Set(1, Number::New(isolate, minor)); 672 | r->Set(2, Number::New(isolate, patchlevel)); 673 | args.GetReturnValue().Set(r); 674 | } 675 | 676 | 677 | /** 678 | * module initialization 679 | */ 680 | void init (Handle target, Handle module, Handle context) 681 | { 682 | Isolate *isolate = Isolate::GetCurrent(); 683 | 684 | // return values from envelope functions 685 | target->Set(String::NewFromUtf8(isolate, "MI_SUCCESS", String::kInternalizedString), Number::New(isolate, MI_SUCCESS)); 686 | target->Set(String::NewFromUtf8(isolate, "MI_FAILURE", String::kInternalizedString), Number::New(isolate, MI_FAILURE)); 687 | 688 | // macro contexts/locations 689 | target->Set(String::NewFromUtf8(isolate, "SMFIM_CONNECT", String::kInternalizedString), Number::New(isolate, SMFIM_CONNECT)); 690 | target->Set(String::NewFromUtf8(isolate, "SMFIM_HELO", String::kInternalizedString), Number::New(isolate, SMFIM_HELO)); 691 | target->Set(String::NewFromUtf8(isolate, "SMFIM_ENVFROM", String::kInternalizedString), Number::New(isolate, SMFIM_ENVFROM)); 692 | target->Set(String::NewFromUtf8(isolate, "SMFIM_ENVRCPT", String::kInternalizedString), Number::New(isolate, SMFIM_ENVRCPT)); 693 | target->Set(String::NewFromUtf8(isolate, "SMFIM_DATA", String::kInternalizedString), Number::New(isolate, SMFIM_DATA)); 694 | target->Set(String::NewFromUtf8(isolate, "SMFIM_EOM", String::kInternalizedString), Number::New(isolate, SMFIM_EOM)); 695 | target->Set(String::NewFromUtf8(isolate, "SMFIM_EOH", String::kInternalizedString), Number::New(isolate, SMFIM_EOH)); 696 | 697 | // negotiate flags 698 | target->Set(String::NewFromUtf8(isolate, "SMFIF_NONE", String::kInternalizedString), Number::New(isolate, SMFIF_NONE)); 699 | target->Set(String::NewFromUtf8(isolate, "SMFIF_ADDHDRS", String::kInternalizedString), Number::New(isolate, SMFIF_ADDHDRS)); 700 | target->Set(String::NewFromUtf8(isolate, "SMFIF_CHGBODY", String::kInternalizedString), Number::New(isolate, SMFIF_CHGBODY)); 701 | target->Set(String::NewFromUtf8(isolate, "SMFIF_MODBODY", String::kInternalizedString), Number::New(isolate, SMFIF_MODBODY)); 702 | target->Set(String::NewFromUtf8(isolate, "SMFIF_ADDRCPT", String::kInternalizedString), Number::New(isolate, SMFIF_ADDRCPT)); 703 | target->Set(String::NewFromUtf8(isolate, "SMFIF_DELRCPT", String::kInternalizedString), Number::New(isolate, SMFIF_DELRCPT)); 704 | target->Set(String::NewFromUtf8(isolate, "SMFIF_CHGHDRS", String::kInternalizedString), Number::New(isolate, SMFIF_CHGHDRS)); 705 | target->Set(String::NewFromUtf8(isolate, "SMFIF_QUARANTINE", String::kInternalizedString), Number::New(isolate, SMFIF_QUARANTINE)); 706 | target->Set(String::NewFromUtf8(isolate, "SMFIF_CHGFROM", String::kInternalizedString), Number::New(isolate, SMFIF_CHGFROM)); 707 | target->Set(String::NewFromUtf8(isolate, "SMFIF_ADDRCPT_PAR", String::kInternalizedString), Number::New(isolate, SMFIF_ADDRCPT_PAR)); 708 | target->Set(String::NewFromUtf8(isolate, "SMFIF_SETSYMLIST", String::kInternalizedString), Number::New(isolate, SMFIF_SETSYMLIST)); 709 | 710 | // normal callback return values 711 | target->Set(String::NewFromUtf8(isolate, "SMFIS_CONTINUE", String::kInternalizedString), Number::New(isolate, SMFIS_CONTINUE)); 712 | target->Set(String::NewFromUtf8(isolate, "SMFIS_REJECT", String::kInternalizedString), Number::New(isolate, SMFIS_REJECT)); 713 | target->Set(String::NewFromUtf8(isolate, "SMFIS_DISCARD", String::kInternalizedString), Number::New(isolate, SMFIS_DISCARD)); 714 | target->Set(String::NewFromUtf8(isolate, "SMFIS_ACCEPT", String::kInternalizedString), Number::New(isolate, SMFIS_ACCEPT)); 715 | target->Set(String::NewFromUtf8(isolate, "SMFIS_TEMPFAIL", String::kInternalizedString), Number::New(isolate, SMFIS_TEMPFAIL)); 716 | target->Set(String::NewFromUtf8(isolate, "SMFIS_NOREPLY", String::kInternalizedString), Number::New(isolate, SMFIS_NOREPLY)); 717 | target->Set(String::NewFromUtf8(isolate, "SMFIS_SKIP", String::kInternalizedString), Number::New(isolate, SMFIS_SKIP)); 718 | 719 | // callback return val for negotiate only 720 | target->Set(String::NewFromUtf8(isolate, "SMFIS_ALL_OPTS", String::kInternalizedString), Number::New(isolate, SMFIS_ALL_OPTS)); 721 | 722 | Envelope::Init(target); 723 | 724 | NODE_SET_METHOD(target, "start", milter_start); 725 | NODE_SET_METHOD(target, "setbacklog", milter_setbacklog); 726 | NODE_SET_METHOD(target, "setdbg", milter_setdbg); 727 | NODE_SET_METHOD(target, "settimeout", milter_settimeout); 728 | NODE_SET_METHOD(target, "stop", milter_stop); 729 | NODE_SET_METHOD(target, "version", milter_version); 730 | } 731 | 732 | NODE_MODULE_CONTEXT_AWARE(milter, init) 733 | --------------------------------------------------------------------------------