1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
//
//  httpd-util - A set of utilities for building HTTP microservices
//  Copyright (C) 2019  packom.net
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.
//

extern crate pnet;
#[macro_use]
extern crate log;
extern crate openssl;
extern crate signal_hook;
#[macro_use]
extern crate clap;

#[cfg(test)]
mod tests;

use clap::App;
use openssl::error::ErrorStack;
use openssl::ssl::{SslAcceptor, SslAcceptorBuilder, SslMethod};
use std::env;
use std::net::{SocketAddr, ToSocketAddrs};
use signal_hook::{register, SIGINT, SIGTERM};

const AFTER_HELP: &str = "Configured using environment variables:
    [SERVER_IP] - Local IP address or domain name to bind to
    [SERVER_PORT] - Local port to bind to
    [HTTPS] - HTTPS should be used (instead of HTTP) for this microservice
    [RUST_LOG] - Logging level, one of error, warn, info, debug, trace";

pub fn init_app(name: &str, author: &str, about: &str, args: Vec<&str>, envs: Vec<&'static str>) {
    env_logger::init();

    let version = format!("{}, {}", crate_version!(), openssl::version::version());
    info!("Version {}", version);

    let mut after_help = AFTER_HELP.to_string();
    for arg in args {
        after_help += &format!("\n    {}", arg);
    }

    let _matches = App::new(name)
        .author(author)
        .version(version.as_str())
        .about(about)
        .after_help(after_help.as_str())
        .get_matches();

    log_env(envs);

    reg_for_sigs();
}

const SSL_KEY_VAR: &str = "SSL_KEY";
const SSL_KEY_DEF: &str = "/ssl/key.pem";
const SSL_CERT_VAR: &str = "SSL_CERT";
const SSL_CERT_DEF: &str = "/ssl/cert.pem";
const SERVER_IP_VAR: &str = "SERVER_IP";
const SERVER_IP_DEF: &str = "0.0.0.0";
const SERVER_PORT_VAR: &str = "SERVER_PORT";
const SERVER_PORT_DEF: &str = "8080";
const HTTPS: &str = "HTTPS";
const RUST_LOG: &str = "RUST_LOG";
const NOT_PRESENT: &str = "<not present>";

pub fn ssl() -> Result<SslAcceptorBuilder, ErrorStack> {
    // Builds an SSL implementation for Simple HTTPS from some hard-coded file names
    let mut ssl = SslAcceptor::mozilla_intermediate_v5(SslMethod::tls())?;

    // Server authentication
    let key = get_env_str(SSL_KEY_VAR, SSL_KEY_DEF);
    let cert = get_env_str(SSL_CERT_VAR, SSL_CERT_DEF);
    debug!("Loading SSL key from {}", key);
    ssl.set_private_key_file(key, openssl::ssl::SslFiletype::PEM)?;
    debug!("Loading SSL cert from {}", cert);
    ssl.set_certificate_chain_file(cert)?;
    ssl.check_private_key()?;

    Ok(ssl)
}

fn get_env_str(var: &str, default: &str) -> String {
    match env::var(var) {
        Ok(rsp) => rsp,
        Err(_) => String::from(default),
    }
}

pub fn log_env(envs: Vec<&str>) {
    info!("Environment configuration:");
    for env in get_env_strings(envs) {
        info!("{}", env);
    }
}

pub fn get_env_strings(envs: Vec<&str>) -> Vec<String> {
    let mut envs_i = vec![SSL_KEY_VAR, SSL_CERT_VAR, SERVER_IP_VAR, SERVER_PORT_VAR, HTTPS, RUST_LOG];
    for env in envs {
        envs_i.push(env)
    }
    envs_i
        .iter()
        .map(|val| {
            format!(
                "{}: {}",
                val,
                get_env_str(val, NOT_PRESENT)
            )
        })
        .collect::<Vec<String>>()
}

pub fn https() -> bool {
    match env::var(HTTPS) {
        Ok(_) => true,
        Err(_) => false,
    }
}

pub fn get_addr(ip_var: &str, ip_default: &str, port_var: &str, port_default: &str) -> SocketAddr {
    let addr = [get_ip(ip_var, ip_default), get_port(port_var, port_default)].join(":");
    match addr.to_socket_addrs() {
        Ok(a) => a.as_slice()[0],
        Err(e) => panic!("Failed to get address: {}", e),
    }
}

pub fn get_server_addr() -> SocketAddr {
    get_addr(
        SERVER_IP_VAR,
        SERVER_IP_DEF,
        SERVER_PORT_VAR,
        SERVER_PORT_DEF,
    )
}

fn get_ip(ip_var: &str, ip_default: &str) -> String {
    match env::var(ip_var) {
        Ok(ip) => {
            // Test this IP is of the correct format
            match [&ip, SERVER_PORT_DEF].join(":").to_socket_addrs() {
                Ok(_) => ip,
                Err(_) => get_fallback_ip(ip_default),
            }
        }
        Err(_) => get_fallback_ip(ip_default),
    }
}

// Returns the IPv4 address of the first non-loopback interface, or passed in
// default as last resort.
fn get_fallback_ip(ip_default: &str) -> String {
    let mut ip = String::from(ip_default);
    for int in pnet::datalink::interfaces() {
        if !int.name.starts_with("lo") {
            for ipn in int.ips {
                if ipn.is_ipv4() {
                    ip = ipn.ip().to_string();
                    break;
                }
            }
        }
    }
    debug!("Using fallback IP address {}", ip);
    ip
}

fn get_port(port_var: &str, port_default: &str) -> String {
    match env::var(port_var) {
        Ok(port) => {
            // Test this port is of the correct format
            match [SERVER_IP_DEF, &port].join(":").parse::<SocketAddr>() {
                Ok(_) => port,
                Err(_) => String::from(port_default),
            }
        }
        Err(_) => String::from(port_default),
    }
}

macro_rules! reg_sig {
    ($sig: expr, $fn: tt) => {
        unsafe { register($sig, || $fn()) }
            .and_then(|_| {
                debug!("Registered for {}", stringify!($sig));
                Ok(())
            })
            .or_else(|e| {
                warn!("Failed to register for {} {:?}", stringify!($sig), e);
                Err(e)
            })
            .ok();
    }
}

macro_rules! handle_sig {
    ($sig: expr, $st: tt) => {
        warn!("{} caught - exiting", stringify!($sig));
        std::process::exit(128 + $sig);
    }
}

pub fn reg_for_sigs() {
    reg_sig!(SIGINT, on_sigint);
    reg_sig!(SIGTERM, on_sigterm);
}

fn on_sigint() {
    handle_sig!(SIGINT, "SIGINT");
}

fn on_sigterm() {
    handle_sig!(SIGTERM, "SIGTERM");
}