1 /* SPDX-License-Identifier: BSD-2-Clause
3 Copyright (c) 2023, Thorsten Kukuk <kukuk@suse.com>
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are met:
8 1. Redistributions of source code must retain the above copyright notice,
9 this list of conditions and the following disclaimer.
11 2. Redistributions in binary form must reproduce the above copyright
12 notice, this list of conditions and the following disclaimer in the
13 documentation and/or other materials provided with the distribution.
15 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
19 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25 POSSIBILITY OF SUCH DAMAGE.
42 static char *lastlog2_path
= LL2_DEFAULT_DATABASE
;
50 static int print_entry(const char *user
, int64_t ll_time
,
51 const char *tty
, const char *rhost
,
52 const char *pam_service
, const char *error
)
56 struct tm
*tm
, tm_buf
;
58 /* IPv6 address is at maximum 39 characters.
59 But for LL-addresses (fe80+only) the interface should be set,
60 so LL-address + % + IFNAMSIZ. */
61 const int maxIPv6Addrlen
= 42;
63 /* Print only if older than b days */
64 if (bflg
&& ((time (NULL
) - ll_time
) < b_days
))
67 /* Print only if newer than t days */
68 if (tflg
&& ((time (NULL
) - ll_time
) > t_days
))
70 /* this is necessary if you compile this on architectures with
71 a 32bit time_t type. */
72 time_t t_time
= ll_time
;
73 tm
= localtime_r(&t_time
, &tm_buf
);
77 strftime(datetime
, sizeof(datetime
), "%a %b %e %H:%M:%S %z %Y", tm
);
82 datep
= "**Never logged in**";
85 printf("Username Port From%*s Latest%*s%s\n",
86 maxIPv6Addrlen
- 4, " ",
87 sflg
? (int) strlen(datep
) -5 : 0,
88 " ", sflg
? "Service" : "");
91 printf("%-16s %-8.8s %*s %s%*s%s\n", user
, tty
? tty
: "",
92 -maxIPv6Addrlen
, rhost
? rhost
: "", datep
,
93 sflg
? 31 - (int) strlen(datep
) : 0,
94 (sflg
&& pam_service
) ? " " : "",
95 sflg
? (pam_service
? pam_service
: "") : "");
98 printf("\nError: %s\n", error
);
103 static void __attribute__((__noreturn__
)) usage(void)
105 FILE *output
= stdout
;
107 fputs(USAGE_HEADER
, output
);
108 fprintf(output
, _(" %s [options]\n"), program_invocation_short_name
);
110 fputs(USAGE_OPTIONS
, output
);
111 fputs(_(" -b, --before DAYS Print only records older than DAYS\n"), output
);
112 fputs(_(" -C, --clear Clear record of a user (requires -u)\n"), output
);
113 fputs(_(" -d, --database FILE Use FILE as lastlog2 database\n"), output
);
114 fputs(_(" -i, --import FILE Import data from old lastlog file\n"), output
);
115 fputs(_(" -r, --rename NEWNAME Rename existing user to NEWNAME (requires -u)\n"), output
);
116 fputs(_(" -s, --service Display PAM service\n"), output
);
117 fputs(_(" -S, --set ySet lastlog record to current time (requires -u)\n"), output
);
118 fputs(_(" -t, --time DAYS Print only lastlog records more recent than DAYS\n"), output
);
119 fputs(_(" -u, --user LOGIN Print lastlog record of the specified LOGIN\n"), output
);
121 fputs(USAGE_SEPARATOR
, output
);
122 fprintf(output
, USAGE_HELP_OPTIONS(25));
123 fprintf(output
, USAGE_MAN_TAIL("lastlog2(8)"));
128 /* Check if an user exists on the system */
129 #define has_user(_x) (getpwnam(_x) != NULL)
131 int main(int argc
, char **argv
)
133 static const struct option longopts
[] = {
134 {"before", required_argument
, NULL
, 'b'},
135 {"clear", no_argument
, NULL
, 'C'},
136 {"database", required_argument
, NULL
, 'd'},
137 {"help", no_argument
, NULL
, 'h'},
138 {"import", required_argument
, NULL
, 'i'},
139 {"rename", required_argument
, NULL
, 'r'},
140 {"service", no_argument
, NULL
, 's'},
141 {"set", no_argument
, NULL
, 'S'},
142 {"time", required_argument
, NULL
, 't'},
143 {"user", required_argument
, NULL
, 'u'},
144 {"version", no_argument
, NULL
, 'v'},
145 {NULL
, 0, NULL
, '\0'}
153 const char *user
= NULL
;
154 const char *newname
= NULL
;
155 const char *lastlog_file
= NULL
;
156 struct ll2_context
*db_context
= NULL
;
160 while ((c
= getopt_long(argc
, argv
, "b:Cd:hi:r:sSt:u:v", longopts
, NULL
)) != -1) {
162 case 'b': /* before DAYS; Print only records older than DAYS */
166 days
= strtoul_or_err(optarg
, _("Cannot parse days"));
167 b_days
= (time_t) days
* (24L * 3600L) /* seconds/DAY */;
171 case 'C': /* clear; Clear record of a user (requires -u) */
174 case 'd': /* database <FILE>; Use FILE as lastlog2 database */
175 lastlog2_path
= optarg
;
177 case 'h': /* help; Display this help message and exit */
180 case 'i': /* import <FILE>; Import data from old lastlog file */
181 lastlog_file
= optarg
;
184 case 'r': /* rename <NEWNAME>; Rename existing user to NEWNAME (requires -u) */
188 case 's': /* service; Display PAM service */
191 case 'S': /* set; Set lastlog record to current time (requires -u) */
192 /* Set lastlog record of a user to the current time. */
195 case 't': /* time <DAYS>; Print only lastlog records more recent than DAYS */
199 days
= strtoul_or_err(optarg
, _("Cannot parse days"));
200 t_days
= (time_t) days
* (24L * 3600L) /* seconds/DAY */;
204 case 'u': /* user <LOGIN>; Print lastlog record of the specified LOGIN */
208 case 'v': /* version; Print version number and exit */
209 print_version(EXIT_SUCCESS
);
212 errtryhelp(EXIT_FAILURE
);
216 if ((Cflg
+ Sflg
+ iflg
) > 1)
217 errx(EXIT_FAILURE
, _("Option -C, -i and -S cannot be used together"));
219 db_context
= ll2_new_context(lastlog2_path
);
221 errx(EXIT_FAILURE
, _("Couldn't initialize lastlog2 environment"));
224 /* Importing entries */
225 if (ll2_import_lastlog(db_context
, lastlog_file
, &error
) != 0) {
226 warnx(_("Couldn't import entries from '%s'"), lastlog_file
);
232 if (Cflg
|| Sflg
|| rflg
) {
233 /* udpating, inserting and removing entries */
234 if (!uflg
|| strlen(user
) == 0) {
235 warnx(_("Options -C, -r and -S require option -u to specify the user"));
239 if ((Cflg
|| Sflg
) && !has_user(user
)) {
240 warnx(_("User '%s' does not exist."), user
);
245 if (ll2_remove_entry(db_context
, user
, &error
) != 0) {
246 warnx(_("Couldn't remove entry for '%s'"), user
);
254 if (time(&ll_time
) == -1) {
255 warn(_("Could not determine current time"));
259 if (ll2_update_login_time(db_context
, user
, ll_time
, &error
) != 0) {
260 warnx(_("Couldn't update login time for '%s'"), user
);
266 if (ll2_rename_user(db_context
, user
, newname
, &error
) != 0) {
267 warnx(_("Couldn't rename entry '%s' to '%s'"), user
, newname
);
276 /* print user specific information */
280 char *service
= NULL
;
282 if (!has_user(user
)) {
283 warnx(_("User '%s' does not exist."), user
);
287 /* We ignore errors, if the user is not in the database he did never login */
288 ll2_read_entry(db_context
, user
, &ll_time
, &tty
, &rhost
,
291 print_entry(user
, ll_time
, tty
, rhost
, service
, NULL
);
295 /* print all information */
296 if (ll2_read_all(db_context
, print_entry
, &error
) != 0) {
297 warnx(_("Couldn't read entries for all users"));
302 ll2_unref_context(db_context
);
305 ll2_unref_context(db_context
);
307 errx(EXIT_FAILURE
, "%s", error
);