Changed follow-symlink option -l to -L.
[userinfo.git] / src / modules / login.c
blobbd31c7d336ad01339394e9074a8af98dc7b88cf9
1 /* $Id: login.c,v 1.5 2006-01-21 22:44:06 bjk Exp $ */
2 /*
3 Copyright (C) 2001-2005 Ben Kibbey <bjk@arbornet.org>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 #include <stdio.h>
20 #include <unistd.h>
21 #include <stdlib.h>
22 #include <sys/types.h>
23 #include <sys/stat.h>
24 #include <errno.h>
25 #include <time.h>
26 #include <pwd.h>
27 #include <ctype.h>
28 #include <utmp.h>
30 #ifdef HAVE_CONFIG_H
31 #include <config.h>
32 #endif
34 #include "login.h"
36 #ifndef HAVE_STRSEP
37 #include "../strsep.c"
38 #endif
40 #ifndef HAVE_ERR_H
41 #include "../err.c"
42 #endif
44 void ui_module_init(int *chainable)
46 #ifdef DEBUG
47 fprintf(stderr, "%s: ui_module_init()\n", __FILE__);
48 #endif
50 *chainable = 0;
51 time(&now);
52 return;
55 void ui_module_exit()
57 #ifdef DEBUG
58 fprintf(stderr, "%s: ui_module_exit()\n", __FILE__);
59 #endif
61 #ifdef HAVE_PROCFS
62 if (procdir)
63 closedir(procdir);
64 #endif
66 #ifdef HAVE_KVM_H
67 if (kd)
68 kvm_close(kd);
69 #endif
71 if (lastlogfd)
72 close(lastlogfd);
74 return;
77 #ifndef UTMPX_FORMAT
78 /* This is for *BSD (login process id). */
79 #ifdef BSD_KVM
80 char *ui_module_pid(uid_t uid, int multi)
82 static int firstrun;
83 static char line[LINE_MAX];
84 int cnt, i;
85 pid_t pid = 0;
86 char errbuf[LINE_MAX];
87 struct kinfo_proc *kp;
89 line[0] = '\0';
91 if (!kd && firstrun)
92 return "!";
94 if (!kd) {
95 firstrun = 1;
97 #ifdef __NetBSD__
98 if ((kd = kvm_openfiles(NULL, NULL, NULL,
99 #else
100 if ((kd = kvm_openfiles(_PATH_DEVNULL, _PATH_DEVNULL, _PATH_DEVNULL,
101 #endif
102 O_RDONLY, errbuf)) == NULL) {
103 warnx("%s", errbuf);
104 return "!";
108 if ((kp = kvm_getprocs(kd, KERN_PROC_UID, uid, &cnt)) == NULL) {
109 warnx("kvm_getprocs(): %s", kvm_geterr(kd));
110 return "!";
113 for (i = 0; i < cnt; i++) {
114 char buf[32];
116 #if __FreeBSD_version < 500000
117 if (kp[i].kp_eproc.e_flag & EPROC_SLEADER && kp[i].kp_eproc.e_tdev !=
118 -1) {
119 pid = kp[i].kp_eproc.e_ppid;
121 * pid = kp[i].kp_proc.p_pid;
124 if (pid == 1)
125 continue;
126 #else
127 if (kp[i].ki_kiflag & KI_SLEADER && kp[i].ki_tdev != -1) {
128 pid = kp[i].ki_pid;
129 #endif
130 snprintf(buf, sizeof(buf), "%i%c", pid, multi);
131 strncat(line, buf, sizeof(line));
135 if (line[0] == '\0')
136 return "!";
138 line[strlen(line) - 1] = '\0';
139 return line;
142 /* This is for Linux and Solaris. */
143 #elif defined(HAVE_PROCFS)
144 #include <sys/types.h>
145 #include <sys/stat.h>
147 #ifdef HAVE_DIRENT_H
148 #include <dirent.h>
149 #endif
151 #ifdef __svr4__
152 #include <unistd.h>
153 #include <procfs.h>
154 #endif
156 char *ui_module_pid(uid_t uid, int multi)
158 static int firstrun;
159 struct dirent *ent;
160 struct stat st;
161 static char line[LINE_MAX];
162 pid_t *pids = 0;
163 int pid_index = 0;
165 #ifdef __svr4__
166 int fd;
167 struct pstatus pstat;
168 #else
169 FILE *fp;
170 #endif
172 line[0] = '\0';
174 if (!procdir && firstrun)
175 return "!";
177 if (!procdir) {
178 firstrun = 1;
180 if ((procdir = opendir("/proc")) == NULL) {
181 warn("%s", "/proc");
182 return "!";
186 rewinddir(procdir);
188 again:
189 while ((ent = readdir(procdir)) != NULL) {
190 pid_t pid = -1;
191 char filename[FILENAME_MAX];
192 char buf[LINE_MAX];
193 int i;
195 #ifndef __svr4__
196 char *t;
197 #endif
199 if (!isdigit((unsigned char) *ent->d_name))
200 continue;
202 #ifdef __linux__
203 snprintf(filename, sizeof(filename), "/proc/%s/stat", ent->d_name);
204 #else
205 snprintf(filename, sizeof(filename), "/proc/%s/status", ent->d_name);
206 #endif
208 if (stat(filename, &st) == -1)
209 continue;
212 * The current user owns this file (process id).
214 if (st.st_uid == uid) {
215 #ifdef __svr4__
216 if ((fd = open(filename, O_RDONLY)) == -1)
217 continue;
219 if (pread(fd, &pstat, sizeof(struct pstatus), 0) !=
220 sizeof(struct pstatus)) {
221 close(fd);
222 continue;
225 pid = pstat.pr_ppid;
226 close(fd);
227 #else
228 if ((fp = fopen(filename, "r")) == NULL)
229 continue;
231 if ((t = fgets(buf, sizeof(buf), fp)) == NULL) {
232 fclose(fp);
233 continue;
236 #ifdef __linux__
237 if ((i = sscanf(buf, "%*i %*s %*c %*i %*i %i", &pid)) < 1) {
238 #endif
240 #else
241 if ((i = sscanf(buf, "%*s %*i %li", &ppid)) < 1) {
242 #endif
244 fclose(fp);
245 continue;
248 fclose(fp);
249 #endif
252 * Skip duplicate pids.
254 for (i = 0; i < pid_index; i++) {
255 if (pids[i] == pid)
256 goto again;
259 snprintf(buf, sizeof(buf), "%li%c", (unsigned long) pid, multi);
260 strncat(line, buf, sizeof(line));
262 if ((pids =
263 realloc(pids, (pid_index + 2) * sizeof(pid_t *))) == NULL) {
264 warn("realloc()");
265 continue;
268 pids[pid_index++] = pid;
272 if (pid_index)
273 free(pids);
275 if (line[0] == '\0')
276 return "!";
278 line[strlen(line) - 1] = '\0';
279 return line;
281 #else
282 /* Unsupported OS. */
283 char *ui_module_pid(uid_t uid, int multi)
285 return "!";
287 #endif
288 #endif
290 /* Break up the last login string into sections and add the sections to the
291 * output string array if needed. */
292 static void last_strings(char *str)
294 int i = 0;
295 char *buf;
296 const char *line, *host, *when;
298 line = host = when = (str) ? "-" : "!";
300 while ((buf = strsep(&str, ",")) != NULL) {
301 if (!buf[0])
302 continue;
304 switch (i++) {
305 case 0:
306 line = buf;
307 break;
308 case 1:
309 host = buf;
310 break;
311 case 2:
312 when = buf;
313 break;
314 default:
315 break;
319 for (i = 0; i < strlen(last_options); i++) {
320 switch (last_options[i]) {
321 case 'y':
322 add_string(&strings, line);
323 break;
324 case 'h':
325 add_string(&strings, host);
326 break;
327 case 't':
328 add_string(&strings, when);
329 break;
330 case 'a':
331 add_string(&strings, line);
332 add_string(&strings, host);
333 add_string(&strings, when);
334 default:
335 break;
339 return;
342 /* Get the lastlog structure from the lastlog file. */
343 static char *lastlogin(uid_t uid, char *tf)
345 int count;
346 long offset;
347 static char buf[LINE_MAX];
349 #ifdef __NetBSD__
350 #ifdef UTMPX_FORMAT
351 char tmp[64], htmp[UTX_HOSTSIZE + 1];
352 #else
353 char tmp[64], htmp[UT_HOSTSIZE + 1];
354 #endif
355 #else
356 char tmp[64], htmp[UT_HOSTSIZE + 1];
357 #endif
358 struct lastlog last;
360 buf[0] = tmp[0] = htmp[0] = '\0';
362 if (lastlogfd < 0)
363 return NULL;
365 if (!lastlogfd) {
366 if ((lastlogfd = open(_PATH_LASTLOG, O_RDONLY)) == -1) {
367 warn("%s", _PATH_LASTLOG);
368 return NULL;
372 offset = (long) uid *sizeof(struct lastlog);
374 if (lseek(lastlogfd, offset, SEEK_SET) == -1) {
375 warn("%s", _PATH_LASTLOG);
376 return NULL;
379 if ((count = read(lastlogfd, &last, sizeof(struct lastlog))) !=
380 sizeof(struct lastlog)) {
381 if (count == -1)
382 warn("%s", _PATH_LASTLOG);
384 return NULL;
387 if (last.ll_line[0] == '\0')
388 strncpy(buf, "!", sizeof(buf));
389 else
390 strncpy(buf, last.ll_line, sizeof(buf));
392 strncat(buf, ",", sizeof(buf));
393 strncpy(htmp, last.ll_host, sizeof(htmp));
395 #ifdef __NetBSD__
396 #ifdef UTMPX_FORMAT
397 htmp[UTX_HOSTSIZE] = '\0';
398 #else
399 htmp[UT_HOSTSIZE] = '\0';
400 #endif
401 #else
402 htmp[UT_HOSTSIZE] = '\0';
403 #endif
405 if (htmp[0] && isalnum((unsigned char) htmp[0]))
406 strncat(buf, htmp, sizeof(buf));
407 else {
409 * If a users tty is tty1-n, it must be a console
410 * * login.
412 if (last.ll_line[0] && isdigit((unsigned char) last.ll_line[3]))
413 strncat(buf, "-", sizeof(buf));
414 else
415 strncat(buf, "!", sizeof(buf));
418 strncat(buf, ",", sizeof(buf));
420 if (last.ll_time)
421 strncat(buf, stamp(last.ll_time, tf), sizeof(buf));
422 else
423 strncat(buf, "!", sizeof(buf));
425 return buf;
428 /* This will return an array of utmp structures if a user is logged in, NULL
429 * otherwise. We'll try to keep the utmp file descriptor open if possible to
430 * speed things up a bit. */
431 static UTMP **get_utmp(const char *user)
433 UTMP **logins = NULL;
435 #ifdef UTMPX_FORMAT
436 UTMP *u;
437 #else
438 UTMP u;
439 int count;
440 static int fd;
442 if (fd < 0)
443 return NULL;
445 if (!fd) {
446 if ((fd = open(_PATH_UTMP, O_RDONLY)) == -1) {
447 warn("%s", _PATH_UTMP);
448 return NULL;
451 #endif
453 login_count = 0;
455 #ifdef UTMPX_FORMAT
456 setutent();
458 while ((u = getutxent()) != NULL) {
459 if (strcmp(u->ut_name, user) == 0) {
460 #else
461 lseek(fd, 0, SEEK_SET);
463 while ((count = read(fd, &u, sizeof(UTMP))) == sizeof(UTMP)) {
464 if (strcmp(u.ut_name, user) == 0) {
465 #endif
466 if ((logins = realloc(logins,
467 (login_count + 2) * sizeof(UTMP *))) ==
468 NULL) {
469 warn("realloc()");
470 return NULL;
473 if ((logins[login_count] = malloc(sizeof(UTMP))) == NULL) {
474 warn("malloc()");
475 return NULL;
478 #ifdef UTMPX_FORMAT
479 #ifdef __NetBSD__
480 strncpy(logins[login_count]->ut_name, u->ut_name, UTX_NAMESIZE);
481 strncpy(logins[login_count]->ut_line, u->ut_line, UTX_LINESIZE);
482 strncpy(logins[login_count]->ut_host, u->ut_host, UTX_HOSTSIZE);
483 logins[login_count]->ut_pid = u->ut_pid;
484 #else
485 strncpy(logins[login_count]->ut_name, u->ut_name, UT_NAMESIZE);
486 strncpy(logins[login_count]->ut_line, u->ut_line, UT_LINESIZE);
487 strncpy(logins[login_count]->ut_host, u->ut_host, UT_HOSTSIZE);
488 logins[login_count]->ut_tv.tv_sec = u->ut_tv.tv_sec;
489 logins[login_count]->ut_pid = u->ut_pid;
490 #endif
491 #else
492 strncpy(logins[login_count]->ut_name, u.ut_name, UT_NAMESIZE);
493 strncpy(logins[login_count]->ut_line, u.ut_line, UT_LINESIZE);
494 strncpy(logins[login_count]->ut_host, u.ut_host, UT_HOSTSIZE);
495 logins[login_count]->ut_host[UT_HOSTSIZE] = '\0';
496 logins[login_count]->ut_time = u.ut_time;
497 #endif
498 logins[++login_count] = NULL;
502 return logins;
505 /* The 'mesg' status of the logged in user. */
506 static char *msgstat(UTMP ** u, int multi)
508 static char line[LINE_MAX];
509 int i;
511 line[0] = '\0';
513 for (i = 0; i < login_count; i++) {
514 char filename[FILENAME_MAX];
515 struct stat st;
516 char m[2] = { multi, '\0' };
518 snprintf(filename, sizeof(filename), "%s%s", _PATH_DEV, u[i]->ut_line);
520 if (stat(filename, &st) == -1)
521 strncat(line, "!", sizeof(line));
522 else
523 strncat(line,
524 (st.st_mode & S_IWGRP || st.st_mode & S_IWOTH) ? "1" : "0",
525 sizeof(line));
527 strncat(line, m, sizeof(line));
530 if (line[0] == '\0')
531 return "!";
533 line[strlen(line) - 1] = '\0';
534 return line;
537 /* Returns the users idle time in seconds. */
538 static char *idle(UTMP ** u, int multi)
540 static char line[LINE_MAX];
541 time_t t;
542 struct stat st;
543 int i;
545 line[0] = '\0';
547 for (i = 0; i < login_count; i++) {
548 char buf[FILENAME_MAX];
549 char m[2] = { multi, '\0' };
551 snprintf(buf, sizeof(buf), "%s%s", _PATH_DEV, u[i]->ut_line);
553 if (stat(buf, &st) == -1) {
554 strncat(line, "!", sizeof(line));
555 strncat(line, m, sizeof(line));
556 continue;
559 #ifdef UTMPX_FORMAT
560 if (u[i]->ut_tv.tv_sec > st.st_atime) {
561 #else
562 if (u[i]->ut_time > st.st_atime) {
563 #endif
564 strncat(line, "-", sizeof(line));
565 strncat(line, m, sizeof(line));
566 continue;
569 t = st.st_atime;
571 #ifdef UTMPX_FORMAT
572 if (t < u[i]->ut_tv.tv_sec)
573 t = u[i]->ut_tv.tv_sec;
574 #else
575 if (t < u[i]->ut_time)
576 t = u[i]->ut_time;
577 #endif
579 snprintf(buf, sizeof(buf), "%lu", (now - t <= 0) ? 0 : now - t);
580 strncat(line, buf, sizeof(line));
581 strncat(line, m, sizeof(line));
584 if (line[0] == '\0')
585 return "!";
587 line[strlen(line) - 1] = '\0';
588 return line;
591 /* This is output if the -h command line option is passed to the main program.
593 void ui_module_help()
595 #ifdef DEBUG
596 fprintf(stderr, "%s: ui_module_help()\n", __FILE__);
597 #endif
599 printf(" Login information [-L (-%s)]:\n", LOGIN_OPTION_ORDER);
600 printf("\t-y tty\t\t\t\t");
601 printf("-m message status\n");
602 printf("\t-t login time stamp\t\t");
603 printf("-d duration in minutes\n");
604 printf("\t-h hostname\t\t\t");
605 printf("-i seconds idle\n");
606 printf("\t-p login process id\n");
607 printf("\t-l lastlog information"
608 " (any of tt[y],[h]ostname,[t]ime, or [a]ll)\n\n");
609 return;
612 /* This is the equivalent to main() only without argc and argv available. */
613 int ui_module_exec(char ***s, const struct passwd *pw, const int multi,
614 const int verbose, char *tf)
616 char *p = options;
617 UTMP **u = NULL, **up;
618 char buf[255];
620 login_count = 0;
621 u = get_utmp(pw->pw_name);
622 strings = *s;
624 while (*p) {
625 char line[LINE_MAX] = { '\0' };
626 int i;
627 char m[2] = { multi, '\0' };
628 #ifdef __NetBSD__
629 #ifdef UTMPX_FORMAT
630 char htmp[UTX_HOSTSIZE + 1];
631 #else
632 char htmp[UT_HOSTSIZE + 1];
633 #endif
634 #else
635 char htmp[UT_HOSTSIZE + 1];
636 #endif
638 switch (*p) {
639 case 'i':
640 add_string(&strings, (u) ? idle(u, multi) : "!");
641 break;
642 case 'l':
643 last_strings(lastlogin(pw->pw_uid, tf));
644 break;
645 case 'h':
646 for (i = 0; i < login_count; i++) {
647 if (u[i]->ut_host[0]
648 && isalnum((unsigned char) u[i]->ut_host[0])) {
649 strcpy(htmp, u[i]->ut_host);
650 htmp[sizeof(htmp) - 1] = '\0';
651 strncat(line, htmp, sizeof(line));
653 else {
655 * If a users tty is tty1-n, it must be a console
656 * * login.
658 if (u[i]->ut_line[0]
659 && isdigit((unsigned char) u[i]->ut_line[3]))
660 strncat(line, "-", sizeof(line));
661 else
662 strncat(line, "!", sizeof(line));
665 strncat(line, m, sizeof(line));
668 if (line[0] == '\0')
669 strncpy(line, "!", sizeof(line));
670 else
671 line[strlen(line) - 1] = '\0';
673 add_string(&strings, line);
674 break;
675 case 'y':
676 for (i = 0; i < login_count; i++) {
677 if (u[i]->ut_line[0])
678 strncat(line, u[i]->ut_line, sizeof(line));
679 else
680 strncat(line, "!", sizeof(line));
682 strncat(line, m, sizeof(line));
685 if (line[0] == '\0')
686 strncpy(line, "!", sizeof(line));
687 else
688 line[strlen(line) - 1] = '\0';
690 add_string(&strings, line);
691 break;
692 case 'm':
693 add_string(&strings, msgstat(u, multi));
694 break;
695 case 't':
696 for (i = 0; i < login_count; i++) {
697 #ifdef UTMPX_FORMAT
698 strncat(line, stamp(u[i]->ut_tv.tv_sec, tf), sizeof(line));
699 #else
700 strncat(line, stamp(u[i]->ut_time, tf), sizeof(line));
701 #endif
702 strncat(line, m, sizeof(line));
705 if (line[0] == '\0')
706 strncpy(line, "!", sizeof(line));
707 else
708 line[strlen(line) - 1] = '\0';
710 add_string(&strings, line);
711 break;
712 case 'd':
713 for (i = 0; i < login_count; i++) {
714 #ifdef UTMPX_FORMAT
715 if ((now - u[i]->ut_tv.tv_sec) > 60) {
716 snprintf(buf, sizeof(buf), "%lu",
717 ((now - u[i]->ut_tv.tv_sec) / 60));
718 #else
719 if ((now - u[i]->ut_time) > 60) {
720 snprintf(buf, sizeof(buf), "%lu",
721 ((now - u[i]->ut_time) / 60));
722 #endif
723 strncat(line, buf, sizeof(line));
725 else
726 strncat(line, "-", sizeof(line));
728 strncat(line, m, sizeof(line));
731 if (line[0] == '\0')
732 strncpy(line, "!", sizeof(line));
733 else
734 line[strlen(line) - 1] = '\0';
736 add_string(&strings, line);
737 break;
738 case 'p':
739 #ifdef UTMPX_FORMAT
740 for (i = 0; i < login_count; i++) {
741 if (u[i]->ut_pid) {
742 snprintf(buf, sizeof(buf), "%li", (long) u[i]->ut_pid);
743 strncat(line, buf, sizeof(line));
745 else
746 strncat(line, "!", sizeof(line));
748 strncat(line, m, sizeof(line));
751 if (line[0] == '\0')
752 strncpy(line, "!", sizeof(line));
753 else
754 line[strlen(line) - 1] = '\0';
756 add_string(&strings, line);
757 #else
758 add_string(&strings, (u) ? ui_module_pid(pw->pw_uid, multi) : "!");
759 #endif
760 break;
761 default:
762 break;
765 p++;
768 if (login_count) {
769 for (up = u; *up; *up++)
770 free(*up);
772 free(u);
775 *s = strings;
776 return EXIT_SUCCESS;
779 /* See if the last login options (-l) are valid. */
780 static int parse_last_options(const char *args)
782 int i = 0;
784 for (i = 0; i < strlen(args); i++) {
785 switch (args[i]) {
786 case 'y':
787 case 'h':
788 case 't':
789 case 'a':
790 break;
791 default:
792 return 1;
796 return 0;
799 char *ui_module_options_init(char **defaults)
801 *defaults = "L";
802 return LOGIN_OPTION_STRING;
805 /* Check module option validity. */
806 int ui_module_options(int argc, char **argv)
808 int opt;
809 char *p = options;
811 #ifdef DEBUG
812 fprintf(stderr, "%s: ui_module_options()\n", __FILE__);
813 #endif
815 while ((opt = getopt(argc, argv, LOGIN_OPTION_STRING)) != -1) {
816 switch (opt) {
817 case 'l':
818 last_options = optarg;
819 break;
820 case 'L':
821 case 'p':
822 case 'd':
823 case 'i':
824 case 'm':
825 case 'y':
826 case 'h':
827 case 't':
828 break;
829 case '?':
830 warnx("login: invalid option -- %c", optopt);
831 default:
832 return 1;
835 if (opt == 'l') {
836 if (parse_last_options(last_options))
837 return 1;
840 if (opt == 'L') {
841 strncpy(options, LOGIN_OPTION_ORDER, sizeof(options));
842 last_options = "a";
843 break;
846 *p++ = opt;
847 *p = '\0';
850 return 0;