On init systems (like SLES11), login prompt will be displayed to user only after initiating all the startup programs. So we can rely on getting login prompt time from wtmp file (as suggested by user "Orsiris de Jong").
Below is the python script to get login prompt time after last reboot.
#!/usr/bin/env python3
import subprocess
import re
from datetime import datetime
from typing import List
class UnableToFindLoginTimeError(Exception):
pass
class UnexpectedCommandException(Exception):
pass
class CommandResult:
def __init__(self, command, stdout, stderr, exit_code):
self.command = command
self.stdout = stdout
self.stderr = stderr
self.exit_code = exit_code
def validate(self, check_stderr=True, check_exit_code=True):
if (check_stderr and self.stderr) or (check_exit_code and self.exit_code != 0):
raise UnexpectedCommandException('Unexpected command result')
def run_command(command, timeout=600):
completed_process = subprocess.run(
command,
encoding='utf-8',
timeout=timeout,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=True,
errors='ignore',
)
command_result = CommandResult(
stdout=completed_process.stdout.strip(),
stderr=completed_process.stderr.strip(),
exit_code=completed_process.returncode,
command=command,
)
return command_result
def _extract_data_in_square_brackets(row: str) -> List[str]:
"""
Extract data within square brackets from a string.
Example:
'[One] [Two ] [Three ] [Wed Aug 25 09:21:59 2021 UTC]'
returns
['One', 'Two', 'Three', 'Wed Aug 25 09:21:59 2021 UTC']
"""
regex = r'\[(.*?)\]'
columns_list = re.findall(regex, row)
return [item.strip() for item in columns_list]
def convert_datetime_string_to_epoch(datetime_str: str) -> int:
"""
Run following command to automatically parse datetime string (in any valid format) into epoch:
# date -d '{datetime_str}' +%s
Note: The Unix epoch is the number of seconds that have elapsed since January 1, 1970 (midnight UTC/GMT).
At any point of time, epoch will be same throughout all time zones.
Example: epoch for "Fri Sep 3 11:08:09 UTC 2021" will be 1630667289.
"""
command_result = run_command(f"date -d '{datetime_str}' +%s")
command_result.validate()
epoch = round(float(command_result.stdout.strip()))
return epoch
def get_login_time_from_wtmp() -> datetime:
"""
Read through /var/log/wtmp binary file using utmpdump
and find least LOGIN time after last reboot.
wtmp gives historical data of utmp (gives information about user logins, logouts, system boot etc.).
In case of failed logins, we see multiple entries for same tty in the output.
In such case, we get first occurred entry after last reboot since that is when
startup processes have been completed and welcome screen appeared.
Sample:
-------
Output:
[2] [00000] [~~ ] [reboot ] [~ ] [3.10.0-957.12.2.el7.x86_64] [0.0.0.0 ] [Mon Aug 16 06:21:06 2021 UTC]
[6] [01828] [tty1] [LOGIN ] [tty1 ] [ ] [0.0.0.0 ] [Mon Aug 16 06:21:26 2021 UTC]
[2] [00000] [~~ ] [reboot ] [~ ] [3.10.0-957.12.2.el7.x86_64] [0.0.0.0 ] [Wed Aug 25 09:21:34 2021 UTC]
[6] [01815] [tty1] [LOGIN ] [tty1 ] [ ] [0.0.0.0 ] [Wed Aug 25 09:21:59 2021 UTC]
Returns: "Wed Aug 25 09:21:59 2021 UTC" as datetime object
"""
command_result = run_command(f'utmpdump /var/log/wtmp | grep -e reboot -e LOGIN')
command_result.validate(check_stderr=False)
output = command_result.stdout
list_of_login_epochs = []
# read output in reverse order
for line in output.splitlines()[::-1]:
if 'reboot' in line:
# exit loop since we dont require data before last reboot
break
items = _extract_data_in_square_brackets(line)
if 'LOGIN' in items:
login_time_str = items[-1].strip()
epoch = convert_datetime_string_to_epoch(login_time_str)
list_of_login_epochs.append(epoch)
if not list_of_login_epochs:
raise UnableToFindLoginTimeError()
# collect least login time out of all since we need time at which login was prompted first.
least_epoch = min(list_of_login_epochs)
login_datetime_object = datetime.utcfromtimestamp(round(least_epoch))
return login_datetime_object
if __name__ == '__main__':
print(
f'Login screen was displayed to user after last reboot at {get_login_time_from_wtmp()} UTC'
)