login.so: cannot be an error if there is no hostname (-h and -lh); only
[userinfo.git] / src / modules / login.c
blob74245bb8c1749a2d87201fc44ba9ca58dbcb8b35
1 /*
2 Copyright (C) 2001-2011 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 #ifndef HAVE_STRSEP
33 #include "../strsep.c"
34 #endif
36 #ifndef HAVE_ERR_H
37 #include "../err.c"
38 #endif
40 #include "login.h"
41 #include "common.h"
43 #define LOGIN_OPTION_ORDER "pdimyhtl"
44 #define LOGIN_OPTION_STRING "Lpdimyhtl:"
46 static char options[9]; /* NULL terminated. */
47 static char *last_options;
48 static char **strings;
49 static int lastlogfd;
50 static time_t now;
51 static int login_count;
53 void add_string(char ***, const char *);
54 char *stamp(time_t, const char *);
56 void ui_module_init(int *chainable)
58 *chainable = 0;
59 time(&now);
62 void ui_module_exit()
64 #ifdef HAVE_PROCFS
65 if (procdir)
66 closedir(procdir);
67 #endif
69 #ifdef HAVE_KVM_H
70 if (kd)
71 kvm_close(kd);
72 #endif
74 if (lastlogfd)
75 close(lastlogfd);
78 static void free_logins(UTMP **u)
80 if (login_count) {
81 UTMP **up;
83 for (up = u; *up; up++)
84 free(*up);
86 free(u);
90 #ifndef HAVE_UTMPX_H
91 /* This is for *BSD (login process id). */
92 #ifdef BSD_KVM
93 static char *get_pid(uid_t uid, int multi)
95 static int firstrun;
96 static char line[LINE_MAX];
97 int cnt, i;
98 pid_t pid = 0;
99 char errbuf[LINE_MAX];
100 struct kinfo_proc *kp;
102 line[0] = '\0';
104 if (!kd && firstrun)
105 return "!";
107 if (!kd) {
108 firstrun = 1;
110 #ifdef __NetBSD__
111 if ((kd = kvm_openfiles(NULL, NULL, NULL,
112 O_RDONLY, errbuf)) == NULL) {
113 #else
114 if ((kd = kvm_openfiles(_PATH_DEVNULL, _PATH_DEVNULL, _PATH_DEVNULL,
115 O_RDONLY, errbuf)) == NULL) {
116 #endif
117 warnx("%s", errbuf);
118 return "!";
122 if ((kp = kvm_getprocs(kd, KERN_PROC_UID, uid, &cnt)) == NULL) {
123 warnx("kvm_getprocs(): %s", kvm_geterr(kd));
124 return "!";
127 for (i = 0; i < cnt; i++) {
128 char buf[32];
130 #if __FreeBSD_version < 500000
131 if (kp[i].kp_eproc.e_flag & EPROC_SLEADER && kp[i].kp_eproc.e_tdev !=
132 -1) {
133 pid = kp[i].kp_eproc.e_ppid;
135 * pid = kp[i].kp_proc.p_pid;
138 if (pid == 1)
139 continue;
140 #else
141 if (kp[i].ki_kiflag & KI_SLEADER && kp[i].ki_tdev != -1) {
142 pid = kp[i].ki_pid;
143 #endif
144 snprintf(buf, sizeof(buf), "%i%c", pid, multi);
145 safe_strncat(line, buf, sizeof(line));
149 if (line[0] == '\0')
150 return "!";
152 line[strlen(line) - 1] = '\0';
153 return line;
156 /* This is for Linux and Solaris. */
157 #elif defined(HAVE_PROCFS)
158 #include <sys/types.h>
159 #include <sys/stat.h>
161 #ifdef HAVE_DIRENT_H
162 #include <dirent.h>
163 #endif
165 #ifdef __svr4__
166 #include <unistd.h>
167 #include <procfs.h>
168 #endif
170 static char *get_pid(uid_t uid, int multi)
172 static int firstrun;
173 struct dirent *ent;
174 struct stat st;
175 static char line[LINE_MAX];
176 pid_t *pids = 0, *tpids;
177 int pid_index = 0;
179 #ifdef __svr4__
180 int fd;
181 struct pstatus pstat;
182 #else
183 FILE *fp;
184 #endif
186 line[0] = '\0';
188 if (!procdir && firstrun)
189 return "!";
191 if (!procdir) {
192 firstrun = 1;
194 if ((procdir = opendir("/proc")) == NULL) {
195 warn("%s", "/proc");
196 return "!";
200 rewinddir(procdir);
202 again:
203 while ((ent = readdir(procdir)) != NULL) {
204 pid_t pid = -1;
205 char filename[FILENAME_MAX];
206 char buf[LINE_MAX];
207 int i;
209 #ifndef __svr4__
210 char *t;
211 #endif
213 if (!isdigit((unsigned char) *ent->d_name))
214 continue;
216 #ifdef __linux__
217 snprintf(filename, sizeof(filename), "/proc/%s/stat", ent->d_name);
218 #else
219 snprintf(filename, sizeof(filename), "/proc/%s/status", ent->d_name);
220 #endif
222 if (stat(filename, &st) == -1)
223 continue;
226 * The current user owns this file (process id).
228 if (st.st_uid == uid) {
229 #ifdef __svr4__
230 if ((fd = open(filename, O_RDONLY)) == -1)
231 continue;
233 if (pread(fd, &pstat, sizeof(struct pstatus), 0) !=
234 sizeof(struct pstatus)) {
235 close(fd);
236 continue;
239 pid = pstat.pr_ppid;
240 close(fd);
241 #else
242 if ((fp = fopen(filename, "r")) == NULL)
243 continue;
245 if ((t = fgets(buf, sizeof(buf), fp)) == NULL) {
246 fclose(fp);
247 continue;
250 #ifdef __linux__
251 if ((i = sscanf(buf, "%*i %*s %*c %*i %*i %i", &pid)) < 1) {
252 #endif
254 #else
255 if ((i = sscanf(buf, "%*s %*i %li", &ppid)) < 1) {
256 #endif
258 fclose(fp);
259 continue;
262 fclose(fp);
263 #endif
266 * Skip duplicate pids.
268 for (i = 0; i < pid_index; i++) {
269 if (pids[i] == pid)
270 goto again;
273 snprintf(buf, sizeof(buf), "%li%c", (unsigned long) pid, multi);
274 safe_strncat(line, buf, sizeof(line));
276 if ((tpids =
277 realloc(pids, (pid_index + 2) * sizeof(pid_t *))) == NULL) {
278 warn("realloc()");
279 continue;
282 pids = tpids;
283 pids[pid_index++] = pid;
287 if (pid_index)
288 free(pids);
290 if (line[0] == '\0')
291 return "!";
293 line[strlen(line) - 1] = '\0';
294 return line;
296 #else
297 /* Unsupported OS. */
298 static char *get_pid(uid_t uid, int multi)
300 return "!";
302 #endif
303 #endif
305 /* Break up the last login string into sections and add the sections to the
306 * output string array if needed. */
307 static void last_strings(char *str)
309 int i = 0;
310 char *buf;
311 const char *line, *host, *when;
313 line = host = when = (str) ? "-" : "!";
315 while ((buf = strsep(&str, ",")) != NULL) {
316 if (!buf[0])
317 continue;
319 switch (i++) {
320 case 0:
321 line = buf;
322 break;
323 case 1:
324 host = buf;
325 break;
326 case 2:
327 when = buf;
328 break;
329 default:
330 break;
334 for (i = 0; i < strlen(last_options); i++) {
335 switch (last_options[i]) {
336 case 'y':
337 add_string(&strings, line);
338 break;
339 case 'h':
340 add_string(&strings, host);
341 break;
342 case 't':
343 add_string(&strings, when);
344 break;
345 case 'a':
346 add_string(&strings, line);
347 add_string(&strings, host);
348 add_string(&strings, when);
349 default:
350 break;
355 /* Get the lastlog structure from the lastlog file. */
356 #if __FreeBSD_version >= 900000
357 static char *lastlogin(const struct passwd *pw, char *tf)
359 struct utmpx *last;
360 static char buf[LINE_MAX];
362 if (setutxdb(UTXDB_LASTLOGIN, NULL) == -1) {
363 warn("lastlog");
364 return NULL;
367 last = getutxuser(pw->pw_name);
369 if (!last)
370 return NULL;
372 snprintf(buf, sizeof(buf), "%s,%s,%s",
373 !last->ut_line[0] ? "!" : last->ut_line,
374 (!last->ut_host[0] || !isalnum(last->ut_host[0])) ? "-" : last->ut_host,
375 !last->ut_time[0] ? "!" : stamp(last->ut_time, tf));
377 return buf;
379 #else
380 static char *lastlogin(const struct passwd *pw, char *tf)
382 int count;
383 long offset;
384 static char buf[LINE_MAX];
385 struct lastlog last;
387 if (lastlogfd < 0)
388 return NULL;
390 if (!lastlogfd) {
391 if ((lastlogfd = open(_PATH_LASTLOG, O_RDONLY)) == -1) {
392 warn("%s", _PATH_LASTLOG);
393 return NULL;
397 offset = (long) pw->pw_uid * sizeof(struct lastlog);
399 if (lseek(lastlogfd, offset, SEEK_SET) == -1) {
400 warn("%s", _PATH_LASTLOG);
401 return NULL;
404 if ((count = read(lastlogfd, &last, sizeof(struct lastlog))) !=
405 sizeof(struct lastlog)) {
406 if (count == -1)
407 warn("%s", _PATH_LASTLOG);
409 return NULL;
412 #ifdef __NetBSD__
413 #ifdef HAVE_UTMPX_H
414 last.ll_host[UTX_HOSTSIZE-1] = '\0';
415 last.ll_line[UTX_LINESIZE-1] = '\0';
416 #else
417 last.ll_host[UT_HOSTSIZE-1] = '\0';
418 last.ll_line[UT_LINESIZE-1] = '\0';
419 #endif
420 #else
421 last.ll_host[UT_HOSTSIZE-1] = '\0';
422 last.ll_line[UT_LINESIZE-1] = '\0';
423 #endif
425 snprintf(buf, sizeof(buf), "%s,%s,%s",
426 !last.ll_line[0] ? "!" : last.ll_line,
427 (!last.ll_host[0] || !isalnum(last.ll_host[0])) ?
428 !isdigit(last.ll_line[3]) ? "!" : "-" : last.ll_host,
429 !last.ll_time ? "!" : stamp(last.ll_time, tf));
430 return buf;
432 #endif
434 /* This will return an array of utmp structures if a user is logged in, NULL
435 * otherwise. We'll try to keep the utmp file descriptor open if possible to
436 * speed things up a bit. */
437 static UTMP **get_utmp(const char *user)
439 UTMP **logins = NULL;
440 #ifdef HAVE_UTMPX_H
441 UTMP *u;
442 #else
443 UTMP u;
444 int count;
445 static int fd;
447 if (fd < 0)
448 return NULL;
450 if (!fd) {
451 if ((fd = open(_PATH_UTMP, O_RDONLY)) == -1) {
452 warn("%s", _PATH_UTMP);
453 return NULL;
456 #endif
458 login_count = 0;
460 #ifdef HAVE_UTMPX_H
461 setutxent();
463 while ((u = getutxent()) != NULL) {
464 if (!strcmp(u->ut_user, user) && u->ut_type != DEAD_PROCESS) {
465 #else
466 lseek(fd, 0, SEEK_SET);
468 while ((count = read(fd, &u, sizeof(UTMP))) == sizeof(UTMP)) {
469 if (strcmp(u.ut_name, user) == 0) {
470 #endif
471 UTMP **tmp;
473 if ((tmp = realloc(logins,
474 (login_count + 2) * sizeof(UTMP *))) ==
475 NULL) {
476 warn("realloc()");
477 free_logins(logins);
478 return NULL;
481 logins = tmp;
483 if ((logins[login_count] = malloc(sizeof(UTMP))) == NULL) {
484 warn("malloc()");
485 free_logins(logins);
486 return NULL;
489 #ifdef HAVE_UTMPX_H
490 #ifdef __NetBSD__
491 memcpy(logins[login_count]->ut_name, u->ut_name, UTX_NAMESIZE);
492 logins[login_count]->ut_name[UTX_NAMESIZE-1] = 0;
493 memcpy(logins[login_count]->ut_line, u->ut_line, UTX_LINESIZE);
494 logins[login_count]->ut_line[UTX_LINESIZE-1] = 0;
495 memcpy(logins[login_count]->ut_host, u->ut_host, UTX_HOSTSIZE);
496 logins[login_count]->ut_host[UTX_HOSTSIZE-1] = 0;
497 logins[login_count]->ut_pid = u->ut_pid;
498 #else
499 memcpy(logins[login_count]->ut_user, u->ut_user, UT_NAMESIZE);
500 logins[login_count]->ut_user[UT_NAMESIZE-1] = 0;
501 memcpy(logins[login_count]->ut_line, u->ut_line, UT_LINESIZE);
502 logins[login_count]->ut_line[UT_LINESIZE-1] = 0;
503 memcpy(logins[login_count]->ut_host, u->ut_host, UT_HOSTSIZE);
504 logins[login_count]->ut_host[UT_HOSTSIZE-1] = 0;
505 logins[login_count]->ut_tv.tv_sec = u->ut_tv.tv_sec;
506 logins[login_count]->ut_pid = u->ut_pid;
507 #endif
508 #else
509 memcpy(logins[login_count]->ut_name, u.ut_name, UT_NAMESIZE);
510 logins[login_count]->ut_name[UT_NAMESIZE-1] = 0;
511 memcpy(logins[login_count]->ut_line, u.ut_line, UT_LINESIZE);
512 logins[login_count]->ut_line[UT_LINESIZE-1] = 0;
513 memcpy(logins[login_count]->ut_host, u.ut_host, UT_HOSTSIZE);
514 logins[login_count]->ut_host[UT_HOSTSIZE-1] = 0;
515 logins[login_count]->ut_time = u.ut_time;
516 #endif
517 logins[++login_count] = NULL;
521 return logins;
524 /* The 'mesg' status of the logged in user. */
525 static char *msgstat(UTMP **u, int multi)
527 static char line[LINE_MAX];
528 int i;
530 line[0] = '\0';
532 for (i = 0; i < login_count; i++) {
533 char filename[FILENAME_MAX];
534 struct stat st;
535 char m[2] = { multi, '\0' };
537 snprintf(filename, sizeof(filename), "%s%s", _PATH_DEV, u[i]->ut_line);
539 if (stat(filename, &st) == -1)
540 safe_strncat(line, "!", sizeof(line));
541 else
542 safe_strncat(line,
543 (st.st_mode & S_IWGRP || st.st_mode & S_IWOTH) ? "1" : "0",
544 sizeof(line));
546 safe_strncat(line, m, sizeof(line));
549 if (line[0] == '\0')
550 return "!";
552 line[strlen(line) - 1] = '\0';
553 return line;
556 /* Returns the users idle time in seconds. */
557 static char *idle(UTMP **u, int multi)
559 static char line[LINE_MAX];
560 time_t t;
561 struct stat st;
562 int i;
564 line[0] = '\0';
566 for (i = 0; i < login_count; i++) {
567 char buf[FILENAME_MAX];
568 char m[2] = { multi, '\0' };
570 snprintf(buf, sizeof(buf), "%s%s", _PATH_DEV, u[i]->ut_line);
572 if (stat(buf, &st) == -1) {
573 safe_strncat(line, "!", sizeof(line));
574 safe_strncat(line, m, sizeof(line));
575 continue;
578 #ifdef HAVE_UTMPX_H
579 if (u[i]->ut_tv.tv_sec > st.st_atime) {
580 #else
581 if (u[i]->ut_time > st.st_atime) {
582 #endif
583 safe_strncat(line, "-", sizeof(line));
584 safe_strncat(line, m, sizeof(line));
585 continue;
588 t = st.st_atime;
590 #ifdef HAVE_UTMPX_H
591 if (t < u[i]->ut_tv.tv_sec)
592 t = u[i]->ut_tv.tv_sec;
593 #else
594 if (t < u[i]->ut_time)
595 t = u[i]->ut_time;
596 #endif
598 snprintf(buf, sizeof(buf), "%lu", (now - t <= 0) ? 0 : now - t);
599 safe_strncat(line, buf, sizeof(line));
600 safe_strncat(line, m, sizeof(line));
603 if (line[0] == '\0')
604 return "!";
606 line[strlen(line) - 1] = '\0';
607 return line;
610 /* This is output if the -h command line option is passed to the main program.
612 void ui_module_help()
614 printf(" Login information [-L (-%s)]:\n", LOGIN_OPTION_ORDER);
615 printf("\t-y tty\t\t\t\t");
616 printf("-m message status\n");
617 printf("\t-t login time stamp\t\t");
618 printf("-d duration in minutes\n");
619 printf("\t-h hostname\t\t\t");
620 printf("-i seconds idle\n");
621 printf("\t-p login process id\n");
622 printf("\t-l lastlog information"
623 " (any of tt[y],[h]ostname,[t]ime, or [a]ll)\n\n");
624 return;
627 /* This is the equivalent to main() only without argc and argv available. */
628 int ui_module_exec(char ***s, const struct passwd *pw, const int multi,
629 const int verbose, char *tf)
631 char *p = options;
632 UTMP **u = NULL;
633 char buf[255];
635 login_count = 0;
636 u = get_utmp(pw->pw_name);
637 strings = *s;
639 while (*p) {
640 char line[LINE_MAX] = { '\0' };
641 int i;
642 char m[2] = { multi, '\0' };
644 switch (*p) {
645 case 'i':
646 add_string(&strings, (u) ? idle(u, multi) : "!");
647 break;
648 case 'l':
649 last_strings(lastlogin(pw, tf));
650 break;
651 case 'h':
652 for (i = 0; i < login_count; i++) {
653 if (u[i]->ut_host[0]
654 && isalnum((unsigned char) u[i]->ut_host[0]))
655 safe_strncat(line, u[i]->ut_host, sizeof(line));
656 else
657 safe_strncat(line, "-", sizeof(line));
659 safe_strncat(line, m, sizeof(line));
662 if (line[0] == '\0')
663 strncpy(line, "!", sizeof(line));
664 else
665 line[strlen(line) - 1] = '\0';
667 add_string(&strings, line);
668 break;
669 case 'y':
670 for (i = 0; i < login_count; i++) {
671 if (u[i]->ut_line[0])
672 safe_strncat(line, u[i]->ut_line, sizeof(line));
673 else
674 safe_strncat(line, "!", sizeof(line));
676 safe_strncat(line, m, sizeof(line));
679 if (line[0] == '\0')
680 strncpy(line, "!", sizeof(line));
681 else
682 line[strlen(line) - 1] = '\0';
684 add_string(&strings, line);
685 break;
686 case 'm':
687 add_string(&strings, msgstat(u, multi));
688 break;
689 case 't':
690 for (i = 0; i < login_count; i++) {
691 #ifdef HAVE_UTMPX_H
692 safe_strncat(line, stamp(u[i]->ut_tv.tv_sec, tf), sizeof(line));
693 #else
694 safe_strncat(line, stamp(u[i]->ut_time, tf), sizeof(line));
695 #endif
696 safe_strncat(line, m, sizeof(line));
699 if (line[0] == '\0')
700 strncpy(line, "!", sizeof(line));
701 else
702 line[strlen(line) - 1] = '\0';
704 add_string(&strings, line);
705 break;
706 case 'd':
707 for (i = 0; i < login_count; i++) {
708 #ifdef HAVE_UTMPX_H
709 if ((now - u[i]->ut_tv.tv_sec) > 60) {
710 snprintf(buf, sizeof(buf), "%lu",
711 ((now - u[i]->ut_tv.tv_sec) / 60));
712 #else
713 if ((now - u[i]->ut_time) > 60) {
714 snprintf(buf, sizeof(buf), "%lu",
715 ((now - u[i]->ut_time) / 60));
716 #endif
717 safe_strncat(line, buf, sizeof(line));
719 else
720 safe_strncat(line, "-", sizeof(line));
722 safe_strncat(line, m, sizeof(line));
725 if (line[0] == '\0')
726 strncpy(line, "!", sizeof(line));
727 else
728 line[strlen(line) - 1] = '\0';
730 add_string(&strings, line);
731 break;
732 case 'p':
733 #ifdef HAVE_UTMPX_H
734 for (i = 0; i < login_count; i++) {
735 if (u[i]->ut_pid) {
736 snprintf(buf, sizeof(buf), "%li", (long) u[i]->ut_pid);
737 safe_strncat(line, buf, sizeof(line));
739 else
740 safe_strncat(line, "!", sizeof(line));
742 safe_strncat(line, m, sizeof(line));
745 if (line[0] == '\0')
746 strncpy(line, "!", sizeof(line));
747 else
748 line[strlen(line) - 1] = '\0';
750 add_string(&strings, line);
751 #else
752 add_string(&strings, (u) ? get_pid(pw->pw_uid, multi) : "!");
753 #endif
754 break;
755 default:
756 break;
759 p++;
762 free_logins(u);
763 *s = strings;
764 return EXIT_SUCCESS;
767 /* See if the last login options (-l) are valid. */
768 static int parse_last_options(const char *args)
770 int i = 0;
772 for (i = 0; i < strlen(args); i++) {
773 switch (args[i]) {
774 case 'y':
775 case 'h':
776 case 't':
777 case 'a':
778 break;
779 default:
780 return 1;
784 return 0;
787 char *ui_module_options_init(char **defaults)
789 *defaults = "L";
790 return LOGIN_OPTION_STRING;
793 /* Check module option validity. */
794 int ui_module_options(int argc, char **argv)
796 int opt;
797 char *p = options;
799 while ((opt = getopt(argc, argv, LOGIN_OPTION_STRING)) != -1) {
800 switch (opt) {
801 case 'l':
802 if (parse_last_options(optarg))
803 return 1;
805 last_options = optarg;
806 break;
807 case 'L':
808 strncpy(options, LOGIN_OPTION_ORDER, sizeof(options));
809 last_options = "a";
810 return 0;
811 case 'p':
812 case 'd':
813 case 'i':
814 case 'm':
815 case 'y':
816 case 'h':
817 case 't':
818 break;
819 case '?':
820 warnx("login: invalid option -- %c", optopt);
821 default:
822 return 1;
825 *p++ = opt;
826 *p = '\0';
829 return 0;