1
2 // central.c - communication with central server
3
4 #include "qwsvdef.h"
5 #include <curl/curl.h>
6
7 #define GENERATE_CHALLENGE_PATH "Authentication/GenerateChallenge"
8 #define VERIFY_RESPONSE_PATH "Authentication/VerifyResponse"
9 #define CHECKIN_PATH "ServerApi/Checkin"
10 #define MIN_CHECKIN_PERIOD 60
11
12 #ifdef _WIN32
13 #pragma comment(lib, "libcurld.lib")
14 #pragma comment(lib, "ws2_32.lib")
15 #pragma comment(lib, "wldap32.lib")
16 #endif
17
18 static cvar_t sv_www_address = { "sv_www_address", "" };
19 static cvar_t sv_www_authkey = { "sv_www_authkey", "" };
20 static cvar_t sv_www_checkin_period = { "sv_www_checkin_period", "60" };
21
22 static CURLM* curl_handle = NULL;
23
24 static double last_checkin_time;
25
26 #define MAX_RESPONSE_LENGTH (4096*2)
27
28 struct web_request_data_s;
29
30 typedef void(*web_response_func_t)(struct web_request_data_s* req, qbool valid);
31 static void Web_ConstructURL(char* url, const char* path, int sizeof_url);
32 static int utf_encode_string(char* value, char* encoded_value);
33 static void Web_SubmitRequestForm(const char* url, struct curl_httppost *first_form_ptr, struct curl_httppost *last_form_ptr, web_response_func_t callback, const char* requestId, void* internal_data);
34
35 typedef struct web_request_data_s {
36 CURL* handle;
37 double time_sent;
38
39 // response from server
40 char response[MAX_RESPONSE_LENGTH];
41 size_t response_length;
42
43 // form data
44 struct curl_httppost *first_form_ptr;
45 struct curl_httppost *last_form_ptr;
46
47 // called when response complete
48 web_response_func_t onCompleteCallback;
49 void* internal_data;
50 char* request_id; // if set, content will be passed to game-mod. will be Q_free()'d
51
52 struct web_request_data_s* next;
53 } web_request_data_t;
54
utf8Encode(char in,char * out1,char * out2)55 static int utf8Encode(char in, char* out1, char* out2) {
56 if (in <= 0x7F) {
57 // <= 127 is encoded as single byte, no translation
58 *out1 = in;
59 return 1;
60 }
61 else {
62 // Two byte characters... 5 bits then 6
63 *out1 = 0xC0 | ((in >> 6) & 0x1F);
64 *out2 = 0x80 | (in & 0x3F);
65 return 2;
66 }
67 }
68
69 static web_request_data_t* web_requests;
70
CheckFileExists(const char * path)71 static qbool CheckFileExists(const char* path)
72 {
73 FILE* f;
74 if (!(f = fopen(path, "rb"))) {
75 return false;
76 }
77 fclose(f);
78 return true;
79 }
80
Web_StandardTokenWrite(void * buffer,size_t size,size_t nmemb,void * userp)81 size_t Web_StandardTokenWrite(void* buffer, size_t size, size_t nmemb, void* userp)
82 {
83 web_request_data_t* data = (web_request_data_t*)userp;
84 size_t available = sizeof(data->response) - data->response_length;
85 if (size * nmemb > available) {
86 Con_DPrintf("WWW: Response too large, size*nmemb = %d, available %d\n", size * nmemb, available);
87 return 0;
88 }
89 else if (size * nmemb > 0) {
90 Con_DPrintf("WWW: response received, writing %d bytes\n", size * nmemb);
91 memcpy(data->response + data->response_length, buffer, size * nmemb);
92 data->response_length += size * nmemb;
93 Con_DPrintf("WWW: response_length now %d bytes\n", data->response_length);
94 }
95 return size * nmemb;
96 }
97
98 typedef struct response_field_s {
99 const char* name;
100 const char** value;
101 } response_field_t;
102
ProcessWebResponse(web_request_data_t * req,response_field_t * fields,int field_count)103 static int ProcessWebResponse(web_request_data_t* req, response_field_t* fields, int field_count)
104 {
105 char* colon, *newline;
106 char* start = req->response;
107 int i;
108 int total_fields = 0;
109
110 // Response should be multiple lines, <Key>:<Value>\n
111 // For multi-line values, prefix end of line with $
112 req->response[req->response_length] = '\0';
113 while ((colon = strchr(start, ':'))) {
114 newline = strchr(colon, '\n');
115 if (newline) {
116 while (newline && newline != colon + 1 && *(newline - 1) == '$') {
117 *(newline - 1) = ' ';
118 newline = strchr(newline + 1, '\n');
119 }
120 if (newline) {
121 *newline = '\0';
122 }
123 }
124
125 *colon = '\0';
126 for (i = 0; i < field_count; ++i) {
127 if (!strcmp(start, fields[i].name)) {
128 *fields[i].value = colon + 1;
129 }
130 }
131
132 if (newline == NULL) {
133 break;
134 }
135
136 start = newline + 1;
137 while (*start && (*start == 10 || *start == 13)) {
138 ++start;
139 }
140 ++total_fields;
141 }
142
143 return total_fields;
144 }
145
Auth_GenerateChallengeResponse(web_request_data_t * req,qbool valid)146 void Auth_GenerateChallengeResponse(web_request_data_t* req, qbool valid)
147 {
148 client_t* client = (client_t*) req->internal_data;
149 const char* response = NULL;
150 const char* challenge = NULL;
151 const char* message = NULL;
152 response_field_t fields[] = {
153 { "Result", &response },
154 { "Challenge", &challenge },
155 { "Message", &message }
156 };
157
158 if (client->login_request_time != req->time_sent) {
159 // Ignore result, subsequent request sent
160 return;
161 }
162
163 req->internal_data = NULL;
164 ProcessWebResponse(req, fields, sizeof(fields) / sizeof(fields[0]));
165
166 if (response && !strcmp(response, "Success") && challenge) {
167 char buffer[128];
168 strlcpy(buffer, "//challenge ", sizeof(buffer));
169 strlcat(buffer, challenge, sizeof(buffer));
170 strlcat(buffer, "\n", sizeof(buffer));
171
172 strlcpy(client->login_challenge, challenge, sizeof(client->login_challenge));
173 SV_ClientPrintf2(client, PRINT_HIGH, "Challenge stored...\n", message);
174
175 ClientReliableWrite_Begin (client, svc_stufftext, 2+strlen(buffer));
176 ClientReliableWrite_String (client, buffer);
177 }
178 else if (message) {
179 SV_ClientPrintf2(client, PRINT_HIGH, "Error: %s\n", message);
180 }
181 else {
182 // Maybe add CURLOPT_ERRORBUFFER?
183 SV_ClientPrintf2(client, PRINT_HIGH, "Error: unknown error\n");
184 }
185 }
186
Auth_ProcessLoginAttempt(web_request_data_t * req,qbool valid)187 void Auth_ProcessLoginAttempt(web_request_data_t* req, qbool valid)
188 {
189 client_t* client = (client_t*) req->internal_data;
190 const char* response = NULL;
191 const char* login = NULL;
192 const char* preferred_alias = NULL;
193 const char* message = NULL;
194 const char* flag = NULL;
195 const char* confirmation = NULL;
196 response_field_t fields[] = {
197 { "Result", &response },
198 { "Alias", &preferred_alias },
199 { "Login", &login },
200 { "Message", &message },
201 { "Flag", &flag },
202 { "Confirmation", &confirmation }
203 };
204
205 req->internal_data = NULL;
206 if (client->login_request_time != req->time_sent) {
207 // Ignore result, subsequent request sent
208 return;
209 }
210
211 ProcessWebResponse(req, fields, sizeof(fields) / sizeof(fields[0]));
212
213 if (response && !strcmp(response, "Success")) {
214 if (login) {
215 char oldval[MAX_EXT_INFO_STRING];
216
217 strlcpy(oldval, Info_Get(&client->_userinfo_ctx_, "*auth"), sizeof(oldval));
218 strlcpy(client->login, login, sizeof(client->login));
219
220 Info_SetStar(&client->_userinfo_ctx_, "*auth", login);
221 ProcessUserInfoChange(client, "*auth", oldval);
222 }
223
224 if (confirmation) {
225 strlcpy(client->login_confirmation, confirmation, sizeof(client->login_confirmation));
226 }
227
228 {
229 char oldval[MAX_EXT_INFO_STRING];
230
231 flag = (flag ? flag : "");
232 strlcpy(oldval, Info_Get(&client->_userinfo_ctx_, "*flag"), sizeof(oldval));
233 strlcpy(client->login_flag, flag, sizeof(client->login_flag));
234 Info_SetStar(&client->_userinfo_ctx_, "*flag", flag);
235 ProcessUserInfoChange(client, "*flag", oldval);
236 }
237
238 preferred_alias = preferred_alias ? preferred_alias : login;
239 if (preferred_alias) {
240 strlcpy(client->login_alias, preferred_alias, sizeof(client->login_alias));
241 }
242 client->logged_in_via_web = true;
243
244 SV_LoginWebCheck(client);
245 }
246 else if (message) {
247 SV_ClientPrintf2(client, PRINT_HIGH, "Error: %s\n", message);
248 SV_LoginWebFailed(client);
249 }
250 else {
251 // Maybe add CURLOPT_ERRORBUFFER?
252 SV_ClientPrintf2(client, PRINT_HIGH, "Error: unknown error (invalid response from server)\n");
253 SV_LoginWebFailed(client);
254 }
255 }
256
Web_PostResponse(web_request_data_t * req,qbool valid)257 void Web_PostResponse(web_request_data_t* req, qbool valid)
258 {
259 if (valid) {
260 const char* broadcast = NULL;
261 const char* upload = NULL;
262 const char* uploadPath = NULL;
263
264 response_field_t fields[] = {
265 { "Broadcast", &broadcast },
266 { "UploadPath", &uploadPath },
267 { "Upload", &upload }
268 };
269
270 req->response[req->response_length] = '\0';
271
272 Con_DPrintf("Response from web server:\n");
273 Con_DPrintf(req->response);
274
275 ProcessWebResponse(req, fields, sizeof(fields) / sizeof(fields[0]));
276
277 if (broadcast) {
278 // Server is making announcement
279 SV_BroadcastPrintfEx(PRINT_HIGH, 0, "%s\n", broadcast);
280 }
281 if (upload && uploadPath) {
282 if (strstr(uploadPath, "//") || FS_UnsafeFilename(upload)) {
283 Con_Printf("Upload request deemed unsafe, ignoring...\n");
284 }
285 else {
286 if (!strncmp(upload, "demos/", 6)) {
287 // Ok - could be demo_dir, or full path
288 char demoName[MAX_OSPATH];
289
290 if (sv_demoDir.string[0]) {
291 char url[512];
292 struct curl_httppost *first_form_ptr = NULL;
293 struct curl_httppost *last_form_ptr = NULL;
294
295 snprintf(demoName, sizeof(demoName), "%s/%s/%s", fs_gamedir, sv_demoDir.string, upload + 6);
296 if (!CheckFileExists(demoName) && sv_demoDirAlt.string[0]) {
297 snprintf(demoName, sizeof(demoName), "%s/%s/%s", fs_gamedir, sv_demoDirAlt.string, upload + 6);
298 }
299
300 if (CheckFileExists(demoName)) {
301 Web_ConstructURL(url, uploadPath, sizeof(url));
302
303 curl_formadd(&first_form_ptr, &last_form_ptr,
304 CURLFORM_PTRNAME, "file",
305 CURLFORM_FILE, demoName,
306 CURLFORM_END
307 );
308
309 curl_formadd(&first_form_ptr, &last_form_ptr,
310 CURLFORM_COPYNAME, "localPath",
311 CURLFORM_COPYCONTENTS, upload,
312 CURLFORM_END
313 );
314
315 Web_SubmitRequestForm(url, first_form_ptr, last_form_ptr, Web_PostResponse, "upload", NULL);
316 Con_Printf("Uploading %s...\n", demoName);
317 }
318 else {
319 Con_Printf("Couldn't find file %s, ignoring...\n", demoName);
320 }
321 }
322 }
323 else {
324 Con_Printf("Upload request folder not authorised, ignoring...\n");
325 }
326 }
327 }
328 }
329 else {
330 Con_Printf("Failure contacting central server.\n");
331 }
332
333 if (req->internal_data) {
334 Q_free(req->internal_data);
335 }
336 }
337
Web_SubmitRequestForm(const char * url,struct curl_httppost * first_form_ptr,struct curl_httppost * last_form_ptr,web_response_func_t callback,const char * requestId,void * internal_data)338 static void Web_SubmitRequestForm(const char* url, struct curl_httppost *first_form_ptr, struct curl_httppost *last_form_ptr, web_response_func_t callback, const char* requestId, void* internal_data)
339 {
340 CURL* req = curl_easy_init();
341 web_request_data_t* data = Q_malloc(sizeof(web_request_data_t));
342 CURLFORMcode code = curl_formadd(&first_form_ptr, &last_form_ptr,
343 CURLFORM_PTRNAME, "authKey",
344 CURLFORM_COPYCONTENTS, sv_www_authkey.string,
345 CURLFORM_END
346 );
347
348 data->onCompleteCallback = callback;
349 data->time_sent = curtime;
350 data->internal_data = internal_data;
351 data->handle = req;
352 data->request_id = requestId && requestId[0] ? strdup(requestId) : NULL;
353 data->first_form_ptr = first_form_ptr;
354
355 curl_easy_setopt(req, CURLOPT_URL, url);
356 curl_easy_setopt(req, CURLOPT_WRITEDATA, data);
357 curl_easy_setopt(req, CURLOPT_WRITEFUNCTION, Web_StandardTokenWrite);
358 if (first_form_ptr) {
359 curl_easy_setopt(req, CURLOPT_POST, 1);
360 curl_easy_setopt(req, CURLOPT_HTTPPOST, first_form_ptr);
361 }
362
363 curl_multi_add_handle(curl_handle, req);
364 data->next = web_requests;
365 web_requests = data;
366 }
367
Central_VerifyChallengeResponse(client_t * client,const char * challenge,const char * response)368 void Central_VerifyChallengeResponse(client_t* client, const char* challenge, const char* response)
369 {
370 char url[512];
371 struct curl_httppost *first_form_ptr = NULL;
372 struct curl_httppost *last_form_ptr = NULL;
373 CURLFORMcode code;
374
375 if (!sv_www_address.string[0]) {
376 SV_ClientPrintf2(client, PRINT_HIGH, "Remote logins not supported on this server\n");
377 return;
378 }
379
380 code = curl_formadd(&first_form_ptr, &last_form_ptr,
381 CURLFORM_PTRNAME, "challenge",
382 CURLFORM_COPYCONTENTS, challenge,
383 CURLFORM_END
384 );
385
386 code = curl_formadd(&first_form_ptr, &last_form_ptr,
387 CURLFORM_PTRNAME, "response",
388 CURLFORM_COPYCONTENTS, response,
389 CURLFORM_END
390 );
391
392 Web_ConstructURL(url, VERIFY_RESPONSE_PATH, sizeof(url));
393
394 client->login_request_time = curtime;
395 Web_SubmitRequestForm(url, first_form_ptr, last_form_ptr, Auth_ProcessLoginAttempt, NULL, client);
396 }
397
Central_GenerateChallenge(client_t * client,const char * username,qbool during_login)398 void Central_GenerateChallenge(client_t* client, const char* username, qbool during_login)
399 {
400 char url[512];
401 struct curl_httppost *first_form_ptr = NULL;
402 struct curl_httppost *last_form_ptr = NULL;
403 CURLFORMcode code;
404
405 if (!sv_www_address.string[0]) {
406 SV_ClientPrintf2(client, PRINT_HIGH, "Remote logins not supported on this server\n");
407 return;
408 }
409
410 Web_ConstructURL(url, GENERATE_CHALLENGE_PATH, sizeof(url));
411
412 code = curl_formadd(&first_form_ptr, &last_form_ptr,
413 CURLFORM_PTRNAME, "userName",
414 CURLFORM_COPYCONTENTS, username,
415 CURLFORM_END
416 );
417
418 code = curl_formadd(&first_form_ptr, &last_form_ptr,
419 CURLFORM_PTRNAME, "status",
420 CURLFORM_COPYCONTENTS, during_login ? "0" : "1",
421 CURLFORM_END
422 );
423
424 client->login_request_time = curtime;
425 Web_SubmitRequestForm(url, first_form_ptr, last_form_ptr, Auth_GenerateChallengeResponse, NULL, client);
426 }
427
Web_ConstructURL(char * url,const char * path,int sizeof_url)428 static void Web_ConstructURL(char* url, const char* path, int sizeof_url)
429 {
430 strlcpy(url, sv_www_address.string, sizeof_url);
431 if (url[strlen(url) - 1] != '/') {
432 strlcat(url, "/", sizeof_url);
433 }
434 while (*path == '/') {
435 ++path;
436 }
437 strlcat(url, path, sizeof_url);
438 }
439
440 #define MAX_ENCODED_STRINGLEN 128
441
Web_AddParametersToRequest(int first_param,struct curl_httppost ** first_form_ptr,struct curl_httppost ** last_form_ptr)442 static qbool Web_AddParametersToRequest(int first_param, struct curl_httppost** first_form_ptr, struct curl_httppost** last_form_ptr)
443 {
444 int i;
445
446 for (i = first_param; i < Cmd_Argc() - 1; i += 2) {
447 char encoded_value[MAX_ENCODED_STRINGLEN];
448 int encoded_length = 0;
449 int j;
450 char* name;
451 char* value;
452 CURLFORMcode code;
453
454 name = Cmd_Argv(i);
455 value = Cmd_Argv(i + 1);
456 if (!strcmp(name, "*internal")) {
457 if (!strcmp(value, "authinfo")) {
458 int login_count = 0;
459
460 // Also submit authentication challenges/responses
461 for (j = 0; j < MAX_CLIENTS; ++j) {
462 if (svs.clients[j].state > cs_free) {
463 if (svs.clients[j].login[0] && svs.clients[j].logged_in_via_web) {
464 name = va("logins[%d].name", login_count);
465 utf_encode_string(svs.clients[j].login, encoded_value);
466 code = curl_formadd(first_form_ptr, last_form_ptr,
467 CURLFORM_COPYNAME, name,
468 CURLFORM_COPYCONTENTS, encoded_value,
469 CURLFORM_END
470 );
471
472 if (code != CURLE_OK) {
473 Con_Printf("Request failed (adding to form)\n");
474 return false;
475 }
476
477 name = va("logins[%d].token", login_count);
478 utf_encode_string(svs.clients[j].login_confirmation, encoded_value);
479 code = curl_formadd(first_form_ptr, last_form_ptr,
480 CURLFORM_COPYNAME, name,
481 CURLFORM_COPYCONTENTS, encoded_value,
482 CURLFORM_END
483 );
484
485 if (code != CURLE_OK) {
486 Con_Printf("Request failed (adding to form)\n");
487 return false;
488 }
489
490 ++login_count;
491 }
492 }
493 }
494
495 snprintf(encoded_value, sizeof(encoded_value), "%d", login_count);
496 code = curl_formadd(first_form_ptr, last_form_ptr,
497 CURLFORM_COPYNAME, "logincount",
498 CURLFORM_COPYCONTENTS, encoded_value,
499 CURLFORM_END
500 );
501
502 if (code != CURLE_OK) {
503 Con_Printf("Request failed (adding to form)\n");
504 return false;
505 }
506 }
507 continue;
508 }
509 else {
510 utf_encode_string(value, encoded_value);
511 }
512
513 code = curl_formadd(first_form_ptr, last_form_ptr,
514 CURLFORM_COPYNAME, name,
515 CURLFORM_COPYCONTENTS, encoded_value,
516 CURLFORM_END
517 );
518
519 if (code != CURLE_OK) {
520 Con_Printf("Request failed (adding to form)\n");
521 return false;
522 }
523 }
524
525 return true;
526 }
527
Web_SendRequest(qbool post)528 static void Web_SendRequest(qbool post)
529 {
530 struct curl_httppost *first_form_ptr = NULL;
531 struct curl_httppost *last_form_ptr = NULL;
532 char url[512];
533 char* requestId = NULL;
534
535 if (!sv_www_address.string[0]) {
536 Con_Printf("Address not set - functionality disabled\n");
537 return;
538 }
539
540 if (Cmd_Argc() < 3) {
541 Con_Printf("Usage: %s <url> <request-id> (<key> <value>)*\n", Cmd_Argv(0));
542 return;
543 }
544
545 Web_ConstructURL(url, Cmd_Argv(1), sizeof(url));
546
547 requestId = Cmd_Argv(2);
548 if (requestId[0]) {
549 requestId = Q_strdup(requestId);
550 }
551 else {
552 requestId = NULL;
553 }
554
555 if (!Web_AddParametersToRequest(3, &first_form_ptr, &last_form_ptr)) {
556 curl_formfree(first_form_ptr);
557 return;
558 }
559
560 Web_SubmitRequestForm(url, first_form_ptr, last_form_ptr, Web_PostResponse, requestId, NULL);
561 }
562
utf_encode_string(char * value,char * encoded_value)563 int utf_encode_string(char* value, char* encoded_value)
564 {
565 int j;
566 int encoded_length = 0;
567 for (j = 0; j < strlen(value) && encoded_length < MAX_ENCODED_STRINGLEN - 2; ++j) {
568 encoded_length += utf8Encode(value[j], &encoded_value[encoded_length], &encoded_value[encoded_length + 1]);
569 }
570 encoded_value[encoded_length] = '\0';
571 return encoded_length;
572 }
573
Web_GetRequest_f(void)574 static void Web_GetRequest_f(void)
575 {
576 Web_SendRequest(false);
577 }
578
Web_PostRequest_f(void)579 static void Web_PostRequest_f(void)
580 {
581 Web_SendRequest(true);
582 }
583
Web_PostFileRequest_f(void)584 static void Web_PostFileRequest_f(void)
585 {
586 struct curl_httppost *first_form_ptr = NULL;
587 struct curl_httppost *last_form_ptr = NULL;
588 char* requestId = NULL;
589 char url[512];
590 char path[MAX_OSPATH];
591 CURLFORMcode code = 0;
592 const char* specified;
593
594 if (!sv_www_address.string[0]) {
595 Con_Printf("Address not set - functionality disabled\n");
596 return;
597 }
598
599 if (Cmd_Argc() < 4) {
600 Con_Printf("Usage: %s <url> <request-id> <file> (<key> <value>)*\n", Cmd_Argv(0));
601 return;
602 }
603
604 requestId = Cmd_Argv(2);
605 if (requestId[0]) {
606 requestId = Q_strdup(requestId);
607 }
608 else {
609 requestId = NULL;
610 }
611
612 specified = Cmd_Argv(3);
613 if (specified[0] == '*' && specified[1] == '\0') {
614 const char* demoname = SV_MVDDemoName();
615
616 if (!sv.mvdrecording || !demoname) {
617 Con_Printf("Not recording demo!\n");
618 return;
619 }
620
621 snprintf(path, MAX_OSPATH, "%s/%s/%s", fs_gamedir, sv_demoDir.string, SV_MVDName2Txt(demoname));
622 }
623 else {
624 if (strstr(specified, ".cfg") || FS_UnsafeFilename(specified)) {
625 Con_Printf("Filename invalid\n");
626 return;
627 }
628
629 snprintf(path, MAX_OSPATH, "%s/%s", fs_gamedir, specified);
630 }
631
632 if (FS_UnsafeFilename(path)) {
633 Con_Printf("Filename invalid\n");
634 return;
635 }
636
637 Web_ConstructURL(url, Cmd_Argv(1), sizeof(url));
638
639 if (! CheckFileExists(path)) {
640 Con_Printf("Failed to open file\n");
641 return;
642 }
643
644 code = curl_formadd(&first_form_ptr, &last_form_ptr,
645 CURLFORM_PTRNAME, "file",
646 CURLFORM_FILE, path,
647 CURLFORM_END
648 );
649 if (code != CURLE_OK) {
650 Con_Printf("Request failed (adding file to form)\n");
651 return;
652 }
653
654 // Optional parameters to be sent to web server
655 if (!Web_AddParametersToRequest(4, &first_form_ptr, &last_form_ptr)) {
656 curl_formfree(first_form_ptr);
657 return;
658 }
659
660 Web_SubmitRequestForm(url, first_form_ptr, last_form_ptr, Web_PostResponse, requestId, NULL);
661 }
662
Central_ProcessResponses(void)663 void Central_ProcessResponses(void)
664 {
665 CURLMsg* msg;
666 int running_handles = 0;
667 int messages_in_queue = 0;
668 qbool server_busy = false;
669
670 if (!last_checkin_time) {
671 last_checkin_time = curtime;
672 return;
673 }
674
675 curl_multi_perform(curl_handle, &running_handles);
676
677 server_busy = running_handles || GameStarted();
678
679 while ((msg = curl_multi_info_read(curl_handle, &messages_in_queue))) {
680 if (msg->msg == CURLMSG_DONE) {
681 CURL* handle = msg->easy_handle;
682 CURLcode result = msg->data.result;
683 web_request_data_t** list_pointer = &web_requests;
684
685 curl_multi_remove_handle(curl_handle, handle);
686
687 while (*list_pointer) {
688 web_request_data_t* this = *list_pointer;
689
690 if (this->handle == handle) {
691 // Remove from queue immediately
692 *list_pointer = this->next;
693
694 if (this->request_id && !strcmp(this->request_id, "upload")) {
695 this = this;
696 }
697 if (this->onCompleteCallback) {
698 if (result == CURLE_OK) {
699 this->onCompleteCallback(this, true);
700 }
701 else {
702 Con_DPrintf("ERROR: %s\n", curl_easy_strerror(result));
703 this->onCompleteCallback(this, false);
704 }
705 }
706 else {
707 if (result != CURLE_OK) {
708 Con_DPrintf("ERROR: %s\n", curl_easy_strerror(result));
709 }
710 else {
711 Con_DPrintf("WEB OK, no callback\n");
712 }
713 }
714
715 // free memory
716 curl_formfree(this->first_form_ptr);
717 Q_free(this->request_id);
718 Q_free(this);
719
720 break;
721 }
722
723 list_pointer = &this->next;
724 }
725
726 curl_easy_cleanup(handle);
727 }
728 }
729
730 if (sv_www_address.string[0] && !server_busy && curtime - last_checkin_time > max(MIN_CHECKIN_PERIOD, sv_www_checkin_period.value)) {
731 char url[512];
732
733 Web_ConstructURL(url, CHECKIN_PATH, sizeof(url));
734
735 Web_SubmitRequestForm(url, NULL, NULL, Web_PostResponse, NULL, NULL);
736
737 last_checkin_time = curtime;
738 }
739 else if (server_busy) {
740 last_checkin_time = curtime;
741 }
742 }
743
Central_Shutdown(void)744 void Central_Shutdown(void)
745 {
746 if (curl_handle) {
747 curl_multi_cleanup(curl_handle);
748 curl_handle = NULL;
749 }
750
751 curl_global_cleanup();
752 }
753
Central_Init(void)754 void Central_Init(void)
755 {
756 curl_global_init(CURL_GLOBAL_DEFAULT);
757
758 Cvar_Register(&sv_www_address);
759 Cvar_Register(&sv_www_authkey);
760 Cvar_Register(&sv_www_checkin_period);
761
762 curl_handle = curl_multi_init();
763
764 if (curl_handle) {
765 Cmd_AddCommand("sv_web_get", Web_GetRequest_f);
766 Cmd_AddCommand("sv_web_post", Web_PostRequest_f);
767 Cmd_AddCommand("sv_web_postfile", Web_PostFileRequest_f);
768 }
769 }
770