master uint/magnolia / source / mcproto / auth.c
  1#define _POSIX_C_SOURCE 200112L
  2
  3#include <stdio.h>
  4#include <stdlib.h>
  5#include <string.h>
  6
  7#include <tls.h>
  8
  9#include "internal.h"
 10
 11#define MC_JOIN_HOST "sessionserver.mojang.com"
 12#define MC_JOIN_PORT "443"
 13#define MC_JOIN_PATH "/session/minecraft/join"
 14
 15static int json_string(MCBuffer* buffer, const char* value)
 16{
 17	const unsigned char* p = (const unsigned char*)value;
 18	if (!bufwrite_u8(buffer, '"'))
 19		return 0;
 20	while (*p) {
 21		char   escaped[7];
 22		size_t size = 1;
 23		switch (*p) {
 24		case '"':
 25			escaped[0] = '\\';
 26			escaped[1] = '"';
 27			size = 2;
 28			break;
 29		case '\\':
 30			escaped[0] = '\\';
 31			escaped[1] = '\\';
 32			size = 2;
 33			break;
 34		case '\b':
 35			escaped[0] = '\\';
 36			escaped[1] = 'b';
 37			size = 2;
 38			break;
 39		case '\f':
 40			escaped[0] = '\\';
 41			escaped[1] = 'f';
 42			size = 2;
 43			break;
 44		case '\n':
 45			escaped[0] = '\\';
 46			escaped[1] = 'n';
 47			size = 2;
 48			break;
 49		case '\r':
 50			escaped[0] = '\\';
 51			escaped[1] = 'r';
 52			size = 2;
 53			break;
 54		case '\t':
 55			escaped[0] = '\\';
 56			escaped[1] = 't';
 57			size = 2;
 58			break;
 59		default:
 60			if (*p < 0x20) {
 61				snprintf(escaped, sizeof(escaped), "\\u%04x", *p);
 62				size = 6;
 63			} else {
 64				escaped[0] = (char)*p;
 65			}
 66		}
 67		if (!bufwrite(buffer, escaped, size))
 68			return 0;
 69		++p;
 70	}
 71	return bufwrite_u8(buffer, '"');
 72}
 73
 74static int tls_write_all(struct tls* tls, const void* data, size_t size)
 75{
 76	const u8* bytes = (const u8*)data;
 77	size_t    sent = 0;
 78
 79	while (sent < size) {
 80		ssize_t count = tls_write(tls, bytes + sent, size - sent);
 81		if (count == TLS_WANT_POLLIN || count == TLS_WANT_POLLOUT)
 82			continue;
 83		if (count <= 0)
 84			return 0;
 85		sent += (size_t)count;
 86	}
 87	return 1;
 88}
 89
 90static int tls_read_response(struct tls* tls, MCBuffer* response)
 91{
 92	u8 data[2048];
 93
 94	for (;;) {
 95		ssize_t count = tls_read(tls, data, sizeof(data));
 96		if (count == TLS_WANT_POLLIN || count == TLS_WANT_POLLOUT)
 97			continue;
 98		if (count == 0)
 99			return 1;
100		if (count < 0)
101			return 0;
102		if (!bufwrite(response, data, (size_t)count))
103			return 0;
104		if (response->size > 64 * 1024)
105			return 0;
106	}
107}
108
109static long http_status(const MCBuffer* response)
110{
111	long status;
112
113	if (response->size < 12 || memcmp(response->data, "HTTP/", 5) != 0)
114		return 0;
115	status = strtol((const char*)response->data + 9, NULL, 10);
116	return status;
117}
118
119int mc_join_server(const MCAccount* account, const char* server_id, const u8 shared_secret[16],
120                   const u8* public_key, size_t public_key_size, char* error, size_t error_size)
121{
122	struct tls_config* config = NULL;
123	struct tls*        tls = NULL;
124	MCBuffer           json;
125	MCBuffer           request;
126	MCBuffer           response;
127	char               hash[42];
128	char               headers[512];
129	long               status;
130	int                ok = 0;
131
132	if (!account->access_token || !account->access_token[0] || !account->profile_uuid ||
133	    !account->profile_uuid[0]) {
134		mc_set_error(error, error_size,
135		             "online-mode server requires "
136		             "MANGOLIA_ACCESS_TOKEN and MANGOLIA_PROFILE_UUID");
137		return 0;
138	}
139
140	bufinit(&json);
141	bufinit(&request);
142	bufinit(&response);
143	mc_server_hash(server_id, shared_secret, public_key, public_key_size, hash);
144	if (!bufwrite(&json, "{\"accessToken\":", 15) ||
145	    !json_string(&json, account->access_token) ||
146	    !bufwrite(&json, ",\"selectedProfile\":", 19) ||
147	    !json_string(&json, account->profile_uuid) || !bufwrite(&json, ",\"serverId\":", 12) ||
148	    !json_string(&json, hash) || !bufwrite_u8(&json, '}')) {
149		mc_set_error(error, error_size, "out of memory building session request");
150		goto cleanup;
151	}
152
153	if (tls_init() != 0) {
154		mc_set_error(error, error_size, "libtls initialization failed");
155		goto cleanup;
156	}
157	config = tls_config_new();
158	tls = tls_client();
159	if (!config || !tls) {
160		mc_set_error(error, error_size, "libtls request initialization failed");
161		goto cleanup;
162	}
163	if (tls_configure(tls, config) != 0) {
164		mc_set_error(error, error_size, "libtls configure failed: %s", tls_error(tls));
165		goto cleanup;
166	}
167	if (tls_connect(tls, MC_JOIN_HOST, MC_JOIN_PORT) != 0) {
168		mc_set_error(error, error_size, "session TLS connect failed: %s", tls_error(tls));
169		goto cleanup;
170	}
171	snprintf(headers, sizeof(headers),
172	         "POST " MC_JOIN_PATH " HTTP/1.1\r\n"
173	         "Host: " MC_JOIN_HOST "\r\n"
174	         "User-Agent: Magnolia/1.8.9\r\n"
175	         "Content-Type: application/json\r\n"
176	         "Content-Length: %lu\r\n"
177	         "Connection: close\r\n"
178	         "\r\n",
179	         (unsigned long)json.size);
180	if (!bufwrite(&request, headers, strlen(headers)) ||
181	    !bufwrite(&request, json.data, json.size)) {
182		mc_set_error(error, error_size, "out of memory building session HTTP request");
183		goto cleanup;
184	}
185	if (!tls_write_all(tls, request.data, request.size)) {
186		mc_set_error(error, error_size, "session join request write failed: %s",
187		             tls_error(tls));
188		goto cleanup;
189	}
190	if (!tls_read_response(tls, &response)) {
191		mc_set_error(error, error_size, "session join response read failed: %s",
192		             tls_error(tls));
193		goto cleanup;
194	}
195	status = http_status(&response);
196	if (status != 204) {
197		size_t show = response.size < 400 ? response.size : 400;
198		mc_set_error(error, error_size, "session server rejected join (HTTP %ld): %.*s",
199		             status, (int)show, response.data ? (char*)response.data : "");
200		goto cleanup;
201	}
202	ok = 1;
203
204cleanup:
205	if (json.data)
206		memset(json.data, 0, json.capacity);
207	if (request.data)
208		memset(request.data, 0, request.capacity);
209	if (tls) {
210		tls_close(tls);
211		tls_free(tls);
212	}
213	if (config)
214		tls_config_free(config);
215	buffree(&response);
216	buffree(&request);
217	buffree(&json);
218	return ok;
219}