# Welcome

This is the online API documentation of the postfix-policy-server package with examples so you can start building your own Postix policy services right away!

**postfix-policy-server** (or short: **pps**) provides a simple framework to create [Postfix SMTP Access Policy Delegation Servers](http://www.postfix.org/SMTPD_POLICY_README.html) in Go.

## Installation

First of all, [download](https://golang.org/dl/) and install Go. `1.17` or higher is required.

Installation can be done using the `go get` command:

```bash
$ go get -u github.com/wneessen/postfix-policy-server
```

## **Usage**

The **pps** framework allows you to start a new TCP (or other Go `net.Listener`) server that listens for incoming policy requests from a Postfix mail server.&#x20;

Once a new connection is established and the dataset from Postfix has been sent, the data will be processed as a [PolicySet](https://pkg.go.dev/github.com/wneessen/postfix-policy-server#PolicySet) and handed to a provided `Handle()` method that is given by the user, using the **pps** [Handler](https://pkg.go.dev/github.com/wneessen/postfix-policy-server#Handler) interface.


# Server

Server defines a new policy server with corresponding settings

## New

This method creates a new [Server](/api/server) instance. You can pass [ServerOpt](/api/serveropt) functions to provide optional settings when creating a new instance.

{% tabs %}
{% tab title="Signature" %}

```go
func New(...ServerOpt) Server
```

{% endtab %}

{% tab title="Example" %}

```go
s := pps.New()
```

{% endtab %}
{% endtabs %}

## Changing Server settings

When you already have a [Server](/api/server) instance which settings weren't changed using [ServerOpt](/api/serveropt) functions, you can use the following methods on the [Server ](/api/server)instance to subsequently override them.

### SetAddr

The `SetAddr()` method overrides the listening address on the provided [Server](/api/server) instance.

{% tabs %}
{% tab title="Signature" %}

```go
func (*Server) SetAddr(string)
```

{% endtab %}

{% tab title="Example" %}

```go
// Create a Server with defaults
s := pps.New()

// Override the listening address
s.SetAddr("127.0.0.1")
```

{% endtab %}
{% endtabs %}

### SetPort

The `SetPort()` method will override the listening port on the provided [Server ](/api/server)instance.

{% tabs %}
{% tab title="Siganture" %}

```go
func (*Server) SetPort(string)
```

{% endtab %}

{% tab title="Example" %}

```go
// Create a Server with defaults
s := pps.New()

// Override the listening port
s.SetPort("1234")
```

{% endtab %}
{% endtabs %}

## Starting the Server

Starting a policy server with **pps** is as easy as just executing one method on the [Server ](/api/server)instance. It is important though, that you provide it with a `Context` and a custom type that satisfies the pps [Handler](/api/handler) interface.

### Run

Once you have a Server instance ready, all you need is to execute the Run method on it, to start a TCP policy server that is listening on the address/port combination, that you configured in it (Default: `0.0.0.0:10005`). The `Run()` method returns an error in case it wasn't able to start.

{% tabs %}
{% tab title="Signature" %}

```go
func (*Server) Run(context.Context, Handler) error
```

{% endtab %}

{% tab title="Example" %}

```go
// Create your Server instance with defaults
s := pps.New()

// Create a Context of your choice
ctx := context.Background()

// Create an instance of the type that satisfies the Handler interface
h := Hi{}

// Start the server on 0.0.0.0:10005
if err := s.Run(ctx, h); err != nil {
	panic(err)
}
```

{% endtab %}
{% endtabs %}

### RunWithListener

If you prefer to run your policy service on a different type of listener (i. e. UNIX socket), you can do so by making use of the `RunWithListener()` method. It works similar to the `Run()` method, except that you'll have to provide a `net.Listener` as additional argument.

{% tabs %}
{% tab title="Signature" %}

```go
func (*Server) RunWithListener(context.Context, Handler, net.Listener) error
```

{% endtab %}

{% tab title="Example" %}

```go
// Create your Server instance
s := pps.New()

// We'll create a Context with cancel... just because we can
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

// Let's create a UNIX socket listener on /tmp/pps_test_server
l, err := net.Listen("unix", "/tmp/pps_test_server")
if err != nil {
	t.Errorf("failed to create new UNIX socket listener: %s", err)
}

// Again, we require the Handler interface type
h := Hi{}

// ...to finally start our server on /tmp/pps_test_server
if err := s.RunWithListener(ctx, h, l); err != nil {
	t.Errorf("could not run server: %s", err)
}
```

{% endtab %}
{% endtabs %}

### Contexts

**pps** allows you to influence specific behaviour of your policy server using vaules in the `Context` you provide to the `Run()` or `RunWithListener()` methods. The context keys that you can set are provided via the `CtxKey` type of the pps package.

Currently **pps** supports the following keys:

| Property | Type   | Description                                                                                                                                                                                                                                                                                                                                                    | Default |
| -------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| CtxNoLog | `bool` | <p>When you run a server, pps by default uses <br>Go's <code>log</code> package to log some errors to <br><code>STDERR</code>. If you prefer to keep the execution <br>silent without any logging, you can set the </p><p><code>CtxNoLog</code> context value to <code>true</code> before you</p><p>hand your context to the <code>Run\*()</code> methods.</p> | `nil`   |

{% tabs %}
{% tab title="Example" %}

```go
// Create your Server instance
s := pps.New()

// And your context with a value
ctx := context.WithValue(context.Background(), pps.CtxNoLog, true)

// Again, we require the Handler interface type
h := Hi{}

// We can now start our server with logging disable
if err := s.Run(ctx, h); err != nil {
		t.Errorf("could not run server: %s", err)
}
```

{% endtab %}
{% endtabs %}


# ServerOpt

SeverOpt functions let you override default values

`SeverOpt` is a type that represents a function that allow you to override the default settings of the `New()` method with your own settings.

{% tabs %}
{% tab title="Signature" %}

```go
type ServerOpt func(*Server)
```

{% endtab %}
{% endtabs %}

**pps** provides the following `ServerOpt` functions:

### WithAddr

`WithAddr()` allows you to set the listening address for the TCP server (Default: `0.0.0.0`)

{% tabs %}
{% tab title="Signature" %}

```go
func WithAddr(string) ServerOpt
```

{% endtab %}

{% tab title="Example" %}

```go
s := pps.New(WithAddr("127.0.0.1"))
```

{% endtab %}
{% endtabs %}

### WithPort

`WithPort()` allows you to set the listening port for the TCP server (Default: `10005`)

{% tabs %}
{% tab title="Signature" %}

```go
func WithPort(string) ServerOpt
```

{% endtab %}

{% tab title="Example" %}

```go
s := pps.New(WithPort("10006"))
```

{% endtab %}
{% endtabs %}


# Handler

Handler is the interface for you to process the policy data

The `Handler` interface of the **pps** package is very simple and consists only of one method. When starting a new policy service on your [Server](/api/server) instance, the `Run()` method requires a type that satisfies the `Handler` interface.&#x20;

This provides and easy way for your program to handle the dataset provided by the Postfix server. Once the dataset has been provided and processed by the pps server, it will hand the [PolicySet](/api/policyset) to the `Handle()` method of the `Handler` interface type you provided. All it expects back is a proper response of the type [PostfixResp](/api/postfixresp) that it can write back to Postfix. What you do with the [PolicySet](/api/policyset) in your `Handle()` method is totally up to you.

{% tabs %}
{% tab title="Signature" %}

```go
type Handler interface {
	Handle(*PolicySet) PostfixResp
}
```

{% endtab %}

{% tab title="Example" %}

```go
// Empty struct to act the Handler interface
type Hi struct {
	r PostfixResp
}

// Handle is the function required by the Handler Interface
func (h Hi) Handle(*pps.PolicySet) pps.PostfixResp {
	if h.r == "" {
		h.r = pps.RespDunno
	}
	return h.r
}
```

{% endtab %}
{% endtabs %}


# PolicySet

The data collection provided by the postfix server

The [PolicySet](/api/policyset) type represents all data provided by the postfix server in a typed and self-descriptive manner. Once your policy server receives data from postfix the data will be internally processed and as a result, a [PolicySet](/api/policyset) is created that is then provided as a pointer to the `Handle()` method of your [Handler](/api/handler) interface.

The [PolicySet](/api/policyset) currently consists of the following properties and follows the [Postfix documentation](http://www.postfix.org/SMTPD_POLICY_README.html). Please check the Postfix docs on details to the properties.

{% tabs %}
{% tab title="Signature" %}

```go
type PolicySet struct {
	// Postfix version 2.1 and later
	Request           string
	ProtocolState     string
	ProtocolName      string
	HELOName          string
	QueueId           string
	Sender            string
	Recipient         string
	RecipientCount    uint64
	ClientAddress     net.IP
	ClientName        string
	ReverseClientName string
	Instance          string

	// Postfix version 2.2 and later
	SASLMethod       string
	SASLUsername     string
	SASLSender       string
	Size             uint64
	CCertSubject     string
	CCertIssuer      string
	CCertFingerprint string

	// Postfix version 2.3 and later
	EncryptionProtocol string
	EncryptionCipher   string
	EncryptionKeysize  uint64
	ETRNDomain         string

	// Postfix version 2.5 and later
	Stress bool

	// Postfix version 2.9 and later
	CCertPubkeyFingerprint string

	// Postfix version 3.0 and later
	ClientPort uint64

	// Postfix version 3.1 and later
	PolicyContext string

	// Postfix version 3.2 and later
	ServerAddress net.IP
	ServerPort    uint64

	// postfix-policy-server specific values
	PPSConnId string
}
```

{% endtab %}
{% endtabs %}

Besides of all values provided by the Postfix server, **pps** adds some package specific values, that might be useful in your program. The list of pps-specific values currently is:

| Property  | Type     | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| PPSConnId | `string` | <p>Each time Postfix connects to your policy <br>service, a unique connection id is assigned</p><p>to it. The <code>PPSConnId</code> property represents</p><p>this <code>string</code>.</p><p></p><p><strong>Note: Please keep in mind, that the Postfix</strong> </p><p><strong>specifications require that a connection</strong> </p><p><strong>can be re-used by Postfix. Therefore you</strong></p><p><strong>might see multiple PolicySets with the</strong> </p><p><strong>same connection id.</strong></p> |


# Postfix(Text)Resp

Possible responses for the Postfix server

Once your `Handle()` method finished the **pps** [Handler](/api/handler) interfaces expects it to return a response code, so it can tell Postfix what to do with the mail in question. The [PostfixResp](/api/postfixresp) and the [PostifxTextResp](/api/postfixresp) types define all responses that Postfix accepts and the **pps** package supports.

Please check the [Postfix documentation](http://www.postfix.org/access.5.html) on the details of the various responses.

### PostfixResp

The `PostfixResp` type defines all possible respones to Postfix that do not require any type additional text appended to it. Any of these can be directly used as return value in your Handle() method.

{% tabs %}
{% tab title="Signature" %}

```go
type PostfixResp string

const (
	RespOk            PostfixResp = "OK"
	RespReject        PostfixResp = "REJECT"
	RespDefer         PostfixResp = "DEFER"
	RespDeferIfReject PostfixResp = "DEFER_IF_REJECT"
	RespDeferIfPermit PostfixResp = "DEFER_IF_PERMIT"
	RespDiscard       PostfixResp = "DISCARD"
	RespDunno         PostfixResp = "DUNNO"
	RespHold          PostfixResp = "HOLD"
	RespInfo          PostfixResp = "INFO"
	RespWarn          PostfixResp = "WARN"
)
```

{% endtab %}

{% tab title="Example" %}

```go
func Handle(ps *pps.PolicySet) pps.PostfixResp {
    fmt.Println("Postfix received a mail for:", ps.Recipient)
    return pps.RespDunno    
}
```

{% endtab %}
{% endtabs %}

### PostfixTextResp

The `PostfixTextResp` type defines those kind of responses that require to have additional information appended to it. These types cannot be directly used as return value in your `Handle()` function but need to be converted to a `PostfixResp` type first using the [Custom responses](/api/postfixresp#custom-responses) functions.

{% tabs %}
{% tab title="Siganture" %}

```go
type PostfixTextResp string

const (
	TextRespFilter   PostfixTextResp = "FILTER"
	TextRespPrepend  PostfixTextResp = "PREPEND"
	TextRespRedirect PostfixTextResp = "REDIRECT"
)
```

{% endtab %}
{% endtabs %}

## Custom responses

As already mentioned, the `PostfixResp` type responses do not require any additional information appended to, but most of those reponses allow it. For the `PostfixTextResp` types the additional information is non-optional. **pps** provides methods to create custom responses with both of these types.

### TextResponseOpt

The `TextResponseOpt()` method allows you to append custom text to your `PostfixResp` type responses. It will return a valid `PostfixResp` type that can be used as return value in your `Handle()` method.

{% tabs %}
{% tab title="Signature" %}

```go
func TextResponseOpt(PostfixResp, string) PostfixResp
```

{% endtab %}

{% tab title="Example" %}

```go
custResp := pps.TextResponseOpt(pps.RespDunno, "custom text")
```

{% endtab %}
{% endtabs %}

### TextResponseNonOpt

With the `TextResponseNonOpt()` method you can make use of the `PostfixTextResp` type responses and create a valid `PostfixResp` type with it, that can be used as return value for your `Handle()` method.

{% tabs %}
{% tab title="Signature" %}

```go
func TextResponseNonOpt(PostfixTextResp, string) PostfixResp
```

{% endtab %}

{% tab title="Example" %}

```go
custResp := pps.TextResponseNonOpt(pps.TextRespFilter, "custom:filter")
```

{% endtab %}
{% endtabs %}


# Policy to JSON echo server

A simple code example on how to use pps in your own code

**pps** provides you with a fully functional code example in its [Github Repository](https://github.com/wneessen/postfix-policy-server). The code example is a simple "*Policy data to JSON*" echo server.&#x20;

It accepts Postifx policy requests on the default listening address (`0.0.0.0:10005`), converts the provided [PolicySet](/api/policyset) into JSON, outputs this JSON to `STDOUT` and replies to Postfix with a custom `INFO`response. Check out the code on [Github](https://github.com/wneessen/postfix-policy-server/blob/main/example-code/echo-server/main.go).


