1 /*
2
3 *************************************************************************
4
5 ArmageTron -- Just another Tron Lightcycle Game in 3D.
6 Copyright (C) 2000 Manuel Moos (manuel@moosnet.de)
7
8 **************************************************************************
9
10 This program is free software; you can redistribute it and/or
11 modify it under the terms of the GNU General Public License
12 as published by the Free Software Foundation; either version 2
13 of the License, or (at your option) any later version.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23
24 ***************************************************************************
25
26 Although it is already included in the GPL, let me clarify:
27 anybody, especially the Krawall Gaming network, is allowed
28 to run a server/master server build from modified versions of
29 this file WHITHOUT releasing the source to the public (provided
30 the executable is not distributed).
31
32
33 */
34
35 #include "nKrawall.h"
36 #include "tString.h"
37 #include "tConsole.h"
38 #include "nNetwork.h"
39 #include "tConfiguration.h"
40 #include "tArray.h"
41
42 #include <string>
43 #include <vector>
44 #include <map>
45
46 static nKrawall::nMethod sn_bmd5("bmd5"), sn_md5("md5");
47
48 static tSettingItem< tString > sn_md5Prefix( "MD5_PREFIX", sn_md5.prefix );
49 static tSettingItem< tString > sn_md5Suffix( "MD5_SUFFIX", sn_md5.suffix );
50
51 //! fetch NULL-terminated list of locally supported methods
LocalMethods()52 nKrawall::nMethod const * const * nKrawall::nMethod::LocalMethods()
53 {
54 static nMethod const * methods[] =
55 {
56 &sn_md5,
57 &sn_bmd5,
58 NULL
59 };
60
61 return methods;
62 }
63
64 #ifdef KRAWALL_SERVER
65
66 #include <libxml/nanohttp.h>
67
68 // crude login structure with not too many feathres
69 struct nLogin
70 {
71 tString authority_;
72
73 // scrambled passwords according to our various methods
74 std::map< tString, nKrawall::nScrambledPassword > scrambledPasswords_;
75
76 // access level for the login
77 tAccessLevel accessLevel_;
78
79 // password for team accounts
80 tString password_;
81
FixOKnLogin82 static bool FixOK( tString const & fix )
83 {
84 return fix.StrPos( "%" ) < 0;
85 }
86
nLoginnLogin87 nLogin( char const * authority, tString const & password, tString const & username, tAccessLevel accessLevel )
88 : authority_( authority ), accessLevel_( accessLevel )
89 {
90 // scramble the password before storing it (in case an evil attacker can read our memory,
91 // but not the configuration file where they are stored in plain text :) )
92 nKrawall::nMethod const * const * run = nKrawall::nMethod::LocalMethods();
93 while ( * run )
94 {
95 if ( username.Len() <= 1 &&
96 ( !FixOK( (*run)->prefix ) || !FixOK( (*run)->suffix ) )
97 )
98 {
99 // store plain text password
100 password_ = password;
101 }
102 else
103 {
104 (*run)->ScramblePassword( nKrawall::nScrambleInfo( username ), password, scrambledPasswords_[ (*run)->method ] );
105 }
106
107 ++run;
108 }
109 }
110
111 // check whether a scrambled password sent by a client is correct
CheckPasswordnLogin112 bool CheckPassword( nKrawall::nScrambleInfo const & info, nKrawall::nMethod const & method, nKrawall::nSalt const & salt, nKrawall::nScrambledPassword const & hash, tString & error ) const
113 {
114 // find the correct scrambled password
115 std::map< tString, nKrawall::nScrambledPassword >::const_iterator scrambled = scrambledPasswords_.find( method.method );
116 if ( scrambled != scrambledPasswords_.end() )
117 {
118 // check whether methods match exactly
119 nKrawall::nMethod const * const * run = nKrawall::nMethod::LocalMethods();
120 while ( * run )
121 {
122 nKrawall::nMethod const & compare = **run;
123 if ( compare.method == method.method && !nKrawall::nMethod::Equal(compare, method ) )
124 {
125 error = tOutput( "$login_error_methodmismatch" );
126 return false;
127 }
128
129 ++run;
130 }
131
132 // compare passwords
133 nKrawall::nScrambledPassword scrambledCorrect;
134 method.ScrambleWithSalt( info, (*scrambled).second, salt, scrambledCorrect );
135 bool ret = nKrawall::ArePasswordsEqual( hash, scrambledCorrect );
136 if ( !ret )
137 {
138 error = tOutput( "$login_error_local_password", info.username );
139 }
140 return ret;
141 }
142 else
143 {
144 if ( password_ == "" )
145 {
146 error = "Internal error, local method not found, and no plaintext password stored.";
147 return false;
148 }
149
150 // do a full check on the stored password
151 nKrawall::nScrambledPassword scrambled, correctHash;
152 method.ScramblePassword( info, password_, scrambled );
153 method.ScrambleWithSalt( info, scrambled, salt, correctHash );
154
155 bool ret = nKrawall::ArePasswordsEqual( hash, correctHash );
156 if ( !ret )
157 {
158 error = tOutput( "$login_error_local_password", info.username );
159 }
160 return ret;
161 }
162
163 return false;
164 }
165
nLoginnLogin166 nLogin()
167 {
168 }
169 };
170
171 typedef std::map< tString, nLogin > nLoginMap;
172
173 // database of exact logins
174 static nLoginMap sn_exactLogins;
175
176 // database of inprecise logins
177 static nLoginMap sn_partialLogins;
178
179 // add an admin account
sn_ReadPassword(std::istream & s)180 static void sn_ReadPassword( std::istream & s )
181 {
182 tString username, password;
183 s >> username;
184 if ( !s.good() )
185 {
186 con << tOutput( "$local_user_syntax" );
187 return;
188 }
189 tConfItemBase::EatWhitespace(s);
190 password.ReadLine(s);
191 if ( password == "" )
192 {
193 con << tOutput( "$local_user_syntax" );
194 return;
195 }
196
197 sn_exactLogins[ username ] = nLogin( "", password, username, tAccessLevel_Local );
198 }
199
200 static tConfItemFunc sn_kpa( "LOCAL_USER", sn_ReadPassword );
201 static tAccessLevelSetter sn_kpal( sn_kpa, tAccessLevel_Owner );
202
203 // add team account
sn_ReadTeamPassword(std::istream & s)204 static void sn_ReadTeamPassword( std::istream & s )
205 {
206 tString username, password;
207 s >> username;
208 if ( !s.good() )
209 {
210 con << tOutput( "$local_team_syntax" );
211 return;
212 }
213 tConfItemBase::EatWhitespace(s);
214 password.ReadLine(s);
215 if ( password == "" )
216 {
217 con << tOutput( "$local_team_syntax" );
218 return;
219 }
220
221 sn_partialLogins[ username ] = nLogin( tString("L_TEAM_") + username, password, tString(""), tAccessLevel_TeamMember );
222 }
223
224 static tConfItemFunc sn_kta( "LOCAL_TEAM", sn_ReadTeamPassword );
225 static tAccessLevelSetter sn_ktal( sn_kta, tAccessLevel_Owner );
226
227 // finds a login element as iterator
sn_FindLoginIterator(tString const & username,nLoginMap * & map,bool exact=false)228 nLoginMap::iterator sn_FindLoginIterator( tString const & username, nLoginMap * & map, bool exact = false )
229 {
230 // find exact login
231 map = &sn_exactLogins;
232 nLoginMap::iterator found = sn_exactLogins.find( username );
233 if ( found != sn_exactLogins.end() )
234 {
235 return found;
236 }
237
238 map = &sn_partialLogins;
239 for( int i = username.Len(); i >= 1; --i )
240 {
241 tString partial = username.SubStr( 0, i );
242 nLoginMap::iterator found = map->find( partial );
243 if ( found != map->end() )
244 {
245 return found;
246 }
247
248 if ( exact )
249 {
250 return map->end();
251 }
252 }
253
254 return map->end();
255 }
256
257 // finds login pointer
sn_FindLogin(tString const & username)258 nLogin const * sn_FindLogin( tString const & username )
259 {
260 // find login
261 nLoginMap * map;
262 nLoginMap::iterator found = sn_FindLoginIterator( username, map );
263 if ( found != map->end() )
264 {
265 return &found->second;
266 }
267
268 return 0;
269 }
270
271 // remove an account
sn_ReadPasswordRemove(std::istream & s)272 static void sn_ReadPasswordRemove( std::istream & s )
273 {
274 tString username;
275 s >> username;
276 nLoginMap * map;
277 nLoginMap::iterator found = sn_FindLoginIterator( username, map, true );
278 if ( found != map->end() )
279 {
280 map->erase( found );
281 con << tOutput( "$md5_password_removed", username );
282 }
283 else
284 {
285 con << tOutput( "$md5_password_remove_notfound", username );
286 }
287 }
288
289 static tConfItemFunc sn_kpr( "USER_REMOVE", sn_ReadPasswordRemove );
290
291
292 // fetch the scrambled password of username from the users database
CheckScrambledPassword(nCheckResultBase & result,nPasswordCheckData const & data)293 void nKrawall::CheckScrambledPassword( nCheckResultBase & result,
294 nPasswordCheckData const & data )
295 {
296 // extra salt scrambling process
297 nSalt salt = data.salt;
298 data.method.ScrambleSalt( salt, data.serverAddress );
299
300 // local users
301 if ( result.authority.Len() <= 1 )
302 {
303 // fetch login data
304 nLogin const * login = sn_FindLogin( result.username );
305 if ( !login )
306 {
307 result.success = false;
308 result.error = tOutput( "$login_error_local_nouser", result.username );
309 return;
310 }
311
312 // store relevant authority
313 result.authority = login->authority_;
314
315 // check password
316 result.success = login->CheckPassword( nScrambleInfo( result.username ), data.method, salt, data.hash, result.error );
317 result.accessLevel = login->accessLevel_;
318
319 return;
320 }
321 else
322 {
323 // remote users: build query URL
324 std::ostringstream request;
325
326 request << "?query=check";
327 request << "&method=" << EncodeString( data.method.method );
328 request << "&user=" << EncodeString( result.username );
329 request << "&salt=" << EncodeScrambledPassword( salt );
330 request << "&hash=" << EncodeScrambledPassword( data.hash );
331
332 // read URL content
333 std::stringstream content;
334 int rc = FetchURL( data.fullAuthority, request.str().c_str(), content );
335
336 if (rc == -1)
337 {
338 result.error = tOutput( "$login_error_invalidurl_notfound", result.authority );
339 result.success = false;
340 return;
341 }
342
343 // lots of string copying going on, probably should find a better way.
344 char * buf_temp = strdup( content.str().c_str() );
345 unsigned int len = strlen(buf_temp);
346
347 // get rid of newlines
348 for ( unsigned int i = 0; i < len; ++i )
349 {
350 if ( buf_temp[i] == '\n' )
351 {
352 buf_temp[i] = ' ';
353 }
354 }
355
356 // trailing spaces are ugly
357 while ( len > 0 && buf_temp[len-1] == ' ' )
358 {
359 buf_temp[len-1] = 0;
360 --len;
361 }
362
363 tString buf( buf_temp );
364 free( buf_temp );
365
366 // catch various error codes
367 if ( rc != 200 ) {
368 result.success = false;
369 switch ( rc )
370 {
371 case 404:
372 result.error = tOutput( "$login_error_nouser", buf );
373 break;
374 case 403:
375 case 401:
376 result.error = tOutput( "$login_error_password", buf );
377 break;
378 default:
379 result.error = tOutput( "$login_error_unknown", rc, buf );
380 break;
381 }
382
383 return;
384 }
385
386 // read the buffer
387 tString ret;
388 content >> ret;
389 tToLower( ret );
390
391 // catch the same errros frm the server's response
392 if ( ret == "unknown_user" )
393 {
394 result.error = tOutput( "$login_error_nouser", buf );
395 return;
396 }
397
398 if ( ret == "password_fail" )
399 {
400 result.error = tOutput( "$login_error_password", buf );
401 return;
402 }
403
404 if ( ret != "password_ok" )
405 {
406 result.error << tOutput( "$login_error_unexpected_answer", "PASSWORD_OK ...", buf );
407 return;
408 }
409
410 // everything fine so far. Read the full username returned from the authority.
411 tString fullUserName;
412 fullUserName.ReadLine( content );
413
414 // check on it
415 if ( fullUserName != "" )
416 {
417 tString claimedAuthority;
418 SplitUserName( fullUserName, result.username, claimedAuthority );
419 if ( !CanClaim ( result.authority, claimedAuthority ) )
420 {
421 result.error << tOutput( "$login_error_invalidclaim",
422 result.authority,
423 result.username + "@" + claimedAuthority );
424 return;
425 }
426 else
427 {
428 result.authority = claimedAuthority;
429 }
430 }
431
432 // read additional data, let caller handle it
433 while( true )
434 {
435 tString blurb;
436 blurb.ReadLine( content );
437 if ( content.eof() || content.fail() )
438 {
439 break;
440 }
441 result.blurb.push_back( blurb );
442 }
443
444 result.accessLevel = tAccessLevel_Remote;
445 result.success = true;
446 return;
447 }
448 }
449
FetchURL(tString const & authority,char const * query,std::ostream & target,int maxlen)450 int nKrawall::FetchURL( tString const & authority, char const * query, std::ostream & target, int maxlen )
451 {
452 // compose real URL
453 std::ostringstream fullURL;
454 fullURL << "http://" << authority << "/armaauth/0.1/";
455 fullURL << query;
456
457 // better not. output is not thread safe.
458 // con << "Fetching authentication URL " << fullURL.str() << "\n";
459
460 // fetch URL
461 void * ctxt = xmlNanoHTTPOpen( fullURL.str().c_str(), NULL);
462 if (ctxt == NULL)
463 {
464 return -1;
465 }
466
467 int rc = xmlNanoHTTPReturnCode(ctxt);
468
469 // read content
470 char buf[1000];
471 buf[0] = 0;
472 unsigned int len = 1;
473 while ( len > 0 && maxlen > 0 )
474 {
475 int max = sizeof(buf);
476 if ( max > maxlen )
477 max = maxlen;
478 len = xmlNanoHTTPRead( ctxt, &buf, max );
479 target.write( buf, len );
480 maxlen -= len;
481 }
482
483 xmlNanoHTTPClose(ctxt);
484
485 return rc;
486 }
487
488 #ifdef KRAWALL_SERVER_LEAGUE
489 // TODO: REALLY change this!!
490 static nKrawall::nScrambledPassword key =
491 {
492 13, 12, 12, 12, 12, 12, 12, 12
493 };
494
495
496 // secret key to encrypt server->master server league transfer
SecretLeagueKey()497 const nKrawall::nScrambledPassword& nKrawall::SecretLeagueKey()
498 {
499 return key;
500 }
501
502 // called ON THE MASTER when victim drives against killer's wall
MasterFrag(const tString & killer,const tString & victim)503 void nKrawall::MasterFrag(const tString &killer, const tString& victim)
504 {
505 con << killer << " killed " << victim << "\n";
506 // TODO: REAL league management
507 }
508
509
510 // called ON THE MASTER at the end of a round; the last survivor is stored in
511 // players[numPlayers-1], the first death in players[0]
MasterRoundEnd(const tString * players,int numPlayers)512 void nKrawall::MasterRoundEnd(const tString* players, int numPlayers)
513 {
514 if (numPlayers > 1)
515 {
516 con << players[numPlayers-1] << " survived over ";
517 for (int i = numPlayers-2; i>=0; i--)
518 {
519 con << players[i];
520 if (i > 0)
521 con << " and ";
522 }
523 con << ".\n";
524 }
525 // TODO: REAL league management
526 }
527
528
529
530 // first validity check for the league messages
IsFromKrawall(tString & adress,unsigned int port)531 bool nKrawall::IsFromKrawall(tString& adress, unsigned int port)
532 {
533 return (adress.Len() > 3 &&
534 !strncmp(adress, "127.0.0", 7));
535 }
536
537 // check if a user is from germany (so the master server will require
538 // a password check)
RequireMasterLogin(tString & adress,unsigned int port)539 bool nKrawall::RequireMasterLogin(tString& adress, unsigned int port)
540 {
541 return (adress.Len() > 3 &&
542 !strncmp(adress, "127.0.0", 7));
543 }
544
545 #endif
546 #endif
547