]>
Commit | Line | Data |
---|---|---|
c2e299d0 SS |
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 | ||
c71f3828 KZ |
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) | |
c2e299d0 SS |
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; | |
d2203ae3 | 73 | tm = localtime_r(&t_time, &tm_buf); |
c2e299d0 SS |
74 | if (tm == NULL) |
75 | datep = "(unknown)"; | |
76 | else { | |
c71f3828 | 77 | strftime(datetime, sizeof(datetime), "%a %b %e %H:%M:%S %z %Y", tm); |
c2e299d0 SS |
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", | |
d2203ae3 KZ |
86 | maxIPv6Addrlen - 4, " ", |
87 | sflg ? (int) strlen(datep) -5 : 0, | |
88 | " ", sflg ? "Service" : ""); | |
c2e299d0 SS |
89 | once = 1; |
90 | } | |
91 | printf("%-16s %-8.8s %*s %s%*s%s\n", user, tty ? tty : "", | |
92 | -maxIPv6Addrlen, rhost ? rhost : "", datep, | |
d2203ae3 KZ |
93 | sflg ? 31 - (int) strlen(datep) : 0, |
94 | (sflg && pam_service) ? " " : "", | |
95 | sflg ? (pam_service ? pam_service : "") : ""); | |
c2e299d0 SS |
96 | |
97 | if (error) | |
98 | printf("\nError: %s\n", error); | |
99 | ||
100 | return 0; | |
101 | } | |
102 | ||
c71f3828 | 103 | static void __attribute__((__noreturn__)) usage(void) |
c2e299d0 SS |
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 | ||
af93603e KZ |
128 | /* Check if an user exists on the system */ |
129 | #define has_user(_x) (getpwnam(_x) != NULL) | |
c2e299d0 | 130 | |
c71f3828 | 131 | int main(int argc, char **argv) |
c2e299d0 | 132 | { |
76fcb1c6 | 133 | static const struct option longopts[] = { |
c2e299d0 SS |
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")); | |
d2203ae3 | 167 | b_days = (time_t) days * (24L * 3600L) /* seconds/DAY */; |
c2e299d0 SS |
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")); | |
d2203ae3 | 200 | t_days = (time_t) days * (24L * 3600L) /* seconds/DAY */; |
c2e299d0 SS |
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 | ||
4e86ac6f | 216 | if ((Cflg + Sflg + iflg) > 1) |
d2203ae3 | 217 | errx(EXIT_FAILURE, _("Option -C, -i and -S cannot be used together")); |
c2e299d0 SS |
218 | |
219 | db_context = ll2_new_context(lastlog2_path); | |
220 | if (!db_context) | |
4e86ac6f | 221 | errx(EXIT_FAILURE, _("Couldn't initialize lastlog2 environment")); |
c2e299d0 SS |
222 | |
223 | if (iflg) { | |
224 | /* Importing entries */ | |
225 | if (ll2_import_lastlog(db_context, lastlog_file, &error) != 0) { | |
4e86ac6f KZ |
226 | warnx(_("Couldn't import entries from '%s'"), lastlog_file); |
227 | goto err; | |
c2e299d0 | 228 | } |
4e86ac6f | 229 | goto done; |
c2e299d0 SS |
230 | } |
231 | ||
232 | if (Cflg || Sflg || rflg) { | |
233 | /* udpating, inserting and removing entries */ | |
234 | if (!uflg || strlen(user) == 0) { | |
4e86ac6f KZ |
235 | warnx(_("Options -C, -r and -S require option -u to specify the user")); |
236 | goto err; | |
c2e299d0 SS |
237 | } |
238 | ||
af93603e | 239 | if ((Cflg || Sflg) && !has_user(user)) { |
4e86ac6f KZ |
240 | warnx(_("User '%s' does not exist."), user); |
241 | goto err; | |
c2e299d0 SS |
242 | } |
243 | ||
244 | if (Cflg) { | |
245 | if (ll2_remove_entry(db_context, user, &error) != 0) { | |
4e86ac6f KZ |
246 | warnx(_("Couldn't remove entry for '%s'"), user); |
247 | goto err; | |
c2e299d0 SS |
248 | } |
249 | } | |
250 | ||
251 | if (Sflg) { | |
252 | time_t ll_time = 0; | |
253 | ||
254 | if (time(&ll_time) == -1) { | |
4e86ac6f KZ |
255 | warn(_("Could not determine current time")); |
256 | goto err; | |
c2e299d0 SS |
257 | } |
258 | ||
259 | if (ll2_update_login_time(db_context, user, ll_time, &error) != 0) { | |
4e86ac6f KZ |
260 | warnx(_("Couldn't update login time for '%s'"), user); |
261 | goto err; | |
c2e299d0 | 262 | } |
c2e299d0 SS |
263 | } |
264 | ||
265 | if (rflg) { | |
266 | if (ll2_rename_user(db_context, user, newname, &error) != 0) { | |
4e86ac6f KZ |
267 | warnx(_("Couldn't rename entry '%s' to '%s'"), user, newname); |
268 | goto err; | |
c2e299d0 SS |
269 | } |
270 | } | |
271 | ||
4e86ac6f | 272 | goto done; |
c2e299d0 SS |
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 | ||
af93603e | 282 | if (!has_user(user)) { |
4e86ac6f KZ |
283 | warnx(_("User '%s' does not exist."), user); |
284 | goto err; | |
c2e299d0 SS |
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); | |
4e86ac6f | 292 | goto done; |
c2e299d0 SS |
293 | } |
294 | ||
295 | /* print all information */ | |
296 | if (ll2_read_all(db_context, print_entry, &error) != 0) { | |
4e86ac6f KZ |
297 | warnx(_("Couldn't read entries for all users")); |
298 | goto err; | |
c2e299d0 SS |
299 | } |
300 | ||
4e86ac6f | 301 | done: |
c2e299d0 SS |
302 | ll2_unref_context(db_context); |
303 | exit(EXIT_SUCCESS); | |
4e86ac6f KZ |
304 | err: |
305 | ll2_unref_context(db_context); | |
306 | if (error) | |
307 | errx(EXIT_FAILURE, "%s", error); | |
308 | exit(EXIT_FAILURE); | |
c2e299d0 | 309 | } |