Passed
Pull Request — master (#248)
by Marek
01:51
created

ige.ClientMngr.ClientMngr.__init__()   B

Complexity

Conditions 5

Size

Total Lines 24
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 21
nop 4
dl 0
loc 24
rs 8.9093
c 0
b 0
f 0
1
#
2
#  Copyright 2001 - 2016 Ludek Smid [http://www.ospace.net/]
3
#
4
#  This file is part of Outer Space.
5
#
6
#  Outer Space is free software; you can redistribute it and/or modify
7
#  it under the terms of the GNU General Public License as published by
8
#  the Free Software Foundation; either version 2 of the License, or
9
#  (at your option) any later version.
10
#
11
#  Outer Space is distributed in the hope that it will be useful,
12
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
13
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
#  GNU General Public License for more details.
15
#
16
#  You should have received a copy of the GNU General Public License
17
#  along with Outer Space; if not, write to the Free Software
18
#  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19
#
20
21
import os
22
import hashlib
23
import random
24
import time
25
import log
26
import ige
27
from ige import SecurityException
28
from ige.Const import ADMIN_LOGIN
29
import Authentication
30
from account import Account, AIAccount
31
from ai_parser import AIList
32
from IDataHolder import IDataHolder
33
34
class ClientMngr:
35
36
    def __init__(self, database, authMethod, configDir):
37
        self.configDir = configDir
38
        self.authMethod = authMethod
39
        if not self.authMethod:
40
            self.authMethod = Authentication.defaultMethod
41
        if ige.igeRuntimeMode == 1:
42
            Authentication.init(configDir, self.authMethod, 2048)
43
        elif ige.igeRuntimeMode == 0:
44
            # it is minimum to cater for AI generated passwords
45
            Authentication.init(configDir, self.authMethod, 512)
46
        self._filename = os.path.join(self.configDir, 'accounts')
47
        self.sessions = {}
48
        #
49
        self.accounts = database
50
        # create special key
51
        if not self.accounts.has_key(ADMIN_LOGIN):
52
            log.message("No administator account found! (looking for '%s')" % ADMIN_LOGIN)
53
            log.message("Creating default account")
54
            self.createAccount(None, ADMIN_LOGIN, "tobechanged", "Administrator", "[email protected]")
55
        password = hashlib.sha1(str(random.randrange(0, 1e10))).hexdigest()
56
        open(os.path.join(self.configDir, "token"), "w").write(password)
57
        self.accounts[ADMIN_LOGIN].passwdHash = None # Needs plaintext login from token
58
        self.accounts[ADMIN_LOGIN].passwd = self.accounts[ADMIN_LOGIN].hashPassword(password)
59
        self.generateAIList()
60
61
    def shutdown(self):
62
        self.accounts.shutdown()
63
64
    def checkpoint(self):
65
        self.accounts.checkpoint()
66
67
    def backup(self, basename):
68
        self.accounts.backup(basename)
69
70
    def exists(self, login):
71
        return self.accounts.has_key(str(login))
72
73
    def __getitem__(self, login):
74
        return self.accounts[str(login)]
75
76
    def createAccount(self, sid, login, passwd, nick, email):
77
        log.message('Creating account', login, nick, email)
78
        login = login.strip()
79
        passwd = passwd.strip()
80
        nick = nick.strip()
81
        # check requirement
82
        if len(login) < 4:
83
            raise SecurityException('Login is too short.')
84
        if len(passwd) < 4:
85
            raise SecurityException('Password is too short.')
86
        if len(nick) < 4:
87
            raise SecurityException('Nick is too short.')
88
        # check login, nick and uid
89
        for key in self.accounts.keys():
90
            account = self.accounts[key]
91
            if account.login == login:
92
                raise SecurityException('Login already used.')
93
            elif account.nick == nick:
94
                raise SecurityException('Nick already used.')
95
            elif account.email == email:
96
                raise SecurityException('E-mail already used.')
97
        # create account
98
        account = Account()
99
        # update
100
        account.login = login
101
        account.passwd = account.hashPassword(passwd)
102
        account.nick = nick
103
        account.email = email
104
        account.confToken = hashlib.md5('%s%s%d' % (login, email, time.time())).hexdigest()
105
        self.accounts.create(account, id = str(account.login))
106
        log.message('Account created, confirmation token:', account.confToken)
107
        # TODO send confirmation token to the email address
108
        return 1, None
109
110
    def createAIAccount(self, login, nick, aiType):
111
        if self.accounts.has_key(login):
112
            log.message('AI account already exists, no work needed.', login, nick)
113
            password = self.accounts[login].passwd
114
            return password, None
115
116
        log.message('Creating AI account', login, nick)
117
        login = login.strip()
118
        nick = nick.strip()
119
        # create account
120
        account = AIAccount()
121
        # update
122
        account.login = login
123
        account.nick = nick
124
        account.aiType = aiType
125
        self.accounts.create(account, id = str(account.login))
126
        log.message('AI account created')
127
        self.generateAIList()
128
        return 1, None
129
130
    def generateAIList(self):
131
        aiList = AIList(self.configDir)
132
        aiList.removeAll()
133
        for login in self.accounts.keys():
134
            account = self.accounts[login]
135
            if not account.isAI:
136
                continue
137
            aiList.add(login, account.passwd, account.aiType)
138
        aiList.save()
139
        log.message('AI list regenerated')
140
141
    def removeAIAccount(self, login):
142
        self.accounts.delete(login)
143
        self.generateAIList()
144
        return 1, None
145
146
    def resetAIAccounts(self):
147
        for login in self.accounts.keys():
148
            account = self.accounts[login]
149
            if account.isAI:
150
                self.accounts.delete(login)
151
        self.generateAIList()
152
        return 1, None
153
154
155
    def hello(self, sid, login, clientId):
156
        log.debug(clientId, 'connected. User', repr(login))
157
        login = str(login)
158
        # create sort of cookie
159
        while 1:
160
            sid = hashlib.sha256(str(random.random())).hexdigest()
161
            if not self.sessions.has_key(sid):
162
                break
163
        challenge = Authentication.getWelcomeString(self.authMethod)
164
        session = Session(sid)
165
        session.challenge = challenge
166
        session.clientIdent = clientId
167
        self.sessions[sid] = session
168
        account = self.accounts.get(login, None)
169
        return (sid, challenge), None
170
171
    def login(self, sid, login, cpasswd, hostID):
172
        login = login.strip()
173
        if not login:
174
            raise SecurityException("Specify login, please.")
175
        if login in ():
176
            raise SecurityException("Account blocked")
177
        log.debug(sid, 'login', repr(login), 'hostid', hostID)
178
        login = str(login)
179
        challenge = self.sessions[sid].challenge
180
        log.debug("Trying local login for user", login)
181
        if not self.accounts.has_key(login):
182
            raise SecurityException('Wrong login and/or password.')
183
        account = self.accounts[login]
184
        if not Authentication.verify(cpasswd, account, challenge):
185
            raise SecurityException('Wrong login and/or password.')
186
        # setup session
187
        self.sessions[sid].setAttrs(account.login, account.nick, account.email)
188
        account.lastLogin = time.time()
189
        account.addHostID(hostID)
190
        return 1, None
191
192
    def getAccountData(self, sid):
193
        session = self.getSession(sid)
194
        result = IDataHolder()
195
        result.login = session.login
196
        result.nick = session.nick
197
        result.email = session.email
198
        return result, None
199
200
    def getSession(self, sid):
201
        session = self.sessions.get(sid, None)
202
        if not session:
203
            raise SecurityException('No such session id.')
204
        return session
205
206
    def getSessionByCID(self, cid):
207
        # TODO more effective - used by GameMngr.sendMessage
208
        for sid in self.sessions.keys():
209
            session = self.sessions[sid]
210
            if session.cid == cid:
211
                return session
212
        return None
213
214
    def logout(self, sid):
215
        session = self.sessions.get(sid, None)
216
        if session:
217
            try:
218
                log.debug(sid, 'logout', self.sessions[sid].login)
219
            except AttributeError:
220
                pass
221
            del self.sessions[sid]
222
            return 1, None
223
        else:
224
            raise SecurityException('No such session id.')
225
226
    def changePassword(self, sid, old, new):
227
        session = self.sessions[sid]
228
        if session:
229
            if self.accounts[session.login].passwd == old:
230
                self.accounts[session.login].passwd = new
231
                return 1, None
232
            else:
233
                raise SecurityException('Wrong password.')
234
        else:
235
            raise SecurityException('No such session id.')
236
237
    def cleanupSessions(self, sid):
238
        session = self.sessions.get(sid, None)
239
        if session:
240
            log.debug('cleaning up sessions')
241
            now = time.time()
242
            deleted = 0
243
            for id in self.sessions.keys():
244
                if self.sessions[id].timeout < now:
245
                    log.debug("Deleting session", self.sessions[id].sid, getattr(self.sessions[id], "login", "<unknown>"))
246
                    del self.sessions[id]
247
                    deleted += 1
248
            log.debug('cleanup finished (%d active, %d deleted)' % (len(self.sessions), deleted))
249
            return None, None
250
        else:
251
            raise SecurityException('No such session id.')
252
253
    def exportAccounts(self, sid):
254
        # check admin
255
        session = self.getSession(sid)
256
        if session.login != ADMIN_LOGIN:
257
            raise SecurityException('You cannot issue this command.')
258
        # export accounts
259
        f = open(os.path.join(self.configDir,"accounts.txt"), "w")
260
        for account in self.accounts.keys():
261
            account = self.accounts[account]
262
            print >>f, "%s\t%s\t%s\t%s" % (
263
                account.nick.encode("utf-8"),
264
                account.login.encode("utf-8"),
265
                account.passwd.encode("utf-8"),
266
                account.email.encode("utf-8")
267
            )
268
        f.close()
269
        return None, None
270
271
    def serverShutdown(self, sid):
272
        # check admin
273
        session = self.getSession(sid)
274
        if session.login != ADMIN_LOGIN:
275
            raise SecurityException('You cannot issue this command.')
276
        log.message('Shutting down server')
277
        import ige.RPCServer
278
        ige.RPCServer.running = 0
279
        return 1, None
280
281
    # new rpc interface wrappers
282
    def rpc_hello(self, login, clientId):
283
        return self.hello(None, login, clientId)[0]
284
285
    def rpc_login(self, sid, login, cpasswd, hostID):
286
        return self.login(sid, login, cpasswd, hostID)[0]
287
288
    def rpc_logout(self, sid):
289
        return self.logout(sid)[0]
290
291
    def rpc_createAccount(self, sid, login, passwd, nick, email):
292
        return self.createAccount(sid, login, passwd, nick, email)[0]
293
294
# Session class keeps various session data
295
class Session:
296
297
    def __init__(self, sid):
298
        self.cid = None
299
        self.sid = sid
300
        self.messages = {}
301
        self.touch()
302
303
    def setAttrs(self, login, nick, email):
304
        self.login = login
305
        self.nick = nick
306
        self.email = email
307
308
    def touch(self):
309
        # 10 minutes timeout of session
310
        self.timeout = time.time() + 10 * 60
311
312
class Mapping(dict):
313
    pass
314