Skip to content

Commit

Permalink
Implement sending/receiving app custom control protocol in server mode
Browse files Browse the repository at this point in the history
Change-Id: I4a6a6ef41e15896b8b79d795b2a6de86b8ae4c84
Signed-off-by: Arne Schwabe <[email protected]>
  • Loading branch information
schwabe committed Jan 16, 2024
1 parent ef2917f commit 90edc4e
Show file tree
Hide file tree
Showing 8 changed files with 323 additions and 1 deletion.
11 changes: 11 additions & 0 deletions doc/management-notes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1171,6 +1171,17 @@ CLIENT notification types:
response. Mechanisms that need multiple rounds or more complex answers
should implement a different response type than CR_RESPONSE.

(6) App custom control message

This message is an application specific payload message that is transported
over the OpenVPN control channel.

>CLIENT:ACC,{CID},{KID},{protocol},{fragment},{msg_base64}

The protocol is a short ascii string identifying the app specific custom
protocol and identifies the protocol. If fragment is set to 1, the message
is not complete and the next message should be concatenated to this message
to form a complete message.

Variables:

Expand Down
4 changes: 4 additions & 0 deletions src/openvpn/forward.c
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,10 @@ check_incoming_control_channel(struct context *c)
{
receive_cr_response(c, &buf);
}
else if (buf_string_match_head_str(&buf, "ACC"))
{
receive_acc_message(c, &buf);
}
else if (buf_string_match_head_str(&buf, "AUTH_PENDING"))
{
receive_auth_pending(c, &buf);
Expand Down
38 changes: 38 additions & 0 deletions src/openvpn/manage.c
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,21 @@ in_extra_dispatch(struct management *man)
}
break;

case IEC_ACC_MSG:
if (man->persist.callback.acc_msg)
{
bool status = (*man->persist.callback.acc_msg)
(man->persist.callback.arg,
man->connection.in_extra_cid,
man->connection.in_extra_kid,
man->connection.in_extra
);
man->connection.in_extra = NULL;
report_command_status(status, "acc-msg");
return;
}
break;

case IEC_PK_SIGN:
man->connection.ext_key_state = EKS_READY;
buffer_list_free(man->connection.ext_key_input);
Expand Down Expand Up @@ -1088,6 +1103,21 @@ man_client_pending_auth(struct management *man, const char *cid_str,
}
}

static void
man_client_acc_msg(struct management *man, const char *cid_str,
const char *kid_str)
{
struct man_connection *mc = &man->connection;
mc->in_extra_cid = 0;
mc->in_extra_kid = 0;
if (parse_cid(cid_str, &mc->in_extra_cid)
&& parse_uint(kid_str, "KID", &mc->in_extra_kid))
{
mc->in_extra_cmd = IEC_ACC_MSG;
in_extra_reset(mc, IER_NEW);
}
}

static void
man_client_auth(struct management *man, const char *cid_str, const char *kid_str, const bool extra)
{
Expand Down Expand Up @@ -1597,6 +1627,13 @@ man_dispatch_command(struct management *man, struct status_output *so, const cha
man_client_pending_auth(man, p[1], p[2], p[3], p[4]);
}
}
else if (streq(p[0], "acc-msg"))
{
if (man_need(man, p, 2, 0))
{
man_client_acc_msg(man, p[1], p[2]);
}
}
else if (streq(p[0], "rsa-sig"))
{
man_pk_sig(man, "rsa-sig");
Expand Down Expand Up @@ -2970,6 +3007,7 @@ management_notify_client_cr_response(unsigned mda_key_id,
}
}


void
management_connection_established(struct management *management,
struct man_def_auth_context *mdac,
Expand Down
13 changes: 13 additions & 0 deletions src/openvpn/manage.h
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,10 @@ struct management_callback
const char *reason,
const char *client_reason,
struct buffer_list *cc_config); /* ownership transferred */
bool (*acc_msg) (void *arg,
const unsigned long cid,
const unsigned int mda_key_id,
struct buffer_list *msg);
bool (*client_pending_auth) (void *arg,
const unsigned long cid,
const unsigned int kid,
Expand Down Expand Up @@ -300,6 +304,7 @@ struct man_connection {
#define IEC_RSA_SIGN 3
#define IEC_CERTIFICATE 4
#define IEC_PK_SIGN 5
#define IEC_ACC_MSG 6
int in_extra_cmd;
struct buffer_list *in_extra;
unsigned long in_extra_cid;
Expand Down Expand Up @@ -427,6 +432,14 @@ void management_notify_client_cr_response(unsigned mda_key_id,
const struct env_set *es,
const char *response);

void
management_notify_acc_fragment(unsigned mda_key_id,
const struct man_def_auth_context *mdac,
const struct env_set *es,
const char *protocol,
bool fragment,
const char *msg);

char *management_query_pk_sig(struct management *man, const char *b64_data,
const char *algorithm);

Expand Down
60 changes: 60 additions & 0 deletions src/openvpn/multi.c
Original file line number Diff line number Diff line change
Expand Up @@ -4103,6 +4103,65 @@ management_client_auth(void *arg,
return ret;
}

static bool
management_client_acc_msg(void *arg,
const unsigned long cid,
const unsigned int mda_key_id,
struct buffer_list *input) /* ownership transferred */
{
struct gc_arena gc = gc_new();
struct multi_context *m = (struct multi_context *) arg;
struct multi_instance *mi = lookup_by_cid(m, cid);

if (mi && buffer_list_defined(input))
{
struct tls_multi *multi = mi->context.c2.tls_multi;
struct tls_session *session = lookup_session_by_mda_key_id(multi, mda_key_id);

const struct buffer *proto_buf = buffer_list_peek(input);
const char *protocol = string_alloc(BSTR(proto_buf), &gc);
buffer_list_pop(input);

const struct buffer *encoding_buf = buffer_list_peek(input);
char *encoding = buf_str(encoding_buf);
char *flag = NULL;
bool b64encoding = false;
bool fragment = false;

while ((flag = strsep(&encoding, ":")))
{
if (!strcmp(flag, "6"))
{
b64encoding = true;
}
else if (!strcmp(flag, "F"))
{
fragment = true;
}
}
buffer_list_pop(input);

buffer_list_aggregate_separator(input, 10000, "");
const struct buffer *acc_msg_buf = buffer_list_peek(input);

if (!proto_buf || !input || !acc_msg_buf)
{
gc_free(&gc);
buffer_list_free(input);
return false;
}

send_acc_message(multi, session, protocol,
fragment, buf_str(acc_msg_buf),
b64encoding);
multi_schedule_context_wakeup(m, mi);
}

buffer_list_free(input);
gc_free(&gc);
return true;
}

static char *
management_get_peer_info(void *arg, const unsigned long cid)
{
Expand Down Expand Up @@ -4139,6 +4198,7 @@ init_management_callback_multi(struct multi_context *m)
cb.n_clients = management_callback_n_clients;
cb.kill_by_cid = management_kill_by_cid;
cb.client_auth = management_client_auth;
cb.acc_msg = management_client_acc_msg;
cb.client_pending_auth = management_client_pending_auth;
cb.get_peer_info = management_get_peer_info;
management_set_callback(management, &cb);
Expand Down
183 changes: 183 additions & 0 deletions src/openvpn/push.c
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,159 @@ receive_cr_response(struct context *c, const struct buffer *buffer)
msg(D_PUSH, "CR response was sent by client ('%s')", m);
}


/**
* Extract a field from buf that end with the \c sep character. The
* returned string is allocated in the gc_arena. If the seperater character
* is not found, the function returns the nullptr.
*/
char *
extract_field(struct buffer *buf, char sep, struct gc_arena *gc)
{
const uint8_t *seppos = memchr(BPTR(buf), sep, buf_len(buf));
if (!seppos)
{
return NULL;
}
size_t field_len = seppos - BPTR(buf);


char *field = gc_malloc(field_len + 1, false, gc);
strncpy(field, BSTR(buf), field_len);

buf_advance(buf, (int)field_len + 1);
return field;
}

void
receive_acc_message(struct context *c, const struct buffer *buffer)
{
struct gc_arena gc = gc_new();
const char *err_reason = "";

/* Example message: ACC,muppets,15,A,I am Miss Piggy */
struct buffer buf = *buffer;

if (!buf_advance(&buf, strlen("ACC")) || buf_read_u8(&buf) != ',' || !BLEN(&buf))
{
err_reason = "missing , after ACC";
goto err;
}

/* extract protocol, payload length, flags substrings */
char *protocol = extract_field(&buf, ',', &gc);
if (!protocol)
{
err_reason = "could not extract protocol field";
goto err;
}
int payload_len = 0;
if (!buffer_read_int(&buf, &payload_len))
{
err_reason = "could not extract payload length field";
goto err;
}

/* comma after the length */
if (buf_read_u8(&buf) != ',')
{
err_reason = "missing , after payload len";
goto err;
}

char *flags = extract_field(&buf, ',', &gc);

if (!flags)
{
err_reason = "could not extract flags field";
goto err;
}

/* We have a final NUL byte in the control message buffer */
if (buf_len(&buf) != payload_len + 1)
{
char *tmp = gc_malloc(512, 1, &gc);
snprintf(tmp, 512, "field length %d, payload length %d mismatch",
payload_len, buf_len(&buf) - 1);
err_reason = tmp;
goto err;
}

bool base64enc = false;
bool asciienc = false;
bool fragment = false;

char *token = NULL;
while ((token = strsep(&flags, ":")))
{
if (streq(token, "A"))
{
asciienc = true;
}
else if (streq(token, "6"))
{
base64enc = false;
}
else if (streq(token, "F"))
{
fragment = true;
}
else
{
goto err;
}
}

/* The message should be encoded with exactly one encoding */
if (base64enc + asciienc != 1)
{
goto err;
}

#ifdef ENABLE_MANAGEMENT
const char *payload_msg = NULL;

/* For simplicity, we always encode payload to be base64 encoded
* if not already in base64 format */
if (asciienc)
{
char *b64out = NULL;
ASSERT(openvpn_base64_encode(BPTR(&buf), payload_len, &b64out) >= 0);
gc_addspecial(b64out, free, &gc);
payload_msg = b64out;
}
else
{
payload_msg = BSTR(&buf);
}

if (management)
{
struct tls_session *session = &c->c2.tls_multi->session[TM_ACTIVE];
struct man_def_auth_context *mda = session->opt->mda_context;
unsigned int mda_key_id = get_primary_key(c->c2.tls_multi)->mda_key_id;


msg(M_CLIENT, ">CLIENT:ACC,%lu,%u,%s,%d,%s",
mda->cid, mda_key_id, protocol, fragment, payload_msg);
}
#endif /* ifdef ENABLE_MANAGEMENT */
msg(D_PUSH, "custom app control message (protocol '%s', fragment %d)",
protocol, fragment);

gc_free(&gc);
return;

err:
dmsg(D_PUSH, "BUF CONTENT: %s",
format_hex(BPTR(&buf), BLEN(&buf), 80, &gc));

msg(D_PUSH_ERRORS, "WARNING: Received malformed custom app control channel "
"(%s) message control message: %s", err_reason,
BSTR(buffer));
gc_free(&gc);
}

/**
* Parse the keyword for the AUTH_PENDING request
* @param buffer buffer containing the keywords, the buffer's
Expand Down Expand Up @@ -431,6 +584,36 @@ send_auth_failed(struct context *c, const char *client_reason)
gc_free(&gc);
}

bool
send_acc_message(struct tls_multi *tls_multi,
struct tls_session *session,
const char *protocol, bool fragment,
const char *msg, bool base64)
{
/* TODO check client capabilities */
/* 3 for the encoding, potential F, and , 1 for the final flag, 5 for the message size itself */
const size_t max_header_size = strlen("ACC,") + 3 + strlen(protocol) + 1 + 5;

size_t len = max_header_size + strlen(msg);

if (len > PUSH_BUNDLE_SIZE)
{
return false;
}

struct gc_arena gc = gc_new();
struct buffer buf = alloc_buf_gc(len, &gc);

/* Example message: ACC,muppets,15,A,I am Miss Piggy */
buf_printf(&buf, "ACC,%s,%zu,%s%s,%s", protocol,
strlen(msg),
base64 ? "6" : "A",
fragment ? ":F" : "",
msg);

send_control_channel_string_dowork(session, BSTR(&buf), D_PUSH);
return true;
}

bool
send_auth_pending_messages(struct tls_multi *tls_multi,
Expand Down
Loading

0 comments on commit 90edc4e

Please sign in to comment.