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