]> git.ipfire.org Git - thirdparty/util-linux.git/blob - misc-utils/lastlog2.c
4029f5e48edb1ed11ec5cac73f31a864ece416ac
[thirdparty/util-linux.git] / misc-utils / lastlog2.c
1 /* SPDX-License-Identifier: BSD-2-Clause
2
3 Copyright (c) 2023, Thorsten Kukuk <kukuk@suse.com>
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are met:
7
8 1. Redistributions of source code must retain the above copyright notice,
9 this list of conditions and the following disclaimer.
10
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.
14
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.
26 */
27
28 #include <pwd.h>
29 #include <time.h>
30 #include <errno.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <getopt.h>
35 #include <limits.h>
36
37 #include "nls.h"
38 #include "c.h"
39 #include "strutils.h"
40 #include "lastlog2.h"
41
42 static char *lastlog2_path = LL2_DEFAULT_DATABASE;
43
44 static int bflg;
45 static time_t b_days;
46 static int tflg;
47 static time_t t_days;
48 static int sflg;
49
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)
53 {
54 static int once = 0;
55 char *datep;
56 struct tm *tm, tm_buf;
57 char datetime[80];
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;
62
63 /* Print only if older than b days */
64 if (bflg && ((time (NULL) - ll_time) < b_days))
65 return 0;
66
67 /* Print only if newer than t days */
68 if (tflg && ((time (NULL) - ll_time) > t_days))
69 return 0;
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);
74 if (tm == NULL)
75 datep = "(unknown)";
76 else {
77 strftime(datetime, sizeof(datetime), "%a %b %e %H:%M:%S %z %Y", tm);
78 datep = datetime;
79 }
80
81 if (ll_time == 0)
82 datep = "**Never logged in**";
83
84 if (!once) {
85 printf("Username Port From%*s Latest%*s%s\n",
86 maxIPv6Addrlen - 4, " ",
87 sflg ? (int) strlen(datep) -5 : 0,
88 " ", sflg ? "Service" : "");
89 once = 1;
90 }
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 : "") : "");
96
97 if (error)
98 printf("\nError: %s\n", error);
99
100 return 0;
101 }
102
103 static void __attribute__((__noreturn__)) usage(void)
104 {
105 FILE *output = stdout;
106
107 fputs(USAGE_HEADER, output);
108 fprintf(output, _(" %s [options]\n"), program_invocation_short_name);
109
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);
120
121 fputs(USAGE_SEPARATOR, output);
122 fprintf(output, USAGE_HELP_OPTIONS(25));
123 fprintf(output, USAGE_MAN_TAIL("lastlog2(8)"));
124
125 exit(EXIT_SUCCESS);
126 }
127
128 /* Check if an user exists on the system */
129 #define has_user(_x) (getpwnam(_x) != NULL)
130
131 int main(int argc, char **argv)
132 {
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'}
146 };
147 char *error = NULL;
148 int Cflg = 0;
149 int iflg = 0;
150 int rflg = 0;
151 int Sflg = 0;
152 int uflg = 0;
153 const char *user = NULL;
154 const char *newname = NULL;
155 const char *lastlog_file = NULL;
156 struct ll2_context *db_context = NULL;
157
158 int c;
159
160 while ((c = getopt_long(argc, argv, "b:Cd:hi:r:sSt:u:v", longopts, NULL)) != -1) {
161 switch (c) {
162 case 'b': /* before DAYS; Print only records older than DAYS */
163 {
164 unsigned long days;
165 errno = 0;
166 days = strtoul_or_err(optarg, _("Cannot parse days"));
167 b_days = (time_t) days * (24L * 3600L) /* seconds/DAY */;
168 bflg = 1;
169 }
170 break;
171 case 'C': /* clear; Clear record of a user (requires -u) */
172 Cflg = 1;
173 break;
174 case 'd': /* database <FILE>; Use FILE as lastlog2 database */
175 lastlog2_path = optarg;
176 break;
177 case 'h': /* help; Display this help message and exit */
178 usage();
179 break;
180 case 'i': /* import <FILE>; Import data from old lastlog file */
181 lastlog_file = optarg;
182 iflg = 1;
183 break;
184 case 'r': /* rename <NEWNAME>; Rename existing user to NEWNAME (requires -u) */
185 rflg = 1;
186 newname = optarg;
187 break;
188 case 's': /* service; Display PAM service */
189 sflg = 1;
190 break;
191 case 'S': /* set; Set lastlog record to current time (requires -u) */
192 /* Set lastlog record of a user to the current time. */
193 Sflg = 1;
194 break;
195 case 't': /* time <DAYS>; Print only lastlog records more recent than DAYS */
196 {
197 unsigned long days;
198 errno = 0;
199 days = strtoul_or_err(optarg, _("Cannot parse days"));
200 t_days = (time_t) days * (24L * 3600L) /* seconds/DAY */;
201 tflg = 1;
202 }
203 break;
204 case 'u': /* user <LOGIN>; Print lastlog record of the specified LOGIN */
205 uflg = 1;
206 user = optarg;
207 break;
208 case 'v': /* version; Print version number and exit */
209 print_version(EXIT_SUCCESS);
210 break;
211 default:
212 errtryhelp(EXIT_FAILURE);
213 }
214 }
215
216 if ((Cflg + Sflg + iflg) > 1)
217 errx(EXIT_FAILURE, _("Option -C, -i and -S cannot be used together"));
218
219 db_context = ll2_new_context(lastlog2_path);
220 if (!db_context)
221 errx(EXIT_FAILURE, _("Couldn't initialize lastlog2 environment"));
222
223 if (iflg) {
224 /* Importing entries */
225 if (ll2_import_lastlog(db_context, lastlog_file, &error) != 0) {
226 warnx(_("Couldn't import entries from '%s'"), lastlog_file);
227 goto err;
228 }
229 goto done;
230 }
231
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"));
236 goto err;
237 }
238
239 if ((Cflg || Sflg) && !has_user(user)) {
240 warnx(_("User '%s' does not exist."), user);
241 goto err;
242 }
243
244 if (Cflg) {
245 if (ll2_remove_entry(db_context, user, &error) != 0) {
246 warnx(_("Couldn't remove entry for '%s'"), user);
247 goto err;
248 }
249 }
250
251 if (Sflg) {
252 time_t ll_time = 0;
253
254 if (time(&ll_time) == -1) {
255 warn(_("Could not determine current time"));
256 goto err;
257 }
258
259 if (ll2_update_login_time(db_context, user, ll_time, &error) != 0) {
260 warnx(_("Couldn't update login time for '%s'"), user);
261 goto err;
262 }
263 }
264
265 if (rflg) {
266 if (ll2_rename_user(db_context, user, newname, &error) != 0) {
267 warnx(_("Couldn't rename entry '%s' to '%s'"), user, newname);
268 goto err;
269 }
270 }
271
272 goto done;
273 }
274
275 if (user) {
276 /* print user specific information */
277 int64_t ll_time = 0;
278 char *tty = NULL;
279 char *rhost = NULL;
280 char *service = NULL;
281
282 if (!has_user(user)) {
283 warnx(_("User '%s' does not exist."), user);
284 goto err;
285 }
286
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,
289 &service, NULL);
290
291 print_entry(user, ll_time, tty, rhost, service, NULL);
292 goto done;
293 }
294
295 /* print all information */
296 if (ll2_read_all(db_context, print_entry, &error) != 0) {
297 warnx(_("Couldn't read entries for all users"));
298 goto err;
299 }
300
301 done:
302 ll2_unref_context(db_context);
303 exit(EXIT_SUCCESS);
304 err:
305 ll2_unref_context(db_context);
306 if (error)
307 errx(EXIT_FAILURE, "%s", error);
308 exit(EXIT_FAILURE);
309 }