Skip to content

Instantly share code, notes, and snippets.

@ztmr
Created March 13, 2014 20:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ztmr/9536769 to your computer and use it in GitHub Desktop.
Save ztmr/9536769 to your computer and use it in GitHub Desktop.
UNIX Login Records (utmp/wtmp) Parser in Erlang
%% 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