This commit is contained in:
Dolf Schimmel (Freeaqingme)
2015-11-24 17:44:53 +01:00
5 changed files with 188 additions and 76 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
# Compiled test programs
samplefilter
samplefilter2

View File

@@ -1,2 +1,95 @@
# gomilter
Go Bindings for libmilter
Go Bindings for Sendmail's libmilter
Tested on Linux and FreeBSD
## Installation
The Sendmail header file libmilter/mfapi.h is required. For Redhat/CentOS, install the sendmail-devel package:
```sh
yum install sendmail-devel
```
Install the gomilter package:
```sh
go get github.com/leonrbaker/gomilter
```
##Usage
The milter is implemented in a struct. Start by defining your own struct type and embeding the gomilter MilterRaw struct.
```go
type Mymilter struct {
gomilter.MilterRaw // Embed the basic functionality.
}
```
Milter callbacks are added by implementing methods for the struct with matching predefined names.
### Callbacks
* Connect
* Helo
* EnvFrom
* EnvRcpt
* Header
* Eoh
* Body
* Eom
* Abort
* Close
Not all the callbacks need to be defined. The callbacks are explained on the milter.org site. Unfortunately the milter.org site has been shut down but it is still on [web.archive.org](http://web.archive.org/web/20150510034154/https://www.milter.org/developers/api/index)
### Message Modification Functions
* AddHeader
* ChgHeader
* InsHeader
* ChgFrom
* AddRcpt
* AddRcpt_Par
* DelRcpt
* ReplaceBody
### Other Message Handling Functions
* progress
### Startup
The Socket field of the milter struct must be set. For example:
```go
mymilter.Socket = "unix:/var/gomilter/socket"
```
Control is handed over to the libmilter smfi_main function by calling the Run method and passing it a pointer to your milter struct
```go
gomilter.Run(mymilter)
```
The milter has a Stop method which calls the libmilter smfi_stop function.
### Private Data
libmilter is able to store private data for a connection. This data can be accessed from other functions and callbacks for the same connection. You can pass a pointer to any data structure to SetPriv. The data is retrieved with GetPriv
```go
t := T{1, 2, 3}
m.SetPriv(ctx, &t)
```
Retrieve the data with
```go
var t T
m.GetPriv(ctx, &t))
```
GetPriv should only be called once. If the private data is needed in another function or callback then call SetPriv again.
## Sample Programs
There are two sample programs included, samplefilter.go and samplefilter2.go
##Other Libraries
A usefull MIME parsing library is [go.enmime](https://github.com/jhillyerd/go.enmime)

View File

@@ -35,6 +35,7 @@ import (
"reflect"
"strings"
"unsafe"
"log"
)
type sockaddr_in struct {
@@ -76,6 +77,7 @@ type Milter interface {
GetDebug() bool
GetFlags() int
GetSocket() string
GetLogger() *log.Logger
}
// An "empty" Milter with no callback functions
@@ -84,6 +86,7 @@ type MilterRaw struct {
Debug bool
Flags int
Socket string
Logger *log.Logger
}
func (m *MilterRaw) GetFilterName() string {
@@ -102,6 +105,10 @@ func (m *MilterRaw) GetSocket() string {
return m.Socket
}
func (m *MilterRaw) GetLogger() *log.Logger {
return m.Logger
}
// ********* Callback checking types *********
type checkForConnect interface {
Connect(ctx uintptr, hostname string, ip net.IP) (sfsistat int8)
@@ -145,6 +152,7 @@ type checkForClose interface {
// ********* Global Milter variable *********
var milter Milter
var logger *log.Logger
// ********* Utility Functions (not exported) *********
@@ -223,7 +231,7 @@ func Go_xxfi_connect(ctx *C.SMFICTX, hostname *C.char, hostaddr *C._SOCK_ADDR) (
ip = net.IP(C.GoBytes(unsafe.Pointer(&sa_in.sin6_addr), 16))
} else {
if milter.GetDebug() {
LoggerPrintln("hostaddr.sa_family value not implemented")
logger.Println("hostaddr.sa_family value not implemented")
}
ip = net.ParseIP("::")
}
@@ -231,7 +239,7 @@ func Go_xxfi_connect(ctx *C.SMFICTX, hostname *C.char, hostaddr *C._SOCK_ADDR) (
m := milter.(checkForConnect)
code := m.Connect(ctxptr, C.GoString(hostname), ip)
if milter.GetDebug() {
LoggerPrintf("Connect callback returned: %d\n", code)
logger.Printf("Connect callback returned: %d\n", code)
}
return C.sfsistat(code)
}
@@ -241,7 +249,7 @@ 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() {
LoggerPrintf("Helo callback returned: %d\n", code)
logger.Printf("Helo callback returned: %d\n", code)
}
return C.sfsistat(code)
}
@@ -252,7 +260,7 @@ func Go_xxfi_envfrom(ctx *C.SMFICTX, argv **C.char) C.sfsistat {
m := milter.(checkForEnvFrom)
code := m.EnvFrom(ctx2int(ctx), cStringArrayToSlice(argv))
if milter.GetDebug() {
LoggerPrintf("EnvFrom callback returned: %d\n", code)
logger.Printf("EnvFrom callback returned: %d\n", code)
}
return C.sfsistat(code)
}
@@ -263,7 +271,7 @@ func Go_xxfi_envrcpt(ctx *C.SMFICTX, argv **C.char) C.sfsistat {
m := milter.(checkForEnvRcpt)
code := m.EnvRcpt(ctx2int(ctx), cStringArrayToSlice(argv))
if milter.GetDebug() {
LoggerPrintf("EnvRcpt callback returned: %d\n", code)
logger.Printf("EnvRcpt callback returned: %d\n", code)
}
return C.sfsistat(code)
}
@@ -273,7 +281,7 @@ 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() {
LoggerPrintf("Header callback returned: %d\n", code)
logger.Printf("Header callback returned: %d\n", code)
}
return C.sfsistat(code)
}
@@ -284,7 +292,7 @@ func Go_xxfi_eoh(ctx *C.SMFICTX) C.sfsistat {
m := milter.(checkForEoh)
code := m.Eoh(ctx2int(ctx))
if milter.GetDebug() {
LoggerPrintf("Eoh callback returned: %d\n", code)
logger.Printf("Eoh callback returned: %d\n", code)
}
return C.sfsistat(code)
}
@@ -301,7 +309,7 @@ func Go_xxfi_body(ctx *C.SMFICTX, bodyp *C.uchar, bodylen C.size_t) C.sfsistat {
m := milter.(checkForBody)
code := m.Body(ctx2int(ctx), b)
if milter.GetDebug() {
LoggerPrintf("Body callback returned: %d\n", code)
logger.Printf("Body callback returned: %d\n", code)
}
return C.sfsistat(code)
}
@@ -312,7 +320,7 @@ func Go_xxfi_eom(ctx *C.SMFICTX) C.sfsistat {
m := milter.(checkForEom)
code := m.Eom(ctx2int(ctx))
if milter.GetDebug() {
LoggerPrintf("Eom callback returned: %d\n", code)
logger.Printf("Eom callback returned: %d\n", code)
}
return C.sfsistat(code)
}
@@ -323,7 +331,7 @@ func Go_xxfi_abort(ctx *C.SMFICTX) C.sfsistat {
m := milter.(checkForAbort)
code := m.Abort(ctx2int(ctx))
if milter.GetDebug() {
LoggerPrintf("Abort callback returned: %d\n", code)
logger.Printf("Abort callback returned: %d\n", code)
}
return C.sfsistat(code)
}
@@ -334,7 +342,7 @@ func Go_xxfi_close(ctx *C.SMFICTX) C.sfsistat {
m := milter.(checkForClose)
code := m.Close(ctx2int(ctx))
if milter.GetDebug() {
LoggerPrintf("Close callback returned: %d\n", code)
logger.Printf("Close callback returned: %d\n", code)
}
return C.sfsistat(code)
}
@@ -629,14 +637,17 @@ func progress(ctx uintptr) int {
// ********* Run the milter *********
func Stop() {
C.smfi_stop()
}
func Run(amilter Milter) int {
milter = amilter
// Setup logging
logger = milter.GetLogger()
if logger == nil {
// No logger defined so create a simple logger to Stdout
logger = log.New(os.Stdout, "", 0)
}
if milter.GetDebug() {
LoggerPrintf("Debugging enabled")
logger.Println("Debugging enabled")
}
// Declare an empty smfiDesc structure
@@ -647,7 +658,7 @@ func Run(amilter Milter) int {
defer C.free(unsafe.Pointer(fname))
smfilter.xxfi_name = fname
if milter.GetDebug() {
LoggerPrintf("Filter Name: %s\n", C.GoString(smfilter.xxfi_name))
logger.Printf("Filter Name: %s\n", C.GoString(smfilter.xxfi_name))
}
// Set version code
@@ -656,7 +667,7 @@ func Run(amilter Milter) int {
// Set Flags
smfilter.xxfi_flags = C.ulong(milter.GetFlags())
if milter.GetDebug() {
LoggerPrintf("Flags: 0x%b\n", smfilter.xxfi_flags)
logger.Printf("Flags: 0x%b\n", smfilter.xxfi_flags)
}
// Set Callbacks if they are implemented
@@ -664,117 +675,117 @@ func Run(amilter Milter) int {
// Check if Connect method was implemented
if _, ok := milter.(checkForConnect); ok {
if milter.GetDebug() {
LoggerPrintln("Connect callback implemented")
logger.Println("Connect callback implemented")
}
C.setConnect(&smfilter)
} else {
if milter.GetDebug() {
LoggerPrintln("Connect callback not implemented")
logger.Println("Connect callback not implemented")
}
}
// Check if Helo method was implemented
if _, ok := milter.(checkForHelo); ok {
if milter.GetDebug() {
LoggerPrintln("Helo callback implemented")
logger.Println("Helo callback implemented")
}
C.setHelo(&smfilter)
} else {
if milter.GetDebug() {
LoggerPrintln("Helo callback not implemented")
logger.Println("Helo callback not implemented")
}
}
// Check if EnvFrom method was implemented
if _, ok := milter.(checkForEnvFrom); ok {
if milter.GetDebug() {
LoggerPrintln("EnvFrom callback implemented")
logger.Println("EnvFrom callback implemented")
}
C.setEnvFrom(&smfilter)
} else {
if milter.GetDebug() {
LoggerPrintln("EnvFrom callback not implemented")
logger.Println("EnvFrom callback not implemented")
}
}
// Check if EnvRcpt method was implemented
if _, ok := milter.(checkForEnvRcpt); ok {
if milter.GetDebug() {
LoggerPrintln("EnvRcpt callback implemented")
logger.Println("EnvRcpt callback implemented")
}
C.setEnvRcpt(&smfilter)
} else {
if milter.GetDebug() {
LoggerPrintln("EnvRcpt callback not implemented")
logger.Println("EnvRcpt callback not implemented")
}
}
// Check if Header method was implemented
if _, ok := milter.(checkForHeader); ok {
if milter.GetDebug() {
LoggerPrintln("Header callback implemented")
logger.Println("Header callback implemented")
}
C.setHeader(&smfilter)
} else {
if milter.GetDebug() {
LoggerPrintln("Header callback not implemented")
logger.Println("Header callback not implemented")
}
}
// Check if Eoh method was implemented
if _, ok := milter.(checkForEoh); ok {
if milter.GetDebug() {
LoggerPrintln("Eoh callback implemented")
logger.Println("Eoh callback implemented")
}
C.setEoh(&smfilter)
} else {
if milter.GetDebug() {
LoggerPrintln("Eoh callback not implemented")
logger.Println("Eoh callback not implemented")
}
}
// Check if Body method was implemented
if _, ok := milter.(checkForBody); ok {
if milter.GetDebug() {
LoggerPrintln("Body callback implemented")
logger.Println("Body callback implemented")
}
C.setBody(&smfilter)
} else {
if milter.GetDebug() {
LoggerPrintln("Body callback not implemented")
logger.Println("Body callback not implemented")
}
}
// Check if Eom method was implemented
if _, ok := milter.(checkForEom); ok {
if milter.GetDebug() {
LoggerPrintln("Eom callback implemented")
logger.Println("Eom callback implemented")
}
C.setEom(&smfilter)
} else {
if milter.GetDebug() {
LoggerPrintln("Eom callback not implemented")
logger.Println("Eom callback not implemented")
}
}
// Check if Abort method was implemented
if _, ok := milter.(checkForAbort); ok {
if milter.GetDebug() {
LoggerPrintln("Abort callback implemented")
logger.Println("Abort callback implemented")
}
C.setAbort(&smfilter)
} else {
if milter.GetDebug() {
LoggerPrintln("Abort callback not implemented")
logger.Println("Abort callback not implemented")
}
}
// Check if Close method was implemented
if _, ok := milter.(checkForClose); ok {
if milter.GetDebug() {
LoggerPrintln("Close callback implemented")
logger.Println("Close callback implemented")
}
C.setClose(&smfilter)
} else {
if milter.GetDebug() {
LoggerPrintln("Close callback not implemented")
logger.Println("Close callback not implemented")
}
}
if milter.GetDebug() {
LoggerPrintln("smfilter:")
LoggerPrintln(fmt.Sprint(smfilter))
logger.Println("smfilter:")
logger.Println(fmt.Sprint(smfilter))
}
// Setup socket connection
@@ -791,29 +802,26 @@ func Run(amilter Milter) int {
csocket := C.CString(socket)
defer C.free(unsafe.Pointer(csocket))
if code := C.smfi_setconn(csocket); code != 0 {
LoggerPrintf("smfi_setconn failed: %d\n", code)
logger.Printf("smfi_setconn failed: %d\n", code)
}
// Register the filter
if code := C.smfi_register(smfilter); code == C.MI_FAILURE {
LoggerPrintf("smfi_register failed: %d\n", code)
logger.Printf("smfi_register failed: %d\n", code)
}
// Hand control to libmilter
if milter.GetDebug() {
LoggerPrintln("Handing over to libmilter")
logger.Println("Handing over to libmilter")
}
result := C.smfi_main()
if milter.GetDebug() {
LoggerPrintf("smfi_main returned: %v\n", result)
logger.Printf("smfi_main returned: %v\n", result)
}
return int(result)
}
var LoggerPrintln func(...interface{})
var LoggerPrintf func(string, ...interface{})
func init() {
LoggerPrintln = func(i ...interface{}) { fmt.Println(i...) }
LoggerPrintf = func(i string, j ...interface{}) { fmt.Printf(i, j...) }
func Stop() {
C.smfi_stop()
}

View File

@@ -40,7 +40,8 @@ func main() {
mymilter := new(Mymilter)
mymilter.FilterName = "TestFilter"
mymilter.Debug = true
mymilter.Flags = gomilter.AddHdrs | gomilter.AddRcpt
mymilter.Flags = gomilter.ADDHDRS | gomilter.ADDRCPT
mymilter.Socket = "unix:/var/milterattachcheck/socket"
// Start Milter
gomilter.Run(mymilter)

View File

@@ -7,7 +7,8 @@ Copyright (c) 2015 Leon Baker
package main
import (
"fmt"
"log"
"os"
m "github.com/leonrbaker/gomilter"
)
@@ -22,11 +23,13 @@ type T struct {
C string
}
var logger *log.Logger
// 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)
logger.Println("mymilter.Connect was called")
logger.Printf("hostname: %s\n", hostname)
logger.Printf("ip: %s\n", ip)
t := T{1, hostname, "Test"}
m.SetPriv(ctx, &t)
@@ -35,53 +38,53 @@ func (milter *Mymilter) Connect(ctx uintptr, hostname, ip string) (sfsistat int8
}
func (milter *Mymilter) Helo(ctx uintptr, helohost string) (sfsistat int8) {
fmt.Println("mymilter.Helo was called")
fmt.Printf("helohost: %s\n", helohost)
logger.Println("mymilter.Helo was called")
logger.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)
logger.Println("mymilter.EnvFrom was called")
logger.Printf("myargv: %s\n", myargv)
// Show the value of a symbol
fmt.Printf("{mail_addr}: %v\n", m.GetSymVal(ctx, "{mail_addr}"))
logger.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)
logger.Println("mymilter.EnvRcpt was called")
logger.Printf("myargv: %s\n", myargv)
// Show the value of a symbol
fmt.Printf("{rcpt_addr}: %v\n", m.GetSymVal(ctx, "{rcpt_addr}"))
logger.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)
logger.Println("mymilter.Header was called")
logger.Printf("header field: %s\n", headerf)
logger.Printf("header value: %s\n", headerv)
return
}
func (milter *Mymilter) Eoh(ctx uintptr) (sfsistat int8) {
fmt.Println("mymilter.Eoh was called")
logger.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))
logger.Println("mymilter.Body was called")
logger.Println(string(body))
logger.Printf("Body Length: %d\n", len(body))
return
}
func (milter *Mymilter) Eom(ctx uintptr) (sfsistat int8) {
fmt.Println("mymilter.Eom was called")
logger.Println("mymilter.Eom was called")
var t T
fmt.Println("m.GetPri:", m.GetPriv(ctx, &t))
fmt.Println("t:", t)
logger.Println("m.GetPri:", m.GetPriv(ctx, &t))
logger.Println("t:", t)
m.AddHeader(ctx, "LEONUX-Mailer",
"test server;\n\ttest1=\"foobar\"")
@@ -92,19 +95,23 @@ func (milter *Mymilter) Eom(ctx uintptr) (sfsistat int8) {
}
func (milter *Mymilter) Abort(ctx uintptr) (sfsistat int8) {
fmt.Println("mymilter.Abort was called")
logger.Println("mymilter.Abort was called")
return
}
func (milter *Mymilter) Close(ctx uintptr) (sfsistat int8) {
fmt.Println("mymilter.Close was called")
logger.Println("mymilter.Close was called")
return
}
func main() {
mymilter := new(Mymilter)
mymilter.FilterName = "TestFilter"
logger = log.New(os.Stdout, "", log.LstdFlags)
mymilter.Logger = logger
mymilter.Debug = true
mymilter.Flags = m.ADDHDRS | m.ADDRCPT | m.CHGFROM | m.CHGBODY
mymilter.Socket = "unix:/var/milterattachcheck/socket"