login.so: OpenBSD fixes.
[userinfo.git] / src / modules / login.c
blob4a8039442e9a7674ac3744fe72991509ccda21d8
1 /*
2 Copyright (C) 2001-2013 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
18 #ifdef HAVE_CONFIG_H
19 #include <config.h>
20 #endif
22 #include <stdio.h>
23 #include <unistd.h>
24 #include <stdlib.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <errno.h>
28 #include <time.h>
29 #include <pwd.h>
30 #include <ctype.h>
32 #ifdef HAVE_LIMITS_H
33 #include <limits.h>
34 #ifndef LINE_MAX
35 #ifdef _POSIX2_LINE_MAX
36 #define LINE_MAX _POSIX2_LINE_MAX
37 #else
38 #define LINE_MAX 2048
39 #endif
40 #endif
41 #endif
43 #ifndef HAVE_STRSEP
44 #include "../strsep.c"
45 #endif
47 #ifndef HAVE_ERR_H
48 #include "../err.c"
49 #endif
51 #include "login.h"
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;
59 static time_t now;
60 static int login_count;
61 #if (!defined(__FreeBSD_version) || __FreeBSD_version < 900000)
62 static int lastlogfd;
63 #endif
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)
71 *chainable = 0;
72 time(&now);
75 void ui_module_exit()
77 #ifdef HAVE_PROCFS
78 if (procdir)
79 closedir(procdir);
80 #endif
82 #ifdef HAVE_KVM_H
83 if (kd)
84 kvm_close(kd);
85 #endif
87 #if (!defined(__FreeBSD_version) || __FreeBSD_version < 900000)
88 if (lastlogfd)
89 close(lastlogfd);
90 #endif
93 static void free_logins(UTMP **u)
95 if (login_count) {
96 UTMP **up;
98 for (up = u; *up; up++)
99 free(*up);
101 free(u);
105 #ifndef HAVE_UTMPX_H
106 /* This is for *BSD (login process id). */
107 #ifdef BSD_KVM
108 static char *get_pid(uid_t uid, int multi)
110 static int firstrun;
111 static char line[LINE_MAX];
112 int cnt, i;
113 char errbuf[LINE_MAX];
114 struct kinfo_proc *kp;
116 line[0] = '\0';
118 if (!kd && firstrun)
119 return "!";
121 if (!kd) {
122 firstrun = 1;
124 #if defined(__NetBSD__) || defined (__OpenBSD__)
125 if ((kd = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, errbuf)) == NULL) {
126 #else
127 if ((kd = kvm_openfiles(_PATH_DEVNULL, _PATH_DEVNULL, _PATH_DEVNULL,
128 O_RDONLY, errbuf)) == NULL) {
129 #endif
130 warnx("%s", errbuf);
131 return "!";
135 #ifdef __OpenBSD__
136 if ((kp = kvm_getprocs(kd, KERN_PROC_UID, uid, sizeof(struct kinfo_proc),
137 &cnt)) == NULL) {
138 warnx("kvm_getprocs(): %s", kvm_geterr(kd));
139 return "!";
141 #else
142 if ((kp = kvm_getprocs(kd, KERN_PROC_UID, uid, &cnt)) == NULL) {
143 warnx("kvm_getprocs(): %s", kvm_geterr(kd));
144 return "!";
146 #endif
148 for (i = 0; i < cnt; i++) {
149 char buf[32];
150 pid_t pid = 0;
152 #ifdef __OpenBSD__
153 if ((kp[i].p_eflag & EPROC_SLEADER) && kp[i].p_tdev != -1)
154 pid = kp[i].p_pid;
155 #else
156 #if __FreeBSD_version < 500000
157 if (kp[i].kp_eproc.e_flag & EPROC_SLEADER && kp[i].kp_eproc.e_tdev
158 != -1) {
159 pid = kp[i].kp_eproc.e_ppid;
161 * pid = kp[i].kp_proc.p_pid;
164 #else
165 if (kp[i].ki_kiflag & KI_SLEADER && kp[i].ki_tdev != -1) {
166 pid = kp[i].ki_pid;
168 #endif
169 #endif
170 if (!pid || pid == 1)
171 continue;
173 snprintf(buf, sizeof(buf), "%i%c", pid, multi);
174 safe_strncat(line, buf, sizeof(line));
177 if (line[0] == '\0')
178 return "!";
180 line[strlen(line) - 1] = '\0';
181 return line;
184 /* This is for Linux and Solaris. */
185 #elif defined(HAVE_PROCFS)
186 #include <sys/types.h>
187 #include <sys/stat.h>
189 #ifdef HAVE_DIRENT_H
190 #include <dirent.h>
191 #endif
193 #ifdef __sun__
194 #include <unistd.h>
195 #include <procfs.h>
196 #endif
198 static char *get_pid(uid_t uid, int multi)
200 static int firstrun;
201 struct dirent *ent;
202 struct stat st;
203 static char line[LINE_MAX];
204 pid_t *pids = 0, *tpids;
205 int pid_index = 0;
207 #ifdef __sun__
208 int fd;
209 struct pstatus pstat;
210 #else
211 FILE *fp;
212 #endif
214 line[0] = '\0';
216 if (!procdir && firstrun)
217 return "!";
219 if (!procdir) {
220 firstrun = 1;
222 if ((procdir = opendir("/proc")) == NULL) {
223 warn("%s", "/proc");
224 return "!";
228 rewinddir(procdir);
230 again:
231 while ((ent = readdir(procdir)) != NULL) {
232 pid_t pid = -1;
233 char filename[FILENAME_MAX];
234 char buf[LINE_MAX];
235 int i;
237 #ifndef __sun__
238 char *t;
239 #endif
241 if (!isdigit((unsigned char) *ent->d_name))
242 continue;
244 #ifdef __linux__
245 snprintf(filename, sizeof(filename), "/proc/%s/stat", ent->d_name);
246 #else
247 snprintf(filename, sizeof(filename), "/proc/%s/status", ent->d_name);
248 #endif
250 if (stat(filename, &st) == -1)
251 continue;
254 * The current user owns this file (process id).
256 if (st.st_uid == uid) {
257 #ifdef __sun__
258 if ((fd = open(filename, O_RDONLY)) == -1)
259 continue;
261 if (pread(fd, &pstat, sizeof(struct pstatus), 0) !=
262 sizeof(struct pstatus)) {
263 close(fd);
264 continue;
267 pid = pstat.pr_ppid;
268 close(fd);
269 #else
270 if ((fp = fopen(filename, "r")) == NULL)
271 continue;
273 if ((t = fgets(buf, sizeof(buf), fp)) == NULL) {
274 fclose(fp);
275 continue;
278 #ifdef __linux__
279 if ((i = sscanf(buf, "%*i %*s %*c %*i %*i %i", &pid)) < 1) {
280 #endif
282 #else
283 if ((i = sscanf(buf, "%*s %*i %li", &ppid)) < 1) {
284 #endif
286 fclose(fp);
287 continue;
290 fclose(fp);
291 #endif
294 * Skip duplicate pids.
296 for (i = 0; i < pid_index; i++) {
297 if (pids[i] == pid)
298 goto again;
301 snprintf(buf, sizeof(buf), "%li%c", (unsigned long) pid, multi);
302 safe_strncat(line, buf, sizeof(line));
304 if ((tpids =
305 realloc(pids, (pid_index + 2) * sizeof(pid_t *))) == NULL) {
306 warn("realloc()");
307 continue;
310 pids = tpids;
311 pids[pid_index++] = pid;
315 if (pid_index)
316 free(pids);
318 if (line[0] == '\0')
319 return "!";
321 line[strlen(line) - 1] = '\0';
322 return line;
324 #else
325 /* Unsupported OS. */
326 static char *get_pid(uid_t uid, int multi)
328 return "!";
330 #endif
331 #endif
333 /* Break up the last login string into sections and add the sections to the
334 * output string array if needed. */
335 static void last_strings(char *str)
337 int i = 0;
338 char *buf;
339 const char *line, *host, *when;
341 line = host = when = (str) ? "-" : "!";
343 while ((buf = strsep(&str, ",")) != NULL) {
344 if (!buf[0])
345 continue;
347 switch (i++) {
348 case 0:
349 line = buf;
350 break;
351 case 1:
352 host = buf;
353 break;
354 case 2:
355 when = buf;
356 break;
357 default:
358 break;
362 for (i = 0; i < strlen(last_options); i++) {
363 switch (last_options[i]) {
364 case 'y':
365 add_string(&strings, line);
366 break;
367 case 'h':
368 add_string(&strings, host);
369 break;
370 case 't':
371 add_string(&strings, when);
372 break;
373 case 'a':
374 add_string(&strings, line);
375 add_string(&strings, host);
376 add_string(&strings, when);
377 default:
378 break;
383 /* Get the lastlog structure from the lastlog file. */
384 #if __FreeBSD_version >= 900000
385 static char *lastlogin(const struct passwd *pw, char *tf)
387 struct utmpx *last;
388 static char buf[LINE_MAX];
390 if (setutxdb(UTXDB_LASTLOGIN, NULL) == -1) {
391 warn("lastlog");
392 return NULL;
395 last = getutxuser(pw->pw_name);
397 if (!last)
398 return NULL;
400 snprintf(buf, sizeof(buf), "%s,%s,%s",
401 !last->ut_line[0] ? "!" : last->ut_line,
402 (!last->ut_host[0] || !isalnum(last->ut_host[0])) ? "-" : last->ut_host,
403 stamp(last->ut_tv.tv_sec, tf));
405 return buf;
407 #else
408 static char *lastlogin(const struct passwd *pw, char *tf)
410 int count;
411 long offset;
412 static char buf[LINE_MAX];
413 struct lastlog last;
415 if (lastlogfd < 0)
416 return NULL;
418 if (!lastlogfd) {
419 if ((lastlogfd = open(_PATH_LASTLOG, O_RDONLY)) == -1) {
420 warn("%s", _PATH_LASTLOG);
421 return NULL;
425 offset = (long) pw->pw_uid * sizeof(struct lastlog);
427 if (lseek(lastlogfd, offset, SEEK_SET) == -1) {
428 warn("%s", _PATH_LASTLOG);
429 return NULL;
432 if ((count = read(lastlogfd, &last, sizeof(struct lastlog))) !=
433 sizeof(struct lastlog)) {
434 if (count == -1)
435 warn("%s", _PATH_LASTLOG);
437 return NULL;
440 #ifdef __NetBSD__
441 #ifdef HAVE_UTMPX_H
442 last.ll_host[UTX_HOSTSIZE-1] = '\0';
443 last.ll_line[UTX_LINESIZE-1] = '\0';
444 #else
445 last.ll_host[UT_HOSTSIZE-1] = '\0';
446 last.ll_line[UT_LINESIZE-1] = '\0';
447 #endif
448 #else
449 last.ll_host[UT_HOSTSIZE-1] = '\0';
450 last.ll_line[UT_LINESIZE-1] = '\0';
451 #endif
453 snprintf(buf, sizeof(buf), "%s,%s,%s",
454 !last.ll_line[0] ? "!" : last.ll_line,
455 (!last.ll_host[0] || !isalnum(last.ll_host[0])) ?
456 !isdigit(last.ll_line[3]) ? "!" : "-" : last.ll_host,
457 !last.ll_time ? "!" : stamp(last.ll_time, tf));
458 return buf;
460 #endif
462 /* This will return an array of utmp structures if a user is logged in, NULL
463 * otherwise. We'll try to keep the utmp file descriptor open if possible to
464 * speed things up a bit. */
465 static UTMP **get_utmp(const char *user)
467 UTMP **logins = NULL;
468 #if defined HAVE_UTMPX_H && defined (HAVE_SETUTXENT)
469 UTMP *u;
470 #else
471 UTMP u;
472 int count;
473 static int fd;
475 if (fd < 0)
476 return NULL;
478 if (!fd) {
479 if ((fd = open(_PATH_UTMP, O_RDONLY)) == -1) {
480 warn("%s", _PATH_UTMP);
481 return NULL;
484 #endif
486 login_count = 0;
488 #if defined HAVE_UTMPX_H && defined (HAVE_SETUTXENT)
489 setutxent();
491 while ((u = getutxent()) != NULL) {
492 if (!strcmp(u->ut_user, user) && u->ut_type != DEAD_PROCESS) {
493 #else
494 lseek(fd, 0, SEEK_SET);
496 while ((count = read(fd, &u, sizeof(UTMP))) == sizeof(UTMP)) {
497 if (strcmp(u.ut_name, user) == 0) {
498 #endif
499 UTMP **tmp;
501 if ((tmp = realloc(logins,
502 (login_count + 2) * sizeof(UTMP *))) ==
503 NULL) {
504 warn("realloc()");
505 free_logins(logins);
506 return NULL;
509 logins = tmp;
511 if ((logins[login_count] = malloc(sizeof(UTMP))) == NULL) {
512 warn("malloc()");
513 free_logins(logins);
514 return NULL;
517 #if defined HAVE_UTMPX_H && defined (HAVE_SETUTXENT)
518 #ifdef __NetBSD__
519 memcpy(logins[login_count]->ut_name, u->ut_name, UTX_NAMESIZE);
520 logins[login_count]->ut_name[UTX_NAMESIZE-1] = 0;
521 memcpy(logins[login_count]->ut_line, u->ut_line, UTX_LINESIZE);
522 logins[login_count]->ut_line[UTX_LINESIZE-1] = 0;
523 memcpy(logins[login_count]->ut_host, u->ut_host, UTX_HOSTSIZE);
524 logins[login_count]->ut_host[UTX_HOSTSIZE-1] = 0;
525 logins[login_count]->ut_pid = u->ut_pid;
526 #else
527 memcpy(logins[login_count]->ut_user, u->ut_user, UT_NAMESIZE);
528 logins[login_count]->ut_user[UT_NAMESIZE-1] = 0;
529 memcpy(logins[login_count]->ut_line, u->ut_line, UT_LINESIZE);
530 logins[login_count]->ut_line[UT_LINESIZE-1] = 0;
531 memcpy(logins[login_count]->ut_host, u->ut_host, UT_HOSTSIZE);
532 logins[login_count]->ut_host[UT_HOSTSIZE-1] = 0;
533 logins[login_count]->ut_tv.tv_sec = u->ut_tv.tv_sec;
534 logins[login_count]->ut_pid = u->ut_pid;
535 #endif
536 #else
537 memcpy(logins[login_count]->ut_name, u.ut_name, UT_NAMESIZE);
538 logins[login_count]->ut_name[UT_NAMESIZE-1] = 0;
539 memcpy(logins[login_count]->ut_line, u.ut_line, UT_LINESIZE);
540 logins[login_count]->ut_line[UT_LINESIZE-1] = 0;
541 memcpy(logins[login_count]->ut_host, u.ut_host, UT_HOSTSIZE);
542 logins[login_count]->ut_host[UT_HOSTSIZE-1] = 0;
543 logins[login_count]->ut_time = u.ut_time;
544 #endif
545 logins[++login_count] = NULL;
549 return logins;
552 /* The 'mesg' status of the logged in user. */
553 static char *msgstat(UTMP **u, int multi)
555 static char line[LINE_MAX];
556 int i;
558 line[0] = '\0';
560 for (i = 0; i < login_count; i++) {
561 char filename[FILENAME_MAX];
562 struct stat st;
563 char m[2] = { multi, '\0' };
565 snprintf(filename, sizeof(filename), "%s%s", _PATH_DEV, u[i]->ut_line);
567 if (stat(filename, &st) == -1)
568 safe_strncat(line, "!", sizeof(line));
569 else
570 safe_strncat(line,
571 (st.st_mode & S_IWGRP || st.st_mode & S_IWOTH) ? "1" : "0",
572 sizeof(line));
574 safe_strncat(line, m, sizeof(line));
577 if (line[0] == '\0')
578 return "!";
580 line[strlen(line) - 1] = '\0';
581 return line;
584 /* Returns the users idle time in seconds. */
585 static char *idle(UTMP **u, int multi)
587 static char line[LINE_MAX];
588 time_t t;
589 struct stat st;
590 int i;
592 line[0] = '\0';
594 for (i = 0; i < login_count; i++) {
595 char buf[FILENAME_MAX];
596 char m[2] = { multi, '\0' };
598 snprintf(buf, sizeof(buf), "%s%s", _PATH_DEV, u[i]->ut_line);
600 if (stat(buf, &st) == -1) {
601 safe_strncat(line, "!", sizeof(line));
602 safe_strncat(line, m, sizeof(line));
603 continue;
606 #ifdef HAVE_UTMPX_H
607 if (u[i]->ut_tv.tv_sec > st.st_atime) {
608 #else
609 if (u[i]->ut_time > st.st_atime) {
610 #endif
611 safe_strncat(line, "-", sizeof(line));
612 safe_strncat(line, m, sizeof(line));
613 continue;
616 t = st.st_atime;
618 #ifdef HAVE_UTMPX_H
619 if (t < u[i]->ut_tv.tv_sec)
620 t = u[i]->ut_tv.tv_sec;
621 #else
622 if (t < u[i]->ut_time)
623 t = u[i]->ut_time;
624 #endif
626 snprintf(buf, sizeof(buf), "%lu", (now - t <= 0) ? 0 : now - t);
627 safe_strncat(line, buf, sizeof(line));
628 safe_strncat(line, m, sizeof(line));
631 if (line[0] == '\0')
632 return "!";
634 line[strlen(line) - 1] = '\0';
635 return line;
638 /* This is output if the -h command line option is passed to the main program.
640 void ui_module_help()
642 printf(" Login information [-L (-%s)]:\n", LOGIN_OPTION_ORDER);
643 printf("\t-y tty\t\t\t\t");
644 printf("-m message status\n");
645 printf("\t-t login time stamp\t\t");
646 printf("-d duration in minutes\n");
647 printf("\t-h hostname\t\t\t");
648 printf("-i seconds idle\n");
649 printf("\t-p login process id\n");
650 printf("\t-l lastlog information"
651 " (any of tt[y],[h]ostname,[t]ime, or [a]ll)\n\n");
652 return;
655 /* This is the equivalent to main() only without argc and argv available. */
656 int ui_module_exec(char ***s, const struct passwd *pw, const int multi,
657 const int verbose, char *tf)
659 char *p = options;
660 UTMP **u = NULL;
661 char buf[255];
663 login_count = 0;
664 u = get_utmp(pw->pw_name);
665 strings = *s;
667 while (*p) {
668 char line[LINE_MAX] = { '\0' };
669 int i;
670 char m[2] = { multi, '\0' };
672 switch (*p) {
673 case 'i':
674 add_string(&strings, (u) ? idle(u, multi) : "!");
675 break;
676 case 'l':
677 last_strings(lastlogin(pw, tf));
678 break;
679 case 'h':
680 for (i = 0; i < login_count; i++) {
681 if (u[i]->ut_host[0]
682 && isalnum((unsigned char) u[i]->ut_host[0]))
683 safe_strncat(line, u[i]->ut_host, sizeof(line));
684 else
685 safe_strncat(line, "-", sizeof(line));
687 safe_strncat(line, m, sizeof(line));
690 if (line[0] == '\0')
691 strncpy(line, "!", sizeof(line));
692 else
693 line[strlen(line) - 1] = '\0';
695 add_string(&strings, line);
696 break;
697 case 'y':
698 for (i = 0; i < login_count; i++) {
699 if (u[i]->ut_line[0])
700 safe_strncat(line, u[i]->ut_line, sizeof(line));
701 else
702 safe_strncat(line, "!", sizeof(line));
704 safe_strncat(line, m, sizeof(line));
707 if (line[0] == '\0')
708 strncpy(line, "!", sizeof(line));
709 else
710 line[strlen(line) - 1] = '\0';
712 add_string(&strings, line);
713 break;
714 case 'm':
715 add_string(&strings, msgstat(u, multi));
716 break;
717 case 't':
718 for (i = 0; i < login_count; i++) {
719 #ifdef HAVE_UTMPX_H
720 safe_strncat(line, stamp(u[i]->ut_tv.tv_sec, tf), sizeof(line));
721 #else
722 safe_strncat(line, stamp(u[i]->ut_time, tf), sizeof(line));
723 #endif
724 safe_strncat(line, m, sizeof(line));
727 if (line[0] == '\0')
728 strncpy(line, "!", sizeof(line));
729 else
730 line[strlen(line) - 1] = '\0';
732 add_string(&strings, line);
733 break;
734 case 'd':
735 for (i = 0; i < login_count; i++) {
736 #ifdef HAVE_UTMPX_H
737 if ((now - u[i]->ut_tv.tv_sec) > 60) {
738 snprintf(buf, sizeof(buf), "%lu",
739 ((now - u[i]->ut_tv.tv_sec) / 60));
740 #else
741 if ((now - u[i]->ut_time) > 60) {
742 snprintf(buf, sizeof(buf), "%lu",
743 ((now - u[i]->ut_time) / 60));
744 #endif
745 safe_strncat(line, buf, sizeof(line));
747 else
748 safe_strncat(line, "-", sizeof(line));
750 safe_strncat(line, m, sizeof(line));
753 if (line[0] == '\0')
754 strncpy(line, "!", sizeof(line));
755 else
756 line[strlen(line) - 1] = '\0';
758 add_string(&strings, line);
759 break;
760 case 'p':
761 #ifdef HAVE_UTMPX_H
762 for (i = 0; i < login_count; i++) {
763 if (u[i]->ut_pid) {
764 snprintf(buf, sizeof(buf), "%li", (long) u[i]->ut_pid);
765 safe_strncat(line, buf, sizeof(line));
767 else
768 safe_strncat(line, "!", sizeof(line));
770 safe_strncat(line, m, sizeof(line));
773 if (line[0] == '\0')
774 strncpy(line, "!", sizeof(line));
775 else
776 line[strlen(line) - 1] = '\0';
778 add_string(&strings, line);
779 #else
780 add_string(&strings, (u) ? get_pid(pw->pw_uid, multi) : "!");
781 #endif
782 break;
783 default:
784 break;
787 p++;
790 free_logins(u);
791 *s = strings;
792 return EXIT_SUCCESS;
795 /* See if the last login options (-l) are valid. */
796 static int parse_last_options(const char *args)
798 int i = 0;
800 for (i = 0; i < strlen(args); i++) {
801 switch (args[i]) {
802 case 'y':
803 case 'h':
804 case 't':
805 case 'a':
806 break;
807 default:
808 return 1;
812 return 0;
815 char *ui_module_options_init(char **defaults)
817 *defaults = "L";
818 return LOGIN_OPTION_STRING;
821 /* Check module option validity. */
822 int ui_module_options(int argc, char **argv)
824 int opt;
825 char *p = options;
827 while ((opt = getopt(argc, argv, LOGIN_OPTION_STRING)) != -1) {
828 switch (opt) {
829 case 'l':
830 if (parse_last_options(optarg))
831 return 1;
833 last_options = optarg;
834 break;
835 case 'L':
836 strncpy(options, LOGIN_OPTION_ORDER, sizeof(options));
837 last_options = "a";
838 return 0;
839 case 'p':
840 case 'd':
841 case 'i':
842 case 'm':
843 case 'y':
844 case 'h':
845 case 't':
846 break;
847 case '?':
848 warnx("login: invalid option -- %c", optopt);
849 default:
850 return 1;
853 *p++ = opt;
854 *p = '\0';
857 return 0;