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