974 lines
27 KiB
C++
974 lines
27 KiB
C++
|
|
/*
|
||
|
|
Copyright (c) 2014 IOnU Security Inc. All rights reserved
|
||
|
|
Created March 2014 by Kendrick Webster
|
||
|
|
|
||
|
|
K2Client/proxy.c - implementation of proxy.h
|
||
|
|
*/
|
||
|
|
#include <sys/types.h>
|
||
|
|
#ifndef WIN32
|
||
|
|
#include <netdb.h>
|
||
|
|
#include <sys/socket.h>
|
||
|
|
#include <netinet/in.h>
|
||
|
|
#include <arpa/inet.h>
|
||
|
|
#include <strings.h>
|
||
|
|
#include <unistd.h>
|
||
|
|
#include <sys/time.h>
|
||
|
|
#else
|
||
|
|
#include <Winsock2.h>
|
||
|
|
#include <ws2tcpip.h>
|
||
|
|
#include <io.h>
|
||
|
|
#pragma comment (lib, "Ws2_32.lib")
|
||
|
|
#endif
|
||
|
|
#include <pthread.h>
|
||
|
|
#include <ctype.h>
|
||
|
|
#include <string.h>
|
||
|
|
#include <stdio.h>
|
||
|
|
#include <stdlib.h>
|
||
|
|
#include <signal.h>
|
||
|
|
#include <errno.h>
|
||
|
|
#ifdef __MACH__
|
||
|
|
#include <mach/mach_time.h>
|
||
|
|
#else
|
||
|
|
#include <time.h>
|
||
|
|
#endif
|
||
|
|
//#include "eyeinterface.h"
|
||
|
|
#include "eyeutils.h"
|
||
|
|
#include "constants.h"
|
||
|
|
#include "util.h"
|
||
|
|
#include "platform_binding.h"
|
||
|
|
#include "proxy.h"
|
||
|
|
|
||
|
|
#ifdef WIN32
|
||
|
|
#define ssize_t int
|
||
|
|
#define strdup _strdup
|
||
|
|
#else
|
||
|
|
#define SOCKET_ERROR -1
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#define RECEIVE_LOOP_CYCLES_PER_SECOND 20 /* affects how quickly proxy_stop() completes, tradeoff with battery life */
|
||
|
|
#define RECEIVE_TIMEOUT_MICROSECONDS (1000000 / RECEIVE_LOOP_CYCLES_PER_SECOND)
|
||
|
|
#define RECEIVE_BUFFER_SIZE (((K2IPC_MAX_PACKET_SIZE * 8 + 5) / 6) + 512) /* base64 encoding, +512 for HTTP headers */
|
||
|
|
|
||
|
|
/*
|
||
|
|
Poll messages (GET /recv/<message>, base64) consist of the following fields:
|
||
|
|
|
||
|
|
octets description
|
||
|
|
--------------------------------------------------------------------------
|
||
|
|
9 nonce (random initialization vector)
|
||
|
|
4 sequence number (see init message)
|
||
|
|
n client URN (string, no terminator, implicit length)
|
||
|
|
9 message integrity code (MIC)
|
||
|
|
|
||
|
|
All fields after the nonce are encrypted using an auto-hashing stream cipher
|
||
|
|
with a hard-coded key. The decrypted MIC will match only for properly encrypted
|
||
|
|
packets. Spurious packets fail the MIC check. This is used to increase the
|
||
|
|
difficulty of DOS attacks. The sequence number is included to prevent replay
|
||
|
|
DOS attacks.
|
||
|
|
|
||
|
|
The response to a valid poll message is a text document containing the next
|
||
|
|
received packet for the client URN, base64 encoded, followed by a period '.'
|
||
|
|
alone on a line. In case no packet arrives within the poll timeout interval,
|
||
|
|
the response is "timeout".
|
||
|
|
|
||
|
|
Invalid messages result in a 404 response (delayed to minimize DOS impact).
|
||
|
|
*/
|
||
|
|
/*
|
||
|
|
Send messages (POST to /send) consist of the following fields:
|
||
|
|
|
||
|
|
octets description
|
||
|
|
--------------------------------------------------------------------------
|
||
|
|
9 nonce (random initialization vector)
|
||
|
|
4 sequence number (see init message)
|
||
|
|
n K2 UDP packet (containing client URN)
|
||
|
|
9 message integrity code (MIC)
|
||
|
|
|
||
|
|
Encryption is done in the same manner (and for the same reasons) as for poll
|
||
|
|
messages. The sequence number in send messages is independent from the
|
||
|
|
sequence number in poll messages.
|
||
|
|
|
||
|
|
The K2 UDP packet must be of a type that contains a client URN. This is the
|
||
|
|
case for all K2 UDP packet types that are sent from a client to the daemon.
|
||
|
|
|
||
|
|
The response to a valid send message is a text document containing a single
|
||
|
|
line with the text "OK".
|
||
|
|
|
||
|
|
Invalid messages result in a 404 response (delayed to minimize DOS impact).
|
||
|
|
*/
|
||
|
|
/*
|
||
|
|
Init messages (GET /init/<message>, base64) consist of the following fields:
|
||
|
|
|
||
|
|
octets description
|
||
|
|
--------------------------------------------------------------------------
|
||
|
|
9 nonce (random initialization vector)
|
||
|
|
n client URN (string, no terminator, implicit length)
|
||
|
|
9 message integrity code (MIC)
|
||
|
|
|
||
|
|
The response to an init message is a sequence number message:
|
||
|
|
|
||
|
|
octets description
|
||
|
|
--------------------------------------------------------------------------
|
||
|
|
9 nonce (random initialization vector)
|
||
|
|
4 poll sequence number (randomly initialized)
|
||
|
|
4 send sequence number (randomly initialized)
|
||
|
|
9 message integrity code (MIC)
|
||
|
|
|
||
|
|
The sequence numbers are randomly-generated and stored as part of a session's
|
||
|
|
state (associated with the client URN) when a session is initially established.
|
||
|
|
Upon receiving each poll or send message, the corresponding sequence number is
|
||
|
|
checked, and, if it matches, incremented modulo 2^32.
|
||
|
|
|
||
|
|
A 'session' remains active as long as there is traffic (i.e. K2IPC hearbeat
|
||
|
|
messages). An absence of traffic causes a session to timeout.
|
||
|
|
|
||
|
|
Repeated init messages return the current sequence numbers associated with the
|
||
|
|
session. The reply (to all init messages) is delayed a small amount to limit
|
||
|
|
the impact of a replay attack. Although the delay could be selectively applied
|
||
|
|
only for existing sessions, doing so would leak information about who is
|
||
|
|
currently logged in. The delay is thus chosen to be small enough to have a
|
||
|
|
minimal impact on clients while still being large enough to limit traffic and
|
||
|
|
server load in a replay DOS attack.
|
||
|
|
*/
|
||
|
|
#define K2PROXY_NONCE_SIZE 9
|
||
|
|
#define K2PROXY_MIC_SIZE 9
|
||
|
|
#define K2PROXY_SEQUENCE_NUMBER_SIZE 4
|
||
|
|
#define K2PROXY_MESSAGE_OVERHEAD (K2PROXY_NONCE_SIZE + K2PROXY_MIC_SIZE)
|
||
|
|
static const char* const K2PROXY_KEY = "ICIRY4arfFbqGz9YGZaFRvv6+RNnXrsy";
|
||
|
|
#define K2PROXY_KEY_MIX_CYCLES 32
|
||
|
|
static const uint8_t K2PROXY_MIC[K2PROXY_MIC_SIZE] = {0x2b, 0x26, 0x21, 0xff, 0xbc, 0x60, 0xc1, 0x4b, 0x14};
|
||
|
|
static const char* const POLL_TIMEOUT_RESPONSE = "timeout";
|
||
|
|
|
||
|
|
static pthread_t sender_thread_id, receiver_thread_id;
|
||
|
|
static volatile int receiver_running; /* TRUE if receiver thread was started and not yet stopped */
|
||
|
|
static volatile int receiver_end_thread; /* TRUE if receiver thread should exit */
|
||
|
|
volatile int is_proxy_session_active; /* TRUE after receiver has received "init" response and not yet stopped */
|
||
|
|
|
||
|
|
static const char* client_urn;
|
||
|
|
static size_t client_urn_len;
|
||
|
|
static char* proxy_host;
|
||
|
|
static struct sockaddr_in proxy_addr;
|
||
|
|
static void (*on_packet_received)(uint8_t* buf, size_t len);
|
||
|
|
|
||
|
|
static uint32_t send_sequence;
|
||
|
|
static uint32_t poll_sequence;
|
||
|
|
|
||
|
|
static const char* const HTTP_GET_MESSAGE = "\
|
||
|
|
GET /%s/%s HTTP/1.1\r\n\
|
||
|
|
Host: %s\r\n\
|
||
|
|
User-Agent: K2Client\r\n\
|
||
|
|
Connection: close\r\n\
|
||
|
|
\r\n";
|
||
|
|
|
||
|
|
static const char* const SENDER_HTTP_MESSAGE = "\
|
||
|
|
POST /send HTTP/1.1\r\n\
|
||
|
|
Host: %s\r\n\
|
||
|
|
User-Agent: K2Client\r\n\
|
||
|
|
Connection: close\r\n\
|
||
|
|
Content-Type: application/x-www-form-urlencoded\r\n\
|
||
|
|
Content-Length: %lu\r\n\
|
||
|
|
\r\n\
|
||
|
|
data=%s\r\n";
|
||
|
|
|
||
|
|
/*
|
||
|
|
Mutex for protecting module-scope data that may be accessed by multiple threads.
|
||
|
|
|
||
|
|
Mutex-protected data:
|
||
|
|
send_sequence - written by receiver thread, read/modified by sender thread
|
||
|
|
|
||
|
|
All other module-scope data is accessed without the mutex. Most module-scope data
|
||
|
|
is written only when the threads are not running, then read by both threads.
|
||
|
|
|
||
|
|
Three threads can touch this module's data:
|
||
|
|
1). platform_binding module's thread (via this module's interface functions)
|
||
|
|
2). local receiver thread (loops to receive packets)
|
||
|
|
3). local sender thread (sends a single packet)
|
||
|
|
*/
|
||
|
|
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
|
||
|
|
#define LOCK_MUTEX pthread_mutex_lock(&mutex);
|
||
|
|
#define UNLOCK_MUTEX pthread_mutex_unlock(&mutex);
|
||
|
|
|
||
|
|
|
||
|
|
/* returns length of an un-padded base64 string representing <bytes> octets of data */
|
||
|
|
static size_t b64minlen(size_t bytes)
|
||
|
|
{
|
||
|
|
return ((bytes * 8) + 5) / 6;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* returns length of a padded base64 string representing <bytes> octets of data */
|
||
|
|
static size_t b64len(size_t bytes)
|
||
|
|
{
|
||
|
|
return ((b64minlen(bytes) + 3) / 4) * 4;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* encodes <buf> as a base64 string that is URL-safe, malloc'd here, caller free's */
|
||
|
|
static char* to_base64_urlsafe(uint8_t *buf, size_t len)
|
||
|
|
{
|
||
|
|
char *base64 = (char*)malloc(b64len(len) + 1);
|
||
|
|
sequencelogic::Base64Encode(buf, len, base64);
|
||
|
|
char *p = base64;
|
||
|
|
char c;
|
||
|
|
while (0 != (c = *p))
|
||
|
|
{
|
||
|
|
switch (c)
|
||
|
|
{
|
||
|
|
case '+': *p = '-'; break;
|
||
|
|
case '/': *p = '_'; break;
|
||
|
|
case '=': *p = 0; break;
|
||
|
|
}
|
||
|
|
++p;
|
||
|
|
}
|
||
|
|
return base64;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
Opens a socket, sets its receive timeout, and connects the socket to the proxy.
|
||
|
|
Returns socket handle if successful, otherwise INVALID_SOCKET.
|
||
|
|
*/
|
||
|
|
static SOCKET get_socket_connected_to_proxy(void)
|
||
|
|
{
|
||
|
|
SOCKET sockfd = socket(AF_INET, SOCK_STREAM, 0);
|
||
|
|
if (INVALID_SOCKET == sockfd)
|
||
|
|
{
|
||
|
|
upcall_errno("socket");
|
||
|
|
return INVALID_SOCKET;
|
||
|
|
}
|
||
|
|
|
||
|
|
struct timeval tv;
|
||
|
|
tv.tv_sec = 0;
|
||
|
|
tv.tv_usec = RECEIVE_TIMEOUT_MICROSECONDS;
|
||
|
|
if (0 != setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof(tv)))
|
||
|
|
{
|
||
|
|
upcall_errno("setsockopt(SO_RCVTIMEO)");
|
||
|
|
#ifdef WIN32
|
||
|
|
closesocket(sockfd);
|
||
|
|
#else
|
||
|
|
close(sockfd);
|
||
|
|
#endif
|
||
|
|
return INVALID_SOCKET;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (0 != connect(sockfd, (const struct sockaddr*)&proxy_addr, sizeof(proxy_addr)))
|
||
|
|
{
|
||
|
|
upcall_errno("connect");
|
||
|
|
#ifdef WIN32
|
||
|
|
closesocket(sockfd);
|
||
|
|
#else
|
||
|
|
close(sockfd);
|
||
|
|
#endif
|
||
|
|
return INVALID_SOCKET;
|
||
|
|
}
|
||
|
|
|
||
|
|
return sockfd;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
Encrypts a proxy message
|
||
|
|
Adds nonce and MIC fields
|
||
|
|
*/
|
||
|
|
static void encrypt_proxy_message(uint8_t *message, size_t len)
|
||
|
|
{
|
||
|
|
sc_hash_state_t h;
|
||
|
|
memset(&h, 0, sizeof(h));
|
||
|
|
get_random_bytes(message, K2PROXY_NONCE_SIZE);
|
||
|
|
sc_hash_vector(&h, message, K2PROXY_NONCE_SIZE);
|
||
|
|
sc_hash_vector(&h, K2PROXY_KEY, strlen(K2PROXY_KEY));
|
||
|
|
sc_hash_mix(&h, K2PROXY_KEY_MIX_CYCLES);
|
||
|
|
memcpy(message + (len - K2PROXY_MIC_SIZE), K2PROXY_MIC, K2PROXY_MIC_SIZE);
|
||
|
|
sc_hash_encrypt(&h, message + K2PROXY_NONCE_SIZE, len - K2PROXY_NONCE_SIZE);
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
copy <val> to <buf>
|
||
|
|
big-endian uint32_t
|
||
|
|
*/
|
||
|
|
static void write_uint32_BE(uint8_t *buf, uint32_t val)
|
||
|
|
{
|
||
|
|
buf[0] = (uint8_t)(val >> 24);
|
||
|
|
buf[1] = (uint8_t)(val >> 16);
|
||
|
|
buf[2] = (uint8_t)(val >> 8);
|
||
|
|
buf[3] = (uint8_t)(val);
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
reads a big-endian uint32_t from <buf>
|
||
|
|
*/
|
||
|
|
static uint32_t read_uint32_BE(uint8_t *buf)
|
||
|
|
{
|
||
|
|
uint32_t r;
|
||
|
|
r = buf[0];
|
||
|
|
r <<= 8;
|
||
|
|
r |= buf[1];
|
||
|
|
r <<= 8;
|
||
|
|
r |= buf[2];
|
||
|
|
r <<= 8;
|
||
|
|
r |= buf[3];
|
||
|
|
return r;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* helper for <de_chunk>, no safety checks */
|
||
|
|
static size_t hexval(char c)
|
||
|
|
{
|
||
|
|
if (c <= '9')
|
||
|
|
{
|
||
|
|
return c - '0';
|
||
|
|
}
|
||
|
|
else if (c <= 'F')
|
||
|
|
{
|
||
|
|
return c - ('A' - 10);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
return c - ('a' - 10);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* handle "Transfer-Encoding: chunked", replace buffer contents with de-chunked data */
|
||
|
|
static void de_chunk(char* buf, size_t* len, const char** p_body)
|
||
|
|
{
|
||
|
|
enum
|
||
|
|
{
|
||
|
|
S_HEADERS,
|
||
|
|
S_HEADER_CR,
|
||
|
|
S_HEADER_CRLF,
|
||
|
|
S_HEADER_CRLFCR,
|
||
|
|
S_CHUNK_LENGTH,
|
||
|
|
S_CHUNK_LENGTH_CR,
|
||
|
|
S_CHUNK,
|
||
|
|
}
|
||
|
|
state = S_HEADERS;
|
||
|
|
size_t chunklen = 0, n = *len, outlen = 0;
|
||
|
|
char c, *p = buf, *q = buf;
|
||
|
|
while (n-- && (0 != (c = *p++)))
|
||
|
|
{
|
||
|
|
switch (state)
|
||
|
|
{
|
||
|
|
case S_HEADERS:
|
||
|
|
outlen++;
|
||
|
|
if ('\r' == c)
|
||
|
|
{
|
||
|
|
state = S_HEADER_CR;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
case S_HEADER_CR:
|
||
|
|
outlen++;
|
||
|
|
switch (c)
|
||
|
|
{
|
||
|
|
case '\n':
|
||
|
|
state = S_HEADER_CRLF;
|
||
|
|
break;
|
||
|
|
|
||
|
|
case '\r':
|
||
|
|
state = S_HEADER_CR;
|
||
|
|
break;
|
||
|
|
|
||
|
|
default:
|
||
|
|
state = S_HEADERS;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
case S_HEADER_CRLF:
|
||
|
|
outlen++;
|
||
|
|
switch (c)
|
||
|
|
{
|
||
|
|
case '\r':
|
||
|
|
state = S_HEADER_CRLFCR;
|
||
|
|
break;
|
||
|
|
|
||
|
|
case '\n':
|
||
|
|
break;
|
||
|
|
|
||
|
|
default:
|
||
|
|
state = S_HEADERS;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
case S_HEADER_CRLFCR:
|
||
|
|
outlen++;
|
||
|
|
switch (c)
|
||
|
|
{
|
||
|
|
case '\n':
|
||
|
|
chunklen = 0;
|
||
|
|
q = p;
|
||
|
|
if (p_body)
|
||
|
|
{
|
||
|
|
*p_body = q;
|
||
|
|
}
|
||
|
|
state = S_CHUNK_LENGTH;
|
||
|
|
break;
|
||
|
|
|
||
|
|
case '\r':
|
||
|
|
break;
|
||
|
|
|
||
|
|
default:
|
||
|
|
state = S_HEADERS;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
case S_CHUNK_LENGTH:
|
||
|
|
if (isxdigit(c))
|
||
|
|
{
|
||
|
|
chunklen <<= 4;
|
||
|
|
chunklen += hexval(c);
|
||
|
|
}
|
||
|
|
else if ('\r' == c)
|
||
|
|
{
|
||
|
|
if (chunklen)
|
||
|
|
{
|
||
|
|
state = S_CHUNK_LENGTH_CR;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else if ('\n' != c)
|
||
|
|
{
|
||
|
|
log_error("expected chunk length, invalid char (%d) encountered", c);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
case S_CHUNK_LENGTH_CR:
|
||
|
|
switch (c)
|
||
|
|
{
|
||
|
|
case '\n':
|
||
|
|
state = S_CHUNK;
|
||
|
|
break;
|
||
|
|
|
||
|
|
case '\r':
|
||
|
|
break;
|
||
|
|
|
||
|
|
default:
|
||
|
|
log_error("expected chunk length line terminator, invalid char (%d) encountered", c);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
case S_CHUNK:
|
||
|
|
outlen++;
|
||
|
|
*q++ = c;
|
||
|
|
if (0 == --chunklen)
|
||
|
|
{
|
||
|
|
state = S_CHUNK_LENGTH;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if ((size_t)(q - buf) != outlen)
|
||
|
|
{
|
||
|
|
log_error("chunk decoder output length counter(%lu) differs from pointer offset(%u)", (long unsigned)outlen, (uint32_t)(q - buf));
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
*q = 0;
|
||
|
|
*len = outlen;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
Helper for receive_response()
|
||
|
|
|
||
|
|
Parses an HTTP response, calls <handler> with the message contained in the response.
|
||
|
|
|
||
|
|
valid responses contain:
|
||
|
|
1). HTTP headers (content-type: text/plain)
|
||
|
|
2). a blank line
|
||
|
|
3). a line of message text
|
||
|
|
4). a period '.' alone on a line
|
||
|
|
*/
|
||
|
|
static void on_response_received(char* buf, size_t len, void (*handler)(const char* buf, size_t len), const char* context)
|
||
|
|
{
|
||
|
|
if (0 != strstr(buf, "Transfer-Encoding: chunked"))
|
||
|
|
{
|
||
|
|
de_chunk(buf, &len, NULL);
|
||
|
|
}
|
||
|
|
const char* p = strstr(buf, "\r\n\r\n");
|
||
|
|
if (p)
|
||
|
|
{
|
||
|
|
p += 4;
|
||
|
|
}
|
||
|
|
if (p && (len > 16)
|
||
|
|
&& ('\n' == buf[len - 1])
|
||
|
|
&& ('\r' == buf[len - 2])
|
||
|
|
&& ('.' == buf[len - 3])
|
||
|
|
&& ('\n' == buf[len - 4])
|
||
|
|
&& ('\r' == buf[len - 5]))
|
||
|
|
{
|
||
|
|
buf[len - 5] = 0;
|
||
|
|
len -= (p - buf);
|
||
|
|
len -= 5;
|
||
|
|
handler(p, len);
|
||
|
|
}
|
||
|
|
else if (p && (p == strstr(p, POLL_TIMEOUT_RESPONSE)))
|
||
|
|
{
|
||
|
|
log_debug("proxy \"%s\" timeout", context);
|
||
|
|
}
|
||
|
|
else if (p && (p == strstr(p, "404 ")))
|
||
|
|
{
|
||
|
|
log_warn("proxy 404 response for \"%s\" request", context);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
log_warn("proxy response format unrecognized (for \"%s\" request), %lu bytes:\r\n%s", context, (long unsigned)len, buf);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
Receives a proxy response, calls <handler> with the message contained in the response.
|
||
|
|
Returns non-zero on error.
|
||
|
|
*/
|
||
|
|
static int receive_response(SOCKET sockfd, void (*handler)(const char* buf, size_t len), const char* context)
|
||
|
|
{
|
||
|
|
char buf[RECEIVE_BUFFER_SIZE];
|
||
|
|
char *p = buf;
|
||
|
|
size_t s = sizeof(buf);
|
||
|
|
size_t total_len = 0;
|
||
|
|
while (!receiver_end_thread)
|
||
|
|
{
|
||
|
|
#ifdef WIN32
|
||
|
|
int received = recv(sockfd, p, (int)s, 0);
|
||
|
|
#else
|
||
|
|
ssize_t received = recv(sockfd, p, s, 0);
|
||
|
|
#endif
|
||
|
|
if (SOCKET_ERROR == received)
|
||
|
|
{
|
||
|
|
if (!((EAGAIN == errno) || (EWOULDBLOCK == errno)))
|
||
|
|
{
|
||
|
|
upcall_errno("recv");
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else if (0 == received)
|
||
|
|
{
|
||
|
|
total_len = (total_len >= sizeof(buf)) ? (sizeof(buf) - 1) : total_len;
|
||
|
|
buf[total_len] = 0;
|
||
|
|
on_response_received(buf, total_len, handler, context);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
size_t n = (size_t)received;
|
||
|
|
total_len += n;
|
||
|
|
if (n >= s)
|
||
|
|
{
|
||
|
|
upcall_error_string("recv", "packet too large");
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
p += received;
|
||
|
|
s -= received;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
Sends an HTTP request
|
||
|
|
Returns non-zero on error
|
||
|
|
*/
|
||
|
|
static int send_http(SOCKET sockfd, const char* path, const char* request, size_t len)
|
||
|
|
{
|
||
|
|
#ifdef WIN32
|
||
|
|
int sent = send(sockfd, request, (int)len, 0);
|
||
|
|
#else
|
||
|
|
ssize_t sent = send(sockfd, request, len, 0);
|
||
|
|
#endif
|
||
|
|
if (SOCKET_ERROR == sent)
|
||
|
|
{
|
||
|
|
upcall_errno("send");
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
if (len != (size_t)sent)
|
||
|
|
{
|
||
|
|
log_error("proxy /%s/ short write: sent %ld bytes of %lu", path, sent, (long unsigned)len);
|
||
|
|
upcall_error_string("send", "short write");
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
Formats and sends an HTTP GET request containing <message> (cleartext, encrypted here)
|
||
|
|
Returns non-zero on error
|
||
|
|
*/
|
||
|
|
static int send_http_get(SOCKET sockfd, const char* path, uint8_t *message, size_t message_len)
|
||
|
|
{
|
||
|
|
encrypt_proxy_message(message, message_len);
|
||
|
|
char *base64 = to_base64_urlsafe(message, message_len);
|
||
|
|
size_t len1 = strlen(HTTP_GET_MESSAGE) + strlen(proxy_host) + strlen(base64); // larger than needed (not accounting for '%s')
|
||
|
|
char* request = (char*)malloc(len1);
|
||
|
|
size_t len = snprintf(request, len1, HTTP_GET_MESSAGE, path, base64, proxy_host);
|
||
|
|
if (len >= len1)
|
||
|
|
{
|
||
|
|
log_error("proxy /%s/ snprintf truncated from %lu to %lu", path, (long unsigned)len, (long unsigned)len1 - 1);
|
||
|
|
len = len1 - 1;
|
||
|
|
}
|
||
|
|
free(base64);
|
||
|
|
int r = send_http(sockfd, path, request, len);
|
||
|
|
free(request);
|
||
|
|
return r;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
Creates and sends a 'poll' message (/recv/<message>)
|
||
|
|
Returns non-zero on error
|
||
|
|
*/
|
||
|
|
static int send_poll_message(SOCKET sockfd)
|
||
|
|
{
|
||
|
|
size_t message_len = client_urn_len + K2PROXY_SEQUENCE_NUMBER_SIZE + K2PROXY_MESSAGE_OVERHEAD;
|
||
|
|
uint8_t *message = (uint8_t*)malloc(message_len);
|
||
|
|
uint8_t *p = message + K2PROXY_NONCE_SIZE;
|
||
|
|
write_uint32_BE(p, poll_sequence++);
|
||
|
|
p += K2PROXY_SEQUENCE_NUMBER_SIZE;
|
||
|
|
memcpy(p, client_urn, client_urn_len);
|
||
|
|
int r = send_http_get(sockfd, "recv", message, message_len);
|
||
|
|
free(message);
|
||
|
|
return r;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
Creates and sends an 'init' message (/init/<message>)
|
||
|
|
Returns non-zero on error
|
||
|
|
*/
|
||
|
|
static int send_init_message(SOCKET sockfd)
|
||
|
|
{
|
||
|
|
size_t message_len = client_urn_len + K2PROXY_MESSAGE_OVERHEAD;
|
||
|
|
uint8_t *message = (uint8_t*)malloc(message_len);
|
||
|
|
uint8_t *p = message + K2PROXY_NONCE_SIZE;
|
||
|
|
memcpy(p, client_urn, client_urn_len);
|
||
|
|
int r = send_http_get(sockfd, "init", message, message_len);
|
||
|
|
free(message);
|
||
|
|
return r;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
Handles a response to an 'init' message
|
||
|
|
Sets <send_sequence> and <poll_sequence> (module-scope variables)
|
||
|
|
*/
|
||
|
|
static int is_init_response_good; // set to TRUE by on_init_response_received if the response is good, caller initializes to FALSE
|
||
|
|
static void on_init_response_received(const char* buf, size_t len)
|
||
|
|
{
|
||
|
|
uint8_t p[1024];
|
||
|
|
if (len < 768)
|
||
|
|
{
|
||
|
|
size_t n = sequencelogic::Base64Decode(buf, p);
|
||
|
|
sc_hash_state_t h;
|
||
|
|
memset(&h, 0, sizeof(h));
|
||
|
|
sc_hash_vector(&h, p, K2PROXY_NONCE_SIZE);
|
||
|
|
sc_hash_vector(&h, K2PROXY_KEY, strlen(K2PROXY_KEY));
|
||
|
|
sc_hash_mix(&h, K2PROXY_KEY_MIX_CYCLES);
|
||
|
|
sc_hash_decrypt(&h, p + K2PROXY_NONCE_SIZE, len - K2PROXY_NONCE_SIZE);
|
||
|
|
if (0 != memcmp(p + (n - K2PROXY_MIC_SIZE), K2PROXY_MIC, K2PROXY_MIC_SIZE))
|
||
|
|
{
|
||
|
|
log_error("bad MIC in /init/ response from proxy");
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
uint8_t *q = p + K2PROXY_NONCE_SIZE;
|
||
|
|
poll_sequence = read_uint32_BE(q);
|
||
|
|
q += K2PROXY_SEQUENCE_NUMBER_SIZE;
|
||
|
|
uint32_t send_seq = read_uint32_BE(q);
|
||
|
|
LOCK_MUTEX
|
||
|
|
{
|
||
|
|
send_sequence = send_seq;
|
||
|
|
}
|
||
|
|
UNLOCK_MUTEX
|
||
|
|
is_init_response_good = TRUE;
|
||
|
|
log_debug("proxy init response: poll_sequence(%u), send_sequence(%u)", poll_sequence, send_seq);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
log_error("init response too large (%lu bytes)", (long unsigned)len);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
Sends an 'init' message, parses response to <send_sequence>,<poll_sequence> (module-scope variables)
|
||
|
|
Returns non-zero on error
|
||
|
|
*/
|
||
|
|
static int initialize_proxy_session(void)
|
||
|
|
{
|
||
|
|
SOCKET sockfd = get_socket_connected_to_proxy();
|
||
|
|
if (INVALID_SOCKET == sockfd)
|
||
|
|
{
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
log_debug("sending proxy init message");
|
||
|
|
if (send_init_message(sockfd))
|
||
|
|
{
|
||
|
|
error_exit:
|
||
|
|
#ifdef WIN32
|
||
|
|
closesocket(sockfd);
|
||
|
|
#else
|
||
|
|
close(sockfd);
|
||
|
|
#endif
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
is_init_response_good = FALSE;
|
||
|
|
if (receive_response(sockfd, on_init_response_received, "init") || !is_init_response_good)
|
||
|
|
{
|
||
|
|
goto error_exit;
|
||
|
|
}
|
||
|
|
|
||
|
|
#ifdef WIN32
|
||
|
|
if (SOCKET_ERROR == closesocket(sockfd))
|
||
|
|
#else
|
||
|
|
if (-1 == close(sockfd))
|
||
|
|
#endif
|
||
|
|
{
|
||
|
|
upcall_errno("close");
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
Handles a received K2 UDP packet in base64 encoding
|
||
|
|
*/
|
||
|
|
static void on_packet_received_base64(const char* buf, size_t len)
|
||
|
|
{
|
||
|
|
len;
|
||
|
|
uint8_t p[K2IPC_MAX_PACKET_SIZE];
|
||
|
|
size_t n = sequencelogic::Base64Decode(buf, p);
|
||
|
|
log_debug("proxy received packet, %d bytes", (int)n);
|
||
|
|
on_packet_received(p, n);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void* receiver_threadproc(void* arg)
|
||
|
|
{
|
||
|
|
arg;
|
||
|
|
log_debug("proxy receiver thread starting, addr = %s:%d", inet_ntoa(proxy_addr.sin_addr), ntohs(proxy_addr.sin_port));
|
||
|
|
|
||
|
|
if (initialize_proxy_session())
|
||
|
|
{
|
||
|
|
error_exit_1:
|
||
|
|
log_debug("proxy receiver thread exiting due to error");
|
||
|
|
is_proxy_session_active = FALSE;
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
is_proxy_session_active = TRUE;
|
||
|
|
|
||
|
|
while (!receiver_end_thread)
|
||
|
|
{
|
||
|
|
SOCKET sockfd = get_socket_connected_to_proxy();
|
||
|
|
if (INVALID_SOCKET == sockfd)
|
||
|
|
{
|
||
|
|
goto error_exit_1;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (send_poll_message(sockfd))
|
||
|
|
{
|
||
|
|
error_exit:
|
||
|
|
#ifdef WIN32
|
||
|
|
closesocket(sockfd);
|
||
|
|
#else
|
||
|
|
close(sockfd);
|
||
|
|
#endif
|
||
|
|
goto error_exit_1;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (receive_response(sockfd, on_packet_received_base64, "poll"))
|
||
|
|
{
|
||
|
|
goto error_exit;
|
||
|
|
}
|
||
|
|
|
||
|
|
#ifdef WIN32
|
||
|
|
if (SOCKET_ERROR == closesocket(sockfd))
|
||
|
|
#else
|
||
|
|
if (-1 == close(sockfd))
|
||
|
|
#endif
|
||
|
|
{
|
||
|
|
upcall_errno("close");
|
||
|
|
goto error_exit_1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
is_proxy_session_active = FALSE;
|
||
|
|
log_debug("proxy receiver thread exiting");
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
Helper for <format_sender_http_message>
|
||
|
|
Encapsulates a K2IPC UDP <packet> into a proxy message, and encodes it as URL-safe base64 text.
|
||
|
|
Returns base64 text, malloc'd here, caller free's
|
||
|
|
*/
|
||
|
|
static char* encode_sender_proxy_message(uint8_t* packet, size_t packet_len)
|
||
|
|
{
|
||
|
|
size_t proxy_message_len = packet_len + K2PROXY_SEQUENCE_NUMBER_SIZE + K2PROXY_MESSAGE_OVERHEAD;
|
||
|
|
uint8_t* proxy_message = (uint8_t*)malloc(proxy_message_len);
|
||
|
|
uint8_t* p = proxy_message + K2PROXY_NONCE_SIZE;
|
||
|
|
uint32_t send_seq;
|
||
|
|
LOCK_MUTEX
|
||
|
|
{
|
||
|
|
send_seq = send_sequence++;
|
||
|
|
}
|
||
|
|
UNLOCK_MUTEX
|
||
|
|
write_uint32_BE(p, send_seq);
|
||
|
|
p += K2PROXY_SEQUENCE_NUMBER_SIZE;
|
||
|
|
memcpy(p, packet, packet_len);
|
||
|
|
encrypt_proxy_message(proxy_message, proxy_message_len);
|
||
|
|
char* r = to_base64_urlsafe(proxy_message, proxy_message_len);
|
||
|
|
free(proxy_message);
|
||
|
|
return r;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
Helper for <sender_threadproc>, takes a binary K2IPC UDP <packet> (with length prepended),
|
||
|
|
adds a proxy message wrapper (with sequence number), encodes base64, and formats into an
|
||
|
|
HTTP POST request message.
|
||
|
|
|
||
|
|
Returns HTTP POST message text, malloc'd here, caller free's.
|
||
|
|
Sets <*len_out> to length of message text.
|
||
|
|
*/
|
||
|
|
static char* format_sender_http_message(uint8_t* packet, size_t* len_out)
|
||
|
|
{
|
||
|
|
size_t packet_len = read_uint32_BE(packet);
|
||
|
|
char *base64 = encode_sender_proxy_message(packet + sizeof(uint32_t), packet_len);
|
||
|
|
|
||
|
|
size_t n = strlen(base64);
|
||
|
|
size_t len1 = n + strlen(SENDER_HTTP_MESSAGE) + strlen(proxy_host) + 16;
|
||
|
|
char* message = (char*)malloc(len1);
|
||
|
|
size_t len = snprintf(message, len1, SENDER_HTTP_MESSAGE, proxy_host, n + 5 /* 5 for 'data=' */, base64);
|
||
|
|
if (len >= len1)
|
||
|
|
{
|
||
|
|
log_error("proxy sender snprintf truncated from %lu to %lu", (long unsigned)len, (long unsigned)len1 - 1);
|
||
|
|
len = len1 - 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
free(base64);
|
||
|
|
*len_out = len;
|
||
|
|
return message;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void on_sender_response_received(const char* buf, size_t len)
|
||
|
|
{
|
||
|
|
len;
|
||
|
|
log_debug("proxy sender response: %s", buf);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* sends one base64 encoded packet passed in <arg> (malloc'd by caller, free'd here) */
|
||
|
|
static void* sender_threadproc(void* arg)
|
||
|
|
{
|
||
|
|
if (!is_proxy_session_active)
|
||
|
|
{
|
||
|
|
log_warn("proxy send aborted because a proxy session is not active");
|
||
|
|
free(arg);
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
SOCKET sockfd = get_socket_connected_to_proxy();
|
||
|
|
if (INVALID_SOCKET == sockfd)
|
||
|
|
{
|
||
|
|
free(arg);
|
||
|
|
|
||
|
|
error_exit_1:
|
||
|
|
log_debug("proxy sender thread exiting due to error");
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
size_t len;
|
||
|
|
char* message = format_sender_http_message((uint8_t*)arg, &len);
|
||
|
|
free(arg);
|
||
|
|
|
||
|
|
log_debug("proxy send packet, encapsulated as %lu byte HTTP", (long unsigned)len);
|
||
|
|
int r = send_http(sockfd, "send", message, len);
|
||
|
|
free(message);
|
||
|
|
if (r)
|
||
|
|
{
|
||
|
|
error_exit:
|
||
|
|
#ifdef WIN32
|
||
|
|
closesocket(sockfd);
|
||
|
|
#else
|
||
|
|
close(sockfd);
|
||
|
|
#endif
|
||
|
|
goto error_exit_1;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (receive_response(sockfd, on_sender_response_received, "send"))
|
||
|
|
{
|
||
|
|
goto error_exit;
|
||
|
|
}
|
||
|
|
|
||
|
|
#ifdef WIN32
|
||
|
|
if (SOCKET_ERROR == closesocket(sockfd))
|
||
|
|
#else
|
||
|
|
if (-1 == close(sockfd))
|
||
|
|
#endif
|
||
|
|
{
|
||
|
|
upcall_errno("close");
|
||
|
|
goto error_exit_1;
|
||
|
|
}
|
||
|
|
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
/* ------------------ startup, shutdown ------------------ */
|
||
|
|
|
||
|
|
static void start_receiver_thread(void)
|
||
|
|
{
|
||
|
|
int error = pthread_create(&receiver_thread_id, NULL, receiver_threadproc, NULL);
|
||
|
|
if (error)
|
||
|
|
{
|
||
|
|
upcall_error("pthread_create", error);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
receiver_running = TRUE;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void stop_receiver_thread(void)
|
||
|
|
{
|
||
|
|
receiver_end_thread = TRUE;
|
||
|
|
int error = pthread_join(receiver_thread_id, NULL);
|
||
|
|
if (error)
|
||
|
|
{
|
||
|
|
upcall_error("pthread_join", error);
|
||
|
|
}
|
||
|
|
receiver_running = receiver_end_thread = is_proxy_session_active = FALSE;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/* ----------------------- Module interface functions ----------------------- */
|
||
|
|
|
||
|
|
void proxy_start(const struct sockaddr_in* proxy_address, const char* p_host, const char* urn,
|
||
|
|
void (*onPacketReceived)(uint8_t* buf, size_t len))
|
||
|
|
{
|
||
|
|
proxy_stop();
|
||
|
|
proxy_addr = *proxy_address;
|
||
|
|
proxy_host = strdup(p_host);
|
||
|
|
client_urn = urn;
|
||
|
|
client_urn_len = strlen(client_urn);
|
||
|
|
on_packet_received = onPacketReceived;
|
||
|
|
start_receiver_thread();
|
||
|
|
}
|
||
|
|
|
||
|
|
void proxy_send(const uint8_t* buf, unsigned int len)
|
||
|
|
{
|
||
|
|
uint8_t* packet_copy = (uint8_t*)malloc(len + sizeof(uint32_t));
|
||
|
|
write_uint32_BE(packet_copy, len);
|
||
|
|
memcpy(packet_copy + sizeof(uint32_t), buf, len);
|
||
|
|
int error = pthread_create(&sender_thread_id, NULL, sender_threadproc, packet_copy);
|
||
|
|
if (error)
|
||
|
|
{
|
||
|
|
upcall_error("pthread_create", error);
|
||
|
|
free(packet_copy);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void proxy_stop(void)
|
||
|
|
{
|
||
|
|
if (receiver_running)
|
||
|
|
{
|
||
|
|
stop_receiver_thread();
|
||
|
|
}
|
||
|
|
free(proxy_host);
|
||
|
|
proxy_host = NULL;
|
||
|
|
}
|