2 Copyright (C) 2001-2015 Ben Kibbey <bjk@luxsci.net>
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA
25 #include <sys/types.h>
35 #ifdef _POSIX2_LINE_MAX
36 #define LINE_MAX _POSIX2_LINE_MAX
44 #include "../strsep.c"
53 #define LOGIN_OPTION_ORDER "pdimyhtl"
54 #define LOGIN_OPTION_STRING "Lpdimyhtl:"
56 static char options
[9]; /* NULL terminated. */
57 static char *last_options
;
58 static char **strings
;
60 static int login_count
;
61 #if (!defined(__FreeBSD_version) || __FreeBSD_version < 900000)
65 void add_string(char ***, const char *);
66 char *stamp(time_t, const char *);
67 char *safe_strncat(char *, const char *, size_t);
69 void ui_module_init(int *chainable
)
87 #if (!defined(__FreeBSD_version) || __FreeBSD_version < 900000)
93 static void free_logins(UTMP
**u
)
101 for (up
= u
; *up
; up
++)
109 /* This is for *BSD (login process id). */
111 static char *get_pid(uid_t uid
, int multi
)
114 static char line
[LINE_MAX
];
116 char errbuf
[LINE_MAX
];
117 struct kinfo_proc
*kp
;
127 #if defined(__NetBSD__) || defined (__OpenBSD__)
128 if ((kd
= kvm_openfiles(NULL
, NULL
, NULL
, O_RDONLY
, errbuf
)) == NULL
) {
130 if ((kd
= kvm_openfiles(_PATH_DEVNULL
, _PATH_DEVNULL
, _PATH_DEVNULL
,
131 O_RDONLY
, errbuf
)) == NULL
) {
139 if ((kp
= kvm_getprocs(kd
, KERN_PROC_UID
, uid
, sizeof(struct kinfo_proc
),
141 warnx("kvm_getprocs(): %s", kvm_geterr(kd
));
145 if ((kp
= kvm_getprocs(kd
, KERN_PROC_UID
, uid
, &cnt
)) == NULL
) {
146 warnx("kvm_getprocs(): %s", kvm_geterr(kd
));
151 for (i
= 0; i
< cnt
; i
++) {
156 if ((kp
[i
].p_eflag
& EPROC_SLEADER
) && kp
[i
].p_tdev
!= -1)
159 #if __FreeBSD_version < 500000
160 if (kp
[i
].kp_eproc
.e_flag
& EPROC_SLEADER
&& kp
[i
].kp_eproc
.e_tdev
162 pid
= kp
[i
].kp_eproc
.e_ppid
;
164 * pid = kp[i].kp_proc.p_pid;
168 if (kp
[i
].ki_kiflag
& KI_SLEADER
&& kp
[i
].ki_tdev
!= -1) {
173 if (!pid
|| pid
== 1)
176 snprintf(buf
, sizeof(buf
), "%i%c", pid
, multi
);
177 safe_strncat(line
, buf
, sizeof(line
));
183 line
[strlen(line
) - 1] = '\0';
187 /* This is for Linux and Solaris. */
188 #elif defined(HAVE_PROCFS)
189 #include <sys/types.h>
190 #include <sys/stat.h>
201 static char *get_pid(uid_t uid
, int multi
)
206 static char line
[LINE_MAX
];
207 pid_t
*pids
= 0, *tpids
;
212 struct pstatus pstat
;
219 if (!procdir
&& firstrun
)
225 if ((procdir
= opendir("/proc")) == NULL
) {
234 while ((ent
= readdir(procdir
)) != NULL
) {
236 char filename
[FILENAME_MAX
];
244 if (!isdigit((unsigned char) *ent
->d_name
))
248 snprintf(filename
, sizeof(filename
), "/proc/%s/stat", ent
->d_name
);
250 snprintf(filename
, sizeof(filename
), "/proc/%s/status", ent
->d_name
);
253 if (stat(filename
, &st
) == -1)
257 * The current user owns this file (process id).
259 if (st
.st_uid
== uid
) {
261 if ((fd
= open(filename
, O_RDONLY
)) == -1)
264 if (pread(fd
, &pstat
, sizeof(struct pstatus
), 0) !=
265 sizeof(struct pstatus
)) {
273 if ((fp
= fopen(filename
, "r")) == NULL
)
276 if ((t
= fgets(buf
, sizeof(buf
), fp
)) == NULL
) {
282 if ((i
= sscanf(buf
, "%*i %*s %*c %*i %*i %i", &pid
)) < 1) {
286 if ((i = sscanf(buf, "%*s %*i %li", &ppid)) < 1) {
297 * Skip duplicate pids.
299 for (i
= 0; i
< pid_index
; i
++) {
304 snprintf(buf
, sizeof(buf
), "%li%c", (unsigned long) pid
, multi
);
305 safe_strncat(line
, buf
, sizeof(line
));
308 realloc(pids
, (pid_index
+ 2) * sizeof(pid_t
*))) == NULL
) {
314 pids
[pid_index
++] = pid
;
324 line
[strlen(line
) - 1] = '\0';
328 /* Unsupported OS. */
329 static char *get_pid(uid_t uid
, int multi
)
336 /* Break up the last login string into sections and add the sections to the
337 * output string array if needed. */
338 static void last_strings(char *str
)
342 const char *line
, *host
, *when
;
344 line
= host
= when
= (str
) ? "-" : "!";
346 while ((buf
= strsep(&str
, ",")) != NULL
) {
365 for (i
= 0; i
< strlen(last_options
); i
++) {
366 switch (last_options
[i
]) {
368 add_string(&strings
, line
);
371 add_string(&strings
, host
);
374 add_string(&strings
, when
);
377 add_string(&strings
, line
);
378 add_string(&strings
, host
);
379 add_string(&strings
, when
);
386 /* Get the lastlog structure from the lastlog file. */
387 #ifdef HAVE_GETLASTLOGX
388 static char *lastlogin(const struct passwd
*pw
, char *tf
)
390 struct lastlogx
*last
= getlastlogx(_PATH_LASTLOGX
, pw
->pw_uid
, NULL
);
391 static char buf
[LINE_MAX
];
396 snprintf(buf
, sizeof(buf
), "%s,%s,%s",
397 !last
->ll_line
[0] ? "!" : last
->ll_line
,
398 (!last
->ll_host
[0] || !isalnum(last
->ll_host
[0])) ? "-" : last
->ll_host
,
399 stamp(last
->ll_tv
.tv_sec
, tf
));
403 #if __FreeBSD_version >= 900000
404 static char *lastlogin(const struct passwd
*pw
, char *tf
)
407 static char buf
[LINE_MAX
];
409 if (setutxdb(UTXDB_LASTLOGIN
, NULL
) == -1) {
414 last
= getutxuser(pw
->pw_name
);
419 snprintf(buf
, sizeof(buf
), "%s,%s,%s",
420 !last
->ut_line
[0] ? "!" : last
->ut_line
,
421 (!last
->ut_host
[0] || !isalnum(last
->ut_host
[0])) ? "-" : last
->ut_host
,
422 stamp(last
->ut_tv
.tv_sec
, tf
));
427 static char *lastlogin(const struct passwd
*pw
, char *tf
)
431 static char buf
[LINE_MAX
];
438 if ((lastlogfd
= open(_PATH_LASTLOG
, O_RDONLY
)) == -1) {
439 warn("%s", _PATH_LASTLOG
);
444 offset
= (long) pw
->pw_uid
* sizeof(struct lastlog
);
446 if (lseek(lastlogfd
, offset
, SEEK_SET
) == -1) {
447 warn("%s", _PATH_LASTLOG
);
451 if ((count
= read(lastlogfd
, &last
, sizeof(struct lastlog
))) !=
452 sizeof(struct lastlog
)) {
454 warn("%s", _PATH_LASTLOG
);
461 last
.ll_host
[UTX_HOSTSIZE
-1] = '\0';
462 last
.ll_line
[UTX_LINESIZE
-1] = '\0';
464 last
.ll_host
[UT_HOSTSIZE
-1] = '\0';
465 last
.ll_line
[UT_LINESIZE
-1] = '\0';
468 last
.ll_host
[UT_HOSTSIZE
-1] = '\0';
469 last
.ll_line
[UT_LINESIZE
-1] = '\0';
472 snprintf(buf
, sizeof(buf
), "%s,%s,%s",
473 !last
.ll_line
[0] ? "!" : last
.ll_line
,
474 (!last
.ll_host
[0] || !isalnum(last
.ll_host
[0])) ?
475 !isdigit(last
.ll_line
[3]) ? "!" : "-" : last
.ll_host
,
476 !last
.ll_time
? "!" : stamp(last
.ll_time
, tf
));
482 /* This will return an array of utmp structures if a user is logged in, NULL
483 * otherwise. We'll try to keep the utmp file descriptor open if possible to
484 * speed things up a bit. */
485 static UTMP
**get_utmp(const char *user
)
487 UTMP
**logins
= NULL
;
488 #if defined HAVE_UTMPX_H && defined (HAVE_SETUTXENT)
499 if ((fd
= open(_PATH_UTMP
, O_RDONLY
)) == -1) {
500 warn("%s", _PATH_UTMP
);
508 #if defined HAVE_UTMPX_H && defined (HAVE_SETUTXENT)
511 while ((u
= getutxent()) != NULL
) {
512 if (!strncmp(u
->ut_user
, user
, UT_NAMESIZE
) && u
->ut_type
!= DEAD_PROCESS
) {
514 lseek(fd
, 0, SEEK_SET
);
516 while ((count
= read(fd
, &u
, sizeof(UTMP
))) == sizeof(UTMP
)) {
517 if (strncmp(u
.ut_name
, user
, UT_NAMESIZE
) == 0) {
521 if ((tmp
= realloc(logins
,
522 (login_count
+ 2) * sizeof(UTMP
*))) ==
531 if ((logins
[login_count
] = malloc(sizeof(UTMP
))) == NULL
) {
537 #if defined HAVE_UTMPX_H && defined (HAVE_SETUTXENT)
539 memcpy(logins
[login_count
]->ut_name
, u
->ut_name
, UTX_NAMESIZE
);
540 logins
[login_count
]->ut_name
[UTX_NAMESIZE
-1] = 0;
541 memcpy(logins
[login_count
]->ut_line
, u
->ut_line
, UTX_LINESIZE
);
542 logins
[login_count
]->ut_line
[UTX_LINESIZE
-1] = 0;
543 memcpy(logins
[login_count
]->ut_host
, u
->ut_host
, UTX_HOSTSIZE
);
544 logins
[login_count
]->ut_host
[UTX_HOSTSIZE
-1] = 0;
545 logins
[login_count
]->ut_pid
= u
->ut_pid
;
547 memcpy(logins
[login_count
]->ut_user
, u
->ut_user
, UT_NAMESIZE
);
548 logins
[login_count
]->ut_user
[UT_NAMESIZE
-1] = 0;
549 memcpy(logins
[login_count
]->ut_line
, u
->ut_line
, UT_LINESIZE
);
550 logins
[login_count
]->ut_line
[UT_LINESIZE
-1] = 0;
551 memcpy(logins
[login_count
]->ut_host
, u
->ut_host
, UT_HOSTSIZE
);
552 logins
[login_count
]->ut_host
[UT_HOSTSIZE
-1] = 0;
553 logins
[login_count
]->ut_tv
.tv_sec
= u
->ut_tv
.tv_sec
;
554 logins
[login_count
]->ut_pid
= u
->ut_pid
;
557 memcpy(logins
[login_count
]->ut_name
, u
.ut_name
, UT_NAMESIZE
);
558 logins
[login_count
]->ut_name
[UT_NAMESIZE
-1] = 0;
559 memcpy(logins
[login_count
]->ut_line
, u
.ut_line
, UT_LINESIZE
);
560 logins
[login_count
]->ut_line
[UT_LINESIZE
-1] = 0;
561 memcpy(logins
[login_count
]->ut_host
, u
.ut_host
, UT_HOSTSIZE
);
562 logins
[login_count
]->ut_host
[UT_HOSTSIZE
-1] = 0;
563 logins
[login_count
]->ut_time
= u
.ut_time
;
565 logins
[++login_count
] = NULL
;
572 /* The 'mesg' status of the logged in user. */
573 static const char *msgstat(UTMP
**u
, int multi
)
575 static char line
[LINE_MAX
];
580 for (i
= 0; i
< login_count
; i
++) {
581 char filename
[FILENAME_MAX
];
583 char m
[2] = { multi
, '\0' };
585 snprintf(filename
, sizeof(filename
), "%s%s", _PATH_DEV
, u
[i
]->ut_line
);
587 if (stat(filename
, &st
) == -1)
588 safe_strncat(line
, "!", sizeof(line
));
591 (st
.st_mode
& S_IWGRP
|| st
.st_mode
& S_IWOTH
) ? "1" : "0",
594 safe_strncat(line
, m
, sizeof(line
));
600 line
[strlen(line
) - 1] = '\0';
604 /* Returns the users idle time in seconds. */
605 static const char *idle(UTMP
**u
, int multi
)
607 static char line
[LINE_MAX
];
614 for (i
= 0; i
< login_count
; i
++) {
615 char buf
[FILENAME_MAX
];
616 char m
[2] = { multi
, '\0' };
618 snprintf(buf
, sizeof(buf
), "%s%s", _PATH_DEV
, u
[i
]->ut_line
);
620 if (stat(buf
, &st
) == -1) {
621 safe_strncat(line
, "!", sizeof(line
));
622 safe_strncat(line
, m
, sizeof(line
));
627 if (u
[i
]->ut_tv
.tv_sec
> st
.st_atime
) {
629 if (u
[i
]->ut_time
> st
.st_atime
) {
631 safe_strncat(line
, "-", sizeof(line
));
632 safe_strncat(line
, m
, sizeof(line
));
639 if (t
< u
[i
]->ut_tv
.tv_sec
)
640 t
= u
[i
]->ut_tv
.tv_sec
;
642 if (t
< u
[i
]->ut_time
)
646 snprintf(buf
, sizeof(buf
), "%lu", (now
- t
<= 0) ? 0 : now
- t
);
647 safe_strncat(line
, buf
, sizeof(line
));
648 safe_strncat(line
, m
, sizeof(line
));
654 line
[strlen(line
) - 1] = '\0';
658 /* This is output if the -h command line option is passed to the main program.
660 void ui_module_help()
662 printf(" Login information [-L (-%s)]:\n", LOGIN_OPTION_ORDER
);
663 printf("\t-y tty\t\t\t\t");
664 printf("-m message status\n");
665 printf("\t-t login time stamp\t\t");
666 printf("-d duration in minutes\n");
667 printf("\t-h hostname\t\t\t");
668 printf("-i seconds idle\n");
669 printf("\t-p login process id\n");
670 printf("\t-l lastlog information"
671 " (any of tt[y],[h]ostname,[t]ime, or [a]ll)\n\n");
675 /* This is the equivalent to main() only without argc and argv available. */
676 int ui_module_exec(char ***s
, const struct passwd
*pw
, const int multi
,
677 const int verbose
, char *tf
)
684 u
= get_utmp(pw
->pw_name
);
688 char line
[LINE_MAX
] = { '\0' };
690 char m
[2] = { multi
, '\0' };
694 add_string(&strings
, (u
) ? idle(u
, multi
) : "!");
697 last_strings(lastlogin(pw
, tf
));
700 for (i
= 0; i
< login_count
; i
++) {
702 && isalnum((unsigned char) u
[i
]->ut_host
[0]))
703 safe_strncat(line
, u
[i
]->ut_host
, sizeof(line
));
705 safe_strncat(line
, "-", sizeof(line
));
707 safe_strncat(line
, m
, sizeof(line
));
711 strncpy(line
, "!", sizeof(line
));
713 line
[strlen(line
) - 1] = '\0';
715 add_string(&strings
, line
);
718 for (i
= 0; i
< login_count
; i
++) {
719 if (u
[i
]->ut_line
[0])
720 safe_strncat(line
, u
[i
]->ut_line
, sizeof(line
));
722 safe_strncat(line
, "!", sizeof(line
));
724 safe_strncat(line
, m
, sizeof(line
));
728 strncpy(line
, "!", sizeof(line
));
730 line
[strlen(line
) - 1] = '\0';
732 add_string(&strings
, line
);
735 add_string(&strings
, u
? msgstat(u
, multi
) : "!");
738 for (i
= 0; i
< login_count
; i
++) {
740 safe_strncat(line
, stamp(u
[i
]->ut_tv
.tv_sec
, tf
), sizeof(line
));
742 safe_strncat(line
, stamp(u
[i
]->ut_time
, tf
), sizeof(line
));
744 safe_strncat(line
, m
, sizeof(line
));
748 strncpy(line
, "!", sizeof(line
));
750 line
[strlen(line
) - 1] = '\0';
752 add_string(&strings
, line
);
755 for (i
= 0; i
< login_count
; i
++) {
757 if ((now
- u
[i
]->ut_tv
.tv_sec
) > 60) {
758 snprintf(buf
, sizeof(buf
), "%lu",
759 ((now
- u
[i
]->ut_tv
.tv_sec
) / 60));
761 if ((now
- u
[i
]->ut_time
) > 60) {
762 snprintf(buf
, sizeof(buf
), "%lu",
763 ((now
- u
[i
]->ut_time
) / 60));
765 safe_strncat(line
, buf
, sizeof(line
));
768 safe_strncat(line
, "-", sizeof(line
));
770 safe_strncat(line
, m
, sizeof(line
));
774 strncpy(line
, "!", sizeof(line
));
776 line
[strlen(line
) - 1] = '\0';
778 add_string(&strings
, line
);
782 for (i
= 0; i
< login_count
; i
++) {
784 snprintf(buf
, sizeof(buf
), "%li", (long) u
[i
]->ut_pid
);
785 safe_strncat(line
, buf
, sizeof(line
));
788 safe_strncat(line
, "!", sizeof(line
));
790 safe_strncat(line
, m
, sizeof(line
));
794 strncpy(line
, "!", sizeof(line
));
796 line
[strlen(line
) - 1] = '\0';
798 add_string(&strings
, line
);
800 add_string(&strings
, (u
) ? get_pid(pw
->pw_uid
, multi
) : "!");
815 /* See if the last login options (-l) are valid. */
816 static int parse_last_options(const char *args
)
820 for (i
= 0; i
< strlen(args
); i
++) {
835 const char *ui_module_options_init(char **defaults
)
837 *defaults
= (char *)"L";
838 return LOGIN_OPTION_STRING
;
841 /* Check module option validity. */
842 int ui_module_options(int argc
, char **argv
)
847 while ((opt
= getopt(argc
, argv
, LOGIN_OPTION_STRING
)) != -1) {
850 if (parse_last_options(optarg
))
853 last_options
= optarg
;
856 strncpy(options
, LOGIN_OPTION_ORDER
, sizeof(options
));
857 last_options
= (char *)"a";
868 warnx("login: invalid option -- %c", optopt
);