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}