From 1f5b997daf6019cb5c4c0de831792cd96ac02a35 Mon Sep 17 00:00:00 2001 From: Benjamin Franzke Date: Mon, 23 May 2011 18:03:39 +0200 Subject: initial commit --- src/gen_message.sh | 9 ++ src/messages.txt | 26 ++++ src/mumble.proto | 287 +++++++++++++++++++++++++++++++++++ src/socket.c | 432 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 754 insertions(+) create mode 100644 src/gen_message.sh create mode 100644 src/messages.txt create mode 100644 src/mumble.proto create mode 100644 src/socket.c diff --git a/src/gen_message.sh b/src/gen_message.sh new file mode 100644 index 0000000..4b0d1f8 --- /dev/null +++ b/src/gen_message.sh @@ -0,0 +1,9 @@ +echo "static const struct { const ProtobufCMessageDescriptor *descriptor; const char *name; } messages[] = {" +while read message +do + #lower_name=$(echo $message | tr '[:upper:]' '_[:lower:]' | sed "s/^_\(.*\)$/\1/") + + prefixed_lower_name=$(echo "$message" | sed "s/\(^\|[a-z]\)\([A-Z][A-Z]*\)/\1_\L\2/g") + echo -e "\t/* ${message} */ { &mumble_proto_${prefixed_lower_name}__descriptor, \"$message\" }," +done +echo "};" diff --git a/src/messages.txt b/src/messages.txt new file mode 100644 index 0000000..17dba7e --- /dev/null +++ b/src/messages.txt @@ -0,0 +1,26 @@ +Version +UDPTunnel +Authenticate +Ping +Reject +ServerSync +ChannelRemove +ChannelState +UserRemove +UserState +BanList +TextMessage +PermissionDenied +ACL +QueryUsers +CryptSetup +ContextActionModify +ContextAction +UserList +VoiceTarget +PermissionQuery +CodecVersion +UserStats +RequestBlob +ServerConfig +SuggestConfig diff --git a/src/mumble.proto b/src/mumble.proto new file mode 100644 index 0000000..d2d3c66 --- /dev/null +++ b/src/mumble.proto @@ -0,0 +1,287 @@ +package MumbleProto; + +option optimize_for = SPEED; + +message Version { + optional uint32 version = 1; + optional string release = 2; + optional string os = 3; + optional string os_version = 4; +} + +message UDPTunnel { + required bytes packet = 1; +} + +message Authenticate { + optional string username = 1; + optional string password = 2; + repeated string tokens = 3; + repeated int32 celt_versions = 4; +} + +message Ping { + optional uint64 timestamp = 1; + optional uint32 good = 2; + optional uint32 late = 3; + optional uint32 lost = 4; + optional uint32 resync = 5; + optional uint32 udp_packets = 6; + optional uint32 tcp_packets = 7; + optional float udp_ping_avg = 8; + optional float udp_ping_var = 9; + optional float tcp_ping_avg = 10; + optional float tcp_ping_var = 11; +} + +message Reject { + enum RejectType { + None = 0; + WrongVersion = 1; + InvalidUsername = 2; + WrongUserPW = 3; + WrongServerPW = 4; + UsernameInUse = 5; + ServerFull = 6; + NoCertificate = 7; + } + optional RejectType type = 1; + optional string reason = 2; +} + +message ServerConfig { + optional uint32 max_bandwidth = 1; + optional string welcome_text = 2; + optional bool allow_html = 3; + optional uint32 message_length = 4; + optional uint32 image_message_length = 5; +} + +message ServerSync { + optional uint32 session = 1; + optional uint32 max_bandwidth = 2; + optional string welcome_text = 3; + optional uint64 permissions = 4; +} + +message ChannelRemove { + required uint32 channel_id = 1; +} + +message ChannelState { + optional uint32 channel_id = 1; + optional uint32 parent = 2; + optional string name = 3; + repeated uint32 links = 4; + optional string description = 5; + repeated uint32 links_add = 6; + repeated uint32 links_remove = 7; + optional bool temporary = 8 [default = false]; + optional int32 position = 9 [default = 0]; + optional bytes description_hash = 10; +} + +message UserRemove { + required uint32 session = 1; + optional uint32 actor = 2; + optional string reason = 3; + optional bool ban = 4; +} + +message UserState { + optional uint32 session = 1; + optional uint32 actor = 2; + optional string name = 3; + optional uint32 user_id = 4; + optional uint32 channel_id = 5; + optional bool mute = 6; + optional bool deaf = 7; + optional bool suppress = 8; + optional bool self_mute = 9; + optional bool self_deaf = 10; + optional bytes texture = 11; + optional bytes plugin_context = 12; + optional string plugin_identity = 13; + optional string comment = 14; + optional string hash = 15; + optional bytes comment_hash = 16; + optional bytes texture_hash = 17; + optional bool priority_speaker = 18; + optional bool recording = 19; +} + +message BanList { + message BanEntry { + required bytes address = 1; + required uint32 mask = 2; + optional string name = 3; + optional string hash = 4; + optional string reason = 5; + optional string start = 6; + optional uint32 duration = 7; + } + repeated BanEntry bans = 1; + optional bool query = 2 [default = false]; +} + +message TextMessage { + optional uint32 actor = 1; + repeated uint32 session = 2; + repeated uint32 channel_id = 3; + repeated uint32 tree_id = 4; + required string message = 5; +} + +message PermissionDenied { + enum DenyType { + Text = 0; + Permission = 1; + SuperUser = 2; + ChannelName = 3; + TextTooLong = 4; + H9K = 5; + TemporaryChannel = 6; + MissingCertificate = 7; + UserName = 8; + ChannelFull = 9; + } + optional uint32 permission = 1; + optional uint32 channel_id = 2; + optional uint32 session = 3; + optional string reason = 4; + optional DenyType type = 5; + optional string name = 6; +} + +message ACL { + message ChanGroup { + required string name = 1; + optional bool inherited = 2 [default = true]; + optional bool inherit = 3 [default = true]; + optional bool inheritable = 4 [default = true]; + repeated uint32 add = 5; + repeated uint32 remove = 6; + repeated uint32 inherited_members = 7; + } + message ChanACL { + optional bool apply_here = 1 [default = true]; + optional bool apply_subs = 2 [default = true]; + optional bool inherited = 3 [default = true]; + optional uint32 user_id = 4; + optional string group = 5; + optional uint32 grant = 6; + optional uint32 deny = 7; + } + required uint32 channel_id = 1; + optional bool inherit_acls = 2 [default = true]; + repeated ChanGroup groups = 3; + repeated ChanACL acls = 4; + optional bool query = 5 [default = false]; +} + +message QueryUsers { + repeated uint32 ids = 1; + repeated string names = 2; +} + +message CryptSetup { + optional bytes key = 1; + optional bytes client_nonce = 2; + optional bytes server_nonce = 3; +} + +message ContextActionModify { + enum Context { + Server = 0x01; + Channel = 0x02; + User = 0x04; + } + enum Operation { + Add = 0; + Remove = 1; + } + required string action = 1; + optional string text = 2; + optional uint32 context = 3; + optional Operation operation = 4; +} + +message ContextAction { + optional uint32 session = 1; + optional uint32 channel_id = 2; + required string action = 3; +} + +message UserList { + message User { + required uint32 user_id = 1; + optional string name = 2; + } + repeated User users = 1; +} + +message VoiceTarget { + message Target { + repeated uint32 session = 1; + optional uint32 channel_id = 2; + optional string group = 3; + optional bool links = 4 [default = false]; + optional bool children = 5 [default = false]; + } + optional uint32 id = 1; + repeated Target targets = 2; +} + +message PermissionQuery { + optional uint32 channel_id = 1; + optional uint32 permissions = 2; + optional bool flush = 3 [default = false]; +} + +message CodecVersion { + required int32 alpha = 1; + required int32 beta = 2; + required bool prefer_alpha = 3 [default = true]; +} + +message UserStats { + message Stats { + optional uint32 good = 1; + optional uint32 late = 2; + optional uint32 lost = 3; + optional uint32 resync = 4; + } + + optional uint32 session = 1; + optional bool stats_only = 2 [default = false]; + repeated bytes certificates = 3; + optional Stats from_client = 4; + optional Stats from_server = 5; + + optional uint32 udp_packets = 6; + optional uint32 tcp_packets = 7; + optional float udp_ping_avg = 8; + optional float udp_ping_var = 9; + optional float tcp_ping_avg = 10; + optional float tcp_ping_var = 11; + + optional Version version = 12; + repeated int32 celt_versions = 13; + optional bytes address = 14; + optional uint32 bandwidth = 15; + optional uint32 onlinesecs = 16; + optional uint32 idlesecs = 17; + optional bool strong_certificate = 18 [default = false]; +} + +message SuggestConfig { + optional uint32 version = 1; + optional bool positional = 2; + optional bool push_to_talk = 3; +} + +message RequestBlob { + repeated uint32 session_texture = 1; + repeated uint32 session_comment = 2; + repeated uint32 channel_description = 3; +} diff --git a/src/socket.c b/src/socket.c new file mode 100644 index 0000000..690b94f --- /dev/null +++ b/src/socket.c @@ -0,0 +1,432 @@ +#include +#include +#include + +#include + +#include "mumble.pb-c.h" + +#include "polarssl/net.h" +#include "polarssl/ssl.h" +#include "polarssl/havege.h" + +#include +#include +#include + +#include +#include + +#include "messages.h" + +#define ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0])) + +#define PREAMBLE_SIZE 6 + +struct context { + GMainLoop *loop; + + uint32_t session; + bool authenticated; + + ssl_context ssl; + havege_state hs; + ssl_session ssn; + int sock; + GIOChannel *sock_channel; + + CELTHeader celt_header; + CELTMode *celt_mode; +}; + +enum udp_message_type { + udp_voice_celt_alpha, + udp_ping, + udp_voice_speex, + udp_voice_celt_beta, +}; + +int64_t +decode_varint(uint8_t *data, uint32_t *read, uint32_t left) +{ + int64_t varint = 0; + + /* 1 byte with 7 · 8 + 1 leading zeroes */ + if ((data[0] & 0x80) == 0x00) { + varint = data[0] & 0x7F; + *read = 1; + /* 2 bytes with 6 · 8 + 2 leading zeroes */ + } else if ((data[0] & 0xC0) == 0x80) { + varint = ((data[0] & 0x3F) << 8) | data[1]; + *read = 2; + /* 3 bytes with 5 · 8 + 3 leading zeroes */ + } else if ((data[0] & 0xE0) == 0xC0) { + varint = (((data[0] & 0x1F) << 16) | + (data[1] << 8) | (data[2])); + *read = 3; + /* 4 bytes with 4 · 8 + 4 leading zeroes */ + } else if ((data[0] & 0xF0) == 0xE0) { + varint = (((data[0] & 0x0F) << 24) | (data[1] << 16) | + (data[2] << 8) | (data[3])); + *read = 4; + } else /* if ((data[pos] & 0xF0) == 0xF0) */ { + switch (data[0] & 0xFC) { + /* 32-bit positive number */ + case 0xF0: + varint = ((data[1] << 24) | (data[2] << 16) | + (data[3] << 8) | data[4]); + *read = 1 + 4; + break; + /* 64-bit number */ + case 0xF4: + varint = + ((int64_t)data[1] << 56) | ((int64_t)data[2] << 48) | + ((int64_t)data[3] << 40) | ((int64_t)data[4] << 32) | + (data[5] << 24) | (data[6] << 16) | + (data[7] << 8) | (data[8] << 0); + *read = 1 + 8; + break; + /* Negative varint */ + case 0xF8: + /* FIXME: handle endless recursion */ + varint = -decode_varint(&data[1], read, left - 1); + *read += 1; + break; + /* Negative two bit number */ + case 0xFC: + varint = -(int)(data[0] & 0x03); + *read = 1; + break; + } + } + + return varint; +} + +static void +handle_udp(struct context *ctx, uint8_t *data, uint32_t len) +{ + int64_t session; + int64_t sequence; + int pos = 1; + int read = 0; + +#define PCM_SIZE (48000/100 * 1) + int16_t pcm[PCM_SIZE]; + uint8_t buf[BUFSIZ]; + FILE *f; + int frame_len, term; + CELTDecoder *dec_state; + JitterBuffer *jitter; + CELTMode *mode; + static int iseq = 0; + + session = decode_varint(&data[pos], &read, len-pos); + pos += read; + sequence = decode_varint(&data[pos], &read, len-pos); + pos += read; + printf("session: %ld, sequence: %ld\n", session, sequence); + + f = fopen("foo", "a+"); + + dec_state = celt_decoder_create(ctx->celt_mode, + ctx->celt_header.nb_channels, NULL); + + jitter = jitter_buffer_init(ctx->celt_header.frame_size); + jitter_buffer_ctl(jitter, JITTER_BUFFER_SET_MARGIN, + &ctx->celt_header.frame_size); + + do { + frame_len = (data[pos] & 0x7F); + term = (data[pos] & 0x80) == 0x80; + printf("_len: %d, term: %d\n", frame_len, term); + pos += 1; + + if (frame_len == 0) + break; + + JitterBufferPacket packet; + packet.data = &data[pos]; + packet.len = frame_len; + packet.timestamp = ctx->celt_header.frame_size * iseq++; + packet.span = ctx->celt_header.frame_size; + packet.sequence = 0; + + jitter_buffer_put(jitter, &packet); + + packet.data = buf; + packet.len = BUFSIZ; + jitter_buffer_tick(jitter); + jitter_buffer_get(jitter, &packet, ctx->celt_header.frame_size, NULL); + + if (packet.len == 0) + packet.data = NULL; + + celt_decode(dec_state, packet.data, packet.len, pcm); + fwrite(pcm, sizeof(int16_t), PCM_SIZE, f); + + pos += frame_len; + sequence++; + } while (term); + + fclose(f); + celt_decoder_destroy(dec_state); +} + +static void +add_preamble(uint8_t *buffer, uint16_t type, uint32_t len) +{ + buffer[1] = (type) & 0xff; + buffer[0] = (type >> 8) & 0xff; + + buffer[5] = (len) & 0xff; + buffer[4] = (len >> 8) & 0xff; + buffer[3] = (len >> 16) & 0xff; + buffer[2] = (len >> 24) & 0xff; +} + +static void +get_preamble(uint8_t *buffer, int *type, int *len) +{ + uint16_t msgType; + uint32_t msgLen; + + msgType = buffer[1] | (buffer[0] << 8); + msgLen = buffer[5] | (buffer[4] << 8) | (buffer[3] << 16) | (buffer[2] << 24); + *type = (int)msgType; + *len = (int)msgLen; +} + +static void +recv_version(MumbleProto__Version *version, struct context *ctx) +{ + printf("version: 0x%x\n", version->version); + printf("release: %s\n", version->release); +} + +static void +recv_channel_state(MumbleProto__ChannelState *state, struct context *ctx) +{ + printf("channel: id: %u, parent: %u, name: %s, description: %s, temporary: %d, position: %d\n", + state->channel_id, state->parent, state->name, state->description, state->temporary, state->position); +} + +static void +send_msg(struct context *ctx, ProtobufCMessage *msg) +{ + uint8_t pad[128]; + uint8_t preamble[PREAMBLE_SIZE]; + int ret = 0; + int type = -1; + int i; + ProtobufCBufferSimple buffer = PROTOBUF_C_BUFFER_SIMPLE_INIT(pad); + + for (i = 0; i < ARRAY_SIZE(messages); ++i) + if (messages[i].descriptor == msg->descriptor) + type = i; + assert(type >= 0); + + protobuf_c_message_pack_to_buffer(msg, &buffer.base); + add_preamble(preamble, type, buffer.len); + + while ((ret = ssl_write(&ctx->ssl, preamble, PREAMBLE_SIZE)) <= 0) { + if (ret != POLARSSL_ERR_NET_TRY_AGAIN) { + printf("write failed: %d\n", ret); + abort(); + } + } + while ((ret = ssl_write(&ctx->ssl, buffer.data, buffer.len)) < buffer.len) { + if (ret != POLARSSL_ERR_NET_TRY_AGAIN) { + printf("write failed: %d\n", ret); + abort(); + } + } + + PROTOBUF_C_BUFFER_SIMPLE_CLEAR(&buffer); +} + +typedef void (*callback_t)(void *, void *); + +static void +recv_msg(struct context *ctx, const callback_t *callbacks, uint32_t callback_size) +{ + uint8_t preamble[PREAMBLE_SIZE]; + ProtobufCMessage *msg; + void *data; + int type, len; + int ret, i; + + printf("recv msg\n"); + + do { + ret = ssl_read(&ctx->ssl, preamble, 6); + if (ret == POLARSSL_ERR_NET_CONN_RESET) { + printf("conn reset\n"); + exit(1); + } + } while (ret == POLARSSL_ERR_NET_TRY_AGAIN); + + if (ret <= 0) { + printf("read failed: %d\n", ret); + return; + } + + get_preamble(preamble, &type, &len); + + if (!(type >= 0 && type < ARRAY_SIZE(messages))) { + printf("unknown message type: %d\n", type); + return; + } + + if (len <= 0) { + printf("length 0\n"); + return; + } + + data = malloc(len); + if (data == NULL) { + printf("out of mem\n"); + abort(); + } + ret = ssl_read(&ctx->ssl, data, len); + if (ret == POLARSSL_ERR_NET_CONN_RESET) { + printf("conn reset\n"); + exit(1); + } + + /* tunneled udp data - not a regular protobuf message */ + if (type == 1) { + handle_udp(ctx, data, len); + free(data); + return; + } + + msg = protobuf_c_message_unpack(messages[type].descriptor, NULL, + len, data); + if (msg == NULL) { + printf("message unpack failure\n"); + return ; + } + + printf("debug: received message: %s type:%d, len:%d\n", messages[type].name, type, len); + if (callbacks[type]) + callbacks[type](msg, ctx); + + protobuf_c_message_free_unpacked(msg, NULL); + free(data); +} + +static void +my_debug(void *ctx, int level, const char *str) +{ + if (level <= 1) + printf("polarssl [level %d]: %s\n", level, str); +} + +static void +do_ping(struct context *ctx) +{ + MumbleProto__Ping ping; + struct timeval tv; + + gettimeofday(&tv, NULL); + mumble_proto__ping__init(&ping); + + ping.timestamp = tv.tv_sec; + ping.resync = 1; + + send_msg(ctx, &ping.base); +} + +static const callback_t callbacks[] = { + /* VERSION */ (callback_t) recv_version, + [7] = (callback_t) recv_channel_state, + [127] = NULL, +}; + +static gboolean +_recv(GIOChannel *source, GIOCondition condition, gpointer data) +{ + struct context *ctx = data; + do { + recv_msg(ctx, callbacks, ARRAY_SIZE(callbacks)); + } while (ssl_get_bytes_avail(&ctx->ssl) > 0); + + do_ping(ctx); + + return TRUE; +} + +int main(int argc, char **argv) +{ +#if 1 + char *host = "localhost"; + unsigned int port = 64738; +#else + char *host = "85.214.21.153"; + unsigned int port = 33321; +#endif + struct context ctx; + int ret; + + memset(&ctx, 0, sizeof(ctx)); + + ssl_init(&ctx.ssl); + havege_init( &ctx.hs ); + + ret = net_connect(&ctx.sock, host, port); + ssl_set_endpoint(&ctx.ssl, SSL_IS_CLIENT); + ssl_set_authmode(&ctx.ssl, SSL_VERIFY_NONE); + + ssl_set_rng(&ctx.ssl, havege_rand, &ctx.hs); + ssl_set_dbg(&ctx.ssl, my_debug, NULL); + ssl_set_bio(&ctx.ssl, net_recv, &ctx.sock, net_send, &ctx.sock); + + //ssl_set_session(&ctx.ssl, 1, 600, &ssn); + ssl_set_session(&ctx.ssl, 0, 0, &ctx.ssn); + ssl_set_ciphers(&ctx.ssl, ssl_default_ciphers); + + { + MumbleProto__Version version; + mumble_proto__version__init(&version); + version.version = 0x010203; + version.release = "cmumble 0.1"; + version.os = "Gentoo/Linux"; + send_msg(&ctx, &version.base); + } + + { + MumbleProto__Authenticate authenticate; + mumble_proto__authenticate__init(&authenticate); + authenticate.username = "ben2"; + authenticate.password = ""; + authenticate.n_celt_versions = 1; + authenticate.celt_versions = (int32_t[]) { 0x8000000b }; + send_msg(&ctx, &authenticate.base); + } + + do_ping(&ctx); + +#define SAMPLERATE 48000 +#define CHANNELS 1 + ctx.celt_mode = celt_mode_create(SAMPLERATE, SAMPLERATE / 100, NULL); + celt_header_init(&ctx.celt_header, ctx.celt_mode, CHANNELS); + uint8_t celt_header_packet[sizeof(CELTHeader)]; + printf("extra headers: %d\n", ctx.celt_header.extra_headers); + celt_header_to_packet(&ctx.celt_header, celt_header_packet, sizeof(CELTHeader)); + + g_type_init(); + ctx.loop = g_main_loop_new(NULL, FALSE); + + ctx.sock_channel = g_io_channel_unix_new(ctx.sock); + g_io_add_watch(ctx.sock_channel, G_IO_IN | G_IO_ERR, _recv, &ctx); + + g_main_loop_run(ctx.loop); + + g_main_loop_unref(ctx.loop); + + net_close(ctx.sock); + ssl_free(&ctx.ssl); + memset(&ctx.ssl, 0, sizeof(ctx.ssl)); + +} -- cgit