Created
March 13, 2014 20:51
-
-
Save ztmr/9536769 to your computer and use it in GitHub Desktop.
UNIX Login Records (utmp/wtmp) Parser in Erlang
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
%% UNIX Login Records (utmp/wtmp) Parser | |
%% Only GNU/Linux on x86_64 is supported at the moment. | |
-module (unix_login_records). | |
-export ([ | |
read_utmp/0, read_wtmp/0, | |
parse_file/1, parse/1, | |
record_to_proplist/1 | |
]). | |
-type utmp_type_code () :: integer (). | |
-type utmp_type_atom () :: empty | |
| run_lvl | boot_time | new_time | |
| old_time | init_process | |
| login_process | user_process | |
| dead_process | accounting. | |
-type utmp_time () :: {integer (), integer (), integer ()}. %% erlang:now () format | |
-record (exit_status, { | |
e_termination = 0 :: integer (), | |
e_exit = 0 :: integer () | |
}). | |
-record (utmp, { | |
type = empty :: utmp_type_atom (), | |
pid = 0 :: integer (), | |
line = [] :: string (), | |
id = [] :: string (), | |
user = [] :: string (), | |
host = [] :: string (), | |
exit_status = #exit_status {} :: #exit_status {}, | |
session = [] :: string (), | |
time = now () :: utmp_time (), | |
addr_v6 = {0, 0, 0, 0} :: inet:ip_address () | binary () | |
}). | |
-define (PID_T, 32/little). | |
-define (EMPTY, 0). | |
-define (RUN_LVL, 1). | |
-define (BOOT_TIME, 2). | |
-define (NEW_TIME, 3). | |
-define (OLD_TIME, 4). | |
-define (INIT_PROCESS, 5). | |
-define (LOGIN_PROCESS, 6). | |
-define (USER_PROCESS, 7). | |
-define (DEAD_PROCESS, 8). | |
-define (ACCOUNTING, 9). | |
-define (UT_LINESIZE, 32). | |
-define (UT_NAMESIZE, 32). | |
-define (UT_HOSTSIZE, 256). | |
-spec type_to_atom (TypeCode::utmp_type_code ()) -> utmp_type_atom (). | |
type_to_atom (?EMPTY) -> empty; | |
type_to_atom (?RUN_LVL) -> run_lvl; | |
type_to_atom (?BOOT_TIME) -> boot_time; | |
type_to_atom (?NEW_TIME) -> new_time; | |
type_to_atom (?OLD_TIME) -> old_time; | |
type_to_atom (?INIT_PROCESS) -> init_process; | |
type_to_atom (?LOGIN_PROCESS) -> login_process; | |
type_to_atom (?USER_PROCESS) -> user_process; | |
type_to_atom (?DEAD_PROCESS) -> dead_process; | |
type_to_atom (?ACCOUNTING) -> accounting. | |
-spec record_to_proplist (#utmp {}) -> proplists:proplist (). | |
record_to_proplist (#utmp { | |
type = Type, pid = PID, line = Line, id = ID, | |
user = User, host = Host, exit_status = ExitStatus, | |
session = Session, time = Time, addr_v6 = AddrV6}) -> | |
[{type, Type}, {pid, PID}, {line, Line}, {id, ID}, | |
{user, User}, {host, Host}, {exit_status, ExitStatus}, | |
{session, Session}, {time, Time}, {addr_v6, AddrV6}]. | |
-spec read_utmp () -> [#utmp {}]. | |
read_utmp () -> parse_file ("/var/run/utmp"). | |
-spec read_wtmp () -> [#utmp {}]. | |
read_wtmp () -> parse_file ("/var/log/wtmp"). | |
-spec parse_file (FileName::string ()) -> [#utmp {}]. | |
parse_file (FileName) -> | |
{ok, F} = file:read_file (FileName), | |
parse (F). | |
-spec parse (Data::binary ()) -> [#utmp {}]. | |
parse (Data) when is_binary (Data) -> | |
parse (Data, []). | |
parse (<<>>, Acc) -> lists:reverse (Acc); | |
parse (<<Record:384/bytes, Rest/bytes>>, Acc) -> | |
parse (Rest, [rec (Record)|Acc]). | |
%% XXX: WTF?! short is 2B!!! | |
rec (<<Type:32/little, %% short ut_type = 4B | |
Pid:?PID_T, %% pid_t ut_pid = 4B | |
Line:?UT_LINESIZE/bytes, %% char ut_line [UT_LINESIZE] = 32B | |
ID:4/bytes, %% char ut_id [4] = 4B | |
User:?UT_NAMESIZE/bytes, %% char ut_user [UT_NAMESIZE] = 32B | |
Host:?UT_HOSTSIZE/bytes, %% char ut_host [UT_HOSTSIZE] = 256B | |
ExitStatusRaw:4/bytes, %% struct exit_status ut_exit = 4B | |
SIDAndTimeRaw:12/bytes, %% ut_session + ut_tv = 12B | |
AddrV6:16/bytes, %% int32_t ut_addr_v6 [4] = 16B | |
_:20/bytes>>) -> %% <reserved_for_future_use> = 20B | |
<<Termination:16/little, Exit:16/little>> = ExitStatusRaw, | |
ExitStatus = #exit_status { e_termination = Termination, e_exit = Exit }, | |
%% XXX: the following may differ on 32 and 64 platforms, although | |
%% it is 12 bytes together no matter on platform | |
<<SessionID:?PID_T, Sec:32/little, USec:32/little>> = SIDAndTimeRaw, | |
Time = {Sec div 1000000, Sec rem 1000000, USec}, | |
IPAddr = case AddrV6 of | |
<<IPv4a:8/little, IPv4b:8/little, | |
IPv4c:8/little, IPv4d:8/little, | |
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>> -> | |
{IPv4a, IPv4b, IPv4c, IPv4d}; | |
IPv6 -> | |
IPv6 | |
end, | |
#utmp { type = type_to_atom (Type), | |
pid = Pid, line = stringify (Line), id = stringify (ID), | |
user = stringify (User), host = stringify (Host), | |
exit_status = ExitStatus, time = Time, session = SessionID, | |
addr_v6 = IPAddr }. | |
stringify (X) when is_binary (X) -> | |
stringify (binary_to_list (X)); | |
stringify (X) when is_list (X) -> | |
lists:reverse (stringify_ (lists:reverse (X))). | |
stringify_ ([0|T]) -> stringify_ (T); | |
stringify_ (S) -> S. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment