source: trunk/third/openssh/loginrec.c @ 18759

Revision 18759, 36.2 KB checked in by zacheiss, 22 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r18758, which included commits to RCS files with non-trunk default branches.
Line 
1/*
2 * Copyright (c) 2000 Andre Lucas.  All rights reserved.
3 * Portions copyright (c) 1998 Todd C. Miller
4 * Portions copyright (c) 1996 Jason Downs
5 * Portions copyright (c) 1996 Theo de Raadt
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 3. All advertising materials mentioning features or use of this software
16 *    must display the following acknowledgement:
17 *      This product includes software developed by Markus Friedl.
18 * 4. The name of the author may not be used to endorse or promote products
19 *    derived from this software without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33/**
34 ** loginrec.c:  platform-independent login recording and lastlog retrieval
35 **/
36
37/*
38  The new login code explained
39  ============================
40
41  This code attempts to provide a common interface to login recording
42  (utmp and friends) and last login time retrieval.
43
44  Its primary means of achieving this is to use 'struct logininfo', a
45  union of all the useful fields in the various different types of
46  system login record structures one finds on UNIX variants.
47
48  We depend on autoconf to define which recording methods are to be
49  used, and which fields are contained in the relevant data structures
50  on the local system. Many C preprocessor symbols affect which code
51  gets compiled here.
52
53  The code is designed to make it easy to modify a particular
54  recording method, without affecting other methods nor requiring so
55  many nested conditional compilation blocks as were commonplace in
56  the old code.
57
58  For login recording, we try to use the local system's libraries as
59  these are clearly most likely to work correctly. For utmp systems
60  this usually means login() and logout() or setutent() etc., probably
61  in libutil, along with logwtmp() etc. On these systems, we fall back
62  to writing the files directly if we have to, though this method
63  requires very thorough testing so we do not corrupt local auditing
64  information. These files and their access methods are very system
65  specific indeed.
66
67  For utmpx systems, the corresponding library functions are
68  setutxent() etc. To the author's knowledge, all utmpx systems have
69  these library functions and so no direct write is attempted. If such
70  a system exists and needs support, direct analogues of the [uw]tmp
71  code should suffice.
72
73  Retrieving the time of last login ('lastlog') is in some ways even
74  more problemmatic than login recording. Some systems provide a
75  simple table of all users which we seek based on uid and retrieve a
76  relatively standard structure. Others record the same information in
77  a directory with a separate file, and others don't record the
78  information separately at all. For systems in the latter category,
79  we look backwards in the wtmp or wtmpx file for the last login entry
80  for our user. Naturally this is slower and on busy systems could
81  incur a significant performance penalty.
82
83  Calling the new code
84  --------------------
85
86  In OpenSSH all login recording and retrieval is performed in
87  login.c. Here you'll find working examples. Also, in the logintest.c
88  program there are more examples.
89
90  Internal handler calling method
91  -------------------------------
92
93  When a call is made to login_login() or login_logout(), both
94  routines set a struct logininfo flag defining which action (log in,
95  or log out) is to be taken. They both then call login_write(), which
96  calls whichever of the many structure-specific handlers autoconf
97  selects for the local system.
98
99  The handlers themselves handle system data structure specifics. Both
100  struct utmp and struct utmpx have utility functions (see
101  construct_utmp*()) to try to make it simpler to add extra systems
102  that introduce new features to either structure.
103
104  While it may seem terribly wasteful to replicate so much similar
105  code for each method, experience has shown that maintaining code to
106  write both struct utmp and utmpx in one function, whilst maintaining
107  support for all systems whether they have library support or not, is
108  a difficult and time-consuming task.
109
110  Lastlog support proceeds similarly. Functions login_get_lastlog()
111  (and its OpenSSH-tuned friend login_get_lastlog_time()) call
112  getlast_entry(), which tries one of three methods to find the last
113  login time. It uses local system lastlog support if it can,
114  otherwise it tries wtmp or wtmpx before giving up and returning 0,
115  meaning "tilt".
116
117  Maintenance
118  -----------
119
120  In many cases it's possible to tweak autoconf to select the correct
121  methods for a particular platform, either by improving the detection
122  code (best), or by presetting DISABLE_<method> or CONF_<method>_FILE
123  symbols for the platform.
124
125  Use logintest to check which symbols are defined before modifying
126  configure.ac and loginrec.c. (You have to build logintest yourself
127  with 'make logintest' as it's not built by default.)
128
129  Otherwise, patches to the specific method(s) are very helpful!
130
131*/
132
133/**
134 ** TODO:
135 **   homegrown ttyslot()
136 **   test, test, test
137 **
138 ** Platform status:
139 ** ----------------
140 **
141 ** Known good:
142 **   Linux (Redhat 6.2, Debian)
143 **   Solaris
144 **   HP-UX 10.20 (gcc only)
145 **   IRIX
146 **   NeXT - M68k/HPPA/Sparc (4.2/3.3)
147 **
148 ** Testing required: Please send reports!
149 **   NetBSD
150 **   HP-UX 11
151 **   AIX
152 **
153 ** Platforms with known problems:
154 **   Some variants of Slackware Linux
155 **
156 **/
157
158#include "includes.h"
159
160#include "ssh.h"
161#include "xmalloc.h"
162#include "loginrec.h"
163#include "log.h"
164#include "atomicio.h"
165
166RCSID("$Id: loginrec.c,v 1.1.1.3 2003-02-05 19:04:33 zacheiss Exp $");
167
168#ifdef HAVE_UTIL_H
169#  include <util.h>
170#endif
171
172#ifdef HAVE_LIBUTIL_H
173#   include <libutil.h>
174#endif
175
176/**
177 ** prototypes for helper functions in this file
178 **/
179
180#if HAVE_UTMP_H
181void set_utmp_time(struct logininfo *li, struct utmp *ut);
182void construct_utmp(struct logininfo *li, struct utmp *ut);
183#endif
184
185#ifdef HAVE_UTMPX_H
186void set_utmpx_time(struct logininfo *li, struct utmpx *ut);
187void construct_utmpx(struct logininfo *li, struct utmpx *ut);
188#endif
189
190int utmp_write_entry(struct logininfo *li);
191int utmpx_write_entry(struct logininfo *li);
192int wtmp_write_entry(struct logininfo *li);
193int wtmpx_write_entry(struct logininfo *li);
194int lastlog_write_entry(struct logininfo *li);
195int syslogin_write_entry(struct logininfo *li);
196
197int getlast_entry(struct logininfo *li);
198int lastlog_get_entry(struct logininfo *li);
199int wtmp_get_entry(struct logininfo *li);
200int wtmpx_get_entry(struct logininfo *li);
201
202/* pick the shortest string */
203#define MIN_SIZEOF(s1,s2) ( sizeof(s1) < sizeof(s2) ? sizeof(s1) : sizeof(s2) )
204
205/**
206 ** platform-independent login functions
207 **/
208
209/* login_login(struct logininfo *)     -Record a login
210 *
211 * Call with a pointer to a struct logininfo initialised with
212 * login_init_entry() or login_alloc_entry()
213 *
214 * Returns:
215 *  >0 if successful
216 *  0  on failure (will use OpenSSH's logging facilities for diagnostics)
217 */
218int
219login_login (struct logininfo *li)
220{
221        li->type = LTYPE_LOGIN;
222        return login_write(li);
223}
224
225
226/* login_logout(struct logininfo *)     - Record a logout
227 *
228 * Call as with login_login()
229 *
230 * Returns:
231 *  >0 if successful
232 *  0  on failure (will use OpenSSH's logging facilities for diagnostics)
233 */
234int
235login_logout(struct logininfo *li)
236{
237        li->type = LTYPE_LOGOUT;
238        return login_write(li);
239}
240
241/* login_get_lastlog_time(int)           - Retrieve the last login time
242 *
243 * Retrieve the last login time for the given uid. Will try to use the
244 * system lastlog facilities if they are available, but will fall back
245 * to looking in wtmp/wtmpx if necessary
246 *
247 * Returns:
248 *   0 on failure, or if user has never logged in
249 *   Time in seconds from the epoch if successful
250 *
251 * Useful preprocessor symbols:
252 *   DISABLE_LASTLOG: If set, *never* even try to retrieve lastlog
253 *                    info
254 *   USE_LASTLOG: If set, indicates the presence of system lastlog
255 *                facilities. If this and DISABLE_LASTLOG are not set,
256 *                try to retrieve lastlog information from wtmp/wtmpx.
257 */
258unsigned int
259login_get_lastlog_time(const int uid)
260{
261        struct logininfo li;
262
263        if (login_get_lastlog(&li, uid))
264                return li.tv_sec;
265        else
266                return 0;
267}
268
269/* login_get_lastlog(struct logininfo *, int)   - Retrieve a lastlog entry
270 *
271 * Retrieve a logininfo structure populated (only partially) with
272 * information from the system lastlog data, or from wtmp/wtmpx if no
273 * system lastlog information exists.
274 *
275 * Note this routine must be given a pre-allocated logininfo.
276 *
277 * Returns:
278 *  >0: A pointer to your struct logininfo if successful
279 *  0  on failure (will use OpenSSH's logging facilities for diagnostics)
280 *
281 */
282struct logininfo *
283login_get_lastlog(struct logininfo *li, const int uid)
284{
285        struct passwd *pw;
286
287        memset(li, '\0', sizeof(*li));
288        li->uid = uid;
289
290        /*
291         * If we don't have a 'real' lastlog, we need the username to
292         * reliably search wtmp(x) for the last login (see
293         * wtmp_get_entry().)
294         */
295        pw = getpwuid(uid);
296        if (pw == NULL)
297                fatal("login_get_lastlog: Cannot find account for uid %i", uid);
298
299        /* No MIN_SIZEOF here - we absolutely *must not* truncate the
300         * username */
301        strlcpy(li->username, pw->pw_name, sizeof(li->username));
302
303        if (getlast_entry(li))
304                return li;
305        else
306                return NULL;
307}
308
309
310/* login_alloc_entry(int, char*, char*, char*)    - Allocate and initialise
311 *                                                  a logininfo structure
312 *
313 * This function creates a new struct logininfo, a data structure
314 * meant to carry the information required to portably record login info.
315 *
316 * Returns a pointer to a newly created struct logininfo. If memory
317 * allocation fails, the program halts.
318 */
319struct
320logininfo *login_alloc_entry(int pid, const char *username,
321                             const char *hostname, const char *line)
322{
323        struct logininfo *newli;
324
325        newli = (struct logininfo *) xmalloc (sizeof(*newli));
326        (void)login_init_entry(newli, pid, username, hostname, line);
327        return newli;
328}
329
330
331/* login_free_entry(struct logininfo *)    - free struct memory */
332void
333login_free_entry(struct logininfo *li)
334{
335        xfree(li);
336}
337
338
339/* login_init_entry(struct logininfo *, int, char*, char*, char*)
340 *                                        - initialise a struct logininfo
341 *
342 * Populates a new struct logininfo, a data structure meant to carry
343 * the information required to portably record login info.
344 *
345 * Returns: 1
346 */
347int
348login_init_entry(struct logininfo *li, int pid, const char *username,
349                 const char *hostname, const char *line)
350{
351        struct passwd *pw;
352
353        memset(li, 0, sizeof(*li));
354
355        li->pid = pid;
356
357        /* set the line information */
358        if (line)
359                line_fullname(li->line, line, sizeof(li->line));
360
361        if (username) {
362                strlcpy(li->username, username, sizeof(li->username));
363                pw = getpwnam(li->username);
364                if (pw == NULL)
365                        fatal("login_init_entry: Cannot find user \"%s\"", li->username);
366                li->uid = pw->pw_uid;
367        }
368
369        if (hostname)
370                strlcpy(li->hostname, hostname, sizeof(li->hostname));
371
372        return 1;
373}
374
375/* login_set_current_time(struct logininfo *)    - set the current time
376 *
377 * Set the current time in a logininfo structure. This function is
378 * meant to eliminate the need to deal with system dependencies for
379 * time handling.
380 */
381void
382login_set_current_time(struct logininfo *li)
383{
384        struct timeval tv;
385
386        gettimeofday(&tv, NULL);
387
388        li->tv_sec = tv.tv_sec;
389        li->tv_usec = tv.tv_usec;
390}
391
392/* copy a sockaddr_* into our logininfo */
393void
394login_set_addr(struct logininfo *li, const struct sockaddr *sa,
395               const unsigned int sa_size)
396{
397        unsigned int bufsize = sa_size;
398
399        /* make sure we don't overrun our union */
400        if (sizeof(li->hostaddr) < sa_size)
401                bufsize = sizeof(li->hostaddr);
402
403        memcpy((void *)&(li->hostaddr.sa), (const void *)sa, bufsize);
404}
405
406
407/**
408 ** login_write: Call low-level recording functions based on autoconf
409 ** results
410 **/
411int
412login_write (struct logininfo *li)
413{
414#ifndef HAVE_CYGWIN
415        if ((int)geteuid() != 0) {
416          log("Attempt to write login records by non-root user (aborting)");
417          return 1;
418        }
419#endif
420
421        /* set the timestamp */
422        login_set_current_time(li);
423#ifdef USE_LOGIN
424        syslogin_write_entry(li);
425#endif
426#ifdef USE_LASTLOG
427        if (li->type == LTYPE_LOGIN) {
428                lastlog_write_entry(li);
429        }
430#endif
431#ifdef USE_UTMP
432        utmp_write_entry(li);
433#endif
434#ifdef USE_WTMP
435        wtmp_write_entry(li);
436#endif
437#ifdef USE_UTMPX
438        utmpx_write_entry(li);
439#endif
440#ifdef USE_WTMPX
441        wtmpx_write_entry(li);
442#endif
443        return 0;
444}
445
446#ifdef LOGIN_NEEDS_UTMPX
447int
448login_utmp_only(struct logininfo *li)
449{
450        li->type = LTYPE_LOGIN;
451        login_set_current_time(li);
452# ifdef USE_UTMP
453        utmp_write_entry(li);
454# endif
455# ifdef USE_WTMP
456        wtmp_write_entry(li);
457# endif
458# ifdef USE_UTMPX
459        utmpx_write_entry(li);
460# endif
461# ifdef USE_WTMPX
462        wtmpx_write_entry(li);
463# endif
464        return 0;
465}
466#endif
467
468/**
469 ** getlast_entry: Call low-level functions to retrieve the last login
470 **                time.
471 **/
472
473/* take the uid in li and return the last login time */
474int
475getlast_entry(struct logininfo *li)
476{
477#ifdef USE_LASTLOG
478        return(lastlog_get_entry(li));
479#else /* !USE_LASTLOG */
480
481#ifdef DISABLE_LASTLOG
482        /* On some systems we shouldn't even try to obtain last login
483         * time, e.g. AIX */
484        return 0;
485# else /* DISABLE_LASTLOG */
486        /* Try to retrieve the last login time from wtmp */
487#  if defined(USE_WTMP) && (defined(HAVE_TIME_IN_UTMP) || defined(HAVE_TV_IN_UTMP))
488        /* retrieve last login time from utmp */
489        return (wtmp_get_entry(li));
490#  else /* defined(USE_WTMP) && (defined(HAVE_TIME_IN_UTMP) || defined(HAVE_TV_IN_UTMP)) */
491        /* If wtmp isn't available, try wtmpx */
492#   if defined(USE_WTMPX) && (defined(HAVE_TIME_IN_UTMPX) || defined(HAVE_TV_IN_UTMPX))
493        /* retrieve last login time from utmpx */
494        return (wtmpx_get_entry(li));
495#   else
496        /* Give up: No means of retrieving last login time */
497        return 0;
498#   endif /* USE_WTMPX && (HAVE_TIME_IN_UTMPX || HAVE_TV_IN_UTMPX) */
499#  endif /* USE_WTMP && (HAVE_TIME_IN_UTMP || HAVE_TV_IN_UTMP) */
500# endif /* DISABLE_LASTLOG */
501#endif /* USE_LASTLOG */
502}
503
504
505
506/*
507 * 'line' string utility functions
508 *
509 * These functions process the 'line' string into one of three forms:
510 *
511 * 1. The full filename (including '/dev')
512 * 2. The stripped name (excluding '/dev')
513 * 3. The abbreviated name (e.g. /dev/ttyp00 -> yp00
514 *                               /dev/pts/1  -> ts/1 )
515 *
516 * Form 3 is used on some systems to identify a .tmp.? entry when
517 * attempting to remove it. Typically both addition and removal is
518 * performed by one application - say, sshd - so as long as the choice
519 * uniquely identifies a terminal it's ok.
520 */
521
522
523/* line_fullname(): add the leading '/dev/' if it doesn't exist make
524 * sure dst has enough space, if not just copy src (ugh) */
525char *
526line_fullname(char *dst, const char *src, int dstsize)
527{
528        memset(dst, '\0', dstsize);
529        if ((strncmp(src, "/dev/", 5) == 0) || (dstsize < (strlen(src) + 5))) {
530                strlcpy(dst, src, dstsize);
531        } else {
532                strlcpy(dst, "/dev/", dstsize);
533                strlcat(dst, src, dstsize);
534        }
535        return dst;
536}
537
538/* line_stripname(): strip the leading '/dev' if it exists, return dst */
539char *
540line_stripname(char *dst, const char *src, int dstsize)
541{
542        memset(dst, '\0', dstsize);
543        if (strncmp(src, "/dev/", 5) == 0)
544                strlcpy(dst, src + 5, dstsize);
545        else
546                strlcpy(dst, src, dstsize);
547        return dst;
548}
549
550/* line_abbrevname(): Return the abbreviated (usually four-character)
551 * form of the line (Just use the last <dstsize> characters of the
552 * full name.)
553 *
554 * NOTE: use strncpy because we do NOT necessarily want zero
555 * termination */
556char *
557line_abbrevname(char *dst, const char *src, int dstsize)
558{
559        size_t len;
560
561        memset(dst, '\0', dstsize);
562
563        /* Always skip prefix if present */
564        if (strncmp(src, "/dev/", 5) == 0)
565                src += 5;
566
567#ifdef WITH_ABBREV_NO_TTY
568        if (strncmp(src, "tty", 3) == 0)
569                src += 3;
570#endif
571
572        len = strlen(src);
573
574        if (len > 0) {
575                if (((int)len - dstsize) > 0)
576                        src +=  ((int)len - dstsize);
577
578                /* note: _don't_ change this to strlcpy */
579                strncpy(dst, src, (size_t)dstsize);
580        }
581
582        return dst;
583}
584
585/**
586 ** utmp utility functions
587 **
588 ** These functions manipulate struct utmp, taking system differences
589 ** into account.
590 **/
591
592#if defined(USE_UTMP) || defined (USE_WTMP) || defined (USE_LOGIN)
593
594/* build the utmp structure */
595void
596set_utmp_time(struct logininfo *li, struct utmp *ut)
597{
598# ifdef HAVE_TV_IN_UTMP
599        ut->ut_tv.tv_sec = li->tv_sec;
600        ut->ut_tv.tv_usec = li->tv_usec;
601# else
602#  ifdef HAVE_TIME_IN_UTMP
603        ut->ut_time = li->tv_sec;
604#  endif
605# endif
606}
607
608void
609construct_utmp(struct logininfo *li,
610                    struct utmp *ut)
611{
612        memset(ut, '\0', sizeof(*ut));
613
614        /* First fill out fields used for both logins and logouts */
615
616# ifdef HAVE_ID_IN_UTMP
617        line_abbrevname(ut->ut_id, li->line, sizeof(ut->ut_id));
618# endif
619
620# ifdef HAVE_TYPE_IN_UTMP
621        /* This is done here to keep utmp constants out of struct logininfo */
622        switch (li->type) {
623        case LTYPE_LOGIN:
624                ut->ut_type = USER_PROCESS;
625#ifdef _UNICOS
626                cray_set_tmpdir(ut);
627#endif
628                break;
629        case LTYPE_LOGOUT:
630                ut->ut_type = DEAD_PROCESS;
631#ifdef _UNICOS
632                cray_retain_utmp(ut, li->pid);
633#endif
634                break;
635        }
636# endif
637        set_utmp_time(li, ut);
638
639        line_stripname(ut->ut_line, li->line, sizeof(ut->ut_line));
640
641# ifdef HAVE_PID_IN_UTMP
642        ut->ut_pid = li->pid;
643# endif
644
645        /* If we're logging out, leave all other fields blank */
646        if (li->type == LTYPE_LOGOUT)
647          return;
648
649        /*
650         * These fields are only used when logging in, and are blank
651         * for logouts.
652         */
653
654        /* Use strncpy because we don't necessarily want null termination */
655        strncpy(ut->ut_name, li->username, MIN_SIZEOF(ut->ut_name, li->username));
656# ifdef HAVE_HOST_IN_UTMP
657        strncpy(ut->ut_host, li->hostname, MIN_SIZEOF(ut->ut_host, li->hostname));
658# endif
659# ifdef HAVE_ADDR_IN_UTMP
660        /* this is just a 32-bit IP address */
661        if (li->hostaddr.sa.sa_family == AF_INET)
662                ut->ut_addr = li->hostaddr.sa_in.sin_addr.s_addr;
663# endif
664}
665#endif /* USE_UTMP || USE_WTMP || USE_LOGIN */
666
667/**
668 ** utmpx utility functions
669 **
670 ** These functions manipulate struct utmpx, accounting for system
671 ** variations.
672 **/
673
674#if defined(USE_UTMPX) || defined (USE_WTMPX)
675/* build the utmpx structure */
676void
677set_utmpx_time(struct logininfo *li, struct utmpx *utx)
678{
679# ifdef HAVE_TV_IN_UTMPX
680        utx->ut_tv.tv_sec = li->tv_sec;
681        utx->ut_tv.tv_usec = li->tv_usec;
682# else /* HAVE_TV_IN_UTMPX */
683#  ifdef HAVE_TIME_IN_UTMPX
684        utx->ut_time = li->tv_sec;
685#  endif /* HAVE_TIME_IN_UTMPX */
686# endif /* HAVE_TV_IN_UTMPX */
687}
688
689void
690construct_utmpx(struct logininfo *li, struct utmpx *utx)
691{
692        memset(utx, '\0', sizeof(*utx));
693# ifdef HAVE_ID_IN_UTMPX
694        line_abbrevname(utx->ut_id, li->line, sizeof(utx->ut_id));
695# endif
696
697        /* this is done here to keep utmp constants out of loginrec.h */
698        switch (li->type) {
699        case LTYPE_LOGIN:
700                utx->ut_type = USER_PROCESS;
701                break;
702        case LTYPE_LOGOUT:
703                utx->ut_type = DEAD_PROCESS;
704                break;
705        }
706        line_stripname(utx->ut_line, li->line, sizeof(utx->ut_line));
707        set_utmpx_time(li, utx);
708        utx->ut_pid = li->pid;
709        /* strncpy(): Don't necessarily want null termination */
710        strncpy(utx->ut_name, li->username, MIN_SIZEOF(utx->ut_name, li->username));
711
712        if (li->type == LTYPE_LOGOUT)
713                return;
714
715        /*
716         * These fields are only used when logging in, and are blank
717         * for logouts.
718         */
719
720# ifdef HAVE_HOST_IN_UTMPX
721        strncpy(utx->ut_host, li->hostname, MIN_SIZEOF(utx->ut_host, li->hostname));
722# endif
723# ifdef HAVE_ADDR_IN_UTMPX
724        /* this is just a 32-bit IP address */
725        if (li->hostaddr.sa.sa_family == AF_INET)
726                utx->ut_addr = li->hostaddr.sa_in.sin_addr.s_addr;
727# endif
728# ifdef HAVE_SYSLEN_IN_UTMPX
729        /* ut_syslen is the length of the utx_host string */
730        utx->ut_syslen = MIN(strlen(li->hostname), sizeof(utx->ut_host));
731# endif
732}
733#endif /* USE_UTMPX || USE_WTMPX */
734
735/**
736 ** Low-level utmp functions
737 **/
738
739/* FIXME: (ATL) utmp_write_direct needs testing */
740#ifdef USE_UTMP
741
742/* if we can, use pututline() etc. */
743# if !defined(DISABLE_PUTUTLINE) && defined(HAVE_SETUTENT) && \
744        defined(HAVE_PUTUTLINE)
745#  define UTMP_USE_LIBRARY
746# endif
747
748
749/* write a utmp entry with the system's help (pututline() and pals) */
750# ifdef UTMP_USE_LIBRARY
751static int
752utmp_write_library(struct logininfo *li, struct utmp *ut)
753{
754        setutent();
755        pututline(ut);
756
757#  ifdef HAVE_ENDUTENT
758        endutent();
759#  endif
760        return 1;
761}
762# else /* UTMP_USE_LIBRARY */
763
764/* write a utmp entry direct to the file */
765/* This is a slightly modification of code in OpenBSD's login.c */
766static int
767utmp_write_direct(struct logininfo *li, struct utmp *ut)
768{
769        struct utmp old_ut;
770        register int fd;
771        int tty;
772
773        /* FIXME: (ATL) ttyslot() needs local implementation */
774
775#if defined(HAVE_GETTTYENT)
776        register struct ttyent *ty;
777
778        tty=0;
779
780        setttyent();
781        while ((struct ttyent *)0 != (ty = getttyent())) {
782                tty++;
783                if (!strncmp(ty->ty_name, ut->ut_line, sizeof(ut->ut_line)))
784                        break;
785        }
786        endttyent();
787
788        if((struct ttyent *)0 == ty) {
789                log("utmp_write_entry: tty not found");
790                return(1);
791        }
792#else /* FIXME */
793
794        tty = ttyslot(); /* seems only to work for /dev/ttyp? style names */
795
796#endif /* HAVE_GETTTYENT */
797
798        if (tty > 0 && (fd = open(UTMP_FILE, O_RDWR|O_CREAT, 0644)) >= 0) {
799                (void)lseek(fd, (off_t)(tty * sizeof(struct utmp)), SEEK_SET);
800                /*
801                 * Prevent luser from zero'ing out ut_host.
802                 * If the new ut_line is empty but the old one is not
803                 * and ut_line and ut_name match, preserve the old ut_line.
804                 */
805                if (atomicio(read, fd, &old_ut, sizeof(old_ut)) == sizeof(old_ut) &&
806                        (ut->ut_host[0] == '\0') && (old_ut.ut_host[0] != '\0') &&
807                        (strncmp(old_ut.ut_line, ut->ut_line, sizeof(ut->ut_line)) == 0) &&
808                        (strncmp(old_ut.ut_name, ut->ut_name, sizeof(ut->ut_name)) == 0)) {
809                        (void)memcpy(ut->ut_host, old_ut.ut_host, sizeof(ut->ut_host));
810                }
811
812                (void)lseek(fd, (off_t)(tty * sizeof(struct utmp)), SEEK_SET);
813                if (atomicio(write, fd, ut, sizeof(*ut)) != sizeof(*ut))
814                        log("utmp_write_direct: error writing %s: %s",
815                            UTMP_FILE, strerror(errno));
816
817                (void)close(fd);
818                return 1;
819        } else {
820                return 0;
821        }
822}
823# endif /* UTMP_USE_LIBRARY */
824
825static int
826utmp_perform_login(struct logininfo *li)
827{
828        struct utmp ut;
829
830        construct_utmp(li, &ut);
831# ifdef UTMP_USE_LIBRARY
832        if (!utmp_write_library(li, &ut)) {
833                log("utmp_perform_login: utmp_write_library() failed");
834                return 0;
835        }
836# else
837        if (!utmp_write_direct(li, &ut)) {
838                log("utmp_perform_login: utmp_write_direct() failed");
839                return 0;
840        }
841# endif
842        return 1;
843}
844
845
846static int
847utmp_perform_logout(struct logininfo *li)
848{
849        struct utmp ut;
850
851        construct_utmp(li, &ut);
852# ifdef UTMP_USE_LIBRARY
853        if (!utmp_write_library(li, &ut)) {
854                log("utmp_perform_logout: utmp_write_library() failed");
855                return 0;
856        }
857# else
858        if (!utmp_write_direct(li, &ut)) {
859                log("utmp_perform_logout: utmp_write_direct() failed");
860                return 0;
861        }
862# endif
863        return 1;
864}
865
866
867int
868utmp_write_entry(struct logininfo *li)
869{
870        switch(li->type) {
871        case LTYPE_LOGIN:
872                return utmp_perform_login(li);
873
874        case LTYPE_LOGOUT:
875                return utmp_perform_logout(li);
876
877        default:
878                log("utmp_write_entry: invalid type field");
879                return 0;
880        }
881}
882#endif /* USE_UTMP */
883
884
885/**
886 ** Low-level utmpx functions
887 **/
888
889/* not much point if we don't want utmpx entries */
890#ifdef USE_UTMPX
891
892/* if we have the wherewithall, use pututxline etc. */
893# if !defined(DISABLE_PUTUTXLINE) && defined(HAVE_SETUTXENT) && \
894        defined(HAVE_PUTUTXLINE)
895#  define UTMPX_USE_LIBRARY
896# endif
897
898
899/* write a utmpx entry with the system's help (pututxline() and pals) */
900# ifdef UTMPX_USE_LIBRARY
901static int
902utmpx_write_library(struct logininfo *li, struct utmpx *utx)
903{
904        setutxent();
905        pututxline(utx);
906
907#  ifdef HAVE_ENDUTXENT
908        endutxent();
909#  endif
910        return 1;
911}
912
913# else /* UTMPX_USE_LIBRARY */
914
915/* write a utmp entry direct to the file */
916static int
917utmpx_write_direct(struct logininfo *li, struct utmpx *utx)
918{
919        log("utmpx_write_direct: not implemented!");
920        return 0;
921}
922# endif /* UTMPX_USE_LIBRARY */
923
924static int
925utmpx_perform_login(struct logininfo *li)
926{
927        struct utmpx utx;
928
929        construct_utmpx(li, &utx);
930# ifdef UTMPX_USE_LIBRARY
931        if (!utmpx_write_library(li, &utx)) {
932                log("utmpx_perform_login: utmp_write_library() failed");
933                return 0;
934        }
935# else
936        if (!utmpx_write_direct(li, &ut)) {
937                log("utmpx_perform_login: utmp_write_direct() failed");
938                return 0;
939        }
940# endif
941        return 1;
942}
943
944
945static int
946utmpx_perform_logout(struct logininfo *li)
947{
948        struct utmpx utx;
949
950        construct_utmpx(li, &utx);
951# ifdef HAVE_ID_IN_UTMPX
952        line_abbrevname(utx.ut_id, li->line, sizeof(utx.ut_id));
953# endif
954# ifdef HAVE_TYPE_IN_UTMPX
955        utx.ut_type = DEAD_PROCESS;
956# endif
957
958# ifdef UTMPX_USE_LIBRARY
959        utmpx_write_library(li, &utx);
960# else
961        utmpx_write_direct(li, &utx);
962# endif
963        return 1;
964}
965
966int
967utmpx_write_entry(struct logininfo *li)
968{
969        switch(li->type) {
970        case LTYPE_LOGIN:
971                return utmpx_perform_login(li);
972        case LTYPE_LOGOUT:
973                return utmpx_perform_logout(li);
974        default:
975                log("utmpx_write_entry: invalid type field");
976                return 0;
977        }
978}
979#endif /* USE_UTMPX */
980
981
982/**
983 ** Low-level wtmp functions
984 **/
985
986#ifdef USE_WTMP
987
988/* write a wtmp entry direct to the end of the file */
989/* This is a slight modification of code in OpenBSD's logwtmp.c */
990static int
991wtmp_write(struct logininfo *li, struct utmp *ut)
992{
993        struct stat buf;
994        int fd, ret = 1;
995
996        if ((fd = open(WTMP_FILE, O_WRONLY|O_APPEND, 0)) < 0) {
997                log("wtmp_write: problem writing %s: %s",
998                    WTMP_FILE, strerror(errno));
999                return 0;
1000        }
1001        if (fstat(fd, &buf) == 0)
1002                if (atomicio(write, fd, ut, sizeof(*ut)) != sizeof(*ut)) {
1003                        ftruncate(fd, buf.st_size);
1004                        log("wtmp_write: problem writing %s: %s",
1005                            WTMP_FILE, strerror(errno));
1006                        ret = 0;
1007                }
1008        (void)close(fd);
1009        return ret;
1010}
1011
1012static int
1013wtmp_perform_login(struct logininfo *li)
1014{
1015        struct utmp ut;
1016
1017        construct_utmp(li, &ut);
1018        return wtmp_write(li, &ut);
1019}
1020
1021
1022static int
1023wtmp_perform_logout(struct logininfo *li)
1024{
1025        struct utmp ut;
1026
1027        construct_utmp(li, &ut);
1028        return wtmp_write(li, &ut);
1029}
1030
1031
1032int
1033wtmp_write_entry(struct logininfo *li)
1034{
1035        switch(li->type) {
1036        case LTYPE_LOGIN:
1037                return wtmp_perform_login(li);
1038        case LTYPE_LOGOUT:
1039                return wtmp_perform_logout(li);
1040        default:
1041                log("wtmp_write_entry: invalid type field");
1042                return 0;
1043        }
1044}
1045
1046
1047/* Notes on fetching login data from wtmp/wtmpx
1048 *
1049 * Logouts are usually recorded with (amongst other things) a blank
1050 * username on a given tty line.  However, some systems (HP-UX is one)
1051 * leave all fields set, but change the ut_type field to DEAD_PROCESS.
1052 *
1053 * Since we're only looking for logins here, we know that the username
1054 * must be set correctly. On systems that leave it in, we check for
1055 * ut_type==USER_PROCESS (indicating a login.)
1056 *
1057 * Portability: Some systems may set something other than USER_PROCESS
1058 * to indicate a login process. I don't know of any as I write. Also,
1059 * it's possible that some systems may both leave the username in
1060 * place and not have ut_type.
1061 */
1062
1063/* return true if this wtmp entry indicates a login */
1064static int
1065wtmp_islogin(struct logininfo *li, struct utmp *ut)
1066{
1067        if (strncmp(li->username, ut->ut_name,
1068                MIN_SIZEOF(li->username, ut->ut_name)) == 0) {
1069# ifdef HAVE_TYPE_IN_UTMP
1070                if (ut->ut_type & USER_PROCESS)
1071                        return 1;
1072# else
1073                return 1;
1074# endif
1075        }
1076        return 0;
1077}
1078
1079int
1080wtmp_get_entry(struct logininfo *li)
1081{
1082        struct stat st;
1083        struct utmp ut;
1084        int fd, found=0;
1085
1086        /* Clear the time entries in our logininfo */
1087        li->tv_sec = li->tv_usec = 0;
1088
1089        if ((fd = open(WTMP_FILE, O_RDONLY)) < 0) {
1090                log("wtmp_get_entry: problem opening %s: %s",
1091                    WTMP_FILE, strerror(errno));
1092                return 0;
1093        }
1094        if (fstat(fd, &st) != 0) {
1095                log("wtmp_get_entry: couldn't stat %s: %s",
1096                    WTMP_FILE, strerror(errno));
1097                close(fd);
1098                return 0;
1099        }
1100
1101        /* Seek to the start of the last struct utmp */
1102        if (lseek(fd, -(off_t)sizeof(struct utmp), SEEK_END) == -1) {
1103                /* Looks like we've got a fresh wtmp file */
1104                close(fd);
1105                return 0;
1106        }
1107
1108        while (!found) {
1109                if (atomicio(read, fd, &ut, sizeof(ut)) != sizeof(ut)) {
1110                        log("wtmp_get_entry: read of %s failed: %s",
1111                            WTMP_FILE, strerror(errno));
1112                        close (fd);
1113                        return 0;
1114                }
1115                if ( wtmp_islogin(li, &ut) ) {
1116                        found = 1;
1117                        /* We've already checked for a time in struct
1118                         * utmp, in login_getlast(). */
1119# ifdef HAVE_TIME_IN_UTMP
1120                        li->tv_sec = ut.ut_time;
1121# else
1122#  if HAVE_TV_IN_UTMP
1123                        li->tv_sec = ut.ut_tv.tv_sec;
1124#  endif
1125# endif
1126                        line_fullname(li->line, ut.ut_line,
1127                                      MIN_SIZEOF(li->line, ut.ut_line));
1128# ifdef HAVE_HOST_IN_UTMP
1129                        strlcpy(li->hostname, ut.ut_host,
1130                                MIN_SIZEOF(li->hostname, ut.ut_host));
1131# endif
1132                        continue;
1133                }
1134                /* Seek back 2 x struct utmp */
1135                if (lseek(fd, -(off_t)(2 * sizeof(struct utmp)), SEEK_CUR) == -1) {
1136                        /* We've found the start of the file, so quit */
1137                        close (fd);
1138                        return 0;
1139                }
1140        }
1141
1142        /* We found an entry. Tidy up and return */
1143        close(fd);
1144        return 1;
1145}
1146# endif /* USE_WTMP */
1147
1148
1149/**
1150 ** Low-level wtmpx functions
1151 **/
1152
1153#ifdef USE_WTMPX
1154/* write a wtmpx entry direct to the end of the file */
1155/* This is a slight modification of code in OpenBSD's logwtmp.c */
1156static int
1157wtmpx_write(struct logininfo *li, struct utmpx *utx)
1158{
1159        struct stat buf;
1160        int fd, ret = 1;
1161
1162        if ((fd = open(WTMPX_FILE, O_WRONLY|O_APPEND, 0)) < 0) {
1163                log("wtmpx_write: problem opening %s: %s",
1164                    WTMPX_FILE, strerror(errno));
1165                return 0;
1166        }
1167
1168        if (fstat(fd, &buf) == 0)
1169                if (atomicio(write, fd, utx, sizeof(*utx)) != sizeof(*utx)) {
1170                        ftruncate(fd, buf.st_size);
1171                        log("wtmpx_write: problem writing %s: %s",
1172                            WTMPX_FILE, strerror(errno));
1173                        ret = 0;
1174                }
1175        (void)close(fd);
1176
1177        return ret;
1178}
1179
1180
1181static int
1182wtmpx_perform_login(struct logininfo *li)
1183{
1184        struct utmpx utx;
1185
1186        construct_utmpx(li, &utx);
1187        return wtmpx_write(li, &utx);
1188}
1189
1190
1191static int
1192wtmpx_perform_logout(struct logininfo *li)
1193{
1194        struct utmpx utx;
1195
1196        construct_utmpx(li, &utx);
1197        return wtmpx_write(li, &utx);
1198}
1199
1200
1201int
1202wtmpx_write_entry(struct logininfo *li)
1203{
1204        switch(li->type) {
1205        case LTYPE_LOGIN:
1206                return wtmpx_perform_login(li);
1207        case LTYPE_LOGOUT:
1208                return wtmpx_perform_logout(li);
1209        default:
1210                log("wtmpx_write_entry: invalid type field");
1211                return 0;
1212        }
1213}
1214
1215/* Please see the notes above wtmp_islogin() for information about the
1216   next two functions */
1217
1218/* Return true if this wtmpx entry indicates a login */
1219static int
1220wtmpx_islogin(struct logininfo *li, struct utmpx *utx)
1221{
1222        if ( strncmp(li->username, utx->ut_name,
1223                MIN_SIZEOF(li->username, utx->ut_name)) == 0 ) {
1224# ifdef HAVE_TYPE_IN_UTMPX
1225                if (utx->ut_type == USER_PROCESS)
1226                        return 1;
1227# else
1228                return 1;
1229# endif
1230        }
1231        return 0;
1232}
1233
1234
1235int
1236wtmpx_get_entry(struct logininfo *li)
1237{
1238        struct stat st;
1239        struct utmpx utx;
1240        int fd, found=0;
1241
1242        /* Clear the time entries */
1243        li->tv_sec = li->tv_usec = 0;
1244
1245        if ((fd = open(WTMPX_FILE, O_RDONLY)) < 0) {
1246                log("wtmpx_get_entry: problem opening %s: %s",
1247                    WTMPX_FILE, strerror(errno));
1248                return 0;
1249        }
1250        if (fstat(fd, &st) != 0) {
1251                log("wtmpx_get_entry: couldn't stat %s: %s",
1252                    WTMPX_FILE, strerror(errno));
1253                close(fd);
1254                return 0;
1255        }
1256
1257        /* Seek to the start of the last struct utmpx */
1258        if (lseek(fd, -(off_t)sizeof(struct utmpx), SEEK_END) == -1 ) {
1259                /* probably a newly rotated wtmpx file */
1260                close(fd);
1261                return 0;
1262        }
1263
1264        while (!found) {
1265                if (atomicio(read, fd, &utx, sizeof(utx)) != sizeof(utx)) {
1266                        log("wtmpx_get_entry: read of %s failed: %s",
1267                            WTMPX_FILE, strerror(errno));
1268                        close (fd);
1269                        return 0;
1270                }
1271                /* Logouts are recorded as a blank username on a particular line.
1272                 * So, we just need to find the username in struct utmpx */
1273                if ( wtmpx_islogin(li, &utx) ) {
1274                        found = 1;
1275# ifdef HAVE_TV_IN_UTMPX
1276                        li->tv_sec = utx.ut_tv.tv_sec;
1277# else
1278#  ifdef HAVE_TIME_IN_UTMPX
1279                        li->tv_sec = utx.ut_time;
1280#  endif
1281# endif
1282                        line_fullname(li->line, utx.ut_line, sizeof(li->line));
1283# ifdef HAVE_HOST_IN_UTMPX
1284                        strlcpy(li->hostname, utx.ut_host,
1285                                MIN_SIZEOF(li->hostname, utx.ut_host));
1286# endif
1287                        continue;
1288                }
1289                if (lseek(fd, -(off_t)(2 * sizeof(struct utmpx)), SEEK_CUR) == -1) {
1290                        close (fd);
1291                        return 0;
1292                }
1293        }
1294
1295        close(fd);
1296        return 1;
1297}
1298#endif /* USE_WTMPX */
1299
1300/**
1301 ** Low-level libutil login() functions
1302 **/
1303
1304#ifdef USE_LOGIN
1305static int
1306syslogin_perform_login(struct logininfo *li)
1307{
1308        struct utmp *ut;
1309
1310        if (! (ut = (struct utmp *)malloc(sizeof(*ut)))) {
1311                log("syslogin_perform_login: couldn't malloc()");
1312                return 0;
1313        }
1314        construct_utmp(li, ut);
1315        login(ut);
1316
1317        return 1;
1318}
1319
1320static int
1321syslogin_perform_logout(struct logininfo *li)
1322{
1323# ifdef HAVE_LOGOUT
1324        char line[8];
1325
1326        (void)line_stripname(line, li->line, sizeof(line));
1327
1328        if (!logout(line)) {
1329                log("syslogin_perform_logout: logout() returned an error");
1330#  ifdef HAVE_LOGWTMP
1331        } else {
1332                logwtmp(line, "", "");
1333#  endif
1334        }
1335        /* FIXME: (ATL - if the need arises) What to do if we have
1336         * login, but no logout?  what if logout but no logwtmp? All
1337         * routines are in libutil so they should all be there,
1338         * but... */
1339# endif
1340        return 1;
1341}
1342
1343int
1344syslogin_write_entry(struct logininfo *li)
1345{
1346        switch (li->type) {
1347        case LTYPE_LOGIN:
1348                return syslogin_perform_login(li);
1349        case LTYPE_LOGOUT:
1350                return syslogin_perform_logout(li);
1351        default:
1352                log("syslogin_write_entry: Invalid type field");
1353                return 0;
1354        }
1355}
1356#endif /* USE_LOGIN */
1357
1358/* end of file log-syslogin.c */
1359
1360/**
1361 ** Low-level lastlog functions
1362 **/
1363
1364#ifdef USE_LASTLOG
1365#define LL_FILE 1
1366#define LL_DIR 2
1367#define LL_OTHER 3
1368
1369static void
1370lastlog_construct(struct logininfo *li, struct lastlog *last)
1371{
1372        /* clear the structure */
1373        memset(last, '\0', sizeof(*last));
1374
1375        (void)line_stripname(last->ll_line, li->line, sizeof(last->ll_line));
1376        strlcpy(last->ll_host, li->hostname,
1377                MIN_SIZEOF(last->ll_host, li->hostname));
1378        last->ll_time = li->tv_sec;
1379}
1380
1381static int
1382lastlog_filetype(char *filename)
1383{
1384        struct stat st;
1385
1386        if (stat(LASTLOG_FILE, &st) != 0) {
1387                log("lastlog_perform_login: Couldn't stat %s: %s", LASTLOG_FILE,
1388                        strerror(errno));
1389                return 0;
1390        }
1391        if (S_ISDIR(st.st_mode))
1392                return LL_DIR;
1393        else if (S_ISREG(st.st_mode))
1394                return LL_FILE;
1395        else
1396                return LL_OTHER;
1397}
1398
1399
1400/* open the file (using filemode) and seek to the login entry */
1401static int
1402lastlog_openseek(struct logininfo *li, int *fd, int filemode)
1403{
1404        off_t offset;
1405        int type;
1406        char lastlog_file[1024];
1407
1408        type = lastlog_filetype(LASTLOG_FILE);
1409        switch (type) {
1410                case LL_FILE:
1411                        strlcpy(lastlog_file, LASTLOG_FILE, sizeof(lastlog_file));
1412                        break;
1413                case LL_DIR:
1414                        snprintf(lastlog_file, sizeof(lastlog_file), "%s/%s",
1415                                 LASTLOG_FILE, li->username);
1416                        break;
1417                default:
1418                        log("lastlog_openseek: %.100s is not a file or directory!",
1419                            LASTLOG_FILE);
1420                        return 0;
1421        }
1422
1423        *fd = open(lastlog_file, filemode);
1424        if ( *fd < 0) {
1425                debug("lastlog_openseek: Couldn't open %s: %s",
1426                    lastlog_file, strerror(errno));
1427                return 0;
1428        }
1429
1430        if (type == LL_FILE) {
1431                /* find this uid's offset in the lastlog file */
1432                offset = (off_t) ((long)li->uid * sizeof(struct lastlog));
1433
1434                if ( lseek(*fd, offset, SEEK_SET) != offset ) {
1435                        log("lastlog_openseek: %s->lseek(): %s",
1436                         lastlog_file, strerror(errno));
1437                        return 0;
1438                }
1439        }
1440
1441        return 1;
1442}
1443
1444static int
1445lastlog_perform_login(struct logininfo *li)
1446{
1447        struct lastlog last;
1448        int fd;
1449
1450        /* create our struct lastlog */
1451        lastlog_construct(li, &last);
1452
1453        if (!lastlog_openseek(li, &fd, O_RDWR|O_CREAT))
1454                return(0);
1455
1456        /* write the entry */
1457        if (atomicio(write, fd, &last, sizeof(last)) != sizeof(last)) {
1458                close(fd);
1459                log("lastlog_write_filemode: Error writing to %s: %s",
1460                    LASTLOG_FILE, strerror(errno));
1461                return 0;
1462        }
1463
1464        close(fd);
1465        return 1;
1466}
1467
1468int
1469lastlog_write_entry(struct logininfo *li)
1470{
1471        switch(li->type) {
1472        case LTYPE_LOGIN:
1473                return lastlog_perform_login(li);
1474        default:
1475                log("lastlog_write_entry: Invalid type field");
1476                return 0;
1477        }
1478}
1479
1480static void
1481lastlog_populate_entry(struct logininfo *li, struct lastlog *last)
1482{
1483        line_fullname(li->line, last->ll_line, sizeof(li->line));
1484        strlcpy(li->hostname, last->ll_host,
1485                MIN_SIZEOF(li->hostname, last->ll_host));
1486        li->tv_sec = last->ll_time;
1487}
1488
1489int
1490lastlog_get_entry(struct logininfo *li)
1491{
1492        struct lastlog last;
1493        int fd;
1494
1495        if (!lastlog_openseek(li, &fd, O_RDONLY))
1496                return 0;
1497
1498        if (atomicio(read, fd, &last, sizeof(last)) != sizeof(last)) {
1499                close(fd);
1500                log("lastlog_get_entry: Error reading from %s: %s",
1501                    LASTLOG_FILE, strerror(errno));
1502                return 0;
1503        }
1504
1505        close(fd);
1506
1507        lastlog_populate_entry(li, &last);
1508
1509        return 1;
1510}
1511#endif /* USE_LASTLOG */
Note: See TracBrowser for help on using the repository browser.