2017-07-25 10:56:13 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
// This is postfix autoresponder, which is rewrite of the autoresponder bash script V1.6.3, written by Charles Hamilton - musashi@nefaria.com
|
|
|
|
//
|
2017-07-25 11:51:03 +02:00
|
|
|
// How to make it work on a server with postfix installed:
|
|
|
|
// =======================================================
|
2017-07-25 12:24:23 +02:00
|
|
|
// Create autoresponder username:
|
|
|
|
// useradd -d /var/spool/autoresponder -s $(which nologin) autoresponder
|
2017-07-25 11:51:03 +02:00
|
|
|
//
|
2017-07-25 12:24:23 +02:00
|
|
|
// Copy autoresponder binary to /usr/local/sbin
|
|
|
|
// cp autoresponder /usr/local/sbin/
|
2017-07-25 11:51:03 +02:00
|
|
|
//
|
|
|
|
// RESPONSE_DIR, RATE_LOG_DIR must be created:
|
2017-07-25 12:24:23 +02:00
|
|
|
// mkdir -p /var/spool/autoresponder/log /var/spool/autoresponder/responses
|
|
|
|
// chown -R autoresponder:autoresponder /var/spool/autoresponder
|
|
|
|
// chmod -R 0770 /var/spool/autoresponder
|
2017-07-25 11:51:03 +02:00
|
|
|
//
|
|
|
|
// Edit /etc/postfix/master.cf:
|
|
|
|
// Replace line:
|
|
|
|
// smtp inet n - - - - smtpd
|
|
|
|
// with these two lines (second must begin with at least one space or tab):
|
|
|
|
// smtp inet n - - - - smtpd
|
|
|
|
// -o content_filter=autoresponder:dummy
|
|
|
|
// At the end of file append the following two lines:
|
|
|
|
// autoresponder unix - n n - - pipe
|
2017-07-25 12:24:23 +02:00
|
|
|
// flags=Fq user=autoresponder argv=/usr/local/sbin/autoresponder -s ${sender} -r ${recipient} -S ${sasl_username} -C ${client_address}
|
2017-07-25 11:51:03 +02:00
|
|
|
//
|
|
|
|
//
|
2017-07-25 10:56:13 +02:00
|
|
|
// Written by Uros Juvan <asmpro@gmail.com> 2017
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"flag"
|
|
|
|
"os"
|
2017-07-25 15:34:51 +02:00
|
|
|
"os/exec"
|
|
|
|
"io"
|
|
|
|
"path/filepath"
|
2017-07-25 11:51:03 +02:00
|
|
|
"strings"
|
|
|
|
"log/syslog"
|
2017-07-25 10:56:13 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
const VERSION = "1.0.0001"
|
|
|
|
const DEBUG = true
|
|
|
|
|
2017-07-25 12:24:23 +02:00
|
|
|
const RESPONSE_DIR = "/var/spool/autoresponder/responses"
|
|
|
|
const RATE_LOG_DIR = "/var/spool/autoresponder/log"
|
2017-07-25 11:51:03 +02:00
|
|
|
const SENDMAIL_BIN = "/usr/sbin/sendmail"
|
|
|
|
|
|
|
|
|
2017-07-25 15:34:51 +02:00
|
|
|
var syslg *syslog.Writer = nil
|
|
|
|
|
|
|
|
|
2017-07-25 11:51:03 +02:00
|
|
|
// Function using fmt.Printf for debug printing, but only if DEBUG is true
|
2017-07-25 10:56:13 +02:00
|
|
|
func DebugFmtPrintf(format string, v ...interface{}) {
|
|
|
|
if DEBUG {
|
|
|
|
fmt.Printf("DEBUG: " + format, v...)
|
|
|
|
}
|
|
|
|
}
|
2017-07-25 15:34:51 +02:00
|
|
|
func DebugSyslogFmt(format string, v ...interface{}) {
|
|
|
|
if syslg == nil {
|
|
|
|
return
|
|
|
|
}
|
2017-07-25 11:51:03 +02:00
|
|
|
if DEBUG {
|
|
|
|
syslg.Debug(fmt.Sprintf("DEBUG: " + format, v...))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-25 15:34:51 +02:00
|
|
|
// Return true if file exists and is regular file
|
|
|
|
func fileExists(name string) bool {
|
|
|
|
st, err := os.Lstat(name)
|
|
|
|
if err != nil || (st.Mode() & os.ModeType) != os.FileMode(0) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Send mail from address to address with given mail content being passed as function pointer
|
|
|
|
func sendMail(from, to string, populateStdin func(io.WriteCloser)) error {
|
|
|
|
cmd := exec.Command(SENDMAIL_BIN, "-i", "-f", from, to)
|
|
|
|
stdin, err := cmd.StdinPipe()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = cmd.Start()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
go func() {
|
|
|
|
populateStdin(stdin)
|
|
|
|
}()
|
|
|
|
err = cmd.Wait()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-07-25 11:51:03 +02:00
|
|
|
// Set autoresponse using supplied arguments and stdin (email body)
|
|
|
|
func setAutoresponseViaEmail(recipient, sender, saslUser, clientIp string) error {
|
2017-07-25 15:34:51 +02:00
|
|
|
senderResponsePath := filepath.Join(RESPONSE_DIR, sender)
|
|
|
|
if fileExists(senderResponsePath) {
|
|
|
|
err := deleteAutoresponse(sender, true)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if ! fileExists(senderResponsePath) {
|
|
|
|
syslg.Info(fmt.Sprintf("Autoresponse disabled for address: %v by SASL authenticated user: %v from: %v",
|
|
|
|
sender,saslUser, clientIp))
|
|
|
|
//!!! Send mail via sendmail
|
|
|
|
sendMail(recipient, sender, func(sink io.WriteCloser) {
|
|
|
|
defer sink.Close()
|
|
|
|
sink.Write([]byte(fmt.Sprintf("From: %v\nTo: %v\nSubject: Autoresponder\n\n"+
|
|
|
|
"Autoresponse disabled for %v by SASL authenticated user: %v from: %v\n",
|
|
|
|
recipient, sender, sender, saslUser, clientIp)))
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
}
|
|
|
|
}
|
2017-07-25 11:51:03 +02:00
|
|
|
//!!!
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2017-07-25 10:56:13 +02:00
|
|
|
|
2017-07-25 13:30:03 +02:00
|
|
|
// Forward email using supplied arguments and stdin (email body)
|
|
|
|
func forwardEmailAndAutoresponse(recipient, sender, saslUser, clientIp string, responseRate uint) error {
|
|
|
|
//!!!
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-07-25 15:34:51 +02:00
|
|
|
// Enable autoresponse for email
|
|
|
|
func enableAutoresponse(email string) error {
|
|
|
|
//!!!
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Disable autoresponse for email
|
|
|
|
func disableAutoresponse(email string) error {
|
|
|
|
//!!!
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Enable existing autoresponse for email
|
|
|
|
func enableExAutoresponse(email string) error {
|
|
|
|
//!!!
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete autoresponse for email
|
|
|
|
func deleteAutoresponse(email string, nostdout bool) error {
|
|
|
|
deleteResponsePath := filepath.Join(RESPONSE_DIR, email)
|
|
|
|
if fileExists(deleteResponsePath) {
|
|
|
|
os.Remove(deleteResponsePath)
|
|
|
|
} else {
|
|
|
|
msg := fmt.Sprintf("%v does not exist, thus it cannot be deleted!", deleteResponsePath)
|
|
|
|
if ! nostdout {
|
|
|
|
fmt.Println(msg)
|
|
|
|
}
|
|
|
|
return fmt.Errorf("%v", msg)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-07-25 10:56:13 +02:00
|
|
|
func main() {
|
2017-07-25 11:51:03 +02:00
|
|
|
// Connect to syslog
|
|
|
|
syslg, err := syslog.New(syslog.LOG_MAIL, "autoresponder")
|
|
|
|
if err != nil {
|
2017-07-25 15:34:51 +02:00
|
|
|
fmt.Println(err.Error())
|
2017-07-25 11:51:03 +02:00
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
defer syslg.Close()
|
|
|
|
|
2017-07-25 10:56:13 +02:00
|
|
|
// Parse command line arguments
|
|
|
|
recipientPtr := flag.String("r", "", "Recipient e-mail")
|
|
|
|
senderPtr := flag.String("s", "", "Sender e-mail")
|
|
|
|
saslUserPtr := flag.String("S", "", "SASL authenticated username")
|
|
|
|
clientIpPtr := flag.String("C", "", "Client IP address")
|
|
|
|
enableAutoResponsePtr := flag.String("e", "", "Enable autoresponse")
|
|
|
|
disableAutoResponsePtr := flag.String("d", "", "Disable autoresponse")
|
|
|
|
enableExAutoResponsePtr := flag.String("E", "", "Enable existing autoresponse")
|
|
|
|
deleteAutoResponsePtr := flag.String("D", "", "Delete autoresponse")
|
2017-07-25 13:30:03 +02:00
|
|
|
instructionsPtr := flag.Bool("i", false, "Setup instructions")
|
|
|
|
responseRatePtr := flag.Uint("t", 86400, "Response rate in seconds (0 - send each time)")
|
2017-07-25 10:56:13 +02:00
|
|
|
flag.Parse()
|
|
|
|
|
2017-07-25 15:34:51 +02:00
|
|
|
DebugSyslogFmt("Flags: Recipient: %v, Sender: %v, SASL authenticated username: %v, Client IP: %v, Enable autoresponse: %v, Disable autoresponse: %v, Enable existing autoresponse: %v, Delete autoresponse: %v, Setup instructions: %v, Response rate: %v",
|
2017-07-25 11:51:03 +02:00
|
|
|
*recipientPtr,
|
|
|
|
*senderPtr,
|
|
|
|
*saslUserPtr,
|
|
|
|
*clientIpPtr,
|
|
|
|
*enableAutoResponsePtr,
|
|
|
|
*disableAutoResponsePtr,
|
|
|
|
*enableExAutoResponsePtr,
|
|
|
|
*deleteAutoResponsePtr,
|
2017-07-25 13:30:03 +02:00
|
|
|
*instructionsPtr,
|
|
|
|
*responseRatePtr)
|
2017-07-25 11:51:03 +02:00
|
|
|
|
|
|
|
// If setup instructions are requested, just print them to stdout and exit
|
2017-07-25 13:30:03 +02:00
|
|
|
if *instructionsPtr {
|
2017-07-25 11:51:03 +02:00
|
|
|
fmt.Print(`
|
|
|
|
How to make it work on a server with postfix installed:
|
|
|
|
=======================================================
|
2017-07-25 12:24:23 +02:00
|
|
|
Create autoresponder username:
|
|
|
|
useradd -d /var/spool/autoresponder -s $(which nologin) autoresponder
|
2017-07-25 11:51:03 +02:00
|
|
|
|
2017-07-25 12:24:23 +02:00
|
|
|
Copy autoresponder binary to /usr/local/sbin
|
|
|
|
cp autoresponder /usr/local/sbin/
|
2017-07-25 11:51:03 +02:00
|
|
|
|
|
|
|
RESPONSE_DIR, RATE_LOG_DIR must be created:
|
2017-07-25 12:24:23 +02:00
|
|
|
mkdir -p /var/spool/autoresponder/log /var/spool/autoresponder/responses
|
|
|
|
chown -R autoresponder:autoresponder /var/spool/autoresponder
|
|
|
|
chmod -R 0770 /var/spool/autoresponder
|
2017-07-25 11:51:03 +02:00
|
|
|
|
|
|
|
Edit /etc/postfix/master.cf:
|
|
|
|
Replace line:
|
|
|
|
smtp inet n - - - - smtpd
|
|
|
|
with these two lines (second must begin with at least one space or tab):
|
|
|
|
smtp inet n - - - - smtpd
|
|
|
|
-o content_filter=autoresponder:dummy
|
|
|
|
At the end of file append the following two lines:
|
|
|
|
autoresponder unix - n n - - pipe
|
2017-07-25 12:24:23 +02:00
|
|
|
flags=Fq user=autoresponder argv=/usr/local/sbin/autoresponder -s ${sender} -r ${recipient} -S ${sasl_username} -C ${client_address}
|
2017-07-25 11:51:03 +02:00
|
|
|
`)
|
|
|
|
os.Exit(0)
|
2017-07-25 10:56:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Do some logic on command line arguments
|
|
|
|
// Mode
|
|
|
|
// There are two different modes of operation:
|
|
|
|
// mode=0 represents the actions that can not be executed from the command line
|
|
|
|
// mode=1 represents the actions that can be executed from the command line
|
|
|
|
mode := 0
|
|
|
|
sendResponse := false
|
|
|
|
authenticated := false
|
|
|
|
if *recipientPtr != "" && *senderPtr != "" {
|
|
|
|
sendResponse = true
|
|
|
|
}
|
2017-07-25 11:51:03 +02:00
|
|
|
if *saslUserPtr != "" {
|
2017-07-25 10:56:13 +02:00
|
|
|
authenticated = true
|
|
|
|
}
|
|
|
|
if *enableAutoResponsePtr != "" || *disableAutoResponsePtr != "" || *enableExAutoResponsePtr != "" || *deleteAutoResponsePtr != "" {
|
|
|
|
mode = 1
|
|
|
|
}
|
2017-07-25 15:34:51 +02:00
|
|
|
DebugSyslogFmt("mode=%v, sendResponse=%v, authenticated=%v\n", mode, sendResponse, authenticated)
|
2017-07-25 11:51:03 +02:00
|
|
|
|
2017-07-25 13:30:03 +02:00
|
|
|
// Little more validation of recipient and sender
|
|
|
|
// Remove path ('/') from both recipient and sender
|
|
|
|
*recipientPtr = strings.Replace(*recipientPtr, "/", "", -1)
|
|
|
|
*senderPtr = strings.Replace(*senderPtr, "/", "", -1)
|
2017-07-25 11:51:03 +02:00
|
|
|
recipientParts := strings.Split(*recipientPtr, "@")
|
|
|
|
senderParts := strings.Split(*senderPtr, "@")
|
|
|
|
if len(recipientParts) < 2 {
|
|
|
|
syslg.Err(fmt.Sprintf("Invalid recipient %v", *recipientPtr))
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
if len(senderParts) < 2 {
|
|
|
|
syslg.Err(fmt.Sprintf("Invalid sender %v", *senderPtr))
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
// And now descision making
|
2017-07-25 15:34:51 +02:00
|
|
|
DebugSyslogFmt("recipientUser=%v =? senderUser=%v\n", recipientParts[0], senderParts[0] + "+autoresponse")
|
2017-07-25 13:30:03 +02:00
|
|
|
switch true {
|
|
|
|
// - (un)set autoresponse via email
|
|
|
|
case mode == 0 && recipientParts[0] == senderParts[0] + "+autoresponse":
|
2017-07-25 11:51:03 +02:00
|
|
|
syslg.Info(fmt.Sprintf("Requested autoresponse (un)set via email for email %v", *senderPtr))
|
2017-07-25 13:30:03 +02:00
|
|
|
|
|
|
|
// Do not allow unauthenticated changes
|
|
|
|
if ! authenticated {
|
|
|
|
syslg.Warning(fmt.Sprintf("Unauthenticated attempt to set autoresponse message for %v from %v !",
|
|
|
|
*senderPtr, *clientIpPtr))
|
|
|
|
os.Exit(0)
|
|
|
|
}
|
|
|
|
|
2017-07-25 11:51:03 +02:00
|
|
|
err := setAutoresponseViaEmail(*recipientPtr, *senderPtr, *saslUserPtr, *clientIpPtr)
|
2017-07-25 13:30:03 +02:00
|
|
|
//!!!
|
2017-07-25 11:51:03 +02:00
|
|
|
if err != nil {
|
|
|
|
syslg.Err(err.Error())
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
2017-07-25 13:30:03 +02:00
|
|
|
|
|
|
|
// - forward mail and either send response if set and enough time has passed
|
|
|
|
case mode == 0 && strings.Index(*recipientPtr, "+autoresponse") == -1:
|
|
|
|
syslg.Info(fmt.Sprintf("Requested email forward from %v, to %v", *senderPtr, *recipientPtr))
|
|
|
|
|
|
|
|
err := forwardEmailAndAutoresponse(*recipientPtr, *senderPtr, *saslUserPtr, *clientIpPtr, *responseRatePtr)
|
|
|
|
//!!!
|
|
|
|
if err != nil {
|
|
|
|
syslg.Err(err.Error())
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
// - set autoresponse via cli
|
|
|
|
case mode == 1 && *enableAutoResponsePtr != "":
|
2017-07-25 15:34:51 +02:00
|
|
|
syslg.Info(fmt.Sprintf("Requested enable autoresponse for %v", *enableAutoResponsePtr))
|
|
|
|
|
|
|
|
err := enableAutoresponse(*enableAutoResponsePtr)
|
2017-07-25 13:30:03 +02:00
|
|
|
//!!!
|
2017-07-25 15:34:51 +02:00
|
|
|
if err != nil {
|
|
|
|
syslg.Err(err.Error())
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
2017-07-25 13:30:03 +02:00
|
|
|
|
|
|
|
// - disable autoresponse via cli
|
|
|
|
case mode == 1 && *disableAutoResponsePtr != "":
|
2017-07-25 15:34:51 +02:00
|
|
|
syslg.Info(fmt.Sprintf("Requested disable autoresponse for %v", *disableAutoResponsePtr))
|
|
|
|
|
|
|
|
err := disableAutoresponse(*disableAutoResponsePtr)
|
2017-07-25 13:30:03 +02:00
|
|
|
//!!!
|
2017-07-25 15:34:51 +02:00
|
|
|
if err != nil {
|
|
|
|
syslg.Err(err.Error())
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
2017-07-25 13:30:03 +02:00
|
|
|
|
|
|
|
// - enable existing autoresponse via cli
|
|
|
|
case mode == 1 && *enableExAutoResponsePtr != "":
|
2017-07-25 15:34:51 +02:00
|
|
|
syslg.Info(fmt.Sprintf("Requested enable existing autoresponse for %v", *enableExAutoResponsePtr))
|
|
|
|
|
|
|
|
err := enableExAutoresponse(*enableExAutoResponsePtr)
|
2017-07-25 13:30:03 +02:00
|
|
|
//!!!
|
2017-07-25 15:34:51 +02:00
|
|
|
if err != nil {
|
|
|
|
syslg.Err(err.Error())
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
2017-07-25 13:30:03 +02:00
|
|
|
|
|
|
|
// - delete existing autoresponse via cli
|
|
|
|
case mode == 1 && *deleteAutoResponsePtr != "":
|
2017-07-25 15:34:51 +02:00
|
|
|
syslg.Info(fmt.Sprintf("Requested delete autoresponse for %v", *deleteAutoResponsePtr))
|
|
|
|
|
|
|
|
err := deleteAutoresponse(*deleteAutoResponsePtr, false)
|
2017-07-25 13:30:03 +02:00
|
|
|
//!!!
|
2017-07-25 15:34:51 +02:00
|
|
|
if err != nil {
|
|
|
|
syslg.Err(err.Error())
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
2017-07-25 11:51:03 +02:00
|
|
|
}
|
|
|
|
//!!!
|
2017-07-25 10:56:13 +02:00
|
|
|
}
|