Bart Simons

Bart Simons

Send an SMS notification upon successful SSH authentication with Twilio

 •  Filed under ssh, sms, authentication, notification, twilio

Are your always curious about who logs into your server using SSH? Me too! Sometimes I am just a little paranoid and then I come up with these (crazy) ideas: sending an SMS when somebody successfully logs in over SSH. Why? Well: why not, right?

So, what do we need?

  • A server with an SSH daemon (duh...)
  • A Twilio account

So first of all, let me explain what Twilio is: it's an awesome company that makes placing and receiving calls, sending and receiving SMS and MMS, etcetera super simple. It operates over an API so that developers can create, integrate and automate telecommunication solutions into their applications.

Before continuing, make sure you have an account and make sure you write down your account SID and authentication token, both can be found on the Twilio console page.

Now, we need to prepare our server. Once any user successfully authenticates over SSH, we want to execute a script before any shell process gets spawned. This is how I did it:

First I created a script in the /opt directory called with the following contents in it:


curl -s -X POST '' --data-urlencode 'To=+12345678901' --data-urlencode 'From=+15017250604' --data-urlencode 'Body=Somebody just succesfully SSHed into your server!' -u YOURACCOUNTSIDGOESHERE:YOURAUTHTOKENGOESHERE > /dev/null  

Make sure to replace the account SID and authentication token placeholders by the values stated in your Twilio console and replace the To and From phone numbers to your phone numbers. Also, don't forget to make the script executable:

chmod +x /opt/  

There's only one more thing left to do: modifying the SSH daemon configuration file. For me (I use Ubuntu) it was located at /opt/ssh/sshd_config.

Just add the following line to the bottom of this file:

ForceCommand /opt/; /bin/bash  

And restart your SSH server daemon with:

service sshd restart  

Try it out! For me it worked brilliantly:

It works!

Have a wonderful day! 🎉

Swift + Perfect + Authentication API Example

 •  Filed under swift, perfect, example, authentication

So you want to build your next backend API in Swift? No problem! Perfect has got you covered. Perfect is a collection of libraries to turn your next server-side Swift application into reality. It is blazingly fast and awesome!

So I decided to dive a little bit deeper into the possibilities of Swift and Perfect, and so I quickly decided that an authentication API would be a 'perfect' idea (see what I did there?) to get started. So, what do we need?

  • A computer running Linux or macOS
  • Swift 3 installed
  • A MySQL Server

That's basically all that's needed. Now we need a functional database, so I came up with a SQL table like this:

CREATE TABLE `users` (  
  `id` int(32) NOT NULL,
  `username` varchar(128) NOT NULL,
  `password` varchar(128) NOT NULL

ALTER TABLE `users`  
  ADD UNIQUE KEY `username` (`username`),
  ADD UNIQUE KEY `id` (`id`);

INSERT INTO `users` (`id`, `username`, `password`) VALUES  
(1, 'user1', '7c12772809c1c0c3deda6103b10fdfa0'),
(2, 'user2', '7c12772809c1c0c3deda6103b10fdfa0');

ALTER TABLE `users`  

Perfect. Now - for the next part - initialize a new Swift package in an empty folder:

swift package init --type executable  

This will generate an empty package, so now it's time to edit our Package.swift file. You'll need to add 2 repositories to your project, this is how I have done that:

import PackageDescription

let package = Package(  
    name: "backend-api",
    dependencies: [
        .Package(url: "", majorVersion: 2),
        .Package(url: "", majorVersion: 2)

Cool. So now it is about time to work on the real deal - programming! I've split the project into two separate swift files

  • main.swift : this is for all things related to the web server processing
  • DBOperations.swift : this is a provider to execute MySQL (prepared) statements in the form of a function

My main.swift:

import PerfectLib  
import PerfectHTTP  
import PerfectHTTPServer  
import Foundation

let server = HTTPServer()  
var routes = Routes()

server.serverPort = 8000

routes.add(method: .get, uri: "/", handler: {  
    request, response in

    response.status = HTTPResponseStatus.ok
    response.setHeader(.contentType, value: "application/json")

    response.setBody(string: "{\"status\":\"Backend server is up and running\"}")

routes.add(method: .get, uri: "/oauth", handler: {  
    request, response in

    response.setHeader(.contentType, value: "application/json")

    let username = request.param(name: "username", defaultValue: "")
    let password = request.param(name: "password", defaultValue: "")

    if ((username ?? "").isEmpty || (password ?? "").isEmpty) {
        response.status = HTTPResponseStatus.ok
        response.setBody(string: "{\"status\":\"Invalid request\"}")
    } else {
        if (username ?? "").characters.count >= 8 || (username ?? "").characters.count <= 24{
            if (password ?? "").characters.count == 32 {
                if (username ?? "").rangeOfCharacter(from: CharacterSet.alphanumerics.inverted) == nil && (password ?? "").rangeOfCharacter(from: CharacterSet.alphanumerics.inverted) == nil {
                    response.status = HTTPResponseStatus.ok
                    let queryResult: [[[String: String]]] = executeQuery(query: "SELECT * FROM users WHERE username = ?", parameters: [username ?? ""])
                    if (queryResult.count == 1) {
                        if  ((queryResult[0][2]["password"] ?? "") as String == password ?? "") {
                            response.setBody(string: "{\"status\":\"Authentication request succeeded!\"}")
                        } else {
                            response.setBody(string: "{\"status\":\"Invalid request\"}")
                    } else {
                        response.setBody(string: "{\"status\":\"Invalid request\"}")
                } else {
                    response.status = HTTPResponseStatus.ok
                    response.setBody(string: "{\"status\":\"Invalid request\"}")
            } else {
                response.status = HTTPResponseStatus.ok
                response.setBody(string: "{\"status\":\"Invalid request\"}")
        } else {
            response.status = HTTPResponseStatus.ok
            response.setBody(string: "{\"status\":\"Invalid request\"}")



do {  
    try server.start()
} catch PerfectError.networkError(let err, let msg) {
    print("Network error thrown: \(err) \(msg)")

My DBOperations.swift:

import MySQL

let sqlHost     = ""  
let sqlUser     = "app_authentication"  
let sqlPassword = "testing01"  
let sqlDB       = "app_authentication"

func executeQuery(query: String, parameters: [String]? = nil) -> [[[String: String]]] {  
    var fStore = [[[String: String]]]()

    let mysql = MySQL()
    let connection = mysql.connect(host: sqlHost, user: sqlUser, password: sqlPassword, db: sqlDB)

    guard connection else {
        return [[[String: String]]]()

    defer {

    let fQuery = MySQLStmt(mysql)

    _ = fQuery.prepare(statement: query)

    if parameters != nil {
        for parameter in parameters! {

    _ = fQuery.execute()

    let fResults = fQuery.results()

    _ = fResults.forEachRow {
        e in

        var fSubStore = [[String: String]]()

        for i in 0..<e.count {
            let fFieldName: String? = fQuery.fieldInfo(index: i)!.name
            fSubStore.append(["\(fFieldName!)": "\(e[i]!)"])


    return fStore

You need to save both of these files inside the Sources folder of your package.

To build and run your app, run this in the root of your package directory:

swift build  

Now it is time to test if everything works:
It works!

So: this is how you can build authentication APIs with Swift. Please bare in mind that all code examples given are NOT production ready. I know that it works, but I can definitely already see some points of improvements - too bad that I don't have time to fix that. This demo project is soon to be expected on my GitHub. Thanks for reading and have a nice day ;)

Pretty URLs in Apache - Getting Started Tutorial

 •  Filed under getting started, tutorial, Pretty URLs, Apache, mod_rewrite

Ever wanted to create beautiful URLs for your self-hosted websites? This page has got you covered with all just about the information you need to get started. All functionality which is demonstrated on this page is made possible by mod_rewrite, also known as the 'rewrite' module. This module gives system administrators the possibility to rewrite URL paths, as I shall demonstrate:

Let's say Mr. X has a website called mywebsite.corp with a single page called test.php. He currently visits his page like this:

mywebsite.corp before any modification

Mr.X is not happy with how the php file extension is exposed to the user, and wants to make his page URL look better by removing the php extension. The first thing he has to do is to enable the rewrite module which is fairly simple:

a2enmod rewrite  

Now, all that is left to do is to write the rewrite rule inside a Directory tag in apache.conf like this:

<Directory /var/www/html>  
        Options Indexes FollowSymLinks
        AllowOverride None
        Require all granted
        RewriteEngine On
        RewriteRule ^test$ /test.php

Restart your web server with service apache2 restart and let's see if things work the way we want:

mywebsite.corp after modification

It works! Now, let's dive deeper into what the RewriteRule line does

^ is the character to indicate the root URL of your website
$ is the character to indicate the end of the URL to match

This has been a simple demonstration of what you can do and accomplish with mod_rewrite. However, you can do so much more with this module. Let's take a look at regular expressions and how to use with rewrite rules:

mywebsite.corp before second modification

With this directory block:

<Directory /var/www/html>  
        Options Indexes FollowSymLinks
        AllowOverride None
        Require all granted
        RewriteEngine On
        RewriteRule ^users/(\d+)$ /users.php?id=$1

We are able to create an URL rewrite that functions like this:

mywebsite.corp after second modification

As you can see, you can use capture groups to capture URL parameters which you can use like in the previous example. Pretty handy!

This is just a handful of possibilities, I hope that you find 'em useful. Thanks for reading!

Mirrored object storage with Minio

 •  Filed under mirrored, distributed, object storage, minio

Object storage is on the rise - and that's not without a reason. When you have to deal with large pieces of data, it is very necessary to keep your data organised. Minio offers a perfectly fitted solution that is open source and well documented: it's an Amazon S3 compatible object storage server written in Go. Want to learn more about the project? This is their homepage.

Setting up a single node is a piece of cake, as I shall demonstrate.

# Create a directory for storing 'objects'
mkdir ~/ObjectStorage

# Download and prepare the Minio mode
chmod +x minio  
export MINIO_ACCESS_KEY=yourusernamegoeshere  
export MINIO_SECRET_KEY=yourpasswordgoeshere

# Run the Minio node
./minio server ~/ObjectStorage

It's that easy! You can now access your Minio node at port 9000, try it out in your browser.

Running a clustered of two nodes is also very easy. Let's say that we have two servers in a subnet:


We also have two disks on each server, so that means we also have the following folders mounted to their own disk:

/opt/storage01: disk 1
/opt/storage02: disk 2

Run the following command on both servers to initialise the cluster:

MINIO_ACCESS_KEY=yourusernamegoeshere MINIO_SECRET_KEY=yourpasswordgoeshere ./minio server  

It automatically detects if a server is up or down, and automatically syncs data between nodes.

Compiling a LAMP stack for CentOS - Tutorial

 •  Filed under compiling, compile, LAMP, stack, CentOS

Getting the latest version of Apache, MySQL and PHP up and running on enterprise Linux operating systems can be a tough job. There are easy and quick ways to get around this, number 1 being the use of third-party repositories: but do you really want to use open-source software from third parties that might have been fiddled around with? I know, I can be a little bit paranoid about this. But, there is a second option: compiling the LAMP stack from source. This allows you to get control over:

  • The software versions used in the stack
  • The code that you compile (being able to review the code)
  • Enable and disable functionality that you (don't) need

Compiling the LAMP stack on CentOS from source is not that hard anymore, because I have written a script that automates the build process for you.

My script fetches all the source code that is needed to compile the LAMP stack and then compiles every dependency and/or program one by one. Afterwards, all the software gets installed in the /opt folder (you can always change this if you want).

So - without further ado - here's the script:


TIMEFORMAT='It took %R seconds to complete this process.'  
time {  
    # Defining variables

    # Build local yum cache and install development tools
    echo "[1/16] Installing the 'Developer Tools' yum group.."
    yum -y makecache >/dev/null 2>&1 && yum -y groupinstall "Development Tools" >/dev/null 2>&1

    # Install other necessary binaries
    echo "[2/16] Installing necessary binaries.."
    yum -y install curl cmake >/dev/null 2>&1

    # Download source tarballs
    echo "[3/16] Downloading source tarballs.."
    curl -o libxml2.tar.gz >/dev/null 2>&1
    curl -o openssl.tar.gz >/dev/null 2>&1
    curl -o zlib.tar.gz >/dev/null 2>&1
    curl -o httpd.tar.gz >/dev/null 2>&1
    curl -o pcre.tar.gz >/dev/null 2>&1
    curl -o apr.tar.gz >/dev/null 2>&1
    curl -o apr-util.tar.gz >/dev/null 2>&1
    curl -o ncurses.tar.gz >/dev/null 2>&1
    curl -o mysql.tar.gz -L >/dev/null 2>&1

    # Make source directories
    echo "[4/16] Creating source+build directories.."
    mkdir libxml2
    mkdir openssl
    mkdir zlib
    mkdir httpd
    mkdir pcre
    tar -xvzf httpd.tar.gz -C httpd --strip 1 >/dev/null 2>&1 && rm httpd.tar.gz
    mkdir httpd/srclib/apr
    mkdir httpd/srclib/apr-util
    mkdir ncurses
    mkdir mysql

    # Clone PHP sources
    echo "[5/16] Cloning PHP 7.1.5 from Github.."
    git clone -b php-7.1.5 php7 >/dev/null 2>&1

    # Extract source packages
    echo "[6/16] Extracting source tarballs.."
    tar -xvzf libxml2.tar.gz -C libxml2 --strip 1 >/dev/null 2>&1 && rm libxml2.tar.gz
    tar -xvzf openssl.tar.gz -C openssl --strip 1 >/dev/null 2>&1 && rm openssl.tar.gz
    tar -xvzf zlib.tar.gz -C zlib --strip 1 >/dev/null 2>&1 && rm zlib.tar.gz
    tar -xvzf pcre.tar.gz -C pcre --strip 1 >/dev/null 2>&1 && rm pcre.tar.gz
    tar -xvzf apr.tar.gz -C httpd/srclib/apr --strip 1 >/dev/null 2>&1 && rm apr.tar.gz
    tar -xvzf apr-util.tar.gz -C httpd/srclib/apr-util --strip 1 >/dev/null 2>&1 && rm apr-util.tar.gz
    tar -xvzf ncurses.tar.gz -C ncurses --strip 1 >/dev/null 2>&1 && rm ncurses.tar.gz
    tar -xvzf mysql.tar.gz -C mysql --strip 1 >/dev/null 2>&1 && rm mysql.tar.gz

    # Pre-compile ncurses
    echo "[7/16] Compiling ncurses.."
    mkdir /opt/ncurses
    cd ncurses
    ./configure --prefix=/opt/ncurses --with-shared >/dev/null 2>&1 
    make -j$threads >/dev/null 2>&1 && make install >/dev/null 2>&1
    cd ..

    # Pre-compile mysql
    echo "[8/16] Compiling MySQL.."
    mkdir /opt/mysql
    cd mysql
    cmake -DWITH_BOOST=boost/ -DCMAKE_INSTALL_PREFIX:PATH=/opt/mysql -DCURSES_LIBRARY=/opt/ncurses/lib/ -DCURSES_INCLUDE_PATH=/opt/ncurses/include . >/dev/null 2>&1
    make -j$threads >/dev/null 2>&1 && make install >/dev/null 2>&1
    cd ..

    # Pre-compile openssl
    echo "[9/16] Compiling OpenSSL.."
    mkdir /opt/openssl
    cd openssl
    ./config --prefix=/opt/openssl -fPIC >/dev/null 2>&1
    make -j$threads >/dev/null 2>&1 && make install >/dev/null 2>&1
    cd ..

    # Pre-compile libxml2
    echo "[10/16] Compiling LibXML2.."
    mkdir /opt/libxml2
    cd libxml2
    ./configure --without-python --prefix=/opt/libxml2 >/dev/null 2>&1
    make -j$threads >/dev/null 2>&1 && make install >/dev/null 2>&1
    cd ..

    # Pre-compile pcre
    echo "[11/16] Compiling PCRE.."
    mkdir /opt/pcre
    cd pcre
    ./configure --prefix=/opt/pcre >/dev/null 2>&1
    make -j$threads >/dev/null 2>&1 && make install >/dev/null 2>&1
    cd ..

    # Pre-compile zlib
    echo "[12/16] Compiling zlib.."
    mkdir /opt/zlib
    cd zlib
    ./configure --prefix=/opt/zlib >/dev/null 2>&1
    make -j$threads >/dev/null 2>&1 && make install >/dev/null 2>&1
    cd ..

    # Compile httpd
    echo "[13/16] Compiling httpd.."
    mkdir /opt/httpd
    cd httpd
    ./configure --with-pcre=/opt/pcre --with-z=/opt/zlib --with-libxml2=/opt/libxml2 --with-ssl=/opt/openssl --with-included-apr --enable-ssl --prefix=/opt/httpd >/dev/null 2>&1
    make -j$threads >/dev/null 2>&1 && make install >/dev/null 2>&1
    cd ..

    # Compile PHP7
    echo "[14/16] Compiling PHP7.."
    mkdir /opt/php7
    cd php7
    ./buildconf --force >/dev/null 2>&1 && ./configure --with-apxs2=/opt/httpd/bin/apxs --prefix=/opt/php7 --with-libxml-dir=/opt/libxml2 >/dev/null 2>&1
    make -j$threads >/dev/null 2>&1 && make install >/dev/null 2>&1
    cd .

    # Cleanup time!
    echo "[15/16] Cleaning up.."
    rm -rf php7 && rm -rf httpd && rm -rf zlib && rm -rf pcre && rm -rf libxml2 && rm -rf openssl && rm -rf mysql && rm -rf ncurses

    echo "[16/16] Finishing configuration for httpd, PHP7 and MySQL.."

    useradd -s /sbin/nologin mysql
    mkdir /opt/mysql/data
    chown -R mysql:mysql /opt/mysql/data
    mkdir /opt/mysql/data

    echo ""
    echo "Initializing MySQL:"
    echo ""

    /opt/mysql/bin/mysqld --initialize --datadir=/opt/mysql/data --user=mysql

    echo ""
    echo "You can start the MySQL daemon with:"
    echo "/opt/mysql/bin/mysqld --user=mysql --datadir=/opt/mysql/data --socket=/opt/mysql/data/mysql.sock"
    echo ""

    useradd -s /sbin/nologin www-data
    chown -R www-data:www-data /opt/httpd/htdocs
    sed -i 's/User daemon/User www-data/g' /opt/httpd/conf/httpd.conf
    sed -i 's/Group daemon/Group www-data/g' /opt/httpd/conf/httpd.conf

    mkdir /opt/certificates
    openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /opt/certificates/selfsigned.key -out /opt/certificates/selfsigned.crt -subj "/C=NL/ST=Zuid-Holland/L=Dordrecht/ Department/" >/dev/null 2>&1
    echo "" >> /opt/httpd/conf/httpd.conf
    echo "# Appended at: ${currentdate}" >> /opt/httpd/conf/httpd.conf
    echo "LoadModule ssl_module modules/" >> /opt/httpd/conf/httpd.conf
    echo "<FilesMatch \.php$>" >> /opt/httpd/conf/httpd.conf
    echo "  SetHandler application/x-httpd-php" >> /opt/httpd/conf/httpd.conf
    echo "</FilesMatch>" >> /opt/httpd/conf/httpd.conf
    echo "Listen 443" >> /opt/httpd/conf/httpd.conf
    echo "<VirtualHost *:443>" >> /opt/httpd/conf/httpd.conf
    echo "  SSLEngine on" >> /opt/httpd/conf/httpd.conf
    echo "  SSLCertificateFile /opt/certificates/selfsigned.crt" >> /opt/httpd/conf/httpd.conf
    echo "  SSLCertificateKeyFile /opt/certificates/selfsigned.key" >> /opt/httpd/conf/httpd.conf
    echo "</VirtualHost>" >> /opt/httpd/conf/httpd.conf

    echo "You can start the HTTPD daemon with:"
    echo "/opt/httpd/bin/httpd"
    echo ""
    echo "Done!"
    echo ""
    echo ""

This has been tested on CentOS 7 x64.

You can start MySQL with this command:

/opt/mysql/bin/mysqld --user=mysql --datadir=/opt/mysql/data --socket=/opt/mysql/data/mysql.sock

And you can start httpd with this command:


Should you run into an issue: feel free to leave a comment!