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}