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 : ""; }