Find and parse system wtmp files. This indicate when the user last logged in.
name: Linux.Sys.LastUserLogin
description: |
Find and parse system wtmp files. This indicate when the user last
logged in.
parameters:
- name: wtmpGlobs
default: /var/log/wtmp*
- name: MaxCount
default: 10000
type: int64
- name: LoginType
type: choices
default: Interactive Sessions
choices:
- Interactive Sessions
- All Sessions
description: |
Per default, we are only interested in interactive sessions, if
you want to see more, choose the second option
- name: recent_x_days
default: 100000
type: int
description: |
show all logs within the last X days (default 14 days)
- name: excluded_users
type: regex
default: "ansible|LOGIN"
description: |
List of Users (regex), you are not interested in
export: |
LET FilterLookup = dict(
`Interactive Sessions`="USER_PROCESS|LOGIN_PROCESS",
`All Sessions`="RUN_LVL|BOOT_TIME|INIT_PROCESS|LOGIN_PROCESS|USER_PROCESS")
LET wtmpProfile <= '''
[
["Header", 0, [
["records", 0, "Array", {
"type": "utmp",
"count": "x=>MaxCount",
"max_count": 100000,
}],
]],
["utmp", 384, [
["ut_type", 0, "Enumeration", {
"type": "short int",
"choices": {
"0": "EMPTY",
"1": "RUN_LVL",
"2": "BOOT_TIME",
"5": "INIT_PROCESS",
"6": "LOGIN_PROCESS",
"7": "USER_PROCESS",
"8": "DEAD_PROCESS"
}
}],
["ut_pid", 4, "int"],
["ut_terminal", 8, "String", {"length": 32}],
["ut_terminal_identifier", 40, "String", {"length": 4}],
["ut_user", 44, "String", {"length": 32}],
["ut_hostname", 76, "String", {"length": 256}],
["ut_termination_status", 332, "int"],
["ut_exit_status", 334, "int"],
["ut_session", 336, "int"],
["ut_timestamp", 340, "Timestamp", {
"type": "uint32",
}],
["ut_ip_address", 348, "int64"],
]
]
]]
]'''
sources:
- precondition: |
SELECT OS From info() where OS = 'linux'
query: |
LET LoginType <= get(item=FilterLookup, field=LoginType) || LoginType
LET start_time <= timestamp(epoch=now() - recent_x_days * 3600 * 24)
LET _ <= log(message="Start time %v", args=start_time)
LET parsed = SELECT OSPath, parse_binary(
filename=OSPath,
profile=wtmpProfile,
struct="Header"
) AS Parsed
FROM glob(globs=split(string=wtmpGlobs, sep=","))
// In Order to combine Login/Logout into one Table, we create a
// logout table first
LET logout_table <= SELECT * FROM foreach(row=parsed,
query={
SELECT * FROM foreach(row=Parsed.records,
query={
SELECT ut_type AS logout_Type,
ut_pid as logout_PID,
ut_terminal as logout_Terminal,
ut_timestamp as logout_time
FROM scope()
WHERE logout_Type = "DEAD_PROCESS"
AND logout_time > start_time
})
})
Order by logout_time DESC
SELECT * FROM foreach(row=parsed,
query={
SELECT * FROM foreach(row=Parsed.records,
query={
SELECT OSPath,
ut_type AS login_Type,
ut_id AS login_ID,
ut_pid as login_PID,
ut_hostname as login_Host,
ut_user as login_User,
ip(netaddr4_le=ut_ip_address) AS login_IpAddr,
ut_terminal as login_Terminal,
ut_timestamp as login_time, {
SELECT logout_time
FROM logout_table
WHERE ut_pid = logout_PID
AND ut_terminal = logout_Terminal
AND ut_timestamp < logout_time
LIMIT 1
} AS logout_time
FROM scope()
WHERE login_Type =~ LoginType
AND NOT login_User =~ excluded_users
AND login_time > start_time
})
})