protocol uint/magnolia / source / mcproto / client.c
  1#define _POSIX_C_SOURCE 200112L
  2
  3#include <errno.h>
  4#include <netdb.h>
  5#include <stdio.h>
  6#include <stdlib.h>
  7#include <string.h>
  8#include <sys/socket.h>
  9#include <unistd.h>
 10
 11#include <zlib.h>
 12
 13#include "internal.h"
 14#include "mg_random.h"
 15
 16#ifndef MSG_NOSIGNAL
 17#define MSG_NOSIGNAL 0
 18#endif
 19
 20/* parse into host and port  + validate */
 21static int split_address(const char* address, char* host, size_t host_size, u16* port, char* error,
 22                         size_t error_size)
 23{
 24	const char* colon = strrchr(address, ':');
 25	const char* end = address + strlen(address);
 26	char*       port_end = NULL;
 27	size_t      length;
 28	long        parsed_port = 25565;
 29
 30	if (address[0] == '[') {
 31		const char* close = strchr(address, ']');
 32		if (!close) {
 33			mc_set_error(error, error_size, "invalid bracketed IPv6 address");
 34			return 0;
 35		}
 36		length = (size_t)(close - address - 1);
 37		address += 1;
 38		if (close[1] == ':') {
 39			parsed_port = strtol(close + 2, &port_end, 10);
 40		} else if (close[1] != '\0') {
 41			mc_set_error(error, error_size, "invalid address suffix");
 42			return 0;
 43		}
 44	} else if (colon && strchr(address, ':') == colon) {
 45		length = (size_t)(colon - address);
 46		parsed_port = strtol(colon + 1, &port_end, 10);
 47	} else {
 48		length = (size_t)(end - address);
 49	}
 50	if ((port_end && *port_end) || length == 0 || length >= host_size || parsed_port < 1 ||
 51	    parsed_port > 65535) {
 52		mc_set_error(error, error_size, "invalid server address");
 53		return 0;
 54	}
 55	memcpy(host, address, length);
 56	host[length] = '\0';
 57	*port = (u16)parsed_port;
 58	return 1;
 59}
 60
 61/* connect to the configured server address */
 62static int tcp_connect(MCClient* client, char* error, size_t error_size)
 63{
 64	struct addrinfo  hints;
 65	struct addrinfo* addresses = NULL;
 66	struct addrinfo* address;
 67	char             service[6];
 68	int              result;
 69
 70	memset(&hints, 0, sizeof(hints));
 71	hints.ai_family = AF_UNSPEC;
 72	hints.ai_socktype = SOCK_STREAM;
 73	snprintf(service, sizeof(service), "%u", client->port);
 74
 75	result = getaddrinfo(client->host, service, &hints, &addresses);
 76	if (result != 0) {
 77		mc_set_error(error, error_size, "could not resolve %s: %s", client->host,
 78		             gai_strerror(result));
 79		return 0;
 80	}
 81
 82	for (address = addresses; address; address = address->ai_next) {
 83		client->socket_fd =
 84		    socket(address->ai_family, address->ai_socktype, address->ai_protocol);
 85		if (client->socket_fd < 0)
 86			continue;
 87		if (connect(client->socket_fd, address->ai_addr, address->ai_addrlen) == 0)
 88			break;
 89		close(client->socket_fd);
 90		client->socket_fd = -1;
 91	}
 92	freeaddrinfo(addresses);
 93
 94	if (client->socket_fd < 0) {
 95		mc_set_error(error, error_size, "could not connect to %s:%u: %s", client->host,
 96		             client->port, strerror(errno));
 97		return 0;
 98	}
 99	return 1;
100}
101
102static int socket_write(MCClient* client, const u8* data, size_t size)
103{
104	u8     encrypted[4096];
105	size_t offset = 0;
106
107	while (offset < size) {
108		size_t chunk = size - offset;
109		size_t sent = 0;
110		if (chunk > sizeof(encrypted))
111			chunk = sizeof(encrypted);
112		memcpy(encrypted, data + offset, chunk);
113		if (client->encrypted)
114			mc_cipher_encrypt(&client->write_cipher, encrypted, chunk);
115		while (sent < chunk) {
116			ssize_t count =
117			    send(client->socket_fd, encrypted + sent, chunk - sent, MSG_NOSIGNAL);
118			if (count > 0) {
119				sent += (size_t)count;
120			} else if (count < 0 && errno == EINTR) {
121				continue;
122			} else {
123				return 0;
124			}
125		}
126		offset += chunk;
127	}
128	return 1;
129}
130
131static int socket_read(MCClient* client, u8* data, size_t size)
132{
133	size_t offset = 0;
134	while (offset < size) {
135		ssize_t count = recv(client->socket_fd, data + offset, size - offset, 0);
136		if (count > 0) {
137			offset += (size_t)count;
138		} else if (count < 0 && errno == EINTR) {
139			continue;
140		} else {
141			return 0;
142		}
143	}
144	if (client->encrypted)
145		mc_cipher_decrypt(&client->read_cipher, data, size);
146	return 1;
147}
148
149static int socket_read_varint(MCClient* client, i32* value)
150{
151	u32 result = 0;
152	int shift;
153	for (shift = 0; shift < 35; shift += 7) {
154		u8 byte;
155		if (!socket_read(client, &byte, 1))
156			return 0;
157		result |= (u32)(byte & 0x7f) << shift;
158		if (!(byte & 0x80)) {
159			*value = (i32)result;
160			return 1;
161		}
162	}
163	return 0;
164}
165
166int mc_client_write(MCClient* client, i32 packet_id, const void* data, size_t size)
167{
168	MCBuffer body;
169	MCBuffer framed;
170	MCBuffer length;
171	u8*      compressed = NULL;
172	uLongf   compressed_size;
173	int      ok = 0;
174
175	if (!client || client->socket_fd < 0 || size > MC_MAX_PACKET_SIZE)
176		return 0;
177	bufinit(&body);
178	bufinit(&framed);
179	bufinit(&length);
180	/* build the packet body... */
181	if (!bufwrite_varint(&body, packet_id) || !bufwrite(&body, data, size))
182		goto cleanup;
183	/* packet layout:
184	   length VarInt
185	   [data length VarInt if compression is enabled]
186	   packet id VarInt
187	   payload
188
189	   https://wikivg.booky.dev/Protocol#Packet_format */
190
191	/* ...then add the packet length and compression header */
192	if (client->compression_threshold >= 0) {
193		/* if in compressed mode, data length 0 means this packet body
194		   is not actually compressed. otherwise it is the size after
195		   zlib decompression
196		   https://wikivg.booky.dev/Protocol#With_compression */
197		if (body.size >= (size_t)client->compression_threshold) {
198			compressed_size = compressBound((uLong)body.size);
199			compressed = (u8*)malloc(compressed_size);
200			if (!compressed ||
201			    compress2(compressed, &compressed_size, body.data, (uLong)body.size,
202			              Z_DEFAULT_COMPRESSION) != Z_OK)
203				goto cleanup;
204			if (!bufwrite_varint(&framed, (i32)body.size) ||
205			    !bufwrite(&framed, compressed, compressed_size))
206				goto cleanup;
207		} else if (!bufwrite_varint(&framed, 0) ||
208		           !bufwrite(&framed, body.data, body.size)) {
209			goto cleanup;
210		}
211	} else if (!bufwrite(&framed, body.data, body.size)) {
212		goto cleanup;
213	}
214	if (bufwrite_varint(&length, (i32)framed.size) &&
215	    socket_write(client, length.data, length.size) &&
216	    socket_write(client, framed.data, framed.size))
217		ok = 1;
218
219cleanup:
220	free(compressed);
221	buffree(&length);
222	buffree(&framed);
223	buffree(&body);
224	return ok;
225}
226
227int mc_client_read(MCClient* client, MCPacket* packet, char* error, size_t error_size)
228{
229	i32      frame_length;
230	MCBuffer frame;
231	MCBuffer decoded;
232	i32      data_length;
233	i32      packet_id;
234	int      decoded_allocated = 0;
235	int      ok = 0;
236
237	memset(packet, 0, sizeof(*packet));
238	bufinit(&frame);
239	bufinit(&decoded);
240	if (!socket_read_varint(client, &frame_length)) {
241		mc_set_error(error, error_size, "connection closed while reading packet length");
242		goto cleanup;
243	}
244	if (frame_length <= 0 || (u32)frame_length > MC_MAX_PACKET_SIZE ||
245	    !bufreserve(&frame, (size_t)frame_length)) {
246		mc_set_error(error, error_size, "invalid packet length: %d", frame_length);
247		goto cleanup;
248	}
249	frame.size = (size_t)frame_length;
250	if (!socket_read(client, frame.data, frame.size)) {
251		mc_set_error(error, error_size, "connection closed while reading packet");
252		goto cleanup;
253	}
254	if (client->compression_threshold >= 0) {
255		/* same as before data length 0 means no compression */
256		if (!bufread_varint(&frame, &data_length) || data_length < 0 ||
257		    (u32)data_length > MC_MAX_PACKET_SIZE) {
258			mc_set_error(error, error_size, "invalid compressed packet header");
259			goto cleanup;
260		}
261		if (data_length == 0) {
262			decoded.data = frame.data + frame.cursor;
263			decoded.size = frame.size - frame.cursor;
264		} else {
265			uLongf output_size = (uLongf)data_length;
266			if (!bufreserve(&decoded, (size_t)data_length) ||
267			    uncompress(decoded.data, &output_size, frame.data + frame.cursor,
268			               (uLong)(frame.size - frame.cursor)) != Z_OK ||
269			    output_size != (uLongf)data_length) {
270				mc_set_error(error, error_size, "invalid zlib packet");
271				goto cleanup;
272			}
273			decoded_allocated = 1;
274			decoded.size = (size_t)output_size;
275		}
276	} else {
277		decoded.data = frame.data;
278		decoded.size = frame.size;
279	}
280	if (!bufread_varint(&decoded, &packet_id)) {
281		mc_set_error(error, error_size, "packet has no valid id");
282		goto cleanup;
283	}
284	packet->size = decoded.size - decoded.cursor;
285	packet->data = (u8*)malloc(packet->size ? packet->size : 1);
286	if (!packet->data) {
287		mc_set_error(error, error_size, "out of memory reading packet");
288		goto cleanup;
289	}
290	memcpy(packet->data, decoded.data + decoded.cursor, packet->size);
291	packet->id = packet_id;
292	ok = 1;
293
294cleanup:
295	if (decoded_allocated) {
296		buffree(&decoded);
297	} else {
298		decoded.data = NULL;
299	}
300	buffree(&frame);
301	if (!ok)
302		mc_packet_free(packet);
303	return ok;
304}
305
306void mc_packet_free(MCPacket* packet)
307{
308	if (!packet)
309		return;
310	free(packet->data);
311	memset(packet, 0, sizeof(*packet));
312}
313
314static int send_handshake(MCClient* client)
315{
316	MCBuffer payload;
317	int      ok;
318	bufinit(&payload);
319	/* handshake to next state 2 switches the connection into login state */
320	ok = bufwrite_varint(&payload, MC_PROTOCOL_1_8_9) &&
321	     bufwrite_string(&payload, client->host) && bufwrite_u16(&payload, client->port) &&
322	     bufwrite_varint(&payload, 2) &&
323	     mc_client_write(client, 0x00, payload.data, payload.size);
324	buffree(&payload);
325	return ok;
326}
327
328static int send_login_start(MCClient* client)
329{
330	MCBuffer payload;
331	int      ok;
332	bufinit(&payload);
333	ok = bufwrite_string(&payload, client->username) &&
334	     mc_client_write(client, 0x00, payload.data, payload.size);
335	buffree(&payload);
336	return ok;
337}
338
339static int handle_encryption(MCClient* client, const MCAccount* account, MCPacket* packet,
340                             char* error, size_t error_size)
341{
342	MCBuffer  input = { packet->data, packet->size, packet->size, 0 };
343	MCBuffer  response;
344	char      server_id[256];
345	const u8* public_key;
346	const u8* verify_token;
347	size_t    public_key_size;
348	size_t    verify_token_size;
349	u8        shared_secret[16];
350	u8*       encrypted_secret = NULL;
351	u8*       encrypted_token = NULL;
352	size_t    encrypted_secret_size = 0;
353	size_t    encrypted_token_size = 0;
354	int       ok = 0;
355
356	/* parse the server encryption request */
357	if (!bufread_string(&input, server_id, sizeof(server_id)) ||
358	    !bufread_bytes(&input, &public_key, &public_key_size) ||
359	    !bufread_bytes(&input, &verify_token, &verify_token_size)) {
360		mc_set_error(error, error_size, "malformed encryption request");
361		return 0;
362	}
363	/* create the AES shared secret for the connection */
364	if (!mg_random_bytes(shared_secret, sizeof(shared_secret))) {
365		mc_set_error(error, error_size, "could not obtain secure random bytes");
366		return 0;
367	}
368	/* join the mojang session, encrypt the secret, verify token */
369	if (!mc_join_server(account, server_id, shared_secret, public_key, public_key_size, error,
370	                    error_size) ||
371	    !mc_rsa_encrypt(public_key, public_key_size, shared_secret, sizeof(shared_secret),
372	                    &encrypted_secret, &encrypted_secret_size, error, error_size) ||
373	    !mc_rsa_encrypt(public_key, public_key_size, verify_token, verify_token_size,
374	                    &encrypted_token, &encrypted_token_size, error, error_size))
375		goto cleanup;
376
377	bufinit(&response);
378	if (bufwrite_varint(&response, (i32)encrypted_secret_size) &&
379	    bufwrite(&response, encrypted_secret, encrypted_secret_size) &&
380	    bufwrite_varint(&response, (i32)encrypted_token_size) &&
381	    bufwrite(&response, encrypted_token, encrypted_token_size) &&
382	    mc_client_write(client, 0x01, response.data, response.size)) {
383		/* enable encryption after sending the unencrypted response */
384		mc_cipher_init(&client->read_cipher, shared_secret);
385		mc_cipher_init(&client->write_cipher, shared_secret);
386		client->encrypted = 1;
387		ok = 1;
388	} else {
389		mc_set_error(error, error_size, "could not send encryption response");
390	}
391	buffree(&response);
392
393cleanup:
394	memset(shared_secret, 0, sizeof(shared_secret));
395	free(encrypted_secret);
396	free(encrypted_token);
397	return ok;
398}
399
400/* process the login packets until we enter play state */
401static int login_loop(MCClient* client, const MCAccount* account, char* error, size_t error_size)
402{
403	/* login state packet ids for protocol 47:
404	   0x00 disconnect
405	   0x01 encryption request
406	   0x02 login success
407	   0x03 set compression
408
409	   https://prismarinejs.github.io/minecraft-data/protocol/pc/1.8/#login.toClient.types */
410	for (;;) {
411		MCPacket packet;
412		MCBuffer input;
413		if (!mc_client_read(client, &packet, error, error_size))
414			return 0;
415		input.data = packet.data;
416		input.size = packet.size;
417		input.capacity = packet.size;
418		input.cursor = 0;
419		if (packet.id == 0x00) {
420			char reason[1024];
421			if (!bufread_string(&input, reason, sizeof(reason)))
422				strcpy(reason, "malformed disconnect reason");
423			mc_set_error(error, error_size, "server disconnected during login: %s",
424			             reason);
425			mc_packet_free(&packet);
426			return 0;
427		}
428		if (packet.id == 0x01) {
429			int ok = handle_encryption(client, account, &packet, error, error_size);
430			mc_packet_free(&packet);
431			if (!ok)
432				return 0;
433			continue;
434		}
435		if (packet.id == 0x02) {
436			char username[64];
437			if (!bufread_string(&input, client->uuid, sizeof(client->uuid)) ||
438			    !bufread_string(&input, username, sizeof(username))) {
439				mc_set_error(error, error_size, "malformed login success packet");
440				mc_packet_free(&packet);
441				return 0;
442			}
443			printf("login success: uuid=%s username=%s\n", client->uuid, username);
444			mc_packet_free(&packet);
445			return 1;
446		}
447		if (packet.id == 0x03) {
448			i32 threshold;
449			if (!bufread_varint(&input, &threshold) || threshold < 0) {
450				mc_set_error(error, error_size, "invalid compression threshold");
451				mc_packet_free(&packet);
452				return 0;
453			}
454			client->compression_threshold = threshold;
455			printf("compression enabled: threshold=%d\n", threshold);
456			mc_packet_free(&packet);
457			continue;
458		}
459		mc_set_error(error, error_size, "unexpected login packet 0x%02x", packet.id);
460		mc_packet_free(&packet);
461		return 0;
462	}
463}
464/*create a client */
465int mc_client_login(MCClient** out, const char* address, const MCAccount* account, char* error,
466                    size_t error_size)
467{
468	MCClient* client;
469	size_t    username_size;
470	/*validate inputs*/
471	*out = NULL;
472	if (error && error_size)
473		error[0] = '\0';
474	if (!address || !account || !account->username) {
475		mc_set_error(error, error_size, "missing server address or account");
476		return 0;
477	}
478	username_size = strlen(account->username);
479	if (username_size == 0 || username_size > 16) {
480		mc_set_error(error, error_size, "Minecraft username must be 1 to 16 characters");
481		return 0;
482	}
483	client = (MCClient*)calloc(1, sizeof(*client));
484	if (!client) {
485		mc_set_error(error, error_size, "out of memory creating client");
486		return 0;
487	}
488	client->socket_fd = -1;
489	client->compression_threshold = -1;
490	memcpy(client->username, account->username, username_size + 1);
491	printf("connecting to %s as %s\n", address, client->username);
492	/*parse server addr */
493	if (!split_address(address, client->host, sizeof(client->host), &client->port, error,
494	                   error_size) ||
495	    /*open tcp */
496	    !tcp_connect(client, error, error_size) || !send_handshake(client) ||
497	    !send_login_start(client)) {
498		if (!error || !error_size || !error[0])
499			mc_set_error(error, error_size, "could not start Minecraft login");
500		mc_client_destroy(client);
501		return 0;
502	}
503	printf("sent handshake and login start\n");
504	if (!login_loop(client, account, error, error_size)) {
505		mc_client_destroy(client);
506		return 0;
507	}
508	*out = client;
509	return 1;
510}
511
512void mc_client_interrupt(MCClient* client)
513{
514	if (client && client->socket_fd >= 0)
515		shutdown(client->socket_fd, SHUT_RDWR);
516}
517
518void mc_client_destroy(MCClient* client)
519{
520	if (!client)
521		return;
522	if (client->socket_fd >= 0)
523		close(client->socket_fd);
524	memset(client, 0, sizeof(*client));
525	free(client);
526}
527
528const char* mc_client_username(const MCClient* client) { return client ? client->username : ""; }
529
530const char* mc_client_uuid(const MCClient* client) { return client ? client->uuid : ""; }