From 6d85fffdb511ef17644094a8329ea85e43fe5e82 Mon Sep 17 00:00:00 2001 From: Davide Beatrici Date: Mon, 20 Jul 2020 02:03:44 +0200 Subject: [PATCH] Cedar: introduce options API in Proto PROTO_OPTION is a structure that describes an option (who would've guessed?). It's designed in a way that allows it to occupy as low memory as possible, while providing great flexibility. The idea is similar to the one implemented in LIST for trivial types, with the difference that PROTO_OPTION doesn't require casting due to the use of union. --- src/Cedar/Proto.c | 118 +++++++++++++++++++++++++++++++++++++- src/Cedar/Proto.h | 33 +++++++++-- src/Cedar/Proto_OpenVPN.c | 19 +++++- src/Cedar/Proto_OpenVPN.h | 3 +- src/Cedar/Proto_SSTP.c | 15 ++++- src/Cedar/Proto_SSTP.h | 3 +- 6 files changed, 179 insertions(+), 12 deletions(-) diff --git a/src/Cedar/Proto.c b/src/Cedar/Proto.c index 4b17d02c..2bb33c91 100644 --- a/src/Cedar/Proto.c +++ b/src/Cedar/Proto.c @@ -2,6 +2,21 @@ #include "Proto_OpenVPN.h" +int ProtoOptionCompare(void *p1, void *p2) +{ + PROTO_OPTION *option_1, *option_2; + + if (p1 == NULL || p2 == NULL) + { + return (p1 == NULL && p2 == NULL ? 0 : (p1 == NULL ? -1 : 1)); + } + + option_1 = *(PROTO_OPTION **)p1; + option_2 = *(PROTO_OPTION **)p2; + + return StrCmpi(option_1->Name, option_2->Name); +} + int ProtoContainerCompare(void *p1, void *p2) { PROTO_CONTAINER *container_1, *container_2; @@ -113,6 +128,35 @@ UINT ProtoSessionHash(void *p) return ret; } +bool ProtoEnabled(const PROTO *proto, const char *name) +{ + PROTO_OPTION *option, tmp_o; + PROTO_CONTAINER *container, tmp_c; + + if (proto == NULL || name == NULL) + { + return false; + } + + tmp_c.Name = name; + + container = Search(proto->Containers, &tmp_c); + if (container == NULL) + { + return false; + } + + tmp_o.Name = PROTO_OPTION_TOGGLE_NAME; + + option = Search(container->Options, &tmp_o); + if (option == NULL || option->Type != PROTO_OPTION_BOOL) + { + return false; + } + + return option->Bool; +} + PROTO *ProtoNew(CEDAR *cedar) { PROTO *proto; @@ -170,7 +214,9 @@ void ProtoDelete(PROTO *proto) PROTO_CONTAINER *ProtoContainerNew(const PROTO_IMPL *impl) { UINT i; + PROTO_OPTION *option; PROTO_CONTAINER *container; + const PROTO_OPTION *impl_options; if (impl == NULL) { @@ -179,8 +225,43 @@ PROTO_CONTAINER *ProtoContainerNew(const PROTO_IMPL *impl) container = Malloc(sizeof(PROTO_CONTAINER)); container->Name = impl->Name(); + container->Options = NewList(ProtoOptionCompare); container->Impl = impl; + option = ZeroMalloc(sizeof(PROTO_OPTION)); + option->Name = PROTO_OPTION_TOGGLE_NAME; + option->Type = PROTO_OPTION_BOOL; + option->Bool = true; + + Add(container->Options, option); + + impl_options = impl->Options(); + + for (i = 0; impl_options[i].Name != NULL; ++i) + { + const PROTO_OPTION *impl_option = &impl_options[i]; + + option = ZeroMalloc(sizeof(PROTO_OPTION)); + option->Name = impl_option->Name; + option->Type = impl_option->Type; + + switch (impl_option->Type) + { + case PROTO_OPTION_BOOL: + option->Bool = impl_option->Bool; + break; + case PROTO_OPTION_STRING: + option->String = CopyStr(impl_option->String); + break; + default: + Debug("ProtoContainerNew(): unhandled option type %u!\n", impl_option->Type); + Free(option); + continue; + } + + Add(container->Options, option); + } + Debug("ProtoContainerNew(): %s\n", container->Name); return container; @@ -188,11 +269,28 @@ PROTO_CONTAINER *ProtoContainerNew(const PROTO_IMPL *impl) void ProtoContainerDelete(PROTO_CONTAINER *container) { + UINT i; + LIST *options; + if (container == NULL) { return; } + options = container->Options; + + for (i = 0; i < LIST_NUM(options); ++i) + { + PROTO_OPTION *option = LIST_DATA(options, i); + if (option->Type == PROTO_OPTION_STRING) + { + Free(option->String); + } + + Free(option); + } + + ReleaseList(options); Free(container); } @@ -229,6 +327,7 @@ const PROTO_CONTAINER *ProtoDetect(const PROTO *proto, const PROTO_MODE mode, co PROTO_SESSION *ProtoNewSession(PROTO *proto, const PROTO_CONTAINER *container, const IP *src_ip, const USHORT src_port, const IP *dst_ip, const USHORT dst_port) { + LIST *options; PROTO_SESSION *session; const PROTO_IMPL *impl; @@ -237,16 +336,20 @@ PROTO_SESSION *ProtoNewSession(PROTO *proto, const PROTO_CONTAINER *container, c return NULL; } + options = container->Options; impl = container->Impl; session = ZeroMalloc(sizeof(PROTO_SESSION)); session->SockEvent = NewSockEvent(); session->InterruptManager = NewInterruptManager(); - if (impl->Init != NULL && impl->Init(&session->Param, proto->Cedar, session->InterruptManager, session->SockEvent, NULL, NULL) == false) + LockList(options); + + if (impl->Init != NULL && impl->Init(&session->Param, container->Options, proto->Cedar, session->InterruptManager, session->SockEvent, NULL, NULL) == false) { Debug("ProtoNewSession(): failed to initialize %s\n", container->Name); + UnlockList(options); ReleaseSockEvent(session->SockEvent); FreeInterruptManager(session->InterruptManager); Free(session); @@ -254,6 +357,8 @@ PROTO_SESSION *ProtoNewSession(PROTO *proto, const PROTO_CONTAINER *container, c return NULL; } + UnlockList(options); + session->Proto = proto; session->Impl = impl; @@ -350,6 +455,7 @@ bool ProtoHandleConnection(PROTO *proto, SOCK *sock, const char *protocol) { const PROTO_CONTAINER *container = NULL; + LIST *options; if (protocol != NULL) { @@ -381,18 +487,26 @@ bool ProtoHandleConnection(PROTO *proto, SOCK *sock, const char *protocol) return false; } + options = container->Options; impl = container->Impl; im = NewInterruptManager(); se = NewSockEvent(); - if (impl->Init != NULL && impl->Init(&impl_data, proto->Cedar, im, se, sock->CipherName, sock->RemoteHostname) == false) + LockList(options); + + if (impl->Init != NULL && impl->Init(&impl_data, options, proto->Cedar, im, se, sock->CipherName, sock->RemoteHostname) == false) { Debug("ProtoHandleConnection(): failed to initialize %s\n", container->Name); + + UnlockList(options); FreeInterruptManager(im); ReleaseSockEvent(se); + return false; } + + UnlockList(options); } SetTimeout(sock, TIMEOUT_INFINITE); diff --git a/src/Cedar/Proto.h b/src/Cedar/Proto.h index 79c6814d..29397d06 100644 --- a/src/Cedar/Proto.h +++ b/src/Cedar/Proto.h @@ -1,6 +1,8 @@ #ifndef PROTO_H #define PROTO_H +#define PROTO_OPTION_TOGGLE_NAME "Enabled" + // OpenVPN sends 2 bytes, thus this is the buffer size. // If another protocol requires more bytes to be detected, the buffer size must be increased. #define PROTO_CHECK_BUFFER_SIZE 2 @@ -9,11 +11,18 @@ typedef enum PROTO_MODE { - PROTO_MODE_UNKNOWN = 0, - PROTO_MODE_TCP = 1, - PROTO_MODE_UDP = 2 + PROTO_MODE_UNKNOWN, + PROTO_MODE_TCP, + PROTO_MODE_UDP } PROTO_MODE; +typedef enum PROTO_OPTION_VALUE +{ + PROTO_OPTION_UNKNOWN, + PROTO_OPTION_STRING, + PROTO_OPTION_BOOL +} PROTO_OPTION_VALUE; + typedef struct PROTO { CEDAR *Cedar; @@ -22,10 +31,22 @@ typedef struct PROTO UDPLISTENER *UdpListener; } PROTO; +typedef struct PROTO_OPTION +{ + char *Name; + PROTO_OPTION_VALUE Type; + union + { + bool Bool; + char *String; + }; +} PROTO_OPTION; + typedef struct PROTO_IMPL { const char *(*Name)(); - bool (*Init)(void **param, CEDAR *cedar, INTERRUPT_MANAGER *im, SOCK_EVENT *se, const char *cipher, const char *hostname); + const PROTO_OPTION *(*Options)(); + bool (*Init)(void **param, const LIST *options, CEDAR *cedar, INTERRUPT_MANAGER *im, SOCK_EVENT *se, const char *cipher, const char *hostname); void (*Free)(void *param); bool (*IsPacketForMe)(const PROTO_MODE mode, const UCHAR *data, const UINT size); bool (*ProcessData)(void *param, TCP_RAW_DATA *in, FIFO *out); @@ -35,6 +56,7 @@ typedef struct PROTO_IMPL typedef struct PROTO_CONTAINER { const char *Name; + LIST *Options; const PROTO_IMPL *Impl; } PROTO_CONTAINER; @@ -56,11 +78,14 @@ typedef struct PROTO_SESSION volatile bool Halt; } PROTO_SESSION; +int ProtoOptionCompare(void *p1, void *p2); int ProtoContainerCompare(void *p1, void *p2); int ProtoSessionCompare(void *p1, void *p2); UINT ProtoSessionHash(void *p); +bool ProtoEnabled(const PROTO *proto, const char *name); + PROTO *ProtoNew(CEDAR *cedar); void ProtoDelete(PROTO *proto); diff --git a/src/Cedar/Proto_OpenVPN.c b/src/Cedar/Proto_OpenVPN.c index 54d40707..f03a9c09 100644 --- a/src/Cedar/Proto_OpenVPN.c +++ b/src/Cedar/Proto_OpenVPN.c @@ -19,6 +19,7 @@ const PROTO_IMPL *OvsGetProtoImpl() static const PROTO_IMPL impl = { OvsName, + OvsOptions, OvsInit, OvsFree, OvsIsPacketForMe, @@ -34,9 +35,23 @@ const char *OvsName() return "OpenVPN"; } -bool OvsInit(void **param, CEDAR *cedar, INTERRUPT_MANAGER *im, SOCK_EVENT *se, const char *cipher, const char *hostname) +const PROTO_OPTION *OvsOptions() { - if (param == NULL || cedar == NULL || im == NULL || se == NULL) + static const PROTO_OPTION options[] = + { + { .Name = "DefaultClientOption", .Type = PROTO_OPTION_STRING, .String = "dev-type tun,link-mtu 1500,tun-mtu 1500,cipher AES-128-CBC,auth SHA1,keysize 128,key-method 2,tls-client" }, + { .Name = "Obfuscation", .Type = PROTO_OPTION_BOOL, .Bool = false }, + { .Name = "ObfuscationMask", .Type = PROTO_OPTION_STRING, .String = ""}, + { .Name = "PushDummyIPv4AddressOnL2Mode", .Type = PROTO_OPTION_BOOL, .Bool = true }, + { .Name = NULL, .Type = PROTO_OPTION_UNKNOWN } + }; + + return options; +} + +bool OvsInit(void **param, const LIST *options, CEDAR *cedar, INTERRUPT_MANAGER *im, SOCK_EVENT *se, const char *cipher, const char *hostname) +{ + if (param == NULL || options == NULL || cedar == NULL || im == NULL || se == NULL) { return false; } diff --git a/src/Cedar/Proto_OpenVPN.h b/src/Cedar/Proto_OpenVPN.h index 8bea7f32..c468d587 100644 --- a/src/Cedar/Proto_OpenVPN.h +++ b/src/Cedar/Proto_OpenVPN.h @@ -210,7 +210,8 @@ struct OPENVPN_SERVER //// Function prototype const PROTO_IMPL *OvsGetProtoImpl(); const char *OvsName(); -bool OvsInit(void **param, CEDAR *cedar, INTERRUPT_MANAGER *im, SOCK_EVENT *se, const char *cipher, const char *hostname); +const PROTO_OPTION *OvsOptions(); +bool OvsInit(void **param, const LIST *options, CEDAR *cedar, INTERRUPT_MANAGER *im, SOCK_EVENT *se, const char *cipher, const char *hostname); void OvsFree(void *param); bool OvsIsPacketForMe(const PROTO_MODE mode, const UCHAR *data, const UINT size); bool OvsProcessData(void *param, TCP_RAW_DATA *in, FIFO *out); diff --git a/src/Cedar/Proto_SSTP.c b/src/Cedar/Proto_SSTP.c index cb06ec74..5391e36c 100644 --- a/src/Cedar/Proto_SSTP.c +++ b/src/Cedar/Proto_SSTP.c @@ -21,6 +21,7 @@ const PROTO_IMPL *SstpGetProtoImpl() static const PROTO_IMPL impl = { SstpName, + SstpOptions, SstpInit, SstpFree, NULL, @@ -36,9 +37,19 @@ const char *SstpName() return "SSTP"; } -bool SstpInit(void **param, struct CEDAR *cedar, INTERRUPT_MANAGER *im, SOCK_EVENT *se, const char *cipher, const char *hostname) +const PROTO_OPTION *SstpOptions() { - if (param == NULL || cedar == NULL || im == NULL || se == NULL) + static const PROTO_OPTION options[] = + { + { .Name = NULL, .Type = PROTO_OPTION_UNKNOWN } + }; + + return options; +} + +bool SstpInit(void **param, const LIST *options, CEDAR *cedar, INTERRUPT_MANAGER *im, SOCK_EVENT *se, const char *cipher, const char *hostname) +{ + if (param == NULL || options == NULL || cedar == NULL || im == NULL || se == NULL) { return false; } diff --git a/src/Cedar/Proto_SSTP.h b/src/Cedar/Proto_SSTP.h index bb090c0b..44e167c2 100644 --- a/src/Cedar/Proto_SSTP.h +++ b/src/Cedar/Proto_SSTP.h @@ -123,8 +123,9 @@ struct SSTP_SERVER //// Function prototype const PROTO_IMPL *SstpGetProtoImpl(); +const PROTO_OPTION *SstpOptions(); const char *SstpName(); -bool SstpInit(void **param, struct CEDAR *cedar, INTERRUPT_MANAGER *im, SOCK_EVENT *se, const char *cipher, const char *hostname); +bool SstpInit(void **param, const LIST *options, CEDAR *cedar, INTERRUPT_MANAGER *im, SOCK_EVENT *se, const char *cipher, const char *hostname); void SstpFree(void *param); bool SstpProcessData(void *param, TCP_RAW_DATA *in, FIFO *out);