1 /*
2 Copyright (C) 2010 COR Entertainment, LLC.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include "client.h"
25 #include "qcommon/md5.h"
26
27 #if defined HAVE_UNISTD_H
28 #include <unistd.h>
29 #endif
30
31 #include "curl/curl.h"
32 CURLM *curlm;
33 CURL *curl;
34
35 #define STAT_PROTOCOL 1
36
37 extern cvar_t *cl_master;
38 extern cvar_t *cl_stats_server;
39
40 extern cvar_t *name;
41 extern cvar_t *stats_password;
42 extern cvar_t *pw_hashed;
43
44 static char szVerificationString[64];
45 static char *cpr; // mostly for unused result warnings
46
statsdb_open(const char * mode)47 static FILE* statsdb_open( const char* mode )
48 {
49 FILE* file;
50 char pathbfr[MAX_OSPATH];
51
52 Com_sprintf (pathbfr, sizeof(pathbfr)-1, "%s/%s", FS_Gamedir(), "stats.db");
53 file = fopen( pathbfr, mode );
54
55 return file;
56 }
57
write_data(const void * buffer,size_t size,size_t nmemb,void * userp)58 static size_t write_data(const void *buffer, size_t size, size_t nmemb, void *userp)
59 {
60 FILE* file;
61 size_t bytecount = 0;
62
63 file = statsdb_open( "a" ); //append, don't rewrite
64
65 if(file) {
66 //write buffer to file
67 bytecount = fwrite( buffer, size, nmemb, file );
68 fclose(file);
69 }
70 return bytecount;
71 }
72
73 //get the stats database
STATS_getStatsDB(void)74 void STATS_getStatsDB( void )
75 {
76 FILE* file;
77 char statserver[128];
78
79 CURL* easyhandle = curl_easy_init() ;
80
81 file = statsdb_open( "w" ); //create new, blank file for writing
82 if(file)
83 fclose(file);
84
85 Com_sprintf(statserver, sizeof(statserver), "%s%s", cl_stats_server->string, "/playerrank.db");
86
87 curl_easy_setopt( easyhandle, CURLOPT_URL, statserver ) ;
88
89 // time out in 5s
90 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5);
91
92 curl_easy_setopt( easyhandle, CURLOPT_WRITEFUNCTION, write_data ) ;
93
94 curl_easy_perform( easyhandle );
95
96 curl_easy_cleanup( easyhandle );
97 }
98
99 //parse the stats database, looking for player match
getPlayerRanking(PLAYERSTATS player)100 PLAYERSTATS getPlayerRanking ( PLAYERSTATS player )
101 {
102 FILE* file;
103 char name[34], points[32], frags[32], totalfrags[32], time[16], totaltime[16], ip[32], poll[16], remote_address[21];
104 int foundplayer = false;
105
106 //open file,
107 file = statsdb_open( "r" ) ;
108
109 if(file != NULL) {
110
111 //parse it, and compare to player name
112 while(player.ranking < 1000) {
113
114 //name. upto 31 chars total, 15 are printable.
115 // fgets needs at least 33 chars. 31 + newline + terminating nul
116 cpr = fgets(name, sizeof(name), file);
117 if ( cpr == NULL )
118 { // end-of-file
119 break;
120 }
121 if ( name[strlen(name) - 1] == '\n' )
122 {
123 name[strlen(name) - 1] = 0; //truncate garbage byte
124 }
125 //remote address
126 cpr = fgets(remote_address, sizeof(remote_address), file);
127 //points
128 cpr = fgets(points, sizeof(points), file);
129 //frags
130 cpr = fgets(frags, sizeof(frags), file);
131 //total frags
132 cpr = fgets(totalfrags, sizeof(totalfrags), file);
133 if(!strcmp(player.playername, name))
134 player.totalfrags = atoi(totalfrags);
135 //current time in poll
136 cpr = fgets(time, sizeof(time), file);
137 //total time
138 cpr = fgets(totaltime, sizeof(totaltime), file);
139 if(!strcmp(player.playername, name))
140 player.totaltime = atof(totaltime);
141 //last server.ip
142 cpr = fgets(ip, sizeof(ip), file);
143 //what poll
144 cpr = fgets(poll, sizeof(poll), file);
145
146 player.ranking++;
147
148 if(!strcmp(player.playername, name)) {
149 foundplayer = true;
150 break; //get out we are done
151 }
152 }
153 fclose(file);
154 }
155
156 if(!foundplayer) {
157 player.ranking = 1000;
158 player.totalfrags = 0;
159 player.totaltime = 1;
160 }
161
162 return player;
163 }
164
165 //get player info by rank
getPlayerByRank(int rank,PLAYERSTATS player)166 PLAYERSTATS getPlayerByRank ( int rank, PLAYERSTATS player )
167 {
168 FILE* file;
169 char name[32], points[32], frags[32], totalfrags[32], time[16], totaltime[16], ip[32], poll[16], remote_address[21];
170 int foundplayer = false;
171
172 //open file,
173 file = statsdb_open( "r" ) ;
174
175 if(file != NULL) {
176
177 //parse it, and compare to player name
178 while(player.ranking < 1000) {
179
180 //name
181 cpr = fgets(name, sizeof(name), file);
182 strcpy(player.playername, name);
183 player.playername[strlen(player.playername)-1] = 0; //remove line feed
184 //remote address
185 cpr = fgets(remote_address, sizeof(remote_address), file);
186 //points
187 cpr = fgets(points, sizeof(points), file);
188 //frags
189 cpr = fgets(frags, sizeof(frags), file);
190 //total frags
191 cpr = fgets(totalfrags, sizeof(totalfrags), file);
192 player.totalfrags = atoi(totalfrags);
193 //current time in poll
194 cpr = fgets(time, sizeof(time), file);
195 //total time
196 cpr = fgets(totaltime, sizeof(totaltime), file);
197 player.totaltime = atof(totaltime);
198 //last server.ip
199 cpr = fgets(ip, sizeof(ip), file);
200 //what poll
201 cpr = fgets(poll, sizeof(poll), file);
202
203 player.ranking++;
204
205 if(player.ranking == rank) {
206 foundplayer = true;
207 break; //get out we are done
208 }
209 }
210 fclose(file);
211 }
212
213 if(!foundplayer) {
214 player.totalfrags = 0;
215 player.totaltime = 1;
216 strcpy(player.playername, "unknown");
217 }
218
219 return player;
220 }
221
222 //This next section deals with account verification
223 //Passwords are hashed before storing or sending, raw strings are never exposed.
224
225 //Send request for login(vstrings are random, unique strings that the server generates for each player - provides an extra layer of encryption)
STATS_RequestVerification(void)226 void STATS_RequestVerification (void)
227 {
228 char *requeststring;
229 netadr_t adr;
230
231 if(currLoginState.validated)
232 return; //already validated
233
234 NET_Config (true);
235
236 currLoginState.requestType = STATSLOGIN;
237
238 requeststring = va("requestvstring\\\\%i\\\\%s", STAT_PROTOCOL, name->string);
239
240 if( NET_StringToAdr( cl_master->string, &adr ) ) {
241 if( !adr.port )
242 adr.port = BigShort( PORT_STATS );
243 Netchan_OutOfBandPrint( NS_CLIENT, adr, requeststring );
244 }
245 else
246 {
247 Com_Printf( "Bad address: %s\n", cl_master->string);
248 }
249 }
250
251 //Send request for password change
STATS_RequestPwChange(void)252 void STATS_RequestPwChange (void)
253 {
254 char *requeststring;
255 netadr_t adr;
256
257 currLoginState.validated = false; //force new login with new password
258
259 NET_Config (true);
260
261 currLoginState.requestType = STATSPWCHANGE;
262
263 requeststring = va("requestvstring\\\\%i\\\\%s", STAT_PROTOCOL, name->string);
264
265 if( NET_StringToAdr( cl_master->string, &adr ) ) {
266 if( !adr.port )
267 adr.port = BigShort( PORT_STATS );
268 Netchan_OutOfBandPrint( NS_CLIENT, adr, requeststring );
269 }
270 else
271 {
272 Com_Printf( "Bad address: %s\n", cl_master->string);
273 }
274 }
275
STATS_EncryptPassword(void)276 void STATS_EncryptPassword(void)
277 {
278 char szPassword[256];
279 char szPassHash[256];
280 char szPassHash2[256];
281
282 //salt
283 Com_sprintf(szPassword, sizeof(szPassword), "%s%s", stats_password->string, szVerificationString);
284 Com_MD5HashString (szPassword, strlen(szPassword), szPassHash, sizeof(szPassHash));
285
286 //salt
287 Com_sprintf(szPassword, sizeof(szPassword), "%s%s", szPassHash, szVerificationString);
288
289 Com_MD5HashString (szPassword, strlen(szPassword), szPassHash, sizeof(szPassHash));
290 Com_HMACMD5String(szPassHash, strlen(szPassHash), szVerificationString, strlen(szVerificationString), szPassHash2, sizeof(szPassHash2));
291
292 Cvar_FullSet("stats_pw_hashed", "1", CVAR_PROFILE);
293 Cvar_FullSet("stats_password", szPassHash2, CVAR_PROFILE);
294
295 currLoginState.hashed = true;
296
297 //get new hashed password
298 stats_password = Cvar_Get("stats_password", "password", CVAR_PROFILE);
299 }
300
301 //Send stats server authentication pw
STATS_AuthenticateStats(char * vstring)302 void STATS_AuthenticateStats (char *vstring)
303 {
304 char *requeststring;
305 netadr_t adr;
306
307 Q_strncpyz2(szVerificationString, vstring, sizeof(szVerificationString));
308
309 NET_Config (true);
310
311 //use md5 encryption on password, never send or store a raw password!
312 if(!pw_hashed->integer)
313 {
314 STATS_EncryptPassword();
315 }
316
317 requeststring = va("login\\\\%i\\\\%s\\\\%s\\\\%s\\\\", STAT_PROTOCOL, name->string, stats_password->string, szVerificationString );
318
319 if( NET_StringToAdr( cl_master->string, &adr ) ) {
320 if( !adr.port )
321 adr.port = BigShort( PORT_STATS );
322 Netchan_OutOfBandPrint( NS_CLIENT, adr, requeststring );
323 }
324 else
325 {
326 Com_Printf( "Bad address: %s\n", cl_master->string);
327 }
328 }
329
330 //Send password change request to server(this is to be called from the menu when a password is edited)
STATS_ChangePassword(char * vstring)331 void STATS_ChangePassword (char *vstring)
332 {
333 char *requeststring;
334 netadr_t adr;
335
336 Q_strncpyz2(szVerificationString, vstring, sizeof(szVerificationString));
337
338 NET_Config (true);
339
340 STATS_EncryptPassword();
341
342 requeststring = va("changepw\\\\%i\\\\%s\\\\%s\\\\%s\\\\%s\\\\", STAT_PROTOCOL, name->string, currLoginState.old_password, stats_password->string, szVerificationString);
343
344 if( NET_StringToAdr( cl_master->string, &adr ) ) {
345 if( !adr.port )
346 adr.port = BigShort( PORT_STATS );
347 Netchan_OutOfBandPrint( NS_CLIENT, adr, requeststring );
348 }
349 else
350 {
351 Com_Printf( "Bad address: %s\n", cl_master->string);
352 }
353 }
354
355 //Logout of stats server
STATS_Logout(void)356 void STATS_Logout (void)
357 {
358 char *requeststring;
359 netadr_t adr;
360
361 if(!currLoginState.validated)
362 return; //no point in logging out, we were never validated!
363
364 NET_Config (true);
365
366 //use md5 encryption on password, never send or store a raw password!
367 if(!pw_hashed->integer)
368 {
369 STATS_EncryptPassword();
370 }
371
372 requeststring = va("logout\\\\%i\\\\%s\\\\%s\\\\%s\\\\", STAT_PROTOCOL, name->string, stats_password->string, szVerificationString );
373
374 if( NET_StringToAdr( cl_master->string, &adr ) ) {
375 if( !adr.port )
376 adr.port = BigShort( PORT_STATS );
377 Netchan_OutOfBandPrint( NS_CLIENT, adr, requeststring );
378 }
379 else
380 {
381 Com_Printf( "Bad address: %s\n", cl_master->string);
382 }
383 }
384