File: manager.py

package info (click to toggle)
timekpr-next 0.5.4-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 3,440 kB
  • sloc: python: 7,938; sh: 98; xml: 39; makefile: 6
file content (355 lines) | stat: -rw-r--r-- 18,584 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
"""
Created on Aug 28, 2018

@author: mjasnik
"""

# import section
import dbus
import time
from gi.repository import GLib

# timekpr imports
from timekpr.common.constants import constants as cons
from timekpr.common.log import log
from timekpr.common.utils import misc


class timekprUserLoginManager(object):
    """Class enables the connection with login1"""

    def __init__(self):
        """Initialize all stuff for login1"""
        log.log(cons.TK_LOG_LEVEL_INFO, "start timekpr login1 manager")

        # variables
        self._login1Object = None
        self._login1ManagerInterface = None
        self._loginManagerVTNr = None
        self._loginManagerVTNrRetries = 0
        self._connectionRetryCount = 0

        # dbus initialization
        self._timekprBus = dbus.SystemBus()

        # init connections
        self._initDbusConnections()

        log.log(cons.TK_LOG_LEVEL_INFO, "finish login1 manager")

    def _initDbusConnections(self):
        """Init connections to dbus"""
        # count retries
        self._connectionRetryCount += 1
        # if there was a connection before, give a big fat warning
        if self._login1ManagerInterface is not None:
            log.log(cons.TK_LOG_LEVEL_INFO, "IMPORTANT WARNING: connection to DBUS was lost, trying to establish it again, retry %i" % (self._connectionRetryCount))

        try:
            log.log(cons.TK_LOG_LEVEL_DEBUG, "getting login1 object on DBUS")
            # dbus performance measurement
            misc.measureTimeElapsed(pStart=True)

            # try to get real connection to our objects and interface
            self._login1Object = self._timekprBus.get_object(cons.TK_DBUS_L1_OBJECT, cons.TK_DBUS_L1_PATH)
            # measurement logging
            log.log(cons.TK_LOG_LEVEL_INFO, "PERFORMANCE (DBUS) - acquiring \"%s\" took too long (%is)" % (cons.TK_DBUS_L1_OBJECT, misc.measureTimeElapsed(pResult=True))) if misc.measureTimeElapsed(pStop=True) >= cons.TK_DBUS_ANSWER_TIME else True

            log.log(cons.TK_LOG_LEVEL_DEBUG, "getting login1 interface on DBUS")

            self._login1ManagerInterface = dbus.Interface(self._login1Object, cons.TK_DBUS_L1_MANAGER_INTERFACE)
            # measurement logging
            log.log(cons.TK_LOG_LEVEL_INFO, "PERFORMANCE (DBUS) - acquiring \"%s\" took too long (%is)" % (cons.TK_DBUS_L1_MANAGER_INTERFACE, misc.measureTimeElapsed(pResult=True))) if misc.measureTimeElapsed(pStop=True) >= cons.TK_DBUS_ANSWER_TIME else True

            log.log(cons.TK_LOG_LEVEL_DEBUG, "got interface, login1 successfully set up")

            # reset retries
            self._connectionRetryCount = 0
        except Exception as exc:
            log.log(cons.TK_LOG_LEVEL_INFO, "ERROR: error getting DBUS login manager: %s" % (exc))
            # reset connections
            self._login1ManagerInterface = None
            self._login1Object = None
            # raise error when too much retries
            if self._connectionRetryCount >= cons.TK_MAX_RETRIES:
                raise

    def _listUsers(self):
        """Exec ListUsers dbus methods (this is the only method which just has to succeed)"""
        # reset counter on retry
        self._connectionRetryCount = 0
        # def result
        loggedInUsersDBUS = None
        wasConnectionLost = False
        # try executing when there are retries left and there is no result
        while loggedInUsersDBUS is None and self._connectionRetryCount < cons.TK_MAX_RETRIES:
            # try get result
            try:
                # exec
                loggedInUsersDBUS = self._login1ManagerInterface.ListUsers()
            except Exception:
                # failure
                wasConnectionLost = True
                # no sleep on first retry
                if self._connectionRetryCount > 0:
                    # wait a little before retry
                    time.sleep(0.5)
                # retry connection
                self._initDbusConnections()
        # pass back the result
        return wasConnectionLost, loggedInUsersDBUS

    def getUserList(self, pSilent=False):
        """Go through a list of logged in users"""
        log.log(cons.TK_LOG_LEVEL_EXTRA_DEBUG, "start getUserList") if not pSilent else True

        # get user list
        wasConnectionLost, loggedInUsersDBUS = self._listUsers()
        loggedInUsers = {}

        # loop through all users
        for rUser in loggedInUsersDBUS:
            # set up dict for every user
            loggedInUsers[str(rUser[1])] = {cons.TK_CTRL_UID: str(int(rUser[0])), cons.TK_CTRL_UNAME: str(rUser[1]), cons.TK_CTRL_UPATH: str(rUser[2])}

        # in case debug
        if not pSilent and log.isDebugEnabled():
            # get all properties
            for key, value in loggedInUsers.items():
                # optimize logging
                uNameLog = "USER: %s" % (key)
                # values and keys
                for keyx, valuex in value.items():
                    uNameLog = "%s, %s: %s" % (uNameLog, keyx, valuex)
                log.log(cons.TK_LOG_LEVEL_DEBUG, uNameLog)

        log.log(cons.TK_LOG_LEVEL_EXTRA_DEBUG, "finish getUserList") if not pSilent else True

        # passing back user tuples
        return wasConnectionLost, loggedInUsers

    def getUserSessionList(self, pUserName, pUserPath):
        """Get up-to-date user session list"""
        # prepare return list
        userSessions = []

        misc.measureTimeElapsed(pStart=True)
        # get dbus object
        login1UserObject = self._timekprBus.get_object(cons.TK_DBUS_L1_OBJECT, pUserPath)
        # measurement logging
        log.log(cons.TK_LOG_LEVEL_INFO, "PERFORMANCE (DBUS) - acquiring \"%s\" took too long (%is)" % (pUserPath, misc.measureTimeElapsed(pResult=True))) if misc.measureTimeElapsed(pStop=True) >= cons.TK_DBUS_ANSWER_TIME else True
        # get dbus interface for properties
        login1UserInterface = dbus.Interface(login1UserObject, cons.TK_DBUS_PROPERTIES_INTERFACE)
        # measurement logging
        log.log(cons.TK_LOG_LEVEL_INFO, "PERFORMANCE (DBUS) - acquiring \"%s\" took too long (%is)" % (cons.TK_DBUS_PROPERTIES_INTERFACE, misc.measureTimeElapsed(pResult=True))) if misc.measureTimeElapsed(pStop=True) >= cons.TK_DBUS_ANSWER_TIME else True

        # dbus performance measurement
        misc.measureTimeElapsed(pStart=True)
        # get all user sessions
        login1UserSessions = login1UserInterface.Get(cons.TK_DBUS_USER_OBJECT, "Sessions")
        # measurement logging
        log.log(cons.TK_LOG_LEVEL_INFO, "PERFORMANCE (DBUS) - getting sessions for \"%s\" took too long (%is)" % (cons.TK_DBUS_USER_OBJECT, misc.measureTimeElapsed(pResult=True))) if misc.measureTimeElapsed(pStop=True) >= cons.TK_DBUS_ANSWER_TIME else True

        # go through all user sessions
        for rUserSession in login1UserSessions:
            # dbus performance measurement
            misc.measureTimeElapsed(pStart=True)

            # get dbus object
            login1SessionObject = self._timekprBus.get_object(cons.TK_DBUS_L1_OBJECT, str(rUserSession[1]))
            # measurement logging
            log.log(cons.TK_LOG_LEVEL_INFO, "PERFORMANCE (DBUS) - acquiring \"%s\" took too long (%is)" % (str(rUserSession[1]), misc.measureTimeElapsed(pResult=True))) if misc.measureTimeElapsed(pStop=True) >= cons.TK_DBUS_ANSWER_TIME else True

            # get dbus interface for properties
            login1SessionInterface = dbus.Interface(login1SessionObject, cons.TK_DBUS_PROPERTIES_INTERFACE)
            # measurement logging
            log.log(cons.TK_LOG_LEVEL_INFO, "PERFORMANCE (DBUS) - acquiring \"%s\" took too long (%is)" % (cons.TK_DBUS_PROPERTIES_INTERFACE, misc.measureTimeElapsed(pResult=True))) if misc.measureTimeElapsed(pStop=True) >= cons.TK_DBUS_ANSWER_TIME else True

            # get all user session properties
            try:
                sessionType = str(login1SessionInterface.Get(cons.TK_DBUS_SESSION_OBJECT, "Type"))
                sessionVTNr = str(int(login1SessionInterface.Get(cons.TK_DBUS_SESSION_OBJECT, "VTNr")))
                sessionSeat = str(login1SessionInterface.Get(cons.TK_DBUS_SESSION_OBJECT, "Seat")[0])
                sessionState = str(login1SessionInterface.Get(cons.TK_DBUS_SESSION_OBJECT, "State"))
                # measurement logging
                log.log(cons.TK_LOG_LEVEL_INFO, "PERFORMANCE (DBUS) - getting \"%s\" took too long (%is)" % (cons.TK_DBUS_SESSION_OBJECT, misc.measureTimeElapsed(pResult=True))) if misc.measureTimeElapsed(pStop=True) >= cons.TK_DBUS_ANSWER_TIME else True

                # add user session to return list
                userSessions.append({"session": rUserSession, "type": sessionType, "vtnr": sessionVTNr, "seat": sessionSeat, "state": sessionState})
            except Exception as exc:
                log.log(cons.TK_LOG_LEVEL_INFO, "ERROR: error getting session properties for session \"%s\" DBUS: %s" % (str(rUserSession[1]), exc))

        # return sessions
        return userSessions

    def determineLoginManagerVT(self, pUserName, pUserPath):
        """Get login manager session VTNr"""
        # if we did not yet find a login manager VTNr
        if self._loginManagerVTNr is None and self._loginManagerVTNrRetries < cons.TK_MAX_RETRIES:
            # def
            loginManager = None
            # loop through login managers
            for rLMan in cons.TK_USERS_LOGIN_MANAGERS.split(";"):
                # since we are checking this with UID less than 1K (or whatever is configured in limits file)
                # we can safely compare usernames or try to use LIKE operator on user name
                # exact match
                if rLMan == pUserName:
                    # we found one
                    loginManager = pUserName
                else:
                    # try to determine if we found one (according to "3.278 Portable Filename Character Set" additional symbols are ._-)
                    for rSymb in (".", "_", "-"):
                        # check for name
                        if "%s%s" % (rSymb, rLMan) in pUserName or "%s%s%s" % (rSymb, rLMan, rSymb) in pUserName or "%s%s" % (rLMan, rSymb) in pUserName:
                            # we found one
                            loginManager = pUserName
                            # first match is ok
                            break

            # determine if we have one like manager
            if loginManager is not None:
                # advance counter
                self._loginManagerVTNrRetries += 1
                # log
                log.log(cons.TK_LOG_LEVEL_DEBUG, "INFO: searching for login manager (%s) VTNr" % (pUserName))
                # VTNr (default)
                loginSessionVTNr = None
                # get user session list
                userSessionList = self.getUserSessionList(pUserName, pUserPath)
                # loop through users and try to guess login managers
                for rSession in userSessionList:
                    # check whether user seems to be login manager user
                    if rSession["type"] in cons.TK_SESSION_TYPES_CTRL:
                        # we got right session, save VTNr
                        loginSessionVTNr = rSession["vtnr"]
                        # done
                        break
                # if we found login manager VTNr
                if loginSessionVTNr is not None and loginSessionVTNr != "":
                    # return VTNr
                    self._loginManagerVTNr = loginSessionVTNr
                    # seat is found
                    log.log(cons.TK_LOG_LEVEL_INFO, "INFO: login manager (%s) TTY found: %s" % (pUserName, self._loginManagerVTNr))
            else:
                # log
                log.log(cons.TK_LOG_LEVEL_DEBUG, "INFO: searching for login manager, user (%s) does not look like one" % (pUserName))
        # in case we tried hard
        elif self._loginManagerVTNr is None and self._loginManagerVTNrRetries == cons.TK_MAX_RETRIES:
            # advance counter (so we never get here again)
            self._loginManagerVTNrRetries += 1
            # seat is NOT found and we'll not try to find it anymore
            log.log(cons.TK_LOG_LEVEL_INFO, "INFO: login manager (%s) TTY is NOT found, giving up until restart" % (pUserName))

    def switchTTY(self, pSeatId, pForce):
        """Swith TTY for login screen"""
        # defaults
        willSwitchTTY = True
        # switch to right TTY (if needed)
        if self._loginManagerVTNr is None:
            log.log(cons.TK_LOG_LEVEL_INFO, "INFO: switching TTY is not possible, login manager TTY was not found")
            willSwitchTTY = False
        elif pSeatId is not None and pSeatId != "":
            # get all necessary objects from DBUS to switch the TTY
            try:
                # it appears that sometimes seats are not available (RDP may not have it)
                seat = self._login1ManagerInterface.GetSeat(pSeatId)
            except Exception as exc:
                # cannot switch as we can't get seat
                willSwitchTTY = False
                log.log(cons.TK_LOG_LEVEL_INFO, "ERROR: error getting seat (%s) from DBUS: %s" % (str(pSeatId), exc))

            # only if we got the seat
            if willSwitchTTY:
                # seat object processing
                login1SeatObject = self._timekprBus.get_object(cons.TK_DBUS_L1_OBJECT, seat)
                login1SeatInterface = dbus.Interface(login1SeatObject, cons.TK_DBUS_SEAT_OBJECT)
                log.log(cons.TK_LOG_LEVEL_INFO, "INFO:%s switching TTY to %s" % (" (forced)" if pForce else "", self._loginManagerVTNr))
                # finally switching the TTY
                if cons.TK_DEV_ACTIVE:
                    log.log(cons.TK_LOG_LEVEL_INFO, "DEVELOPMENT ACTIVE, not switching my sessions, sorry...")
                else:
                    # finally switching the TTY
                    login1SeatInterface.SwitchTo(self._loginManagerVTNr)
        else:
            log.log(cons.TK_LOG_LEVEL_INFO, "INFO: switching TTY is not needed")
            # will not switch
            willSwitchTTY = False

        # in case switch is needed, reschedule it (might not work from first try)
        if willSwitchTTY and not pForce:
            # schedule a switch
            GLib.timeout_add_seconds(cons.TK_POLLTIME, self.switchTTY, pSeatId, True)

        # return false for repeat schedule to be discarded
        return False

    def terminateUserSessions(self, pUserName, pUserPath, pTimekprConfig):
        """Terminate user sessions"""
        log.log(cons.TK_LOG_LEVEL_EXTRA_DEBUG, "start terminateUserSessions")
        log.log(cons.TK_LOG_LEVEL_DEBUG, "inspecting \"%s\" userpath \"%s\" sessions" % (pUserName, pUserPath))

        # get user session list
        userSessionList = self.getUserSessionList(pUserName, pUserPath)
        # indication whether we are killing smth
        sessionsToKill = 0
        lastSeat = None
        userActive = False

        # go through all user sessions
        for rUserSession in userSessionList:
            # if we support this session type and it is not specifically excluded, only then we kill it
            if rUserSession["type"] in pTimekprConfig.getTimekprSessionsCtrl() and rUserSession["type"] not in pTimekprConfig.getTimekprSessionsExcl():
                log.log(cons.TK_LOG_LEVEL_INFO, "(delayed 0.1 sec) killing \"%s\" session %s (%s)" % (pUserName, str(rUserSession["session"][1]), str(rUserSession["type"])))
                # killing time
                if cons.TK_DEV_ACTIVE:
                    log.log(cons.TK_LOG_LEVEL_INFO, "DEVELOPMENT ACTIVE, not killing myself, sorry...")
                else:
                    GLib.timeout_add_seconds(0.1, self._login1ManagerInterface.TerminateSession, rUserSession["session"][0])
                # get last seat
                lastSeat = rUserSession["seat"] if rUserSession["seat"] is not None and rUserSession["seat"] != "" and rUserSession["vtnr"] is not None and rUserSession["vtnr"] != "" and self._loginManagerVTNr != rUserSession["vtnr"] else lastSeat
                # determine whether user is active
                userActive = userActive or rUserSession["state"] == "active"
                # count sessions to kill
                sessionsToKill += 1
            else:
                log.log(cons.TK_LOG_LEVEL_INFO, "saving \"%s\" session %s (%s)" % (pUserName, str(rUserSession["session"][1]), str(rUserSession["type"])))

        # kill leftover processes (if we are killing smth)
        if sessionsToKill > 0 and userActive and lastSeat is not None:
            # timeout
            tmo = cons.TK_POLLTIME - 1
            # switch TTY
            log.log(cons.TK_LOG_LEVEL_INFO, "scheduling a TTY switch sequence after %i seconds" % (tmo))
            # schedule a switch
            GLib.timeout_add_seconds(tmo, self.switchTTY, lastSeat, False)
        else:
            log.log(cons.TK_LOG_LEVEL_INFO, "TTY switch ommitted for user %s" % (pUserName))

        # cleanup
        if sessionsToKill > 0:
            # timeout
            tmo = cons.TK_POLLTIME * 2 + 1
            # dispatch a killer for leftovers
            log.log(cons.TK_LOG_LEVEL_INFO, "dipatching a killer for leftover processes after %i seconds" % (tmo))
            # schedule leftover processes to be killed (it's rather sophisticated killing and checks whether we need to kill gui or terminal processes)
            GLib.timeout_add_seconds(tmo, misc.killLeftoverUserProcesses, pUserName, pTimekprConfig)

        log.log(cons.TK_LOG_LEVEL_EXTRA_DEBUG, "finish terminateUserSessions")

    def suspendComputer(self, pUserName):
        """Suspend computer"""
        # only if we are not in DEV mode
        if cons.TK_DEV_ACTIVE:
            log.log(cons.TK_LOG_LEVEL_INFO, "DEVELOPMENT ACTIVE, not suspending myself, sorry...")
        else:
            log.log(cons.TK_LOG_LEVEL_DEBUG, "start suspendComputer in the name of \"%s\"" % (pUserName))
            GLib.timeout_add_seconds(0.1, self._login1ManagerInterface.Suspend, False)

    def shutdownComputer(self, pUserName):
        """Shutdown computer"""
        # only if we are not in DEV mode
        if cons.TK_DEV_ACTIVE:
            log.log(cons.TK_LOG_LEVEL_INFO, "DEVELOPMENT ACTIVE, not issuing shutdown for myself, sorry...")
        else:
            log.log(cons.TK_LOG_LEVEL_DEBUG, "start shutdownComputer in the name of \"%s\"" % (pUserName))
            GLib.timeout_add_seconds(0.1, self._login1ManagerInterface.PowerOff, False)