]> git.ipfire.org Git - thirdparty/util-linux.git/blame - liblastlog2/src/lastlog2.c
liblastlog2: fix leaks
[thirdparty/util-linux.git] / liblastlog2 / src / lastlog2.c
CommitLineData
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 <errno.h>
30#include <time.h>
31#include <stdio.h>
32#include <stdlib.h>
33#include <string.h>
34#include <limits.h>
35#include <sys/stat.h>
36#include <sqlite3.h>
37#include <lastlog.h>
38
39#include "lastlog2P.h"
40#include "strutils.h"
41
42/* Set the ll2 context/environment */
43/* Returns the context or NULL if an error has happened. */
44extern struct ll2_context * ll2_new_context(const char *db_path)
45{
46 struct ll2_context *context = (struct ll2_context *)malloc(sizeof(struct ll2_context));
47
48 if (context) {
49 if (db_path) {
50 if ((context->lastlog2_path = strdup(db_path)) == NULL) {
51 free(context);
52 context = NULL;
53 }
54 } else {
55 if ((context->lastlog2_path = strdup(LL2_DEFAULT_DATABASE)) == NULL) {
56 free(context);
57 context = NULL;
58 }
59 }
60 }
61 return context;
62}
63
64/* Release ll2 context/environment */
65extern void ll2_unref_context(struct ll2_context *context)
66{
67 if (context)
68 free(context->lastlog2_path);
69 free(context);
70}
71
72/* Returns 0 on success, -ENOMEM or -1 on other failure. */
73static int
74open_database_ro(struct ll2_context *context, sqlite3 **db, char **error)
75{
76 int ret = 0;
77 char *path = LL2_DEFAULT_DATABASE;
78
79 if (context && context->lastlog2_path)
80 path = context->lastlog2_path;
81
82 if (sqlite3_open_v2(path, db, SQLITE_OPEN_READONLY, NULL) != SQLITE_OK) {
83 ret = -1;
84 if (error)
85 if (asprintf(error, "Cannot open database (%s): %s",
86 path, sqlite3_errmsg(*db)) < 0)
87 ret = -ENOMEM;
88
89 sqlite3_close(*db);
90 }
91
92 return ret;
93}
94
95/* Returns 0 on success, -ENOMEM or -1 on other failure. */
96static int
97open_database_rw(struct ll2_context *context, sqlite3 **db, char **error)
98{
99 int ret = 0;
100 char *path = LL2_DEFAULT_DATABASE;
101
102 if (context && context->lastlog2_path)
103 path = context->lastlog2_path;
104
105 if (sqlite3_open(path, db) != SQLITE_OK) {
106 ret = -1;
107 if (error)
108 if (asprintf(error, "Cannot create/open database (%s): %s",
109 path, sqlite3_errmsg(*db)) < 0)
110 ret = -ENOMEM;
111
112 sqlite3_close(*db);
113 }
114
115 return ret;
116}
117
118/* Reads one entry from database and returns that.
119 Returns 0 on success, -ENOMEM or -1 on other failure. */
120static int
121read_entry(sqlite3 *db, const char *user,
122 int64_t *ll_time, char **tty, char **rhost,
123 char **pam_service, char **error)
124{
125 int retval = 0;
126 sqlite3_stmt *res = NULL;
127 static const char *sql = "SELECT Name,Time,TTY,RemoteHost,Service FROM Lastlog2 WHERE Name = ?";
128
129 if (sqlite3_prepare_v2(db, sql, -1, &res, 0) != SQLITE_OK) {
130 retval = -1;
131 if (error)
132 if (asprintf(error, "Failed to execute statement: %s",
133 sqlite3_errmsg(db)) < 0)
134 retval = -ENOMEM;
135 goto out_read_entry;
136 }
137
138 if (sqlite3_bind_text(res, 1, user, -1, SQLITE_STATIC) != SQLITE_OK) {
139 retval = -1;
140 if (error)
141 if (asprintf(error, "Failed to create search query: %s",
142 sqlite3_errmsg(db)) < 0)
143 retval = -ENOMEM;
144 goto out_read_entry;
145 }
146
147 int step = sqlite3_step(res);
148
149 if (step == SQLITE_ROW) {
150 const unsigned char *luser = sqlite3_column_text(res, 0);
151 const unsigned char *uc;
152
153 if (strcmp((const char *)luser, user) != 0) {
154 retval = -1;
155 if (error)
156 if (asprintf(error, "Returned data is for %s, not %s", luser, user) < 0)
157 retval = -ENOMEM;
158 goto out_read_entry;
159 }
160
161 if (ll_time)
162 *ll_time = sqlite3_column_int64(res, 1);
163
164 if (tty) {
165 uc = sqlite3_column_text(res, 2);
166 if (uc != NULL && strlen((const char *)uc) > 0)
167 if ((*tty = strdup((const char *)uc)) == NULL) {
168 retval = -ENOMEM;
169 goto out_read_entry;
170 }
171 }
172 if (rhost) {
173 uc = sqlite3_column_text(res, 3);
174 if (uc != NULL && strlen((const char *)uc) > 0)
175 if ((*rhost = strdup((const char *)uc)) == NULL) {
176 retval = -ENOMEM;
177 goto out_read_entry;
178 }
179 }
180 if (pam_service) {
181 uc = sqlite3_column_text(res, 4);
182 if (uc != NULL && strlen((const char *)uc) > 0)
183 if ((*pam_service = strdup((const char *)uc)) == NULL) {
184 retval = -ENOMEM;
185 goto out_read_entry;
186 }
187 }
188 } else {
189 retval = -1;
190 if (error)
191 if (asprintf(error, "User '%s' not found (%d)", user, step) < 0)
192 retval = -ENOMEM;
193 }
194
195out_read_entry:
196 if (res)
197 sqlite3_finalize(res);
198
199 return retval;
200}
201
202/* reads 1 entry from database and returns that. Returns 0 on success, -ENOMEM or -1 on other failure. */
203int
204ll2_read_entry(struct ll2_context *context, const char *user,
205 int64_t *ll_time, char **tty, char **rhost,
206 char **pam_service, char **error)
207{
208 sqlite3 *db;
209 int retval;
210
211 if ((retval = open_database_ro(context, &db, error)) != 0)
212 return retval;
213
214 retval = read_entry(db, user, ll_time, tty, rhost, pam_service, error);
215
216 sqlite3_close(db);
217
218 return retval;
219}
220
221/* Write a new entry. Returns 0 on success, -ENOMEM or -1 on other failure. */
222static int
223write_entry(sqlite3 *db, const char *user,
224 int64_t ll_time, const char *tty, const char *rhost,
225 const char *pam_service, char **error)
226{
227 int retval = 0;
228 char *err_msg = NULL;
229 sqlite3_stmt *res = NULL;
230 static const char *sql_table = "CREATE TABLE IF NOT EXISTS Lastlog2(Name TEXT PRIMARY KEY, Time INTEGER, TTY TEXT, RemoteHost TEXT, Service TEXT);";
231 static const char *sql_replace = "REPLACE INTO Lastlog2 VALUES(?,?,?,?,?);";
232
233 if (sqlite3_exec(db, sql_table, 0, 0, &err_msg) != SQLITE_OK) {
234 retval = -1;
235 if (error)
236 if (asprintf(error, "SQL error: %s", err_msg) < 0)
237 retval = -ENOMEM;
238
239 sqlite3_free(err_msg);
240 goto out_ll2_read_entry;
241 }
242
243 if (sqlite3_prepare_v2(db, sql_replace, -1, &res, 0) != SQLITE_OK) {
244 retval = -1;
245 if (error)
246 if (asprintf(error, "Failed to execute statement: %s",
247 sqlite3_errmsg(db)) < 0)
248 retval = -ENOMEM;
249 goto out_ll2_read_entry;
250 }
251
252 if (sqlite3_bind_text(res, 1, user, -1, SQLITE_STATIC) != SQLITE_OK) {
253 retval = -1;
254 if (error)
255 if (asprintf(error, "Failed to create replace statement for user: %s",
256 sqlite3_errmsg(db)) < 0)
257 retval = -ENOMEM;
258 goto out_ll2_read_entry;
259 }
260
261 if (sqlite3_bind_int64(res, 2, ll_time) != SQLITE_OK) {
262 retval = -1;
263 if (error)
264 if (asprintf(error, "Failed to create replace statement for ll_time: %s",
265 sqlite3_errmsg(db)) < 0)
266 retval = -ENOMEM;
267 goto out_ll2_read_entry;
268 }
269
270 if (sqlite3_bind_text(res, 3, tty, -1, SQLITE_STATIC) != SQLITE_OK) {
271 retval = -1;
272 if (error)
273 if (asprintf(error, "Failed to create replace statement for tty: %s",
274 sqlite3_errmsg(db)) < 0)
275 retval = -ENOMEM;
276 goto out_ll2_read_entry;
277 }
278
279 if (sqlite3_bind_text(res, 4, rhost, -1, SQLITE_STATIC) != SQLITE_OK) {
280 retval = -1;
281 if (error)
282 if (asprintf(error, "Failed to create replace statement for rhost: %s",
283 sqlite3_errmsg(db)) < 0)
284 retval = -ENOMEM;
285 goto out_ll2_read_entry;
286 }
287
288 if (sqlite3_bind_text(res, 5, pam_service, -1, SQLITE_STATIC) != SQLITE_OK) {
289 retval = -1;
290 if (error)
291 if (asprintf(error, "Failed to create replace statement for PAM service: %s",
292 sqlite3_errmsg(db)) < 0)
293 retval = -ENOMEM;
294 goto out_ll2_read_entry;
295 }
296
297 int step = sqlite3_step(res);
298
299 if (step != SQLITE_DONE) {
300 retval = -1;
301 if (error)
302 if (asprintf(error, "Delete statement did not return SQLITE_DONE: %d",
303 step) < 0)
304 retval = -ENOMEM;
305 goto out_ll2_read_entry;
306 }
307out_ll2_read_entry:
308 if (res)
309 sqlite3_finalize(res);
310
311 return retval;
312}
313
314/* Write a new entry. Returns 0 on success, -ENOMEM or -1 on other failure. */
315int
316ll2_write_entry(struct ll2_context *context, const char *user,
317 int64_t ll_time, const char *tty, const char *rhost,
318 const char *pam_service, char **error)
319{
320 sqlite3 *db;
321 int retval;
322
323 if ((retval = open_database_rw(context, &db, error)) != 0)
324 return retval;
325
326 retval = write_entry(db, user, ll_time, tty, rhost, pam_service, error);
327
328 sqlite3_close(db);
329
330 return retval;
331}
332
333/* Write a new entry with updated login time.
334 Returns 0 on success, -ENOMEM or -1 on other failure. */
335int
336ll2_update_login_time(struct ll2_context *context, const char *user,
337 int64_t ll_time, char **error)
338{
339 sqlite3 *db;
340 int retval;
341 char *tty;
342 char *rhost;
343 char *pam_service;
344
345 if ((retval = open_database_rw(context , &db, error)) != 0)
346 return retval;
347
348 if ((retval = read_entry(db, user, 0, &tty, &rhost, &pam_service, error)) != 0) {
349 sqlite3_close(db);
350 return retval;
351 }
352
353 retval = write_entry(db, user, ll_time, tty, rhost, pam_service, error);
354
355 sqlite3_close(db);
356
357 free(tty);
358 free(rhost);
359 free(pam_service);
360
361 return retval;
362}
363
364
365typedef int (*callback_f)(const char *user, int64_t ll_time,
366 const char *tty, const char *rhost,
367 const char *pam_service, const char *cb_error);
368
369static int
370callback(void *cb_func, __attribute__((unused)) int argc, char **argv, __attribute__((unused)) char **azColName)
371{
372 char *endptr;
373 callback_f print_entry = cb_func;
374
375 errno = 0;
376 char *cb_error = NULL;
377 int64_t ll_time = strtoll(argv[1], &endptr, 10);
378 if ((errno == ERANGE && (ll_time == INT64_MAX || ll_time == INT64_MIN))
379 || (endptr == argv[1]) || (*endptr != '\0'))
380 if (asprintf(&cb_error, "Invalid numeric time entry for '%s': '%s'\n", argv[0], argv[1]) < 0)
381 return -1;
382
383 print_entry(argv[0], ll_time, argv[2], argv[3], argv[4], cb_error);
384 free(cb_error);
385
386 return 0;
387}
388
389/* Reads all entries from database and calls the callback function for each entry.
390 Returns 0 on success, -ENOMEM or -1 on other failure. */
391int
392ll2_read_all(struct ll2_context *context,
393 int (*cb_func)(const char *user, int64_t ll_time,
394 const char *tty, const char *rhost,
395 const char *pam_service, const char *cb_error),
396 char **error)
397{
398 sqlite3 *db;
399 char *err_msg = 0;
400 int retval = 0;
401
402 if ((retval = open_database_ro(context, &db, error)) != 0)
403 return retval;
404
405 static const char *sql = "SELECT Name,Time,TTY,RemoteHost,Service FROM Lastlog2 ORDER BY Name ASC";
406
407 if (sqlite3_exec(db, sql, callback, cb_func, &err_msg) != SQLITE_OK) {
408 retval = -1;
409 if (error)
410 if (asprintf(error, "SQL error: %s", err_msg) < 0)
411 retval = -ENOMEM;
412
413 sqlite3_free(err_msg);
414 }
415
416 sqlite3_close(db);
417
418 return retval;
419}
420
421/* Remove an user entry. Returns 0 on success, -ENOMEM or -1 on other failure. */
422static int
423remove_entry(sqlite3 *db, const char *user, char **error)
424{
425 int retval = 0;
426 sqlite3_stmt *res = NULL;
427 static const char *sql = "DELETE FROM Lastlog2 WHERE Name = ?";
428
429 if (sqlite3_prepare_v2(db, sql, -1, &res, 0) != SQLITE_OK) {
430 if (error)
431 if (asprintf(error, "Failed to execute statement: %s",
432 sqlite3_errmsg(db)) < 0)
433 return -ENOMEM;
434
435 return -1;
436 }
437
438 if (sqlite3_bind_text(res, 1, user, -1, SQLITE_STATIC) != SQLITE_OK) {
439 retval = -1;
440 if (error)
441 if (asprintf(error, "Failed to create delete statement: %s",
442 sqlite3_errmsg(db)) < 0)
443 retval = -ENOMEM;
444 goto out_remove_entry;
445 }
446
447 int step = sqlite3_step(res);
448
449 if (step != SQLITE_DONE) {
450 retval = -1;
451 if (error)
452 if (asprintf(error, "Delete statement did not return SQLITE_DONE: %d",
453 step) < 0)
454 retval = -ENOMEM;
455 }
456out_remove_entry:
457 if (res)
458 sqlite3_finalize(res);
459
460 return retval;
461}
462
463/* Remove an user entry. Returns 0 on success, -ENOMEM or -1 on other failure. */
464int
465ll2_remove_entry(struct ll2_context *context, const char *user,
466 char **error)
467{
468 sqlite3 *db;
469 int retval;
470
471 if ((retval = open_database_rw(context, &db, error)) != 0)
472 return retval;
473
474 retval = remove_entry(db, user, error);
475
476 sqlite3_close(db);
477
478 return retval;
479}
480
481/* Renames an user entry. Returns 0 on success, -ENOMEM or -1 on other failure. */
482int
483ll2_rename_user(struct ll2_context *context, const char *user,
484 const char *newname, char **error)
485{
486 sqlite3 *db;
487 int64_t ll_time;
488 char *tty;
489 char *rhost;
490 char *pam_service;
491 int retval;
492
493 if ((retval = open_database_rw(context, &db, error)) != 0)
494 return retval;
495
496 if ((retval = read_entry(db, user, &ll_time, &tty, &rhost, &pam_service, error) != 0)) {
497 sqlite3_close(db);
498 return retval;
499 }
500
501 if ((retval = write_entry(db, newname, ll_time, tty, rhost, pam_service, error) != 0)) {
502 sqlite3_close(db);
503 free(tty);
504 free(rhost);
505 return retval;
506 }
507
508 retval = remove_entry(db, user, error);
509
510 sqlite3_close(db);
511
512 free(tty);
513 free(rhost);
514 free(pam_service);
515
516 return retval;
517}
518
519/* Import old lastlog file.
520 Returns 0 on success, -ENOMEM or -1 on other failure. */
521int
522ll2_import_lastlog(struct ll2_context *context, const char *lastlog_file,
523 char **error)
524{
525 const struct passwd *pw;
526 struct stat statll;
527 sqlite3 *db;
528 FILE *ll_fp;
529 int retval = 0;
530
531 if ((retval = open_database_rw(context, &db, error)) != 0)
532 return retval;
533
534 ll_fp = fopen(lastlog_file, "r");
535 if (ll_fp == NULL) {
cf0305dd 536 if (error && asprintf(error, "Failed to open '%s': %s",
c2e299d0 537 lastlog_file, strerror(errno)) < 0)
cf0305dd 538 return -ENOMEM;
c2e299d0
SS
539
540 return -1;
541 }
542
543
cf0305dd
KZ
544 if (fstat(fileno(ll_fp), &statll) != 0) {
545 retval = -1;
546 if (error && asprintf(error, "Cannot get size of '%s': %s",
547 lastlog_file, strerror(errno)) < 0)
548 retval = -ENOMEM;
c2e299d0 549
cf0305dd 550 goto done;
c2e299d0
SS
551 }
552
553 setpwent();
554 while ((pw = getpwent()) != NULL ) {
555 off_t offset;
556 struct lastlog ll;
557
558 offset = (off_t) pw->pw_uid * sizeof (ll);
559
560 if ((offset + (off_t)sizeof(ll)) <= statll.st_size) {
561 if (fseeko(ll_fp, offset, SEEK_SET) == -1)
562 continue; /* Ignore seek error */
563
564 if (fread(&ll, sizeof(ll), 1, ll_fp) != 1) {
565 retval = -1;
566 if (error)
567 if (asprintf(error, "Failed to get the entry for UID '%lu'",
568 (unsigned long int)pw->pw_uid) < 0)
569 retval = -ENOMEM;
570 goto out_import_lastlog;
571 }
572
573 if (ll.ll_time != 0) {
574 int64_t ll_time;
575 char tty[sizeof(ll.ll_line) + 1];
576 char rhost[sizeof(ll.ll_host) + 1];
577
578 ll_time = ll.ll_time;
579 mem2strcpy(tty, ll.ll_line, sizeof(ll.ll_line), sizeof(tty));
580 mem2strcpy(rhost, ll.ll_host, sizeof(ll.ll_host), sizeof(rhost));
581
582 if ((retval = write_entry(db, pw->pw_name, ll_time, tty,
583 rhost, NULL, error)) != 0)
584 goto out_import_lastlog;
585 }
586 }
587 }
588out_import_lastlog:
589 endpwent();
590 sqlite3_close(db);
cf0305dd
KZ
591done:
592 fclose(ll_fp);
c2e299d0
SS
593
594 return retval;
595}