mirror of
https://gitlab.com/apparmor/apparmor
synced 2025-08-30 22:05:27 +00:00
Merge Add support for lastlog2 to get last login
lastlog2 is the 2038-safe replacement for wtmp, and in the meantime became part of util-linux. Adjust get_last_login_timestamp() to use the lastlog2 database (/var/lib/lastlog/lastlog2.db) if it exists, and adjust get_last_login_timestamp_lastlog2() to actually do that. (If lastlog2.db doesn't exist, aa-notify will read wtmp as usual.) Unfortunately lastlog2 doesn't have a way to get machine-readable output (for example json), therefore - after trying and failing to parse the lastlog2 output - directly read from lastlog2.db. Let's hope the format never changes ;-) Fixes: https://bugzilla.opensuse.org/show_bug.cgi?id=1228378 Fixes: https://bugzilla.opensuse.org/show_bug.cgi?id=1216660 Fixes: https://gitlab.com/apparmor/apparmor/-/issues/372 I propose this patch for 4.0 and master. Closes #372 MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1282 Approved-by: Christian Boltz <apparmor@cboltz.de> Merged-by: Christian Boltz <apparmor@cboltz.de>
This commit is contained in:
@@ -390,6 +390,7 @@ The aa-notify tool's Python dependencies can be satisfied by installing the
|
|||||||
following packages (Debian package names, other distros may vary):
|
following packages (Debian package names, other distros may vary):
|
||||||
* python3-notify2
|
* python3-notify2
|
||||||
* python3-psutil
|
* python3-psutil
|
||||||
|
* python3-sqlite (part of the python3.NN-stdlib package)
|
||||||
* python3-tk
|
* python3-tk
|
||||||
* python3-ttkthemes
|
* python3-ttkthemes
|
||||||
* python3-gi
|
* python3-gi
|
||||||
|
@@ -15,12 +15,35 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
from apparmor.common import AppArmorBug, DebugLogger
|
from apparmor.common import AppArmorBug, DebugLogger
|
||||||
|
|
||||||
debug_logger = DebugLogger('apparmor.notify')
|
debug_logger = DebugLogger('apparmor.notify')
|
||||||
|
|
||||||
|
|
||||||
|
def get_last_login_timestamp(username, filename='/var/log/wtmp', lastlog2_db='/var/lib/lastlog/lastlog2.db'):
|
||||||
|
"""Get last login for user as epoch timestamp"""
|
||||||
|
|
||||||
|
if os.access(lastlog2_db, os.R_OK):
|
||||||
|
return get_last_login_timestamp_lastlog2(username, lastlog2_db)
|
||||||
|
else:
|
||||||
|
return get_last_login_timestamp_wtmp(username, filename)
|
||||||
|
|
||||||
|
|
||||||
|
def get_last_login_timestamp_lastlog2(username, lastlog2_db='/var/lib/lastlog/lastlog2.db'):
|
||||||
|
"""Execute lastlog2 and get last login for user as epoch timestamp"""
|
||||||
|
|
||||||
|
db = sqlite3.connect('file:%s?mode=ro' % lastlog2_db, uri=True)
|
||||||
|
cur = db.cursor()
|
||||||
|
timestamp = cur.execute('SELECT Time FROM Lastlog2 WHERE Name == ?;', [username]).fetchone()
|
||||||
|
|
||||||
|
if timestamp:
|
||||||
|
return timestamp[0]
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def sane_timestamp(timestamp):
|
def sane_timestamp(timestamp):
|
||||||
"""Check if the given timestamp is in a date range that makes sense for a wtmp file"""
|
"""Check if the given timestamp is in a date range that makes sense for a wtmp file"""
|
||||||
|
|
||||||
@@ -32,7 +55,7 @@ def sane_timestamp(timestamp):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def get_last_login_timestamp(username, filename='/var/log/wtmp'):
|
def get_last_login_timestamp_wtmp(username, filename='/var/log/wtmp'):
|
||||||
"""Directly read wtmp and get last login for user as epoch timestamp"""
|
"""Directly read wtmp and get last login for user as epoch timestamp"""
|
||||||
timestamp = 0
|
timestamp = 0
|
||||||
last_login = 0
|
last_login = 0
|
||||||
|
@@ -12,10 +12,46 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from apparmor.common import AppArmorBug
|
from apparmor.common import AppArmorBug
|
||||||
from apparmor.notify import get_last_login_timestamp, sane_timestamp
|
from apparmor.notify import get_last_login_timestamp, get_last_login_timestamp_wtmp, sane_timestamp
|
||||||
from common_test import AATest, setup_all_loops
|
from common_test import AATest, setup_all_loops
|
||||||
|
|
||||||
|
|
||||||
|
class TestGet_last_login_timestamp(AATest):
|
||||||
|
tests = (
|
||||||
|
# wtmp file lastlog2 db user expected login timestamp
|
||||||
|
(('wtmp-x86_64', 'lastlog2.db', 'root'), 1723749426), # Thu Aug 15 19:17:06 UTC 2024
|
||||||
|
(('wtmp-x86_64', 'lastlog2.db', 'whoever'), 0),
|
||||||
|
(('wtmp-x86_64', 'lastlog2.db', 'cb'), 1726995194), # Sun Sep 22 08:53:14 UTC 2024
|
||||||
|
(('wtmp-x86_64', 'lastlog2.db', 'sddm'), 1721084423), # Mon Jul 15 23:00:23 UTC 2024
|
||||||
|
|
||||||
|
(('wtmp-x86_64', 'does-not-exist', 'root'), 1635070346), # Sun Oct 24 12:12:26 CEST 2021
|
||||||
|
(('wtmp-x86_64', 'does-not-exist', 'whoever'), 0),
|
||||||
|
|
||||||
|
(('wtmp-s390x', 'lastlog2.db', 'root'), 1723749426), # Thu Aug 15 19:17:06 UTC 2024
|
||||||
|
(('wtmp-s390x', 'lastlog2.db', 'whoever'), 0),
|
||||||
|
(('wtmp-s390x', 'does-not-exist', 'linux1'), 1626368772), # Thu Jul 15 19:06:12 CEST 2021
|
||||||
|
(('wtmp-s390x', 'does-not-exist', 'whoever'), 0),
|
||||||
|
|
||||||
|
(('wtmp-aarch64', 'lastlog2.db', 'whoever'), 0),
|
||||||
|
(('wtmp-aarch64', 'does-not-exist', 'guillaume'), 1611562789), # Mon Jan 25 09:19:49 CET 2021
|
||||||
|
(('wtmp-aarch64', 'does-not-exist', 'whoever'), 0),
|
||||||
|
|
||||||
|
(('wtmp-truncated', 'does-not-exist', 'root'), 0),
|
||||||
|
(('wtmp-truncated', 'does-not-exist', 'whoever'), 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
def _run_test(self, params, expected):
|
||||||
|
wtmpdb, lastlog2_db, user = params
|
||||||
|
wtmpdb = 'wtmp-examples/' + wtmpdb
|
||||||
|
lastlog2_db = 'wtmp-examples/' + lastlog2_db
|
||||||
|
self.assertEqual(get_last_login_timestamp(user, wtmpdb, lastlog2_db), expected)
|
||||||
|
|
||||||
|
def test_date_1999(self):
|
||||||
|
with self.assertRaises(AppArmorBug):
|
||||||
|
# wtmp-x86_64-past is hand-edited to Thu Dec 30 00:00:00 CET 1999, which is outside the expected data range
|
||||||
|
get_last_login_timestamp('root', 'wtmp-examples/wtmp-x86_64-past', 'wtmp-examples/does-not-exist')
|
||||||
|
|
||||||
|
|
||||||
class TestSane_timestamp(AATest):
|
class TestSane_timestamp(AATest):
|
||||||
tests = (
|
tests = (
|
||||||
(2524704400, False), # Sun Jan 2 03:46:40 CET 2050
|
(2524704400, False), # Sun Jan 2 03:46:40 CET 2050
|
||||||
@@ -27,7 +63,7 @@ class TestSane_timestamp(AATest):
|
|||||||
self.assertEqual(sane_timestamp(params), expected)
|
self.assertEqual(sane_timestamp(params), expected)
|
||||||
|
|
||||||
|
|
||||||
class TestGet_last_login_timestamp(AATest):
|
class TestGet_last_login_timestamp_wtmp(AATest):
|
||||||
tests = (
|
tests = (
|
||||||
(('wtmp-x86_64', 'root'), 1635070346), # Sun Oct 24 12:12:26 CEST 2021
|
(('wtmp-x86_64', 'root'), 1635070346), # Sun Oct 24 12:12:26 CEST 2021
|
||||||
(('wtmp-x86_64', 'whoever'), 0),
|
(('wtmp-x86_64', 'whoever'), 0),
|
||||||
@@ -43,12 +79,12 @@ class TestGet_last_login_timestamp(AATest):
|
|||||||
def _run_test(self, params, expected):
|
def _run_test(self, params, expected):
|
||||||
filename, user = params
|
filename, user = params
|
||||||
filename = 'wtmp-examples/' + filename
|
filename = 'wtmp-examples/' + filename
|
||||||
self.assertEqual(get_last_login_timestamp(user, filename), expected)
|
self.assertEqual(get_last_login_timestamp_wtmp(user, filename), expected)
|
||||||
|
|
||||||
def test_date_1999(self):
|
def test_date_1999(self):
|
||||||
with self.assertRaises(AppArmorBug):
|
with self.assertRaises(AppArmorBug):
|
||||||
# wtmp-x86_64-past is hand-edited to Thu Dec 30 00:00:00 CET 1999, which is outside the expected data range
|
# wtmp-x86_64-past is hand-edited to Thu Dec 30 00:00:00 CET 1999, which is outside the expected data range
|
||||||
get_last_login_timestamp('root', 'wtmp-examples/wtmp-x86_64-past')
|
get_last_login_timestamp_wtmp('root', 'wtmp-examples/wtmp-x86_64-past')
|
||||||
|
|
||||||
|
|
||||||
setup_all_loops(__name__)
|
setup_all_loops(__name__)
|
||||||
|
BIN
utils/test/wtmp-examples/lastlog2.db
Normal file
BIN
utils/test/wtmp-examples/lastlog2.db
Normal file
Binary file not shown.
Reference in New Issue
Block a user