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