~ivantis/armagetronad/sty+ct+ivantis

« back to all changes in this revision

Viewing changes to src/network/nAuthentication.cpp

  • Committer: ivantis
  • Date: 2008-09-09 21:33:18 UTC
  • Revision ID: ivantis@ivantis.net-20080909213318-k43y6yuq0zd6wbsa
first commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
*/
 
27
 
 
28
#include "nAuthentication.h"
 
29
#include "tMemManager.h"
 
30
#include "tToDo.h"
 
31
#include "tLocale.h"
 
32
#include "tRecorder.h"
 
33
#include "tSysTime.h"
 
34
 
 
35
#include "nNetwork.h"
 
36
#include "nNetObject.h"
 
37
#include "nSocket.h"
 
38
 
 
39
#include <memory>
 
40
#include <string>
 
41
#include <string.h>
 
42
#include <deque>
 
43
 
 
44
#ifdef HAVE_LIBZTHREAD
 
45
#include <zthread/Thread.h>
 
46
#include <zthread/LockedQueue.h>
 
47
//#include <zthread/ClassLockable.h>
 
48
#include <zthread/FastMutex.h>
 
49
#include <zthread/FastRecursiveMutex.h>
 
50
#include <zthread/Guard.h>
 
51
#include <zthread/SynchronousExecutor.h>
 
52
#include <zthread/ThreadedExecutor.h>
 
53
typedef ZThread::ThreadedExecutor nExecutor;
 
54
//typedef ZThread::SynchronousExecutor nExecutor;
 
55
typedef ZThread::FastMutex nMutex;
 
56
#else
 
57
typedef tNonMutex nMutex;
 
58
#endif
 
59
 
 
60
// authority black and whitelists
 
61
static tString sn_AuthorityBlacklist, sn_AuthorityWhitelist;
 
62
tConfItemLine  sn_AuthorityBlacklistConf( "AUTHORITY_BLACKLIST", sn_AuthorityBlacklist );
 
63
tConfItemLine  sn_AuthorityWhitelistConf( "AUTHORITY_WHITELIST", sn_AuthorityWhitelist );
 
64
 
 
65
#ifdef DEBUG
 
66
// list of authorities that get accepted as valid authorities, no questions asked
 
67
static tString sn_AuthorityNoCheck;
 
68
tConfItemLine  sn_AuthorityNoCheckConf( "AUTHORITY_NO_CHECK", sn_AuthorityNoCheck );
 
69
#endif
 
70
 
 
71
static nAuthentication::UserPasswordCallback* S_UserPasswordCallback = NULL;
 
72
static nAuthentication::LoginResultCallback*  S_LoginResultCallback  = NULL;
 
73
 
 
74
// let the game register the callbacks
 
75
void nAuthentication::SetUserPasswordCallback(nAuthentication::UserPasswordCallback* callback)
 
76
{
 
77
    S_UserPasswordCallback = callback;
 
78
}
 
79
 
 
80
void nAuthentication::SetLoginResultCallback (nAuthentication::LoginResultCallback* callback)
 
81
{
 
82
    S_LoginResultCallback = callback;
 
83
}
 
84
 
 
85
// network handler declarations
 
86
 
 
87
static nDescriptor nPasswordRequest(40, &nAuthentication::HandlePasswordRequest, "password_request");
 
88
 
 
89
static nDescriptor nPasswordAnswer(41, &nAuthentication::HandlePasswordAnswer, "password_answer");
 
90
 
 
91
// password request and answer
 
92
static nKrawall::nPasswordRequest sn_request;
 
93
static nKrawall::nPasswordAnswer sn_answer;
 
94
static nKrawall::nSalt sn_salt;
 
95
static int s_inUse = false;
 
96
 
 
97
// finish the request for username and password
 
98
static void FinishHandlePasswordRequest()
 
99
{
 
100
    nKrawall::nScrambledPassword egg;
 
101
 
 
102
    // if the callback exists, get the scrambled password of the wanted user
 
103
    if (S_UserPasswordCallback)
 
104
        (*S_UserPasswordCallback)( sn_request, sn_answer );
 
105
 
 
106
    // scramble the salt with the server address
 
107
    sn_GetAdr( 0, sn_answer.serverAddress );
 
108
    sn_request.ScrambleSalt( sn_salt, sn_answer.serverAddress );
 
109
 
 
110
    // scramble it with the given salt
 
111
    sn_request.ScrambleWithSalt( nKrawall::nScrambleInfo(sn_answer.username), sn_answer.scrambled, sn_salt, egg);
 
112
 
 
113
    // destroy the original password
 
114
    sn_answer.scrambled.Clear();
 
115
 
 
116
    // and send it back
 
117
    nMessage *ret = tNEW(nMessage)(nPasswordAnswer);
 
118
    nKrawall::WriteScrambledPassword(egg, *ret);
 
119
    *ret << sn_answer.username;
 
120
    *ret << sn_answer.aborted;
 
121
    *ret << sn_answer.automatic;
 
122
    *ret << sn_answer.serverAddress;
 
123
    ret->Send(0);
 
124
 
 
125
    s_inUse = false;
 
126
}
 
127
 
 
128
// receive a password request
 
129
void nAuthentication::HandlePasswordRequest(nMessage& m)
 
130
{
 
131
    if (m.SenderID() > 0 || sn_GetNetState() != nCLIENT)
 
132
        Cheater(m.SenderID());
 
133
 
 
134
    sn_answer = nKrawall::nPasswordAnswer();
 
135
    sn_request = nKrawall::nPasswordRequest();
 
136
 
 
137
    // already in the process: return without answer
 
138
    if ( s_inUse )
 
139
        return;
 
140
    s_inUse = true;
 
141
 
 
142
    // read salt and username from the message
 
143
    ReadSalt(m, sn_salt);
 
144
 
 
145
    // read the username as raw as sanely possible
 
146
    m.ReadRaw(sn_answer.username);
 
147
    sn_answer.username.NetFilter();
 
148
 
 
149
    m >> sn_request.message;
 
150
    if (!m.End())
 
151
    {
 
152
        m >> sn_request.failureOnLastTry;
 
153
    }
 
154
    else
 
155
    {
 
156
        sn_request.failureOnLastTry = true;
 
157
    }
 
158
    if (!m.End())
 
159
    {
 
160
        // read method, prefix and suffiox
 
161
        m >> sn_request.method;
 
162
        m.ReadRaw(sn_request.prefix);
 
163
        m.ReadRaw(sn_request.suffix);
 
164
        sn_request.prefix.NetFilter();
 
165
        sn_request.suffix.NetFilter();
 
166
    }
 
167
    else
 
168
    {
 
169
        // clear them
 
170
        sn_request.method = "bmd5";
 
171
        sn_request.prefix = "";
 
172
        sn_request.suffix = "";
 
173
    }
 
174
 
 
175
    // postpone the answer for a better opportunity since it
 
176
    // most likely involves opening a menu and waiting a while (and we
 
177
    // are right now in the process of fetching network messages...)
 
178
    st_ToDo(&FinishHandlePasswordRequest);
 
179
}
 
180
 
 
181
#ifdef KRAWALL_SERVER
 
182
 
 
183
static int sn_UserID( nNetObject * o )
 
184
{
 
185
    if ( !o )
 
186
    {
 
187
        return -1;
 
188
    }
 
189
    return o->Owner();
 
190
}
 
191
 
 
192
class nLoginProcess;
 
193
 
 
194
//! persistent information between login processes
 
195
class nLoginPersistence: 
 
196
    public nMachineDecorator
 
197
{
 
198
    friend class nLoginProcess;
 
199
 
 
200
    nLoginPersistence( int userID )
 
201
    : nMachineDecorator( nMachine::GetMachine( userID ) ),
 
202
      userAuthFailedLastTime( false )
 
203
    {
 
204
    }
 
205
 
 
206
    static nLoginPersistence & Find( int userID )
 
207
    {
 
208
        nMachine & machine = nMachine::GetMachine( userID );
 
209
        nLoginPersistence * ret = machine.GetDecorator< nLoginPersistence >();
 
210
        if ( !ret )
 
211
        {
 
212
            ret = new nLoginPersistence( userID );
 
213
        }
 
214
 
 
215
        return *ret;
 
216
    }
 
217
    
 
218
    virtual void OnDestroy()
 
219
    {
 
220
        delete this;
 
221
    }
 
222
 
 
223
    bool userAuthFailedLastTime;
 
224
};
 
225
 
 
226
//! template that runs void member functions of reference countable objects
 
227
template< class T > class nMemberFunctionRunnerTemplate
 
228
#ifdef HAVE_LIBZTHREAD
 
229
    : public ZThread::Runnable
 
230
#endif
 
231
{
 
232
public:
 
233
    nMemberFunctionRunnerTemplate( T & object, void (T::*function)() )
 
234
    : object_( &object ), function_( function )
 
235
    {
 
236
    }
 
237
 
 
238
    // runs the function
 
239
    void run()
 
240
    {
 
241
        (object_->*function_)();
 
242
    }
 
243
 
 
244
    //! schedule a task for execution at the next convenient break, between game rounds for example
 
245
    static void ScheduleBreak( T & object, void (T::*function)()  )
 
246
    {
 
247
        pendingForBreak_.push_back( nMemberFunctionRunnerTemplate( object, function ) );
 
248
    }
 
249
 
 
250
    //! schedule a task for execution in a background thread
 
251
    static void ScheduleBackground( T & object, void (T::*function)()  )
 
252
    {
 
253
#ifdef HAVE_LIBZTHREAD
 
254
        // schedule the task into a background thread
 
255
        static nExecutor executor;
 
256
        if ( !tRecorder::IsRunning() )
 
257
        {
 
258
            executor.execute( ZThread::Task( new nMemberFunctionRunnerTemplate( object, function ) ) );
 
259
        }
 
260
        else
 
261
        {
 
262
            // don't start threads when we're recording, just do the task at the next opportunity
 
263
            ScheduleBreak( object, function );
 
264
 
 
265
        }
 
266
#else
 
267
        // do it when you can without getting interrupted.
 
268
        ScheduleBreak( object, function );
 
269
#endif
 
270
    }
 
271
 
 
272
    //! schedule a task for execution in the next tToDo call
 
273
    static void ScheduleForeground( T & object, void (T::*function)()  )
 
274
    {
 
275
#ifdef HAVE_LIBZTHREAD
 
276
        Pending().add( nMemberFunctionRunnerTemplate( object, function ) );
 
277
        st_ToDo( FinishAll );
 
278
#else
 
279
        // execute it immedeately
 
280
        (object.*function)();
 
281
#endif
 
282
 
 
283
    }
 
284
 
 
285
    // function that calls tasks scheduled for the next break
 
286
    static void OnBreak()
 
287
    {
 
288
        // finish all pending tasks
 
289
        while( pendingForBreak_.size() > 0 )
 
290
        {
 
291
            nMemberFunctionRunnerTemplate & next = pendingForBreak_.front();
 
292
            next.run();
 
293
            pendingForBreak_.pop_front();
 
294
        }
 
295
    }
 
296
private:
 
297
    //! pointer to the object we should so something with
 
298
    tJUST_CONTROLLED_PTR< T > object_;
 
299
    
 
300
    //! the function to call
 
301
    void (T::*function_)();
 
302
 
 
303
    // taks for the break
 
304
    static std::deque< nMemberFunctionRunnerTemplate > pendingForBreak_;
 
305
 
 
306
#ifdef HAVE_LIBZTHREAD
 
307
    // queue of foreground tasks
 
308
     static ZThread::LockedQueue< nMemberFunctionRunnerTemplate, ZThread::FastMutex > & Pending()
 
309
     {
 
310
         static ZThread::LockedQueue< nMemberFunctionRunnerTemplate, ZThread::FastMutex > pending;
 
311
         return pending;
 
312
     }
 
313
    
 
314
    // function that calls them
 
315
    static void FinishAll()
 
316
    {
 
317
        // finish all pending tasks
 
318
        while( Pending().size() > 0 )
 
319
        {
 
320
            nMemberFunctionRunnerTemplate next = Pending().next();
 
321
            next.run();
 
322
        }
 
323
    }
 
324
#endif
 
325
};
 
326
 
 
327
template< class T >
 
328
std::deque< nMemberFunctionRunnerTemplate<T> > 
 
329
nMemberFunctionRunnerTemplate<T>::pendingForBreak_;
 
330
 
 
331
// convenience wrapper
 
332
class nMemberFunctionRunner
 
333
{
 
334
public:
 
335
    enum ScheduleType
 
336
    {
 
337
        Break,
 
338
        Foreground,
 
339
        Background
 
340
    };
 
341
 
 
342
    template< class T > static void ScheduleBreak( T & object, void (T::*function)() )
 
343
    {
 
344
        nMemberFunctionRunnerTemplate<T>::ScheduleBreak( object, function );
 
345
    }
 
346
 
 
347
    template< class T > static void ScheduleBackground( T & object, void (T::*function)() )
 
348
    {
 
349
        nMemberFunctionRunnerTemplate<T>::ScheduleBackground( object, function );
 
350
    }
 
351
 
 
352
    template< class T > static void ScheduleForeground( T & object, void (T::*function)() )
 
353
    {
 
354
        nMemberFunctionRunnerTemplate<T>::ScheduleForeground( object, function );
 
355
    }
 
356
 
 
357
    template< class T > static void ScheduleMayBlock( T & object, void (T::*function)(), bool block )
 
358
    {
 
359
        if ( block )
 
360
        {
 
361
#ifdef HAVE_LIBZTHREAD
 
362
            ScheduleBackground( object, function );
 
363
#else
 
364
            ScheduleBreak( object, function );
 
365
#endif
 
366
        }
 
367
        else
 
368
        {
 
369
            ScheduleForeground( object, function );
 
370
        }
 
371
    }
 
372
};
 
373
 
 
374
 
 
375
//! manager for logon processes
 
376
class nLoginProcess: 
 
377
    public nMachineDecorator, 
 
378
    public nKrawall::nCheckResult,
 
379
    public nKrawall::nPasswordCheckData,
 
380
    public tReferencable< nLoginProcess, nMutex >
 
381
{
 
382
    // reference counting pointer 
 
383
    typedef tJUST_CONTROLLED_PTR< nLoginProcess > SelfPointer;
 
384
public:
 
385
    nLoginProcess( int userID )
 
386
    : nMachineDecorator( nMachine::GetMachine( userID ) )
 
387
    {
 
388
        // install self reference to keep this object alive
 
389
        selfReference_ = this;
 
390
 
 
391
    // inform the user about delays
 
392
        bool delays = false;
 
393
#ifdef HAVE_LIBZTHREAD
 
394
        delays = tRecorder::IsRunning();
 
395
#endif
 
396
        if ( delays )
 
397
        {
 
398
            sn_ConsoleOut( tOutput( "$login_message_delayed" ), userID );
 
399
        }
 
400
    }
 
401
 
 
402
    ~nLoginProcess()
 
403
    {
 
404
    }
 
405
 
 
406
    static nLoginProcess * Find( int userID )
 
407
    {
 
408
        nMachine & machine = nMachine::GetMachine( userID );
 
409
        return machine.GetDecorator< nLoginProcess >();
 
410
    }
 
411
 
 
412
    // OK, authentication goes in several steps. First, we initialize everything
 
413
    void Init( tString const & authority, tString const & username, nNetObject & user, tString const & message )
 
414
    {
 
415
        this->user = &user;
 
416
        this->username = username;
 
417
        this->message = message;
 
418
        this->authority = authority;
 
419
 
 
420
        clientSupportedMethods = sn_Connections[user.Owner()].supportedAuthenticationMethods_;
 
421
 
 
422
        nMemberFunctionRunner::ScheduleMayBlock( *this, &nLoginProcess::FetchInfoFromAuthority, authority != "" );
 
423
    }
 
424
 
 
425
    // That function triggers fetching of authentication relevant data from the authentication
 
426
    // server in this function which is supposed to run in the background:
 
427
    void FetchInfoFromAuthority();
 
428
 
 
429
    // report an authority info query error to the higher level system
 
430
    bool ReportAuthorityError( tOutput const & error )
 
431
    {
 
432
        // prepare failure report
 
433
        this->success = false;
 
434
        this->error = error;
 
435
        
 
436
        Abort();
 
437
        
 
438
        return false;
 
439
    }
 
440
 
 
441
    // that function again triggers the following foreground action that queries 
 
442
    // the credentials from the client. This object then goes to sleep, 
 
443
    // waiting for a client answer or logout, whichever comes first.
 
444
    void QueryFromClient();
 
445
 
 
446
    // authentication data received from the client is processed here:
 
447
    void ProcessClientAnswer( nMessage & answer );
 
448
 
 
449
    // sanity check the server address
 
450
    bool CheckServerAddress( nMessage & m );
 
451
 
 
452
    // and here we go again: a background task talks with the authority
 
453
    // and determines whether the client is authorized or not.
 
454
    void Authorize();
 
455
 
 
456
    // which, when finished, triggers the foreground task of updating the
 
457
    // game state and informing the client of the success of the operation.
 
458
    void Finish();
 
459
 
 
460
    // the finish task can also be triggered any time by this function:
 
461
    void Abort();
 
462
private:
 
463
    // helper functions
 
464
    
 
465
    // fetches info from remote authority
 
466
    bool FetchInfoFromAuthorityRemote();
 
467
 
 
468
    // fetches info from local authority
 
469
    bool FetchInfoFromAuthorityLocal();
 
470
 
 
471
    tString                message;     //!< message to present to user
 
472
 
 
473
 
 
474
    tString clientSupportedMethods;     //!< methods supported by the client
 
475
 
 
476
 
 
477
    // called when the machine gets destroyed, which happens a bit after
 
478
    // the client logged out. If no process is currently running, destroy the object.
 
479
    virtual void OnDestroy()
 
480
    {
 
481
        SelfPointer keepAlive( this );
 
482
        selfReference_ = 0;
 
483
    }
 
484
 
 
485
    //! pointer to self to keep the object alive while the machine exists
 
486
    SelfPointer selfReference_;
 
487
};
 
488
 
 
489
 
 
490
 
 
491
// That function triggers fetching of authentication relevant data from the authentication
 
492
// server in this function which is supposed to run in the background:
 
493
void nLoginProcess::FetchInfoFromAuthority()
 
494
{
 
495
    // set method to defaults
 
496
    method.method = "bmd5";
 
497
    method.prefix = "";
 
498
    method.suffix = "";
 
499
    
 
500
    bool ret;
 
501
    if ( !tRecorder::IsPlayingBack() )
 
502
    {
 
503
        if ( authority.Len() <= 1 )
 
504
        {
 
505
            // local logins are easy, handle them first
 
506
            ret = FetchInfoFromAuthorityLocal();
 
507
        }
 
508
        else
 
509
        {
 
510
            // remote logins are harder.
 
511
            ret = FetchInfoFromAuthorityRemote();
 
512
        }
 
513
    }
 
514
 
 
515
    // record and playback result.
 
516
    static char const * section = "AUTH_INFO";
 
517
    tRecorder::Playback( section, ret );
 
518
    tRecorder::Playback( section, method.method );
 
519
    tRecorder::Playback( section, method.prefix );
 
520
    tRecorder::Playback( section, method.suffix );
 
521
    tRecorder::Playback( section, authority );
 
522
    tRecorder::Playback( section, error );
 
523
    tRecorder::Record( section, ret );
 
524
    tRecorder::Record( section, method.method );
 
525
    tRecorder::Record( section, method.prefix );
 
526
    tRecorder::Record( section, method.suffix );
 
527
    tRecorder::Record( section, authority );
 
528
    tRecorder::Record( section, error );
 
529
 
 
530
    if ( !ret )
 
531
    {
 
532
        if ( tRecorder::IsPlayingBack() )
 
533
        {
 
534
            Abort();
 
535
        }
 
536
 
 
537
        return;
 
538
    }
 
539
 
 
540
    // and go on
 
541
    nMemberFunctionRunner::ScheduleForeground( *this, &nLoginProcess::QueryFromClient );
 
542
}
 
543
 
 
544
static bool sn_supportRemoteLogins = false;
 
545
static tSettingItem< bool > sn_supportRemoteLoginsConf( "GLOBAL_ID", sn_supportRemoteLogins );
 
546
 
 
547
// legal characters in authority hostnames(besides alnum and dots)
 
548
static bool sn_IsLegalSpecialChar( char c )
 
549
{
 
550
    switch (c)
 
551
    {
 
552
    case '-': // well, ok, this character actually happens to be in many URLs :)
 
553
    case '+': // these not, but let's consider them legal.
 
554
    case '=':
 
555
    case '_':
 
556
        return true;
 
557
    default:
 
558
        return false;
 
559
    }
 
560
}
 
561
 
 
562
// fetches info from remote authority
 
563
bool nLoginProcess::FetchInfoFromAuthorityRemote()
 
564
{
 
565
    if ( !sn_supportRemoteLogins )
 
566
    {
 
567
        return ReportAuthorityError( tOutput("$login_error_noremote") );
 
568
    }
 
569
 
 
570
    {
 
571
        // the hostname part of the authority should not contain uppercase letters
 
572
#ifdef DEBUG
 
573
        if ( tIsInList( sn_AuthorityNoCheck, authority ) )
 
574
        {
 
575
            fullAuthority = authority;
 
576
        }
 
577
        else
 
578
#endif
 
579
        {
 
580
            std::istringstream in( static_cast< const char * >( authority ) );
 
581
            std::ostringstream outShort; // stream for shorthand authority
 
582
            std::ostringstream outFull;  // stream for full authority URL that is to be used for lookups     
 
583
            int c = in.get();
 
584
 
 
585
            // is the authority an abreviation?
 
586
            bool shortcut = true;
 
587
 
 
588
            // is the server a raw IP?
 
589
            bool rawIP = true;
 
590
 
 
591
            // which part we're currently parsing
 
592
            bool inHostName = true;
 
593
            bool inPort = false;
 
594
            bool slash = false;
 
595
            int port = 0;
 
596
 
 
597
            while( !in.eof() )
 
598
            {
 
599
                if ( inHostName )
 
600
                {
 
601
                    // check validity of hostname part
 
602
                    if ( c == '.' )
 
603
                    {
 
604
                        shortcut = false;
 
605
                    }
 
606
                    else if ( isalnum(c) )
 
607
                    {
 
608
                        c = tolower(c);
 
609
                        if ( !isdigit( c ) )
 
610
                        {
 
611
                            rawIP = false;
 
612
                        }
 
613
                    }
 
614
                    else if ( c == ':' )
 
615
                    {
 
616
                        inPort = true;
 
617
                        inHostName = false;
 
618
                    }
 
619
                    else if ( c == '/' )
 
620
                    {
 
621
                        shortcut = false;
 
622
                        slash = true;
 
623
                        inHostName = false;
 
624
                    }
 
625
                    else if ( !sn_IsLegalSpecialChar(c) )
 
626
                    {
 
627
                        return ReportAuthorityError( tOutput( "$login_error_invalidurl_illegal_hostname", authority ) );
 
628
                    }
 
629
                }
 
630
                else if ( inPort )
 
631
                {
 
632
                    if ( c == '/' )
 
633
                    {
 
634
                        shortcut = false;
 
635
                        inPort = false;
 
636
                        slash = true;
 
637
                    }
 
638
                    else if ( !isdigit( c ) )
 
639
                    {
 
640
                        return ReportAuthorityError( tOutput( "$login_error_invalidurl_illegal_port", authority ) );
 
641
                    }
 
642
                    else
 
643
                    {
 
644
                        port *= 10;
 
645
                        port += c - '0';
 
646
                    }
 
647
                }
 
648
                else // must be in path
 
649
                {
 
650
                    if ( c == '/' )
 
651
                    {
 
652
                        if ( slash )
 
653
                        {
 
654
                            return ReportAuthorityError( tOutput( "$login_error_invalidurl_slash", authority ) );
 
655
                        }
 
656
 
 
657
                        slash = true;
 
658
                    }
 
659
                    else
 
660
                    {
 
661
                        if (!isalnum(c) && c != '.' && c != '~' && !sn_IsLegalSpecialChar(c) )
 
662
                        {
 
663
                            return ReportAuthorityError( tOutput( "$login_error_invalidurl_illegal_path", authority )  );
 
664
                        }
 
665
 
 
666
                        slash = false;
 
667
                    }
 
668
                }
 
669
 
 
670
                // shorthand authority must consist of lowercase letters only
 
671
                outShort.put(tolower(c));
 
672
 
 
673
                outFull.put(c);
 
674
 
 
675
                c = in.get();
 
676
            }
 
677
            if ( slash )
 
678
            {
 
679
                return ReportAuthorityError( tOutput( "$login_error_invalidurl_slash", authority ) );
 
680
            }
 
681
            if ( port == 80 )
 
682
            {
 
683
                return ReportAuthorityError( tOutput( "$login_error_invalidurl_defaultport", authority ) );
 
684
            }
 
685
 
 
686
            if ( rawIP )
 
687
            {
 
688
                return ReportAuthorityError( tOutput( "$login_error_invalidurl_rawip", authority ) );
 
689
            }
 
690
 
 
691
            authority = outShort.str().c_str();
 
692
            fullAuthority = outFull.str().c_str();
 
693
 
 
694
            static const char * def = ".authentication.armagetronad.net";
 
695
 
 
696
            // append default authority path
 
697
            if ( authority.Len() > 1 && shortcut )
 
698
            {
 
699
                fullAuthority += def;
 
700
            }
 
701
 
 
702
            // check if the pased authority contains the default ending
 
703
            if ( !shortcut && authority.Reverse().StartsWith( tString( def ).Reverse() ) )
 
704
            {
 
705
                // strip it
 
706
                authority = authority.SubStr( 0, authority.Len() - strlen( def ) - 1 );
 
707
                shortcut = true;
 
708
            }
 
709
        }
 
710
 
 
711
        // check for authority in black and whitelist
 
712
        if ( tIsInList( sn_AuthorityBlacklist, authority ) )
 
713
        {
 
714
            return ReportAuthorityError( tOutput( "$login_error_blacklist", authority ) );
 
715
        }
 
716
 
 
717
        if ( sn_AuthorityWhitelist != "" && !tIsInList( sn_AuthorityWhitelist, authority ) )
 
718
        {
 
719
            return ReportAuthorityError( tOutput( "$login_error_whitelist", authority ) );
 
720
        }
 
721
 
 
722
        // try yo find a better method, fetch method list
 
723
        std::stringstream answer;
 
724
        int rc = nKrawall::FetchURL( fullAuthority, "?query=methods", answer );
 
725
 
 
726
        if ( rc == -1 )
 
727
        {
 
728
            return ReportAuthorityError( tOutput( "$login_error_invalidurl_notfound", authority ) );
 
729
        }
 
730
         
 
731
        tString id;
 
732
        answer >> id;
 
733
        tToLower(id);
 
734
 
 
735
        tString methods;
 
736
        std::ws(answer);
 
737
        methods.ReadLine( answer );
 
738
        tToLower(methods);
 
739
 
 
740
        if ( rc != 200 || id != "methods" )
 
741
        {
 
742
            return ReportAuthorityError( tOutput( "$login_error_nomethodlist", authority, rc, id + " " + methods ) );
 
743
        }
 
744
       
 
745
 
 
746
        method.method = nKrawall::nMethod::BestMethod( 
 
747
            methods,
 
748
            clientSupportedMethods
 
749
            );
 
750
        
 
751
        // check whether a method can be found
 
752
        if ( method.method.Len() <= 1 )
 
753
        {
 
754
            return ReportAuthorityError(
 
755
                tOutput( "$login_error_nomethod", 
 
756
                         clientSupportedMethods,
 
757
                         nKrawall::nMethod::SupportedMethods(),
 
758
                         methods )
 
759
                );
 
760
        }
 
761
    }
 
762
        
 
763
    // fetch md5 prefix and suffix
 
764
    {
 
765
        std::ostringstream query;
 
766
        query << "?query=params";
 
767
        query << "&method=" << nKrawall::EncodeString( method.method );
 
768
        std::ostringstream data;
 
769
        int rc = nKrawall::FetchURL( fullAuthority, query.str().c_str(), data );
 
770
        
 
771
        if ( rc != 200 )
 
772
        {
 
773
            if ( rc == -1 )
 
774
            {
 
775
                return ReportAuthorityError( tOutput( "$login_error_invalidurl_notfound", authority ) );
 
776
            }
 
777
            
 
778
            return ReportAuthorityError( tOutput( "$login_error_nomethodproperties", authority, rc, data.str().c_str() ) );
 
779
        }
 
780
        
 
781
        // read the properties
 
782
        std::istringstream read( data.str() );
 
783
        method = nKrawall::nMethod( static_cast< char const * >( method.method ), read );
 
784
    }
 
785
    
 
786
    return true;
 
787
}
 
788
 
 
789
// fetches info from local authority
 
790
bool nLoginProcess::FetchInfoFromAuthorityLocal()
 
791
{
 
792
    // try yo find a better method
 
793
    if ( !nKrawall::nMethod::BestLocalMethod( 
 
794
             clientSupportedMethods,
 
795
             method
 
796
             )
 
797
        )
 
798
    {
 
799
        return ReportAuthorityError(
 
800
            tOutput( "$login_error_nomethod", 
 
801
                     clientSupportedMethods,
 
802
                     nKrawall::nMethod::SupportedMethods(),
 
803
                     nKrawall::nMethod::SupportedMethods() )
 
804
            );
 
805
    }
 
806
    
 
807
    return true;
 
808
}
 
809
 
 
810
// that function again triggers the following foreground action that queries 
 
811
// the credentials from the client. This object then goes to sleep, 
 
812
// waiting for a client answer or logout, whichever comes first.
 
813
void nLoginProcess::QueryFromClient()
 
814
{
 
815
    // check whether the user disappeared by now (this is run in the main thread,
 
816
    // so no risk of the user disconnecting while the function runs)
 
817
    int userID = sn_UserID( user );
 
818
    if ( userID <= 0 )
 
819
        return;
 
820
 
 
821
    // create a random salt value
 
822
    nKrawall::RandomSalt(salt);
 
823
    
 
824
    // send the salt value and the username to the
 
825
    nMessage *m = tNEW(nMessage)(::nPasswordRequest);
 
826
    nKrawall::WriteSalt(salt, *m);
 
827
    *m << username;
 
828
    *m << static_cast<tString>(message);
 
829
    *m << nLoginPersistence::Find( userID ).userAuthFailedLastTime;
 
830
    
 
831
    // write method info
 
832
    *m << method.method;
 
833
    *m << method.prefix;
 
834
    *m << method.suffix;
 
835
    
 
836
    m->Send(userID);
 
837
 
 
838
    // well, then we wait for the answer.
 
839
    con << tOutput( "$login_message_responded", userID, username, method.method, message );
 
840
}
 
841
 
 
842
// authentication data received from the client is processed here:
 
843
void nLoginProcess::ProcessClientAnswer( nMessage & m )
 
844
{
 
845
    success = false;
 
846
    
 
847
    // read password and username from remote
 
848
    nKrawall::ReadScrambledPassword(m, hash);
 
849
 
 
850
    m.ReadRaw(username);
 
851
    username.NetFilter();
 
852
 
 
853
    aborted = false;
 
854
    automatic = false;
 
855
    if ( !m.End() )
 
856
    {
 
857
        m >> aborted;
 
858
    }
 
859
    if ( !m.End() )
 
860
    {
 
861
        m >> automatic;
 
862
    }
 
863
    if (!m.End())
 
864
    {
 
865
        // read the server address the client used for scrambling
 
866
        m >> serverAddress;
 
867
 
 
868
        // sanity check it, of course :)
 
869
        if ( !CheckServerAddress( m ) )
 
870
        {
 
871
            // no use going on, the server address won't match, password checking will fail.
 
872
            return;
 
873
        }
 
874
    }
 
875
    else
 
876
    {
 
877
        serverAddress = sn_GetMyAddress();
 
878
 
 
879
        if ( method.method != "bmd5" )
 
880
        {
 
881
            con << "WARNING, client did not send the server address. Password checks may fail.\n";
 
882
        }
 
883
    }
 
884
  
 
885
    // and go on
 
886
    nMemberFunctionRunner::ScheduleMayBlock( *this, &nLoginProcess::Authorize, authority != "" );
 
887
}
 
888
 
 
889
static bool sn_trustLAN = false;
 
890
static tSettingItem< bool > sn_TrustLANConf( "TRUST_LAN", sn_trustLAN );
 
891
 
 
892
// sanity check the server address
 
893
bool nLoginProcess::CheckServerAddress( nMessage & m )
 
894
{
 
895
    // check whether we can read our IP from the socket
 
896
    nSocket const * socket = sn_Connections[m.SenderID()].socket;
 
897
    if ( socket )
 
898
    {
 
899
        tString compareAddress = socket->GetAddress().ToString();
 
900
        if ( !compareAddress.StartsWith("*") && compareAddress == serverAddress )
 
901
        {
 
902
            // everything is fine, adresses match
 
903
            return true;
 
904
        }
 
905
    }
 
906
 
 
907
    // check the incoming address, clients from the LAN should be safe
 
908
    if ( sn_trustLAN )
 
909
    {
 
910
        tString peerAddress;
 
911
        sn_GetAdr( m.SenderID(), peerAddress );
 
912
        if ( sn_IsLANAddress( peerAddress ) && sn_IsLANAddress( serverAddress ) )
 
913
        {
 
914
            return true;
 
915
        }
 
916
    }
 
917
 
 
918
    if ( sn_GetMyAddress() == serverAddress )
 
919
    {
 
920
        // all's well
 
921
        return true;
 
922
    }
 
923
 
 
924
    tString hisServerAddress = serverAddress;
 
925
    serverAddress = sn_GetMyAddress();
 
926
 
 
927
    // if we don't know our own address, 
 
928
    if ( sn_GetMyAddress().StartsWith("*") )
 
929
    {
 
930
        // reject authentication.
 
931
        return ReportAuthorityError( tOutput("$login_error_pharm", hisServerAddress, sn_GetMyAddress() ) );
 
932
    }
 
933
 
 
934
    // reject authentication.
 
935
    return ReportAuthorityError( tOutput("$login_error_pharm", hisServerAddress, sn_GetMyAddress() ) );
 
936
}
 
937
 
 
938
// and here we go again: a background task talks with the authority
 
939
// and determines whether the client is authorized or not.
 
940
void nLoginProcess::Authorize()
 
941
{
 
942
    if ( aborted )
 
943
    {
 
944
        success = false;
 
945
        
 
946
        error = tOutput("$login_error_aborted");
 
947
    }
 
948
    else
 
949
    {
 
950
        if ( !tRecorder::IsPlayingBack() )
 
951
        {
 
952
            nKrawall::CheckScrambledPassword( *this, *this );
 
953
        }
 
954
 
 
955
        // record and playback result (required because on playback, a new
 
956
        // salt is generated and this way, a recoding does not contain ANY
 
957
        // exploitable information for password theft: the scrambled password
 
958
        // stored in the incoming network stream has an unknown salt value. )
 
959
        static char const * section = "AUTH_RESULT";
 
960
        tRecorder::Playback( section, username );
 
961
        tRecorder::Playback( section, success );
 
962
        tRecorder::Playback( section, authority );
 
963
        tRecorder::Playback( section, error );
 
964
        tRecorder::Record( section, username );
 
965
        tRecorder::Record( section, success );
 
966
        tRecorder::Record( section, authority );
 
967
        tRecorder::Record( section, error );
 
968
    }
 
969
    
 
970
    Abort();
 
971
}
 
972
 
 
973
// the finish task can also be triggered any time by this function:
 
974
void nLoginProcess::Abort()
 
975
{
 
976
    nMemberFunctionRunner::ScheduleForeground( *this, &nLoginProcess::Finish );
 
977
}
 
978
 
 
979
// which, when finished, triggers the foreground task of updating the
 
980
// game state and informing the client of the success of the operation.
 
981
void nLoginProcess::Finish()
 
982
{
 
983
    // again, userID is safe in this function
 
984
    int userID = sn_UserID( user );
 
985
    if ( userID <= 0 )
 
986
        return;
 
987
 
 
988
    // decorate console with correct sender ID
 
989
    nCurrentSenderID currentSender( userID );
 
990
    
 
991
    // store success for next time
 
992
    nLoginPersistence::Find( userID ).userAuthFailedLastTime = !success;
 
993
 
 
994
    // remove this decorator from public view
 
995
    Remove();
 
996
    
 
997
    if (S_LoginResultCallback)
 
998
        (*S_LoginResultCallback)( *this );
 
999
 
 
1000
    // bye-bye!
 
1001
    Destroy();
 
1002
}
 
1003
 
 
1004
static void sn_Reset(){
 
1005
    int userID = nCallbackLoginLogout::User();
 
1006
 
 
1007
    // kill/detach pending login process
 
1008
    nLoginProcess * process = nLoginProcess::Find( userID );
 
1009
    if ( process )
 
1010
    {
 
1011
        process->Remove();
 
1012
        process->Destroy();
 
1013
    }
 
1014
}
 
1015
 
 
1016
static nCallbackLoginLogout reset(&sn_Reset);
 
1017
 
 
1018
#endif // KRAWALL_SERVER
 
1019
    
 
1020
void nAuthentication::HandlePasswordAnswer(nMessage& m)
 
1021
{
 
1022
#ifdef KRAWALL_SERVER
 
1023
    // find login pricess
 
1024
    nLoginProcess * process = nLoginProcess::Find( m.SenderID() );
 
1025
 
 
1026
    // and delegate to it
 
1027
    if ( process )
 
1028
    {
 
1029
        process->ProcessClientAnswer( m );
 
1030
    }
 
1031
#endif
 
1032
}
 
1033
        
 
1034
// on the server: request user authentification from login slot
 
1035
bool nAuthentication::RequestLogin(const tString & authority, const tString& username, nNetObject & user, const tOutput& message )
 
1036
{
 
1037
#ifdef KRAWALL_SERVER
 
1038
    int userID = user.Owner();
 
1039
    if ( userID <= 0 )
 
1040
    {
 
1041
        return false;
 
1042
    }
 
1043
 
 
1044
    con << tOutput( "$login_message_requested", userID, username, authority );
 
1045
 
 
1046
    // do nothing if there is another login in process for that client
 
1047
    if ( nLoginProcess::Find( userID ) )
 
1048
    {
 
1049
        return false;
 
1050
    }
 
1051
 
 
1052
    // trigger function cascade bouncing between threads
 
1053
    (new nLoginProcess( userID ))->Init( authority, username, user, tString(message) );
 
1054
#endif
 
1055
 
 
1056
    return true;
 
1057
}
 
1058
 
 
1059
//! call when you have some time for lengthy authentication queries to servers
 
1060
void nAuthentication::OnBreak()
 
1061
{
 
1062
#ifdef KRAWALL_SERVER
 
1063
    nMemberFunctionRunnerTemplate< nLoginProcess >::OnBreak();
 
1064
#endif
 
1065
    st_DoToDo();
 
1066
}
 
1067