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