Writing a small postfix to beanstalked service

Today I had the deligthful experience of writing a rather simple binary that pushes mail-message in a beanstalkd queue.

Pretext:

For one of our services withing the application we take incoming mails from our postfix service and push them to one of our REST-Endpoints.

The reason is rather simple company to customer communication that gets automatically assigned to a customer account and stores the communication in a timeline for the customer.

The reason why we use a binary is that SELinux would need execution rights for the interpreter otherwise. Which can pose a possible security risk since we would allow execution of scripts via postfix.

For that reason we decided to use a small rust implementation that gets the message piped via stdin and just wrapps it into a json struct and then pushes it to our beanstalkd queue for further processing.

our rust dependencies are pretty simple serde for easy serialization of our structs and the beanstalkd library ot use our producer

[dependencies]
serde = "1.0.80"
serde_json = "1.0.32"
serde_derive = "1.0.80"
beanstalkd = "0.4.1"

our main.rs is also pretty neat and small

extern crate serde;
extern crate serde_json;
extern crate beanstalkd;
#[macro_use]
extern crate serde_derive;

use std::io::{self, Read};
use beanstalkd::Beanstalkd;

#[derive(Serialize, Deserialize)]
pub struct MailMessage {
    pub body: String
}

fn main() -> io::Result<()> {
    let mut buffer = String::new();
    io::stdin().read_to_string(&mut buffer)?;

    let msg = MailMessage {
        body: buffer
    } ;

    let serialized_message = serde_json::to_string(&msg).unwrap();

    let mut beanstalkd = Beanstalkd::localhost().unwrap();
    let _ = beanstalkd.put(serialized_message.as_str(),
                           0,
                           0,
                           10000
    );

    Ok(())
}

to explain what's happening

    let mut buffer = String::new(); // creates a writable string buffer
    io::stdin().read_to_string(&mut buffer)?; // reads the stdin till it gets the EOF

   // packs it into a struct so we can serialize it with serde to a json
    let msg = MailMessage {
        body: buffer
    } ;
    let serialized_message = serde_json::to_string(&msg).unwrap();

serializes our struct to a json string

    let mut beanstalkd = Beanstalkd::localhost().unwrap();
    let _ = beanstalkd.put(serialized_message.as_str(),
                           0,
                           0,
                           10000
    );

establishes the connection to beanstalkd at localhost and pushes the string into our queue

this really simply application is than compiled with the musl target which creates a static binary so we don't have to care about the particular libraries installed on the host system.

cargo build --target x86_64-unknown-linux-musl

the whole binary is ~ 7MB in size and now we can safely use it for our postfix.

it took me a while longer than writing it with python or another script language but all in all 4h + for a small isolate binary that now allows multiple workers to push our messages to the company environment .... nice.

for testing purposes the consumer:

extern crate beanstalkd;

use beanstalkd::Beanstalkd;

fn main() {
    let mut beanstalkd = Beanstalkd::localhost().unwrap();
    let (id, body) = beanstalkd.reserve().unwrap();
    println!("{}", body);
    let _ = beanstalkd.delete(id);
}

I am still at the beginning with my rust knowledge but such easy going experiences really make me enjoy the language more and more.

I am thinking ...

Write your comment…

Be the first one to comment