]>
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 <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. */ | |
44 | extern 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 */ | |
65 | extern 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. */ | |
73 | static int | |
74 | open_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. */ | |
96 | static int | |
97 | open_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. */ | |
120 | static int | |
121 | read_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 | ||
195 | out_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. */ | |
203 | int | |
204 | ll2_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. */ | |
222 | static int | |
223 | write_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 | } | |
307 | out_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. */ | |
315 | int | |
316 | ll2_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. */ | |
335 | int | |
336 | ll2_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 | ||
365 | typedef 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 | ||
369 | static int | |
370 | callback(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. */ | |
391 | int | |
392 | ll2_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. */ | |
422 | static int | |
423 | remove_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 | } | |
456 | out_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. */ | |
464 | int | |
465 | ll2_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. */ | |
482 | int | |
483 | ll2_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. */ | |
521 | int | |
522 | ll2_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 | } | |
588 | out_import_lastlog: | |
589 | endpwent(); | |
590 | sqlite3_close(db); | |
cf0305dd KZ |
591 | done: |
592 | fclose(ll_fp); | |
c2e299d0 SS |
593 | |
594 | return retval; | |
595 | } |