diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..866caa5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*__pycache__ +*pyc +*pyo +*so diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5f75c17 --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +ReverseProxy/tlsheader.so: tlsheader.c + gcc tlsheader.c -o ReverseProxy/tlsheader.so -fPIC -shared diff --git a/ReverseProxy/__init__.py b/ReverseProxy/__init__.py new file mode 100644 index 0000000..5b31d83 --- /dev/null +++ b/ReverseProxy/__init__.py @@ -0,0 +1,2 @@ +from .reactor import reactor +from . import _click diff --git a/ReverseProxy/_click.py b/ReverseProxy/_click.py new file mode 100644 index 0000000..7348d9a --- /dev/null +++ b/ReverseProxy/_click.py @@ -0,0 +1,23 @@ +import click +import functools +click.core._verify_python3_env = lambda *x: None + +_main = click.core.BaseCommand.main + + +@functools.partial(setattr, click.core.BaseCommand, 'main') +def main(self, args: []=None, prog_name: str=None, complete_var: str=None, standalone_mode: bool=True, **extra): + if args and prog_name is None: + prog_name = args[0] + args = args[1:] + else: + args = None + + if not standalone_mode: + ctx = self.make_context(prog_name, args) + with ctx: + self.invoke(ctx) + return ctx + + return _main(self, args, prog_name, complete_var, standalone_mode, **extra) + diff --git a/ReverseProxy/_proxy.py b/ReverseProxy/_proxy.py new file mode 100644 index 0000000..8d57c9b --- /dev/null +++ b/ReverseProxy/_proxy.py @@ -0,0 +1,25 @@ +import socket +import array + + +def send_fd(sock, fd, msg=b"0"): + return sock.sendmsg([msg], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, array.array("i", [fd]))]) + + +def recv_fd(sock, msglen=1): + fds = array.array("i") + msg, ancdata, flags, addr = sock.recvmsg(msglen, socket.CMSG_LEN(fds.itemsize)) + for cmsg_level, cmsg_type, cmsg_data in ancdata: + if (cmsg_level == socket.SOL_SOCKET and cmsg_type == socket.SCM_RIGHTS): + # Append data, ignoring any truncated integers at the end. + fds.fromstring(cmsg_data[:len(cmsg_data) - (len(cmsg_data) % fds.itemsize)]) + return list(fds)[0] + + +def send_fd_to(path, sock): + if not isinstance(sock, int): + sock = sock.fileno() + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + s.connect(path) + send_fd(s, sock) + diff --git a/ReverseProxy/client/__init__.py b/ReverseProxy/client/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ReverseProxy/client/__main__.py b/ReverseProxy/client/__main__.py new file mode 100644 index 0000000..e238843 --- /dev/null +++ b/ReverseProxy/client/__main__.py @@ -0,0 +1,23 @@ +import sys + +from twisted.internet.protocol import ServerFactory + +from .configuration import Configuration +from .click import cli +from ..reactor import reactor +from .receiver import FDReceiverProtocol +import click.core +click.core.Exit = SystemExit + +def __main__(argv=sys.argv): + from twisted.python.log import startLogging + startLogging(sys.stderr) + configuration: Configuration = cli.main(argv, standalone_mode=False).obj + reactor.listenUNIX(configuration.listenPort, ServerFactory.forProtocol(lambda: FDReceiverProtocol(configuration))) + return reactor.run() + +if __name__=='__main__': + argv = sys.argv + argv[0] = __package__ + raise SystemExit(__main__(argv)) + diff --git a/ReverseProxy/client/click.py b/ReverseProxy/client/click.py new file mode 100644 index 0000000..747fcac --- /dev/null +++ b/ReverseProxy/client/click.py @@ -0,0 +1,41 @@ +from click import group, option, pass_context, pass_obj +from twisted.protocols import tls + +from twisted.python.filepath import FilePath +import importlib.util + + +from .configuration import Configuration +from .ssl import makeContextFactory + + +@group(invoke_without_command=True, chain=True) +@option("--listenPort", type=str, required=True) +@pass_context +def cli(context, listenport): + context.obj = Configuration(listenport) + + +@cli.command() +@pass_obj +@option("--privkey", type=str, required=True) +@option("--cert", type=str, required=True) +@option("--chain", type=str, required=True) +def ssl(conf: Configuration, privkey, cert, chain): + conf.certificate = FilePath(cert) + conf.chain = FilePath(chain) + conf.privateKey = FilePath(privkey) + conf.sslFactory = makeContextFactory(conf) + if conf.factory: + conf.tlsFactory = tls.TLSMemoryBIOFactory(conf.sslFactory, False, conf.factory) + + +@cli.command() +@pass_obj +@option("--application", type=str, required=True) +def launch(conf: Configuration, application: str): + module, attribute = application.rsplit('.', 1) + mod = importlib.import_module(module) + conf.factory = getattr(mod, attribute) + if conf.sslFactory: + conf.tlsFactory = tls.TLSMemoryBIOFactory(conf.sslFactory, False, conf.factory) diff --git a/ReverseProxy/client/configuration.py b/ReverseProxy/client/configuration.py new file mode 100644 index 0000000..275b8e3 --- /dev/null +++ b/ReverseProxy/client/configuration.py @@ -0,0 +1,12 @@ +import attr + + +@attr.s() +class Configuration(object): + listenPort: str = attr.ib() + sslFactory = attr.ib(None) + privateKey = attr.ib(None) + certificate = attr.ib(None) + chain = attr.ib(None) + factory = attr.ib(None) + tlsFactory = attr.ib(None) diff --git a/ReverseProxy/client/receiver.py b/ReverseProxy/client/receiver.py new file mode 100644 index 0000000..b5b8a8d --- /dev/null +++ b/ReverseProxy/client/receiver.py @@ -0,0 +1,32 @@ +from twisted.internet.interfaces import IFileDescriptorReceiver +from twisted.internet.protocol import Protocol +from zope.interface import implementer + +import socket + +from .. import reactor +from ..parsers import is_ssl + + +@implementer(IFileDescriptorReceiver) +class FDReceiverProtocol(Protocol): + descriptor = None + + def __init__(self, conf): + self.conf = conf + + def fileDescriptorReceived(self, descriptor): + # Record the descriptor sent to us + self.descriptor = descriptor + sock = socket.fromfd(descriptor, socket.AF_INET, socket.SOCK_STREAM) + try: + peek = sock.recv(1024, socket.MSG_PEEK) + finally: + sock.close() + + if is_ssl(peek) and self.conf.tlsFactory: + reactor.adoptStreamConnection(descriptor, socket.AF_INET, self.conf.tlsFactory) + + else: + reactor.adoptStreamConnection(descriptor, socket.AF_INET, self.conf.factory) + diff --git a/ReverseProxy/client/ssl.py b/ReverseProxy/client/ssl.py new file mode 100644 index 0000000..85bb39a --- /dev/null +++ b/ReverseProxy/client/ssl.py @@ -0,0 +1,30 @@ +from twisted.internet import ssl + + +# noinspection PyMissingConstructor +class OpenSSLContextFactory(ssl.DefaultOpenSSLContextFactory): + def __init__(self, privateKeyFileName, certificateFileName, chainFileName): + self.sslmethod = ssl.SSL.SSLv23_METHOD + self.privateKeyFileName = privateKeyFileName + self.certificateFileName = certificateFileName + self.chainFileName = chainFileName + self.cacheContext() + + def cacheContext(self): + if self._context is None: + ctx = ssl.SSL.Context(self.sslmethod) + # Disallow SSLv2! It's insecure! SSLv3 has been around since + # 1996. It's time to move on. + ctx.set_options(ssl.SSL.OP_NO_SSLv2) + ctx.set_options(ssl.SSL.OP_NO_SSLv3) + ctx.set_options(ssl.SSL.OP_NO_TLSv1) + ctx.set_options(ssl.SSL.OP_NO_TLSv1_1) + ctx.use_certificate_chain_file(self.chainFileName) + ctx.use_certificate_file(self.certificateFileName) + ctx.use_privatekey_file(self.privateKeyFileName) + + self._context = ctx + + +def makeContextFactory(conf): + return OpenSSLContextFactory(conf.privateKey.path, conf.certificate.path, conf.chain.path) diff --git a/ReverseProxy/parsers.py b/ReverseProxy/parsers.py new file mode 100644 index 0000000..4158ff7 --- /dev/null +++ b/ReverseProxy/parsers.py @@ -0,0 +1,51 @@ +from twisted.python.filepath import FilePath +import ctypes +tlsheader = ctypes.cdll.LoadLibrary(FilePath(__file__).sibling('tlsheader.so').path) +tlsheader.parseHeader.argtypes = [ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p] + + +def parse_tls(data): + out = ctypes.create_string_buffer(255) + datalen = len(data) + success = tlsheader.parseHeader(data, datalen, out) + return success, out.value + + +def parse_http(data): + data = data.replace(b'\r\n', b'\n').split(b'\n\n')[0] + + if b'Host' not in data: + return None + + host = data.split(b'Host:', 1)[1].split(b'\n')[0].strip().split(b':')[0] + return host + + +def parse_host(data): + if data[:3].isalpha(): + try: + host = parse_http(data) + if host: + return host + success, host = parse_tls(data) + if success > -1: + return host + except: + return None + else: + success, host = parse_tls(data) + if success > -1: + return host + if success == -2: + return None + + return parse_http(data) + + +def is_ssl(data): + success, host = parse_tls(data) + if success > -1 or success == -2: + return True + + +__all__ = ['parse_host', 'is_ssl'] diff --git a/ReverseProxy/reactor.py b/ReverseProxy/reactor.py new file mode 100644 index 0000000..90b11d4 --- /dev/null +++ b/ReverseProxy/reactor.py @@ -0,0 +1,6 @@ +from twisted.internet.epollreactor import EPollReactor +from twisted.internet.main import installReactor +reactor: EPollReactor = EPollReactor() + +installReactor(reactor) + diff --git a/ReverseProxy/server/__init__.py b/ReverseProxy/server/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ReverseProxy/server/__main__.py b/ReverseProxy/server/__main__.py new file mode 100644 index 0000000..4081753 --- /dev/null +++ b/ReverseProxy/server/__main__.py @@ -0,0 +1,22 @@ +import sys + +from .click import cli +from .proxy import run +import click.core +click.core.Exit = SystemExit + + +def __main__(argv=sys.argv): + from twisted.python.log import startLogging + startLogging(sys.stderr) + configuration = cli.main(argv, standalone_mode=False).obj + return run(configuration) + + +if __name__=='__main__': + argv = sys.argv + argv[0] = __package__ + + raise SystemExit(__main__(argv)) + + diff --git a/ReverseProxy/server/click.py b/ReverseProxy/server/click.py new file mode 100644 index 0000000..c08d1eb --- /dev/null +++ b/ReverseProxy/server/click.py @@ -0,0 +1,18 @@ +from click import group, option, pass_context, pass_obj +from .configuration import Configuration + + +@group(chain=True) +@option("--listenPort", default=443, type=int) +@option("--listenInterface", default="", type=str) +@pass_context +def cli(context, listenport, listeninterface): + context.obj = Configuration(listenport, listeninterface) + + +@cli.command() +@pass_obj +@option("--hostname", type=str, required=True) +@option("--socket", type=str, required=True) +def vhost(conf: Configuration, socket: str, hostname: str): + conf.addVHost(hostname, socket) diff --git a/ReverseProxy/server/configuration.py b/ReverseProxy/server/configuration.py new file mode 100644 index 0000000..b8c26c7 --- /dev/null +++ b/ReverseProxy/server/configuration.py @@ -0,0 +1,19 @@ +import attr +import re + + +@attr.s() +class VHost(object): + socket: str = attr.ib() + hostname = attr.ib(converter=lambda x: re.compile(bytes(x, 'charmap'))) + + +@attr.s() +class Configuration(object): + listenPort: int = attr.ib() + listenInterface: str = attr.ib() + + vhosts: list = attr.ib(default=attr.Factory(list)) + + def addVHost(self, hostname, socket): + self.vhosts.append(VHost(socket, hostname)) diff --git a/ReverseProxy/server/proxy.py b/ReverseProxy/server/proxy.py new file mode 100644 index 0000000..f984818 --- /dev/null +++ b/ReverseProxy/server/proxy.py @@ -0,0 +1,77 @@ +from twisted.internet.endpoints import UNIXClientEndpoint +from twisted.internet.protocol import ServerFactory, Protocol, Factory +from twisted.internet import error +import socket +from functools import partial + +from ..reactor import reactor +from .configuration import Configuration +from ..parsers import parse_host +from .._proxy import send_fd + + +class ProxyProtocol(Protocol): + def __init__(self, parent): + self.parent = parent + + def fakeRead(self): + sock = self.transport.socket + try: + data = sock.recv(self.transport.bufferSize, socket.MSG_PEEK) + except socket.error as se: + if se.args[0] == socket.EWOULDBLOCK: + return + else: + return error.getConnectError(se) + + host = parse_host(data) + self.parent.passTo(host, self) + + def connectionMade(self): + self.transport.doRead = partial(self.fakeRead) + + +class ProxyFactory(ServerFactory): + def __init__(self, configuration: Configuration): + self.vhosts = configuration.vhosts + self.sockets = set() + + def buildProtocol(self, addr): + return ProxyProtocol(self) + + def passTo(self, host, protocol): + orig_socket = protocol.transport.socket + + if not host: + host = b'default' + try: + for vhost in self.vhosts: + if vhost.hostname.match(host): + dest_socket = vhost.socket + endpoint = UNIXClientEndpoint(reactor, dest_socket) + connected = endpoint.connect(Factory.forProtocol(Protocol)) + connected.addCallback(partial(self.passClientTo, orig_socket)) + connected.addBoth(partial(self.disconnect, protocol)) + return + finally: + pass + # protocol.transport.socket.close() + + @staticmethod + def passClientTo(socket, protocol): + try: + send_fd(protocol.transport.socket, socket.fileno()) + finally: + return protocol + + @staticmethod + def disconnect(orig_protocol, protocol): + if isinstance(protocol, Protocol): + protocol.transport.loseConnection() + orig_protocol.transport._shouldShutdown = False + orig_protocol.transport.loseConnection() + + +def run(configuration: Configuration): + reactor.listenTCP(configuration.listenPort, ProxyFactory(configuration), interface=configuration.listenInterface) + reactor.run() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..66127f8 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +service_identity +click +twisted +attrs diff --git a/tlsheader.c b/tlsheader.c new file mode 100644 index 0000000..3a4ef9d --- /dev/null +++ b/tlsheader.c @@ -0,0 +1,291 @@ +#include "tlslogger.h" + +#include +/* + * Copyright (c) 2011 and 2012, Dustin Lundquist + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This is a minimal TLS implementation intended only to parse the server name + * extension. This was created based primarily on Wireshark dissection of a + * TLS handshake and RFC4366. + */ + +#ifndef TLS_H +#define TLS_H + +#ifndef PROTOCOL_H +#define PROTOCOL_H + +#include + +struct Protocol { + const char *const name; + const uint16_t default_port; + int (*const parse_packet)(const char*, size_t, char **); + const char *const abort_message; + const size_t abort_message_len; +}; + +#endif + +const struct Protocol *const tls_protocol; + +#endif + + +#include +#include /* malloc() */ +#include /* strncpy() */ +#include +#include + + +#define SERVER_NAME_LEN 256 +#define TLS_HEADER_LEN 5 +#define TLS_HANDSHAKE_CONTENT_TYPE 0x16 +#define TLS_HANDSHAKE_TYPE_CLIENT_HELLO 0x01 + +#ifndef MIN +#define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) +#endif + +static const char tls_alert[] = { + 0x15, /* TLS Alert */ + 0x03, 0x01, /* TLS version */ + 0x00, 0x02, /* Payload length */ + 0x02, 0x28, /* Fatal, handshake failure */ +}; + +static int parse_tls_header(const uint8_t*, size_t, char **); +static int parse_extensions(const uint8_t*, size_t, char **); +static int parse_server_name_extension(const uint8_t*, size_t, char **); + +static const struct Protocol tls_protocol_st = { + .name = "tls", + .default_port = 443, + .parse_packet = (int (*const)(const char *, size_t, char **))&parse_tls_header, + .abort_message = tls_alert, + .abort_message_len = sizeof(tls_alert) +}; +const struct Protocol *const tls_protocol = &tls_protocol_st; + + +/* Parse a TLS packet for the Server Name Indication extension in the client + * hello handshake, returning the first servername found (pointer to static + * array) + * + * Returns: + * >=0 - length of the hostname and updates *hostname + * caller is responsible for freeing *hostname + * -1 - Incomplete request + * -2 - No Host header included in this request + * -3 - Invalid hostname pointer + * -4 - malloc failure + * < -4 - Invalid TLS client hello + */ +static int +parse_tls_header(const uint8_t *data, size_t data_len, char **hostname) { + uint8_t tls_content_type; + uint8_t tls_version_major; + uint8_t tls_version_minor; + size_t pos = TLS_HEADER_LEN; + size_t len; + + if (hostname == NULL) + return -3; + + /* Check that our TCP payload is at least large enough for a TLS header */ + if (data_len < TLS_HEADER_LEN) + return -1; + + /* SSL 2.0 compatible Client Hello + * + * High bit of first byte (length) and content type is Client Hello + * + * See RFC5246 Appendix E.2 + */ + if (data[0] & 0x80 && data[2] == 1) { + printf("Received SSL 2.0 Client Hello which can not support SNI.\n"); + return -2; + } + + tls_content_type = data[0]; + if (tls_content_type != TLS_HANDSHAKE_CONTENT_TYPE) { + printf("Request did not begin with TLS handshake.\n"); + return -5; + } + + tls_version_major = data[1]; + tls_version_minor = data[2]; + if (tls_version_major < 3) { + printf("Received SSL %d.%d handshake which can not support SNI.\n", + tls_version_major, tls_version_minor); + + return -2; + } + + /* TLS record length */ + len = ((size_t)data[3] << 8) + + (size_t)data[4] + TLS_HEADER_LEN; + data_len = MIN(data_len, len); + + /* Check we received entire TLS record length */ + if (data_len < len) + return -1; + + /* + * Handshake + */ + if (pos + 1 > data_len) { + return -5; + } + if (data[pos] != TLS_HANDSHAKE_TYPE_CLIENT_HELLO) { + printf("Not a client hello\n"); + + return -5; + } + + /* Skip past fixed length records: + 1 Handshake Type + 3 Length + 2 Version (again) + 32 Random + to Session ID Length + */ + pos += 38; + + /* Session ID */ + if (pos + 1 > data_len) + return -5; + len = (size_t)data[pos]; + pos += 1 + len; + + /* Cipher Suites */ + if (pos + 2 > data_len) + return -5; + len = ((size_t)data[pos] << 8) + (size_t)data[pos + 1]; + pos += 2 + len; + + /* Compression Methods */ + if (pos + 1 > data_len) + return -5; + len = (size_t)data[pos]; + pos += 1 + len; + + if (pos == data_len && tls_version_major == 3 && tls_version_minor == 0) { + printf("Received SSL 3.0 handshake without extensions\n"); + return -2; + } + + /* Extensions */ + if (pos + 2 > data_len) + return -5; + len = ((size_t)data[pos] << 8) + (size_t)data[pos + 1]; + pos += 2; + + if (pos + len > data_len) + return -5; + return parse_extensions(data + pos, len, hostname); +} + +static int +parse_extensions(const uint8_t *data, size_t data_len, char **hostname) { + size_t pos = 0; + size_t len; + + /* Parse each 4 bytes for the extension header */ + while (pos + 4 <= data_len) { + /* Extension Length */ + len = ((size_t)data[pos + 2] << 8) + + (size_t)data[pos + 3]; + + /* Check if it's a server name extension */ + if (data[pos] == 0x00 && data[pos + 1] == 0x00) { + /* There can be only one extension of each type, so we break + our state and move p to beinnging of the extension here */ + if (pos + 4 + len > data_len) + return -5; + return parse_server_name_extension(data + pos + 4, len, hostname); + } + pos += 4 + len; /* Advance to the next extension header */ + } + /* Check we ended where we expected to */ + if (pos != data_len) + return -5; + + return -2; +} + +static int +parse_server_name_extension(const uint8_t *data, size_t data_len, + char **hostname) { + size_t pos = 2; /* skip server name list length */ + size_t len; + + while (pos + 3 < data_len) { + len = ((size_t)data[pos + 1] << 8) + + (size_t)data[pos + 2]; + + if (pos + 3 + len > data_len) + return -5; + + switch (data[pos]) { /* name type */ + case 0x00: /* host_name */ + *hostname = malloc(len + 1); + if (*hostname == NULL) { + printf("malloc() failure\n"); + return -4; + } + + strncpy(*hostname, (const char *)(data + pos + 3), len); + + (*hostname)[len] = '\0'; + + return len; + default: + printf("Unknown server name extension name type: %d\n", + data[pos]); + } + pos += 3 + len; + } + /* Check we ended where we expected to */ + if (pos != data_len) + return -5; + + return -2; +} + + +int parseHeader(const uint8_t *data, size_t data_len, char* out) { + char* hostname; + + int status = parse_tls_header(data, data_len, &hostname); + if (status < 0) { + return status; + } + memccpy(out, hostname, 0, status); + free(hostname); + return status; +} diff --git a/tlslogger.h b/tlslogger.h new file mode 100644 index 0000000..d565729 --- /dev/null +++ b/tlslogger.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2013, Dustin Lundquist + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef LOGGER_H +#define LOGGER_H + + +#define LOG_EMERG 0 +#define LOG_ALERT 1 +#define LOG_CRIT 2 +#define LOG_ERR 3 +#define LOG_WARNING 4 +#define LOG_NOTICE 5 +#define LOG_INFO 6 +#define LOG_DEBUG 7 + + +/* Shorthand to log to global error log */ +void fatal(const char *, ...) + __attribute__ ((format (printf, 1, 2))) + __attribute__ ((noreturn)); +void err(const char *, ...) + __attribute__ ((format (printf, 1, 2))); +void warn(const char *, ...) + __attribute__ ((format (printf, 1, 2))); +void notice(const char *, ...) + __attribute__ ((format (printf, 1, 2))); +void info(const char *, ...) + __attribute__ ((format (printf, 1, 2))); +void debug(const char *, ...); + +#endif