diff --git a/filter.c b/filter.c new file mode 100644 index 0000000..2022cd6 --- /dev/null +++ b/filter.c @@ -0,0 +1,220 @@ +/* +Copyright (c) 2015 Leon Baker +This projected is licensed under the terms of the LGPL-3.0 license. +*/ + +#include "libmilter/mfapi.h" +#include "_cgo_export.h" + + +//#include // Testing with fprint remove!!!! + + +// Set Callback functions in smfiDesc struct + +void setConnect(struct smfiDesc *smfilter) { + smfilter->xxfi_connect = &Go_xxfi_connect; +} + +void setHelo(struct smfiDesc *smfilter) { + smfilter->xxfi_helo = &Go_xxfi_helo; +} + +void setEnvFrom(struct smfiDesc *smfilter) { + smfilter->xxfi_envfrom = &Go_xxfi_envfrom; +} + +void setEnvRcpt(struct smfiDesc *smfilter) { + smfilter->xxfi_envrcpt = &Go_xxfi_envrcpt; +} + +void setHeader(struct smfiDesc *smfilter) { + smfilter->xxfi_header = &Go_xxfi_header; +} + +void setEoh(struct smfiDesc *smfilter) { + smfilter->xxfi_eoh = &Go_xxfi_eoh; +} + +void setBody(struct smfiDesc *smfilter) { + smfilter->xxfi_body = &Go_xxfi_body; +} + +void setEom(struct smfiDesc *smfilter) { + smfilter->xxfi_eom = &Go_xxfi_eom; +} + +void setAbort(struct smfiDesc *smfilter) { + smfilter->xxfi_abort = &Go_xxfi_abort; +} + +void setClose(struct smfiDesc *smfilter) { + smfilter->xxfi_close = &Go_xxfi_close; +} + +// Utility functions for things that we can't do as easily in Go + +// Return the length of a null terminated pointer array +int argv_len(char **argv) { + int argc = 0; + while (*argv++ != NULL) + ++argc; + return argc; +} + +// Wrapper for setmlreply +// Not very elegant way of calling the variadic setmlreply function +int wrap_setmlreply(SMFICTX *ctx, char *rcode, char *xcode, int msgc, char **msgv) { + switch(msgc) { + case 1: + return smfi_setmlreply(ctx, rcode, xcode, msgv[0], NULL); + case 2: + return smfi_setmlreply(ctx, rcode, xcode, msgv[0], msgv[1], NULL); + case 3: + return smfi_setmlreply(ctx, rcode, xcode, msgv[0], msgv[1], msgv[2], + NULL); + case 4: + return smfi_setmlreply(ctx, rcode, xcode, msgv[0], msgv[1], msgv[2], + msgv[3], NULL); + case 5: + return smfi_setmlreply(ctx, rcode, xcode, msgv[0], msgv[1], msgv[2], + msgv[3], msgv[4], NULL); + case 6: + return smfi_setmlreply(ctx, rcode, xcode, msgv[0], msgv[1], msgv[2], + msgv[3], msgv[4], msgv[5], NULL); + case 7: + return smfi_setmlreply(ctx, rcode, xcode, msgv[0], msgv[1], msgv[2], + msgv[3], msgv[4], msgv[5], msgv[6], NULL); + case 8: + return smfi_setmlreply(ctx, rcode, xcode, msgv[0], msgv[1], msgv[2], + msgv[3], msgv[4], msgv[5], msgv[6], msgv[7], NULL); + case 9: + return smfi_setmlreply(ctx, rcode, xcode, msgv[0], msgv[1], msgv[2], + msgv[3], msgv[4], msgv[5], msgv[6], msgv[7], msgv[8], NULL); + case 10: + return smfi_setmlreply(ctx, rcode, xcode, msgv[0], msgv[1], msgv[2], + msgv[3], msgv[4], msgv[5], msgv[6], msgv[7], msgv[8], msgv[9], + NULL); + case 11: + return smfi_setmlreply(ctx, rcode, xcode, msgv[0], msgv[1], msgv[2], + msgv[3], msgv[4], msgv[5], msgv[6], msgv[7], msgv[8], msgv[9], + msgv[10], NULL); + case 12: + return smfi_setmlreply(ctx, rcode, xcode, msgv[0], msgv[1], msgv[2], + msgv[3], msgv[4], msgv[5], msgv[6], msgv[7], msgv[8], msgv[9], + msgv[10], msgv[11], NULL); + case 13: + return smfi_setmlreply(ctx, rcode, xcode, msgv[0], msgv[1], msgv[2], + msgv[3], msgv[4], msgv[5], msgv[6], msgv[7], msgv[8], msgv[9], + msgv[10], msgv[11], msgv[12], NULL); + case 14: + return smfi_setmlreply(ctx, rcode, xcode, msgv[0], msgv[1], msgv[2], + msgv[3], msgv[4], msgv[5], msgv[6], msgv[7], msgv[8], msgv[9], + msgv[10], msgv[11], msgv[12], msgv[13], NULL); + case 15: + return smfi_setmlreply(ctx, rcode, xcode, msgv[0], msgv[1], msgv[2], + msgv[3], msgv[4], msgv[5], msgv[6], msgv[7], msgv[8], msgv[9], + msgv[10], msgv[11], msgv[12], msgv[13], msgv[14], NULL); + case 16: + return smfi_setmlreply(ctx, rcode, xcode, msgv[0], msgv[1], msgv[2], + msgv[3], msgv[4], msgv[5], msgv[6], msgv[7], msgv[8], msgv[9], + msgv[10], msgv[11], msgv[12], msgv[13], msgv[14], msgv[15], NULL); + case 17: + return smfi_setmlreply(ctx, rcode, xcode, msgv[0], msgv[1], msgv[2], + msgv[3], msgv[4], msgv[5], msgv[6], msgv[7], msgv[8], msgv[9], + msgv[10], msgv[11], msgv[12], msgv[13], msgv[14], msgv[15], msgv[16], + NULL); + case 18: + return smfi_setmlreply(ctx, rcode, xcode, msgv[0], msgv[1], msgv[2], + msgv[3], msgv[4], msgv[5], msgv[6], msgv[7], msgv[8], msgv[9], + msgv[10], msgv[11], msgv[12], msgv[13], msgv[14], msgv[15], msgv[16], + msgv[17], NULL); + case 19: + return smfi_setmlreply(ctx, rcode, xcode, msgv[0], msgv[1], msgv[2], + msgv[3], msgv[4], msgv[5], msgv[6], msgv[7], msgv[8], msgv[9], + msgv[10], msgv[11], msgv[12], msgv[13], msgv[14], msgv[15], msgv[16], + msgv[17], msgv[18], NULL); + case 20: + return smfi_setmlreply(ctx, rcode, xcode, msgv[0], msgv[1], msgv[2], + msgv[3], msgv[4], msgv[5], msgv[6], msgv[7], msgv[8], msgv[9], + msgv[10], msgv[11], msgv[12], msgv[13], msgv[14], msgv[15], msgv[16], + msgv[17], msgv[18], msgv[19], NULL); + case 21: + return smfi_setmlreply(ctx, rcode, xcode, msgv[0], msgv[1], msgv[2], + msgv[3], msgv[4], msgv[5], msgv[6], msgv[7], msgv[8], msgv[9], + msgv[10], msgv[11], msgv[12], msgv[13], msgv[14], msgv[15], msgv[16], + msgv[17], msgv[18], msgv[19], msgv[20], NULL); + case 22: + return smfi_setmlreply(ctx, rcode, xcode, msgv[0], msgv[1], msgv[2], + msgv[3], msgv[4], msgv[5], msgv[6], msgv[7], msgv[8], msgv[9], + msgv[10], msgv[11], msgv[12], msgv[13], msgv[14], msgv[15], msgv[16], + msgv[17], msgv[18], msgv[19], msgv[20], msgv[21], NULL); + case 23: + return smfi_setmlreply(ctx, rcode, xcode, msgv[0], msgv[1], msgv[2], + msgv[3], msgv[4], msgv[5], msgv[6], msgv[7], msgv[8], msgv[9], + msgv[10], msgv[11], msgv[12], msgv[13], msgv[14], msgv[15], msgv[16], + msgv[17], msgv[18], msgv[19], msgv[20], msgv[21], msgv[22], NULL); + case 24: + return smfi_setmlreply(ctx, rcode, xcode, msgv[0], msgv[1], msgv[2], + msgv[3], msgv[4], msgv[5], msgv[6], msgv[7], msgv[8], msgv[9], + msgv[10], msgv[11], msgv[12], msgv[13], msgv[14], msgv[15], msgv[16], + msgv[17], msgv[18], msgv[19], msgv[20], msgv[21], msgv[22], msgv[23], + NULL); + case 25: + return smfi_setmlreply(ctx, rcode, xcode, msgv[0], msgv[1], msgv[2], + msgv[3], msgv[4], msgv[5], msgv[6], msgv[7], msgv[8], msgv[9], + msgv[10], msgv[11], msgv[12], msgv[13], msgv[14], msgv[15], msgv[16], + msgv[17], msgv[18], msgv[19], msgv[20], msgv[21], msgv[22], msgv[23], + msgv[24], NULL); + case 26: + return smfi_setmlreply(ctx, rcode, xcode, msgv[0], msgv[1], msgv[2], + msgv[3], msgv[4], msgv[5], msgv[6], msgv[7], msgv[8], msgv[9], + msgv[10], msgv[11], msgv[12], msgv[13], msgv[14], msgv[15], msgv[16], + msgv[17], msgv[18], msgv[19], msgv[20], msgv[21], msgv[22], msgv[23], + msgv[24], msgv[25], NULL); + case 27: + return smfi_setmlreply(ctx, rcode, xcode, msgv[0], msgv[1], msgv[2], + msgv[3], msgv[4], msgv[5], msgv[6], msgv[7], msgv[8], msgv[9], + msgv[10], msgv[11], msgv[12], msgv[13], msgv[14], msgv[15], msgv[16], + msgv[17], msgv[18], msgv[19], msgv[20], msgv[21], msgv[22], msgv[23], + msgv[24], msgv[25], msgv[26], NULL); + case 28: + return smfi_setmlreply(ctx, rcode, xcode, msgv[0], msgv[1], msgv[2], + msgv[3], msgv[4], msgv[5], msgv[6], msgv[7], msgv[8], msgv[9], + msgv[10], msgv[11], msgv[12], msgv[13], msgv[14], msgv[15], msgv[16], + msgv[17], msgv[18], msgv[19], msgv[20], msgv[21], msgv[22], msgv[23], + msgv[24], msgv[25], msgv[26], msgv[27], NULL); + case 29: + return smfi_setmlreply(ctx, rcode, xcode, msgv[0], msgv[1], msgv[2], + msgv[3], msgv[4], msgv[5], msgv[6], msgv[7], msgv[8], msgv[9], + msgv[10], msgv[11], msgv[12], msgv[13], msgv[14], msgv[15], msgv[16], + msgv[17], msgv[18], msgv[19], msgv[20], msgv[21], msgv[22], msgv[23], + msgv[24], msgv[25], msgv[26], msgv[27], msgv[28], NULL); + case 30: + return smfi_setmlreply(ctx, rcode, xcode, msgv[0], msgv[1], msgv[2], + msgv[3], msgv[4], msgv[5], msgv[6], msgv[7], msgv[8], msgv[9], + msgv[10], msgv[11], msgv[12], msgv[13], msgv[14], msgv[15], msgv[16], + msgv[17], msgv[18], msgv[19], msgv[20], msgv[21], msgv[22], msgv[23], + msgv[24], msgv[25], msgv[26], msgv[27], msgv[28], msgv[29], NULL); + case 31: + return smfi_setmlreply(ctx, rcode, xcode, msgv[0], msgv[1], msgv[2], + msgv[3], msgv[4], msgv[5], msgv[6], msgv[7], msgv[8], msgv[9], + msgv[10], msgv[11], msgv[12], msgv[13], msgv[14], msgv[15], msgv[16], + msgv[17], msgv[18], msgv[19], msgv[20], msgv[21], msgv[22], msgv[23], + msgv[24], msgv[25], msgv[26], msgv[27], msgv[28], msgv[29], msgv[30], + NULL); + case 32: + return smfi_setmlreply(ctx, rcode, xcode, msgv[0], msgv[1], msgv[2], + msgv[3], msgv[4], msgv[5], msgv[6], msgv[7], msgv[8], msgv[9], + msgv[10], msgv[11], msgv[12], msgv[13], msgv[14], msgv[15], msgv[16], + msgv[17], msgv[18], msgv[19], msgv[20], msgv[21], msgv[22], msgv[23], + msgv[24], msgv[25], msgv[26], msgv[27], msgv[28], msgv[29], msgv[30], + msgv[31], NULL); + default: return -1; + } + +} + + + + \ No newline at end of file diff --git a/filter.h b/filter.h new file mode 100644 index 0000000..7ce6656 --- /dev/null +++ b/filter.h @@ -0,0 +1,24 @@ +/* +Copyright (c) 2015 Leon Baker +This projected is licensed under the terms of the LGPL-3.0 license. +*/ + +// Set Callback functions in smfiDesc struct +extern void makesmfilter(struct smfiDesc *smfilter); +extern void setConnect(struct smfiDesc *smfilter); +extern void setHelo(struct smfiDesc *smfilter); +extern void setEnvFrom(struct smfiDesc *smfilter); +extern void setEnvRcpt(struct smfiDesc *smfilter); +extern void setHeader(struct smfiDesc *smfilter); +extern void setEoh(struct smfiDesc *smfilter); +extern void setBody(struct smfiDesc *smfilter); +extern void setEom(struct smfiDesc *smfilter); +extern void setAbort(struct smfiDesc *smfilter); +extern void setClose(struct smfiDesc *smfilter); + +// Utility functions for things that we can't do in Go + +// Return the size of a null terminated pointer array +extern int argv_len(char **argv); + +extern int wrap_setmlreply(SMFICTX *ctx, char *rcode, char *xcode, int msgc, char **msgv); \ No newline at end of file diff --git a/gomilter.go b/gomilter.go new file mode 100644 index 0000000..ea17ca3 --- /dev/null +++ b/gomilter.go @@ -0,0 +1,776 @@ +/* + +Copyright (c) 2015 Leon Baker +This projected is licensed under the terms of the LGPL-3.0 license. + +gomilter +Go Bindings for libmilter + +gomilter.go + +*/ + +package gomilter + +/* + +#cgo LDFLAGS: -lmilter + +#include +//#include +#include "libmilter/mfapi.h" +#include "filter.h" + +*/ +import "C" +import ( + "fmt" + "unsafe" + "encoding/binary" + "reflect" + "os" + "strings" + "bytes" + "encoding/gob" +) + +type sockaddr_in struct { + sin_family int8 + sin_port uint8 + sin_addr uint32 + sin_zero uint64 +} + +// Return values for Callback functions +const ( + Continue = iota + Reject + Discard + Accept + Tempfail + _ + _ + Noreply + Skip +) + +// flags +const ( + ADDHDRS = 0x00000001 // 000000001 + CHGBODY = 0x00000002 // 000000010 + ADDRCPT = 0x00000004 // 000000100 + DELRCPT = 0x00000008 // 000001000 + CHGHDRS = 0x00000010 // 000010000 + QUARANTINE = 0x00000020 // 000100000 + CHGFROM = 0x00000040 // 001000000 + ADDRCPT_PAR = 0x00000080 // 010000000 + SETSYMLIST = 0x00000100 // 100000000 +) + +// Interface that must be implemented in order to use gomilter +type Milter interface { + GetFilterName() string + GetDebug() bool + GetFlags() int + GetSocket() string +} + +// An "empty" Milter with no callback functions +type MilterRaw struct { + FilterName string + Debug bool + Flags int + Socket string +} + +func (m *MilterRaw) GetFilterName() string { + return m.FilterName +} + +func (m *MilterRaw) GetDebug() bool { + return m.Debug +} + +func (m *MilterRaw) GetFlags() int { + return m.Flags +} + +func (m *MilterRaw) GetSocket() string { + return m.Socket +} + +// ********* Callback checking types ********* +type checkForConnect interface { + Connect(ctx uintptr, hostname, ip string) (sfsistat int8) +} + +type checkForHelo interface { + Helo(ctx uintptr, helohost string) (sfsistat int8) +} + +type checkForEnvFrom interface { + EnvFrom(ctx uintptr, myargv []string) (sfsistat int8) +} + +type checkForEnvRcpt interface { + EnvRcpt(ctx uintptr, myargv []string) (sfsistat int8) +} + +type checkForHeader interface { + Header(ctx uintptr, headerf, headerv string) (sfsistat int8) +} + +type checkForEoh interface { + Eoh(ctx uintptr) (sfsistat int8) +} + +type checkForBody interface { + Body(ctx uintptr, body []byte) (sfsistat int8) +} + +type checkForEom interface { + Eom(ctx uintptr) (sfsistat int8) +} + +type checkForAbort interface { + Abort(ctx uintptr) (sfsistat int8) +} + +type checkForClose interface { + Close(ctx uintptr) (sfsistat int8) +} + +// ********* Global Milter variable ********* +var milter Milter + +// ********* Utility Functions (not exported) ********* + +type CtxPtr *C.struct_smfi_str + +func int2ctx(ptr uintptr) CtxPtr { + return CtxPtr(unsafe.Pointer(ptr)) +} + +func ctx2int(ctx CtxPtr) uintptr { + return uintptr(unsafe.Pointer(ctx)) +} + +func cStringArrayToSlice(argv **C.char) (GoArgv []string) { + // Build a slice of pointers with C array as a backing + length := int(C.argv_len(argv)) + hdr := reflect.SliceHeader{ + Data: uintptr(unsafe.Pointer(argv)), + Len: length, + Cap: length, + } + argvSlice := *(*[]*C.char)(unsafe.Pointer(&hdr)) + + // Build a string slice for Go friendly strings + GoArgv = make([]string, length, length) + for i := 0; i < length; i++ { + GoArgv[i] = C.GoString(argvSlice[i]) + } + return +} + +func GobEncode(data interface{}) ([]byte, error) { + w := new(bytes.Buffer) + encoder := gob.NewEncoder(w) + err := encoder.Encode(data) + if err != nil { + return nil, err + } + return w.Bytes(), nil +} + +func GobDecode(buf []byte, data interface{}) error { + r := bytes.NewBuffer(buf) + decoder := gob.NewDecoder(r) + err := decoder.Decode(data) + if err != nil { + return err + } + return nil +} + + +// ********* Filter Callback functions ********* +// These are registered with Milter +// They are only called if they get registered but need to be defined anyway + +//export Go_xxfi_connect +func Go_xxfi_connect(ctx *C.SMFICTX, hostname *C.char, hostaddr *C._SOCK_ADDR) C.sfsistat { + ctxptr := ctx2int(ctx) + // Check if the host address is a regular ipv4 address + if hostaddr.sa_family == C.AF_INET { + //fmt.Println(hostaddr.sa_data) + + // hostaddrin is a parallel data structure of the C type hostaddr + //var hostaddrin *sockaddr_in + hostaddrin := (*sockaddr_in)(unsafe.Pointer(hostaddr)) + //fmt.Println(hostaddrin) + ip_addr := make([]byte, 4) + binary.LittleEndian.PutUint32(ip_addr, hostaddrin.sin_addr) + + // Call our application's callback + m := milter.(checkForConnect) + code := m.Connect(ctxptr, C.GoString(hostname), fmt.Sprintf("%d.%d.%d.%d", ip_addr[0], ip_addr[1], ip_addr[2], ip_addr[3])) + if milter.GetDebug() { + fmt.Printf("Connect callback returned: %d\n", code) + } + return C.sfsistat(code) + } + if milter.GetDebug() { + fmt.Println("hostaddr.sa_family value not implemented") + } + return C.SMFIS_CONTINUE +} + +//export Go_xxfi_helo +func Go_xxfi_helo(ctx *C.SMFICTX, helohost *C.char) C.sfsistat { + m := milter.(checkForHelo) + code := m.Helo(ctx2int(ctx), C.GoString(helohost)) + if milter.GetDebug() { + fmt.Printf("Helo callback returned: %d\n", code) + } + return C.sfsistat(code) +} + +//export Go_xxfi_envfrom +func Go_xxfi_envfrom(ctx *C.SMFICTX, argv **C.char) C.sfsistat { + // Call our application's callback + m := milter.(checkForEnvFrom) + code := m.EnvFrom(ctx2int(ctx), cStringArrayToSlice(argv)) + if milter.GetDebug() { + fmt.Printf("EnvFrom callback returned: %d\n", code) + } + return C.sfsistat(code) +} + +//export Go_xxfi_envrcpt +func Go_xxfi_envrcpt(ctx *C.SMFICTX, argv **C.char) C.sfsistat { + // Call our application's callback + m := milter.(checkForEnvRcpt) + code := m.EnvRcpt(ctx2int(ctx), cStringArrayToSlice(argv)) + if milter.GetDebug() { + fmt.Printf("EnvRcpt callback returned: %d\n", code) + } + return C.sfsistat(code) +} + +//export Go_xxfi_header +func Go_xxfi_header(ctx *C.SMFICTX, headerf, headerv *C.char) C.sfsistat { + m := milter.(checkForHeader) + code := m.Header(ctx2int(ctx), C.GoString(headerf), C.GoString(headerv)) + if milter.GetDebug() { + fmt.Printf("Header callback returned: %d\n", code) + } + return C.sfsistat(code) +} + +//export Go_xxfi_eoh +func Go_xxfi_eoh(ctx *C.SMFICTX) C.sfsistat { + // Call our application's callback + m := milter.(checkForEoh) + code := m.Eoh(ctx2int(ctx)) + if milter.GetDebug() { + fmt.Printf("Eoh callback returned: %d\n", code) + } + return C.sfsistat(code) +} + +//export Go_xxfi_body +func Go_xxfi_body(ctx *C.SMFICTX, bodyp *C.uchar, bodylen C.size_t) C.sfsistat { + // Create a byte slice from the body pointer. + // The body pointer just points to a sequence of bytes + // Our bit slice is backed by the original body. No copy is made here + // Be aware that converting the byte slice to string will make a copy + var b []byte + b = (*[1<<30]byte)(unsafe.Pointer(bodyp))[0:bodylen] + // Call our application's callback + m := milter.(checkForBody) + code := m.Body(ctx2int(ctx), b) + if milter.GetDebug() { + fmt.Printf("Body callback returned: %d\n", code) + } + return C.sfsistat(code) +} + +//export Go_xxfi_eom +func Go_xxfi_eom(ctx *C.SMFICTX) C.sfsistat { + // Call our application's callback + m := milter.(checkForEom) + code := m.Eom(ctx2int(ctx)) + if milter.GetDebug() { + fmt.Printf("Eom callback returned: %d\n", code) + } + return C.sfsistat(code) +} + +//export Go_xxfi_abort +func Go_xxfi_abort(ctx *C.SMFICTX) C.sfsistat { + // Call our application's callback + m := milter.(checkForAbort) + code := m.Abort(ctx2int(ctx)) + if milter.GetDebug() { + fmt.Printf("Abort callback returned: %d\n", code) + } + return C.sfsistat(code) +} + +//export Go_xxfi_close +func Go_xxfi_close(ctx *C.SMFICTX) C.sfsistat { + // Call our application's callback + m := milter.(checkForClose) + code := m.Close(ctx2int(ctx)) + if milter.GetDebug() { + fmt.Printf("Close callback returned: %d\n", code) + } + return C.sfsistat(code) +} + +// ********* libmilter Data Access Functions ********* + +func GetSymVal(ctx uintptr, symname string) string { + csymname := C.CString(symname) + defer C.free(unsafe.Pointer(csymname)) + type CtxPtr *C.struct_smfi_str + cval := C.smfi_getsymval(int2ctx(ctx), csymname) + // Note: If you try to free the cval C string it will crash + return C.GoString(cval) +} + +func SetPriv(ctx uintptr, privatedata interface{}) int { + // privatedata seems to work for any data type + // Structs must have exported fields + + // Serialize Go privatedata into a byte slice + bytedata, _ := GobEncode(privatedata) + + // length and size + // length is a uint32 (usually 4 bytes) + // the length will be stored in front of the byte sequence + length := uint32(len(bytedata)) + lengthsize := C.size_t(unsafe.Sizeof(length)) + buf := new(bytes.Buffer) + err := binary.Write(buf, binary.BigEndian, length) + if err != nil { + return -1 + } + lengthbytes := buf.Bytes() + + // Allocate memory for the length and byte sequence + CArray := (*C.uchar)(C.malloc(lengthsize + C.size_t(length))) + + var lenStart, seqStart uintptr + lenStart = uintptr(unsafe.Pointer(CArray)) + seqStart = lenStart + uintptr(lengthsize) + + CArray = (*C.uchar)(unsafe.Pointer(lenStart)) + + for i := uintptr(0); i < uintptr(lengthsize); i++ { + CArray = (*C.uchar)(unsafe.Pointer(lenStart + i)) + *CArray = C.uchar(lengthbytes[i]) + } + + // Now copy the data bytes to the position after the length + for i := uintptr(0); i < uintptr(length); i++ { + CArray = (*C.uchar)(unsafe.Pointer(seqStart + i)) + *CArray = C.uchar(bytedata[i]) + } + + // Call libmilter smfi_setpriv + type CtxPtr *C.struct_smfi_str + return int(C.smfi_setpriv(int2ctx(ctx), unsafe.Pointer(lenStart))) +} + +func GetPriv(ctx uintptr, privatedata interface{}) int { + /* Retrieve the private data stored by the milter + Retrieving the data will release the memory allocated for it + Don't try to retrieve it again unless you call SetPriv first + */ + + // Call libmilter smfi_getpriv to get a pointer to our data + CArray := (*byte)(C.smfi_getpriv(int2ctx(ctx))) + + // Make sure data has been set with a previous call to SetPriv + if CArray == nil { + return -1 + } + + // Read uint32 size bytes from the start of the pointer + var length uint32 + lengthsize := unsafe.Sizeof(length) + lengthbytes := make([]byte, lengthsize) + + lenStart := uintptr(unsafe.Pointer(CArray)) + seqStart := lenStart + uintptr(lengthsize) + + for i := uintptr(0); i < uintptr(lengthsize); i++ { + CArray = (*byte)(unsafe.Pointer(lenStart + i)) + lengthbytes[i] = *CArray + } + + // Binary decode the length bytes + buf := bytes.NewBuffer(lengthbytes) + err := binary.Read(buf, binary.BigEndian, &length) + if err != nil { + return -1 + } + + // Read byte sequence of data + databytes := make([]byte, length) + for i := uintptr(0); i < uintptr(length); i++ { + CArray = (*byte)(unsafe.Pointer(seqStart + i)) + databytes[i] = *CArray + } + + // Free the data malloc'ed by C + C.free(unsafe.Pointer(lenStart)) + C.smfi_setpriv(int2ctx(ctx), nil) + + // Unserialize the data bytes back into a data structure + err = GobDecode(databytes, privatedata) + if err != nil { + return -1 + } + + return 0 +} + +func SetReply (ctx uintptr, rcode, xcode, message string) int { + type CtxPtr *C.struct_smfi_str + crcode := C.CString(rcode) + defer C.free(unsafe.Pointer(crcode)) + cxcode := C.CString(xcode) + defer C.free(unsafe.Pointer(cxcode)) + cmessage := C.CString(message) + defer C.free(unsafe.Pointer(cmessage)) + // Call libmilter setreply + return int(C.smfi_setreply(int2ctx(ctx), crcode, cxcode, cmessage)) +} + +func SetMLReply (ctx uintptr, rcode, xcode string, message ...string) int { + /* Allows up to 32 lines of message + */ + + crcode := C.CString(rcode) + defer C.free(unsafe.Pointer(crcode)) + cxcode := C.CString(xcode) + defer C.free(unsafe.Pointer(cxcode)) + + // Build message C array + // Get size of a C pointer on this system + ptrSize := unsafe.Sizeof(crcode) + + // Allocate the char** array + argv := C.malloc(C.size_t(len(message)) * C.size_t(ptrSize)) + defer C.free(argv) + // Assign each line to its address offset + for i := 0; i < len(message); i++ { + linePtr := (**C.char)(unsafe.Pointer(uintptr(argv) + uintptr(i) * ptrSize)) + line := C.CString(message[i]) + defer C.free(unsafe.Pointer(line)) + *linePtr = line + } + + // Call our C wrapper for setmlreply + // The wrapper is needed as cgo doesn't support variadic C function calls + return int(C.wrap_setmlreply(int2ctx(ctx), crcode, cxcode, C.int(len(message)), (**C.char)(argv))) +} + +// ********* libmilter Message Modification Functions ********* + +func AddHeader(ctx uintptr, headerf, headerv string) int { + /* Add a header to the message. SMFIF_ADDHDRS + */ + + cheaderf := C.CString(headerf) + defer C.free(unsafe.Pointer(cheaderf)) + cheaderv := C.CString(headerv) + defer C.free(unsafe.Pointer(cheaderv)) + + // Call smfi_addheader + return int(C.smfi_addheader(int2ctx(ctx), cheaderf, cheaderv)); +} + +func ChgHeader(ctx uintptr, headerf string, hdridx int, headerv string) int { + /* Change or delete a header. SMFIF_CHGHDRS + */ + + cheaderf := C.CString(headerf) + defer C.free(unsafe.Pointer(cheaderf)) + cheaderv := C.CString(headerv) + defer C.free(unsafe.Pointer(cheaderv)) + + // Call smfi_chgheader + return int(C.smfi_chgheader(int2ctx(ctx), cheaderf, C.int(hdridx), cheaderv)); +} + +func InsHeader(ctx uintptr, hdridx int, headerf, headerv string) int { + /* Insert a header into the message. SMFIF_ADDHDRS + */ + + cheaderf := C.CString(headerf) + defer C.free(unsafe.Pointer(cheaderf)) + cheaderv := C.CString(headerv) + defer C.free(unsafe.Pointer(cheaderv)) + + // Call smfi_insheader + return int(C.smfi_insheader(int2ctx(ctx), C.int(hdridx), cheaderf, cheaderv)); +} + + +func ChgFrom(ctx uintptr, mail, args string) int { + /* Change the envelope sender address. SMFIF_CHGFROM + */ + + cmail := C.CString(mail) + defer C.free(unsafe.Pointer(cmail)) + cargs := C.CString(args) + defer C.free(unsafe.Pointer(cargs)) + + return int(C.smfi_chgfrom(int2ctx(ctx), cmail, cargs)) +} + +func AddRcpt(ctx uintptr, rcpt string) int { + /* Add a recipient to the envelope. SMFIF_ADDRCPT + */ + crcpt := C.CString(rcpt) + defer C.free(unsafe.Pointer(crcpt)) + // Call smfi_addrcpt + return int(C.smfi_addrcpt(int2ctx(ctx), crcpt)) +} + +func AddRcpt_Par(ctx uintptr, rcpt, args string) int { + /* Add a recipient including ESMTP parameter to the envelope. SMFIF_ADDRCPT_PAR + */ + crcpt := C.CString(rcpt) + defer C.free(unsafe.Pointer(crcpt)) + cargs := C.CString(args) + defer C.free(unsafe.Pointer(cargs)) + // Call smfi_addrcpt + return int(C.smfi_addrcpt_par(int2ctx(ctx), crcpt, cargs)) +} + +func DelRcpt(ctx uintptr, rcpt string) int { + /* Delete a recipient from the envelope. SMFIF_DELRCPT + */ + crcpt := C.CString(rcpt) + defer C.free(unsafe.Pointer(crcpt)) + // Call smfi_addrcpt + return int(C.smfi_delrcpt(int2ctx(ctx), crcpt)) +} + +func ReplaceBody(ctx uintptr, body []byte) int { + /* Replace the body of the message. SMFIF_CHGBODY + */ + + // Allocate memory + length := len(body) + + // Allocate memory for the length and byte sequence + cbody := (*C.uchar)(C.malloc(C.size_t(length))) + start := uintptr(unsafe.Pointer(cbody)) + + for i := uintptr(0); i < uintptr(length); i++ { + cbody = (*C.uchar)(unsafe.Pointer(start + i)) + *cbody = C.uchar(body[i]) + } + cbody = (*C.uchar)(unsafe.Pointer(start)) + + // Call smfi_replacebody + return int(C.smfi_replacebody(int2ctx(ctx), cbody, C.int(length))) +} + +// ********* Other Message Handling Functions ********* + +func progress(ctx uintptr) int { + /* Report operation in progress. + */ + + // Call smfi_progress + return int(C.smfi_progress(int2ctx(ctx))) +} + +// ********* Run the milter ********* + +func Run(amilter Milter) int { + milter = amilter + if milter.GetDebug() { + fmt.Println ("Debugging enabled") + } + + // Declare an empty smfiDesc structure + var smfilter C.smfiDesc_str + + // Set filter name + fname := C.CString(milter.GetFilterName()) + defer C.free(unsafe.Pointer(fname)) + smfilter.xxfi_name = fname + if milter.GetDebug() { + fmt.Printf("Filter Name: %s\n", C.GoString(smfilter.xxfi_name)) + } + + // Set version code + smfilter.xxfi_version = C.SMFI_VERSION + + // Set Flags + smfilter.xxfi_flags = C.ulong(milter.GetFlags()) + if milter.GetDebug() { + fmt.Printf("Flags: 0x%b\n", smfilter.xxfi_flags) + } + + // Set Callbacks if they are implemented + + // Check if Connect method was implemented + if _, ok := milter.(checkForConnect); ok { + if milter.GetDebug() { + fmt.Println("Connect callback implemented") + } + C.setConnect(&smfilter) + } else { + if milter.GetDebug() { + fmt.Println("Connect callback not implemented") + } + } + // Check if Helo method was implemented + if _, ok := milter.(checkForHelo); ok { + if milter.GetDebug() { + fmt.Println("Helo callback implemented") + } + C.setHelo(&smfilter) + } else { + if milter.GetDebug() { + fmt.Println("Helo callback not implemented") + } + } + // Check if EnvFrom method was implemented + if _, ok := milter.(checkForEnvFrom); ok { + if milter.GetDebug() { + fmt.Println("EnvFrom callback implemented") + } + C.setEnvFrom(&smfilter) + } else { + if milter.GetDebug() { + fmt.Println("EnvFrom callback not implemented") + } + } + // Check if EnvRcpt method was implemented + if _, ok := milter.(checkForEnvRcpt); ok { + if milter.GetDebug() { + fmt.Println("EnvRcpt callback implemented") + } + C.setEnvRcpt(&smfilter) + } else { + if milter.GetDebug() { + fmt.Println("EnvRcpt callback not implemented") + } + } + // Check if Header method was implemented + if _, ok := milter.(checkForHeader); ok { + if milter.GetDebug() { + fmt.Println("Header callback implemented") + } + C.setHeader(&smfilter) + } else { + if milter.GetDebug() { + fmt.Println("Header callback not implemented") + } + } + // Check if Eoh method was implemented + if _, ok := milter.(checkForEoh); ok { + if milter.GetDebug() { + fmt.Println("Eoh callback implemented") + } + C.setEoh(&smfilter) + } else { + if milter.GetDebug() { + fmt.Println("Eoh callback not implemented") + } + } + // Check if Body method was implemented + if _, ok := milter.(checkForBody); ok { + if milter.GetDebug() { + fmt.Println("Body callback implemented") + } + C.setBody(&smfilter) + } else { + if milter.GetDebug() { + fmt.Println("Body callback not implemented") + } + } + // Check if Eom method was implemented + if _, ok := milter.(checkForEom); ok { + if milter.GetDebug() { + fmt.Println("Eom callback implemented") + } + C.setEom(&smfilter) + } else { + if milter.GetDebug() { + fmt.Println("Eom callback not implemented") + } + } + // Check if Abort method was implemented + if _, ok := milter.(checkForAbort); ok { + if milter.GetDebug() { + fmt.Println("Abort callback implemented") + } + C.setAbort(&smfilter) + } else { + if milter.GetDebug() { + fmt.Println("Abort callback not implemented") + } + } + // Check if Close method was implemented + if _, ok := milter.(checkForClose); ok { + if milter.GetDebug() { + fmt.Println("Close callback implemented") + } + C.setClose(&smfilter) + } else { + if milter.GetDebug() { + fmt.Println("Close callback not implemented") + } + } + + if milter.GetDebug() { + fmt.Println("smfilter:") + fmt.Println(smfilter) + } + + // Setup socket connection + socket := milter.GetSocket() + if socket == "" { + panic("No socket name. Set MilterRaw.Socket") + } + // Try to delete old socket if it exists + socketparts := strings.Split(socket, ":") + if len(socketparts) == 2 { + os.Remove(socketparts[1]) + } + + csocket := C.CString(socket) + defer C.free(unsafe.Pointer(csocket)) + if code := C.smfi_setconn(csocket); code != 0 { + fmt.Printf("smfi_setconn failed: %d\n", code) + } + + // Register the filter + if code := C.smfi_register(smfilter); code == C.MI_FAILURE { + fmt.Printf("smfi_register failed: %d\n", code) + } + + // Hand control to libmilter + if milter.GetDebug() { + fmt.Println("Handing over to libmilter") + } + result := C.smfi_main() + if milter.GetDebug() { + fmt.Printf("smfi_main returned: %v\n", result) + } + return int(result) +} + + + diff --git a/samplefilter.go b/samplefilter.go new file mode 100644 index 0000000..4d727c7 --- /dev/null +++ b/samplefilter.go @@ -0,0 +1,50 @@ +// +build ignore + +/* +Copyright (c) 2015 Leon Baker +This projected is licensed under the terms of the LGPL-3.0 license. + + + +Sample Milter + + + +*/ + +package main + +import ( + + "." + "fmt" + +) + + +type Mymilter struct { + gomilter.MilterRaw // Embed the basic functionality. No callbacks yet +} + +// Define the callback functions we are going to use +func (m *Mymilter) Connect(ctx uintptr, hostname, ip string) (sfsistat int8) { + fmt.Println("mymilter.connect was called") + fmt.Printf("hostname: %s\n", hostname) + fmt.Printf("ip: %s\n", ip) + return gomilter.Reject +} + +func (m *Mymilter) Helo(ctx uintptr, helohost string) (sfsistat int8) { + fmt.Println("mymilter.helo was called") + return +} + +func main() { + mymilter := new(Mymilter) + mymilter.FilterName = "TestFilter" + mymilter.Debug = true + mymilter.Flags = gomilter.AddHdrs|gomilter.AddRcpt + + // Start Milter + gomilter.Run(mymilter) +} \ No newline at end of file diff --git a/samplefilter2.go b/samplefilter2.go new file mode 100644 index 0000000..b877910 --- /dev/null +++ b/samplefilter2.go @@ -0,0 +1,115 @@ +// +build ignore + +/* +Copyright (c) 2015 Leon Baker +*/ + +package main + +import ( + + m "github.com/leonrbaker/gomilter" + "fmt" + +) + +type Mymilter struct { + m.MilterRaw // Embed the basic functionality. No callbacks defined yet +} + +// Data type I want use for private data. Can be any type +type T struct { + A uint8 + B string + C string +} + +// Define the callback functions we are going to use +func (milter *Mymilter) Connect(ctx uintptr, hostname, ip string) (sfsistat int8) { + fmt.Println("mymilter.Connect was called") + fmt.Printf("hostname: %s\n", hostname) + fmt.Printf("ip: %s\n", ip) + + t := T{1, hostname, "Test"} + m.SetPriv(ctx, &t) + + return m.Continue +} + +func (milter *Mymilter) Helo(ctx uintptr, helohost string) (sfsistat int8) { + fmt.Println("mymilter.Helo was called") + fmt.Printf("helohost: %s\n", helohost) + return +} + +func (milter *Mymilter) EnvFrom(ctx uintptr, myargv []string) (sfsistat int8) { + fmt.Println("mymilter.EnvFrom was called") + fmt.Printf("myargv: %s\n", myargv) + // Show the value of a symbol + fmt.Printf("{mail_addr}: %v\n", m.GetSymVal(ctx, "{mail_addr}")) + return +} + +func (milter *Mymilter) EnvRcpt(ctx uintptr, myargv []string) (sfsistat int8) { + fmt.Println("mymilter.EnvRcpt was called") + fmt.Printf("myargv: %s\n", myargv) + // Show the value of a symbol + fmt.Printf("{rcpt_addr}: %v\n", m.GetSymVal(ctx, "{rcpt_addr}")) + return +} + +func (milter *Mymilter) Header(ctx uintptr, headerf, headerv string) (sfsistat int8) { + fmt.Println("mymilter.Header was called") + fmt.Printf("header field: %s\n", headerf) + fmt.Printf("header value: %s\n", headerv) + return +} + +func (milter *Mymilter) Eoh(ctx uintptr) (sfsistat int8) { + fmt.Println("mymilter.Eoh was called") + return +} + +func (milter *Mymilter) Body(ctx uintptr, body []byte) (sfsistat int8) { + // Be careful as a conversion of body to string will make a copy of body + fmt.Println("mymilter.Body was called") + fmt.Println(string(body)) + fmt.Printf("Body Length: %d\n", len(body)) + return +} + +func (milter *Mymilter) Eom(ctx uintptr) (sfsistat int8) { + fmt.Println("mymilter.Eom was called") + + var t T + fmt.Println("m.GetPri:", m.GetPriv(ctx, &t)) + fmt.Println("t:", t) + + m.AddHeader(ctx, "LEONUX-Mailer", +"test server;\n\ttest1=\"foobar\"") + + newBody := []byte("This is a new body") + m.ReplaceBody(ctx, newBody) + return +} + +func (milter *Mymilter) Abort(ctx uintptr) (sfsistat int8) { + fmt.Println("mymilter.Abort was called") + return +} + +func (milter *Mymilter) Close(ctx uintptr) (sfsistat int8) { + fmt.Println("mymilter.Close was called") + return +} + +func main() { + mymilter := new(Mymilter) + mymilter.FilterName = "TestFilter" + mymilter.Debug = true + mymilter.Flags = m.ADDHDRS|m.ADDRCPT|m.CHGFROM|m.CHGBODY + mymilter.Socket = "unix:/var/milterattachcheck/socket" + + // Start Milter + m.Run(mymilter) +} \ No newline at end of file