In awk
, the default value of FS
is a single space character, and that value has a very special meaning. It means fields are separated by sequences of one or more blank characters¹ but also that leading and trailing blanks are ignored.
Here you want to not do that second part, for which you can do:
$ lastb | awk -F '[[:blank:]]+' '{print $5,$6,$7,$3}'
Oct 1 12:07 213.109.202.127
Oct 1 11:48 8.219.222.66
Oct 1 11:03 213.109.202.127
Oct 1 10:34 139.19.117.130
Oct 1 09:58 213.109.202.127
Oct 1 09:40 79.110.62.93
Oct 1 09:34 139.19.117.130
Here, FS
(as set with the -F
option) being [[:blank:]]+
, means that any sequence of one or more blanks constitutes a F
ield S
eparator, including the ones at the start of the line. If a line starts with a blank, that means $1
will be the empty string before those blanks.
That assumes the first 3 fields don't contain whitespace. In practice, that can't be guaranteed. I find that with failed ssh login attempts, not even newlines are escaped, so it's hard to parse that output reliably.
I can do ssh -l $'a b\nc' localhost
and the lastb
output will have:
a b
c ssh:notty 127.0.0.1 Sun Nov 10 14:57 - 14:57 (00:00)
chazelas seat0 login screen Fri Oct 25 08:19 - 08:19 (00:00)
(see also that genuine failed attempt where the third field login screen
contains a space).
I cannot however cause a :
to be included in the username field.
So @EdMorton's approach to count fields from the end would be better here, and made even more reliable if you check that the line contains [[:blank:]]ssh:
to filter out entries not by sshd
, or the first lines of the username for those usernames that contain newlines.
lastb | awk '/[[:blank:]]ssh:/ {print $(NF-5), $(NF-4), $(NF-3), $(NF-7)}'
FYI, the lastb
utility is already gone from Debian since May 2024 (which seems premature to me as most things still log entries into wtmp
/btmp
and few things yet in wtmpdb
; seems it's part of the rush to fix the Y2038 problem) so relying on it may not be future-proof, see the related NEWS
entry in the util-linux
Debian package:
util-linux
(2.40.1-2) unstable; urgency=medium
last
(1) has been split off to the wtmpdb
package.
If you find last
(1) useful, please install wtmpdb
and accept the default
PAM configuration changes from libpam-wtmpdb
.
lastb
(1) is removed. Please see syslog/journal for failed login attempts.
-- Chris Hofstaedtler <[email protected]> Wed, 29 May 2024 23:52:19 +0200
(you also need to install libpam-wtmpdb
which is only recommended by wtmpdb
, not a hard dependency).
Getting timestamp (in standard parsable format with microsecond precision as a bonus²) and from address for failed ssh authentication attempts from journalctl
could look like:
journalctl -qaro short-iso-precise --no-hostname -u ssh.service \
-g '^Failed .* user .* from [\d.]+ port \d+ ssh\d*\z' |
sed -E 's/ .* from( [^ ]+).*/\1/'
Or for some JSON output for easier post-processing:
journalctl -qaro json \
--output-fields=_SOURCE_REALTIME_TIMESTAMP,MESSAGE \
-u ssh.service \
-g '^Failed .* user .* from [\d.]+ port \d+ ssh\d*\z' |
jq -c '
{
"ts": (
._SOURCE_REALTIME_TIMESTAMP as $t |
$t[0:-6] |
tonumber |
strflocaltime("%FT%T." + $t[-6:] + "%z")
)
} + (
.MESSAGE |
capture(" user (?<user>.*) from (?<ip>[^ ]+) port (?<port>\\d+)")
) |
.port |= tonumber'
Which gives something like:
{"ts":"2024-11-10T19:26:12.952796+0000","user":"\\377\\377\\377\\377\\377\\377\\377\\377","ip":"127.0.0.1","port":42126}
{"ts":"2024-11-10T19:23:52.749940+0000","user":"\\001\\002\\003\\004\\005\\006\\r\\v\\t","ip":"127.0.0.1","port":47172}
{"ts":"2024-11-10T19:18:57.094019+0000","user":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","ip":"127.0.0.1","port":59338}
{"ts":"2024-11-10T17:11:17.574040+0000","user":"\\351","ip":"127.0.0.1","port":46272}
{"ts":"2024-11-10T14:58:46.607290+0000","user":"a b c d e f\\nc","ip":"127.0.0.1","port":57374}
{"ts":"2024-11-10T14:57:31.148206+0000","user":"a b\\nc","ip":"127.0.0.1","port":35566}
{"ts":"2024-11-10T14:56:40.154428+0000","user":"a","ip":"127.0.0.1","port":51774}
{"ts":"2024-11-10T14:55:57.128920+0000","user":"foo bar\\nbaz","ip":"127.0.0.1","port":39012}
{"ts":"2024-11-10T14:50:07.156848+0000","user":"foo\\nbar","ip":"127.0.0.1","port":55176}
{"ts":"2024-11-10T14:48:52.938861+0000","user":"root ssh ssh 127.0.0.1 Sun Nov 10 14.38 _ 14.38 00.00\\nbar","ip":"127.0.0.1","port":38166}
{"ts":"2024-11-10T14:34:17.532900+0000","user":"root ssh","ip":"127.0.0.1","port":55498}
{"ts":"2024-11-10T14:29:01.636760+0000","user":"x\\ny","ip":"127.0.0.1","port":54132}
{"ts":"2024-11-10T14:28:43.724771+0000","user":"foo bar","ip":"127.0.0.1","port":37306}
{"ts":"2024-11-09T15:40:59.024960+0000","user":"stephane","ip":"172.17.27.2","port":52536}
{"ts":"2024-11-09T15:40:35.582616+0000","user":"qweq","ip":"172.17.27.2","port":54234}
(also using _SOURCE_REALTIME_TIMESTAMP
to get the time when the event was generated, rather than received).
Where you see my previous attempts at fooling lastb
being handled in a more useful way than by lastb
, even lastb --time-format=iso -w
.
¹ per POSIX, that's newline or any character for which iswblank()
returns true but you'll find in some awk
implementations, that's any character for which iswspace()
returns true (space being a superset of blank which includes newline and other vertical whitespace ones such as carriage return, form feed, vertical tab...). For those that don't support multibyte encodings, it's only the single byte ones (isblank()
/isspace()
).
² See also lastb --time-format=iso
to get that format with lastb
though without subsecond precision.