mirror of
https://github.com/moparisthebest/sslh
synced 2024-11-14 05:05:02 -05:00
9bcb2cdd7a
Added support for configuration file. New protocol probes can be defined using regular expressions that match the first packet sent by the client. sslh now connects timed out connections to the first configured protocol instead of 'ssh' (just make sure ssh is the first defined protocol). sslh now tries protocols in the order in which they are defined (just make sure sslh is the last defined protocol).
217 lines
6.1 KiB
C
217 lines
6.1 KiB
C
/*
|
|
# probe.c: Code for probing protocols
|
|
#
|
|
# Copyright (C) 2007-2012 Yves Rutschle
|
|
#
|
|
# 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 2 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.
|
|
#
|
|
# The full text for the General Public License is here:
|
|
# http://www.gnu.org/licenses/gpl.html
|
|
*/
|
|
|
|
#include <regex.h>
|
|
#include "probe.h"
|
|
|
|
|
|
|
|
static int is_ssh_protocol(const char *p, int len, struct proto*);
|
|
static int is_openvpn_protocol(const char *p, int len, struct proto*);
|
|
static int is_tinc_protocol(const char *p, int len, struct proto*);
|
|
static int is_xmpp_protocol(const char *p, int len, struct proto*);
|
|
static int is_http_protocol(const char *p, int len, struct proto*);
|
|
static int is_true(const char *p, int len, struct proto* proto) { return 1; }
|
|
|
|
/* Table of protocols that have a built-in probe
|
|
*/
|
|
static struct proto builtins[] = {
|
|
/* description service saddr probe */
|
|
{ "ssh", "sshd", NULL, is_ssh_protocol},
|
|
{ "openvpn", NULL, NULL, is_openvpn_protocol },
|
|
{ "tinc", NULL, NULL, is_tinc_protocol },
|
|
{ "xmpp", NULL, NULL, is_xmpp_protocol },
|
|
{ "http", NULL, NULL, is_http_protocol },
|
|
{ "ssl", NULL, NULL, is_true }
|
|
};
|
|
|
|
static struct proto *protocols;
|
|
|
|
struct proto* get_builtins(void) {
|
|
return builtins;
|
|
}
|
|
|
|
int get_num_builtins(void) {
|
|
return ARRAY_SIZE(builtins);
|
|
}
|
|
|
|
/* Returns the protocol to connect to in case of timeout; conventionaly this is
|
|
* the first protocol specified (but maybe we'll make it more explicit some
|
|
* day)
|
|
*/
|
|
struct proto* timeout_protocol(void) {
|
|
return protocols;
|
|
}
|
|
|
|
/* returns the first protocol (caller can then follow the *next pointers) */
|
|
struct proto* get_first_protocol(void)
|
|
{
|
|
return protocols;
|
|
}
|
|
|
|
void set_protocol_list(struct proto* prots)
|
|
{
|
|
protocols = prots;
|
|
}
|
|
|
|
/* Is the buffer the beginning of an SSH connection? */
|
|
static int is_ssh_protocol(const char *p, int len, struct proto *proto)
|
|
{
|
|
if (!strncmp(p, "SSH-", 4)) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Is the buffer the beginning of an OpenVPN connection?
|
|
* (code lifted from OpenVPN port-share option)
|
|
*/
|
|
static int is_openvpn_protocol (const char*p,int len, struct proto *proto)
|
|
{
|
|
#define P_OPCODE_SHIFT 3
|
|
#define P_CONTROL_HARD_RESET_CLIENT_V2 7
|
|
if (len >= 3)
|
|
{
|
|
return p[0] == 0
|
|
&& p[1] >= 14
|
|
&& p[2] == (P_CONTROL_HARD_RESET_CLIENT_V2<<P_OPCODE_SHIFT);
|
|
}
|
|
else if (len >= 2)
|
|
{
|
|
return p[0] == 0 && p[1] >= 14;
|
|
}
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
/* Is the buffer the beginning of a tinc connections?
|
|
* (protocol is undocumented, but starts with "0 " in 1.0.15)
|
|
* */
|
|
static int is_tinc_protocol( const char *p, int len, struct proto *proto)
|
|
{
|
|
return !strncmp(p, "0 ", 2);
|
|
}
|
|
|
|
/* Is the buffer the beginning of a jabber (XMPP) connections?
|
|
* (Protocol is documented (http://tools.ietf.org/html/rfc6120) but for lazy
|
|
* clients, just checking first frame containing "jabber" in xml entity)
|
|
* */
|
|
static int is_xmpp_protocol( const char *p, int len, struct proto *proto)
|
|
{
|
|
return strstr(p, "jabber") ? 1 : 0;
|
|
}
|
|
|
|
static int probe_http_method(const char *p, const char *opt)
|
|
{
|
|
return !strcmp(p, opt);
|
|
}
|
|
|
|
/* Is the buffer the beginning of an HTTP connection? */
|
|
static int is_http_protocol(const char *p, int len, struct proto *proto)
|
|
{
|
|
/* If it's got HTTP in the request (HTTP/1.1) then it's HTTP */
|
|
if (strstr(p, "HTTP"))
|
|
return 1;
|
|
|
|
/* Otherwise it could be HTTP/1.0 without version: check if it's got an
|
|
* HTTP method (RFC2616 5.1.1) */
|
|
probe_http_method(p, "OPTIONS");
|
|
probe_http_method(p, "GET");
|
|
probe_http_method(p, "HEAD");
|
|
probe_http_method(p, "POST");
|
|
probe_http_method(p, "PUT");
|
|
probe_http_method(p, "DELETE");
|
|
probe_http_method(p, "TRACE");
|
|
probe_http_method(p, "CONNECT");
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int regex_probe(const char *p, int len, struct proto *proto)
|
|
{
|
|
regex_t** probe_list = (regex_t**)(proto->data);
|
|
int i=0;
|
|
|
|
while (probe_list[i]) {
|
|
if (!regexec(probe_list[i], p, 0, NULL, 0)) {
|
|
return 1;
|
|
}
|
|
i++;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Read the beginning of data coming from the client connection and check if
|
|
* it's a known protocol. Then leave the data on the defered
|
|
* write buffer of the connection and returns a pointer to the protocol
|
|
* structure
|
|
*/
|
|
struct proto* probe_client_protocol(struct connection *cnx)
|
|
{
|
|
char buffer[BUFSIZ];
|
|
struct proto *p;
|
|
int n;
|
|
|
|
n = read(cnx->q[0].fd, buffer, sizeof(buffer));
|
|
/* It's possible that read() returns an error, e.g. if the client
|
|
* disconnected between the previous call to select() and now. If that
|
|
* happens, we just connect to the default protocol so the caller of this
|
|
* function does not have to deal with a specific failure condition (the
|
|
* connection will just fail later normally). */
|
|
if (n > 0) {
|
|
defer_write(&cnx->q[1], buffer, n);
|
|
|
|
for (p = protocols; p; p = p->next) {
|
|
if (p->probe(buffer, n, p)) {
|
|
return p;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If none worked, return the first one affected (that's completely
|
|
* arbitrary) */
|
|
return protocols;
|
|
}
|
|
|
|
/* Returns the probe for specified protocol:
|
|
* parameter is the description in builtins[], or "regex"
|
|
* */
|
|
T_PROBE* get_probe(const char* description) {
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(builtins); i++) {
|
|
if (!strcmp(builtins[i].description, description)) {
|
|
return builtins[i].probe;
|
|
}
|
|
}
|
|
/* Special case of "regex" probe (we don't want to set it in builtins
|
|
* because builtins is also used to build the command-line options and
|
|
* regexp is not legal on the command line)*/
|
|
if (!strcmp(description, "regex"))
|
|
return regex_probe;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|