Updated email address and copyright.
[userinfo.git] / src / modules / login.c
blob0f354a7f7c0c816b52679d0a6a34d6ceebe68729
1 /*
2 Copyright (C) 2001-2006 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 02111-1307 USA
18 #include <stdio.h>
19 #include <unistd.h>
20 #include <stdlib.h>
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <errno.h>
24 #include <time.h>
25 #include <pwd.h>
26 #include <ctype.h>
27 #include <utmp.h>
29 #ifdef HAVE_CONFIG_H
30 #include <config.h>
31 #endif
33 #include "login.h"
35 #ifndef HAVE_STRSEP
36 #include "../strsep.c"
37 #endif
39 #ifndef HAVE_ERR_H
40 #include "../err.c"
41 #endif
43 void ui_module_init(int *chainable)
45 #ifdef DEBUG
46 fprintf(stderr, "%s: ui_module_init()\n", __FILE__);
47 #endif
49 *chainable = 0;
50 time(&now);
51 return;
54 void ui_module_exit()
56 #ifdef DEBUG
57 fprintf(stderr, "%s: ui_module_exit()\n", __FILE__);
58 #endif
60 #ifdef HAVE_PROCFS
61 if (procdir)
62 closedir(procdir);
63 #endif
65 #ifdef HAVE_KVM_H
66 if (kd)
67 kvm_close(kd);
68 #endif
70 if (lastlogfd)
71 close(lastlogfd);
73 return;
76 #ifndef UTMPX_FORMAT
77 /* This is for *BSD (login process id). */
78 #ifdef BSD_KVM
79 char *ui_module_pid(uid_t uid, int multi)
81 static int firstrun;
82 static char line[LINE_MAX];
83 int cnt, i;
84 pid_t pid = 0;
85 char errbuf[LINE_MAX];
86 struct kinfo_proc *kp;
88 line[0] = '\0';
90 if (!kd && firstrun)
91 return "!";
93 if (!kd) {
94 firstrun = 1;
96 #ifdef __NetBSD__
97 if ((kd = kvm_openfiles(NULL, NULL, NULL,
98 #else
99 if ((kd = kvm_openfiles(_PATH_DEVNULL, _PATH_DEVNULL, _PATH_DEVNULL,
100 #endif
101 O_RDONLY, errbuf)) == NULL) {
102 warnx("%s", errbuf);
103 return "!";
107 if ((kp = kvm_getprocs(kd, KERN_PROC_UID, uid, &cnt)) == NULL) {
108 warnx("kvm_getprocs(): %s", kvm_geterr(kd));
109 return "!";
112 for (i = 0; i < cnt; i++) {
113 char buf[32];
115 #if __FreeBSD_version < 500000
116 if (kp[i].kp_eproc.e_flag & EPROC_SLEADER && kp[i].kp_eproc.e_tdev !=
117 -1) {
118 pid = kp[i].kp_eproc.e_ppid;
120 * pid = kp[i].kp_proc.p_pid;
123 if (pid == 1)
124 continue;
125 #else
126 if (kp[i].ki_kiflag & KI_SLEADER && kp[i].ki_tdev != -1) {
127 pid = kp[i].ki_pid;
128 #endif
129 snprintf(buf, sizeof(buf), "%i%c", pid, multi);
130 strncat(line, buf, sizeof(line));
134 if (line[0] == '\0')
135 return "!";
137 line[strlen(line) - 1] = '\0';
138 return line;
141 /* This is for Linux and Solaris. */
142 #elif defined(HAVE_PROCFS)
143 #include <sys/types.h>
144 #include <sys/stat.h>
146 #ifdef HAVE_DIRENT_H
147 #include <dirent.h>
148 #endif
150 #ifdef __svr4__
151 #include <unistd.h>
152 #include <procfs.h>
153 #endif
155 char *ui_module_pid(uid_t uid, int multi)
157 static int firstrun;
158 struct dirent *ent;
159 struct stat st;
160 static char line[LINE_MAX];
161 pid_t *pids = 0;
162 int pid_index = 0;
164 #ifdef __svr4__
165 int fd;
166 struct pstatus pstat;
167 #else
168 FILE *fp;
169 #endif
171 line[0] = '\0';
173 if (!procdir && firstrun)
174 return "!";
176 if (!procdir) {
177 firstrun = 1;
179 if ((procdir = opendir("/proc")) == NULL) {
180 warn("%s", "/proc");
181 return "!";
185 rewinddir(procdir);
187 again:
188 while ((ent = readdir(procdir)) != NULL) {
189 pid_t pid = -1;
190 char filename[FILENAME_MAX];
191 char buf[LINE_MAX];
192 int i;
194 #ifndef __svr4__
195 char *t;
196 #endif
198 if (!isdigit((unsigned char) *ent->d_name))
199 continue;
201 #ifdef __linux__
202 snprintf(filename, sizeof(filename), "/proc/%s/stat", ent->d_name);
203 #else
204 snprintf(filename, sizeof(filename), "/proc/%s/status", ent->d_name);
205 #endif
207 if (stat(filename, &st) == -1)
208 continue;
211 * The current user owns this file (process id).
213 if (st.st_uid == uid) {
214 #ifdef __svr4__
215 if ((fd = open(filename, O_RDONLY)) == -1)
216 continue;
218 if (pread(fd, &pstat, sizeof(struct pstatus), 0) !=
219 sizeof(struct pstatus)) {
220 close(fd);
221 continue;
224 pid = pstat.pr_ppid;
225 close(fd);
226 #else
227 if ((fp = fopen(filename, "r")) == NULL)
228 continue;
230 if ((t = fgets(buf, sizeof(buf), fp)) == NULL) {
231 fclose(fp);
232 continue;
235 #ifdef __linux__
236 if ((i = sscanf(buf, "%*i %*s %*c %*i %*i %i", &pid)) < 1) {
237 #endif
239 #else
240 if ((i = sscanf(buf, "%*s %*i %li", &ppid)) < 1) {
241 #endif
243 fclose(fp);
244 continue;
247 fclose(fp);
248 #endif
251 * Skip duplicate pids.
253 for (i = 0; i < pid_index; i++) {
254 if (pids[i] == pid)
255 goto again;
258 snprintf(buf, sizeof(buf), "%li%c", (unsigned long) pid, multi);
259 strncat(line, buf, sizeof(line));
261 if ((pids =
262 realloc(pids, (pid_index + 2) * sizeof(pid_t *))) == NULL) {
263 warn("realloc()");
264 continue;
267 pids[pid_index++] = pid;
271 if (pid_index)
272 free(pids);
274 if (line[0] == '\0')
275 return "!";
277 line[strlen(line) - 1] = '\0';
278 return line;
280 #else
281 /* Unsupported OS. */
282 char *ui_module_pid(uid_t uid, int multi)
284 return "!";
286 #endif
287 #endif
289 /* Break up the last login string into sections and add the sections to the
290 * output string array if needed. */
291 static void last_strings(char *str)
293 int i = 0;
294 char *buf;
295 const char *line, *host, *when;
297 line = host = when = (str) ? "-" : "!";
299 while ((buf = strsep(&str, ",")) != NULL) {
300 if (!buf[0])
301 continue;
303 switch (i++) {
304 case 0:
305 line = buf;
306 break;
307 case 1:
308 host = buf;
309 break;
310 case 2:
311 when = buf;
312 break;
313 default:
314 break;
318 for (i = 0; i < strlen(last_options); i++) {
319 switch (last_options[i]) {
320 case 'y':
321 add_string(&strings, line);
322 break;
323 case 'h':
324 add_string(&strings, host);
325 break;
326 case 't':
327 add_string(&strings, when);
328 break;
329 case 'a':
330 add_string(&strings, line);
331 add_string(&strings, host);
332 add_string(&strings, when);
333 default:
334 break;
338 return;
341 /* Get the lastlog structure from the lastlog file. */
342 static char *lastlogin(uid_t uid, char *tf)
344 int count;
345 long offset;
346 static char buf[LINE_MAX];
348 #ifdef __NetBSD__
349 #ifdef UTMPX_FORMAT
350 char tmp[64], htmp[UTX_HOSTSIZE + 1];
351 #else
352 char tmp[64], htmp[UT_HOSTSIZE + 1];
353 #endif
354 #else
355 char tmp[64], htmp[UT_HOSTSIZE + 1];
356 #endif
357 struct lastlog last;
359 buf[0] = tmp[0] = htmp[0] = '\0';
361 if (lastlogfd < 0)
362 return NULL;
364 if (!lastlogfd) {
365 if ((lastlogfd = open(_PATH_LASTLOG, O_RDONLY)) == -1) {
366 warn("%s", _PATH_LASTLOG);
367 return NULL;
371 offset = (long) uid *sizeof(struct lastlog);
373 if (lseek(lastlogfd, offset, SEEK_SET) == -1) {
374 warn("%s", _PATH_LASTLOG);
375 return NULL;
378 if ((count = read(lastlogfd, &last, sizeof(struct lastlog))) !=
379 sizeof(struct lastlog)) {
380 if (count == -1)
381 warn("%s", _PATH_LASTLOG);
383 return NULL;
386 if (last.ll_line[0] == '\0')
387 strncpy(buf, "!", sizeof(buf));
388 else
389 strncpy(buf, last.ll_line, sizeof(buf));
391 strncat(buf, ",", sizeof(buf));
392 strncpy(htmp, last.ll_host, sizeof(htmp));
394 #ifdef __NetBSD__
395 #ifdef UTMPX_FORMAT
396 htmp[UTX_HOSTSIZE] = '\0';
397 #else
398 htmp[UT_HOSTSIZE] = '\0';
399 #endif
400 #else
401 htmp[UT_HOSTSIZE] = '\0';
402 #endif
404 if (htmp[0] && isalnum((unsigned char) htmp[0]))
405 strncat(buf, htmp, sizeof(buf));
406 else {
408 * If a users tty is tty1-n, it must be a console
409 * * login.
411 if (last.ll_line[0] && isdigit((unsigned char) last.ll_line[3]))
412 strncat(buf, "-", sizeof(buf));
413 else
414 strncat(buf, "!", sizeof(buf));
417 strncat(buf, ",", sizeof(buf));
419 if (last.ll_time)
420 strncat(buf, stamp(last.ll_time, tf), sizeof(buf));
421 else
422 strncat(buf, "!", sizeof(buf));
424 return buf;
427 /* This will return an array of utmp structures if a user is logged in, NULL
428 * otherwise. We'll try to keep the utmp file descriptor open if possible to
429 * speed things up a bit. */
430 static UTMP **get_utmp(const char *user)
432 UTMP **logins = NULL;
434 #ifdef UTMPX_FORMAT
435 UTMP *u;
436 #else
437 UTMP u;
438 int count;
439 static int fd;
441 if (fd < 0)
442 return NULL;
444 if (!fd) {
445 if ((fd = open(_PATH_UTMP, O_RDONLY)) == -1) {
446 warn("%s", _PATH_UTMP);
447 return NULL;
450 #endif
452 login_count = 0;
454 #ifdef UTMPX_FORMAT
455 setutent();
457 while ((u = getutxent()) != NULL) {
458 if (strcmp(u->ut_name, user) == 0) {
459 #else
460 lseek(fd, 0, SEEK_SET);
462 while ((count = read(fd, &u, sizeof(UTMP))) == sizeof(UTMP)) {
463 if (strcmp(u.ut_name, user) == 0) {
464 #endif
465 if ((logins = realloc(logins,
466 (login_count + 2) * sizeof(UTMP *))) ==
467 NULL) {
468 warn("realloc()");
469 return NULL;
472 if ((logins[login_count] = malloc(sizeof(UTMP))) == NULL) {
473 warn("malloc()");
474 return NULL;
477 #ifdef UTMPX_FORMAT
478 #ifdef __NetBSD__
479 strncpy(logins[login_count]->ut_name, u->ut_name, UTX_NAMESIZE);
480 strncpy(logins[login_count]->ut_line, u->ut_line, UTX_LINESIZE);
481 strncpy(logins[login_count]->ut_host, u->ut_host, UTX_HOSTSIZE);
482 logins[login_count]->ut_pid = u->ut_pid;
483 #else
484 strncpy(logins[login_count]->ut_name, u->ut_name, UT_NAMESIZE);
485 strncpy(logins[login_count]->ut_line, u->ut_line, UT_LINESIZE);
486 strncpy(logins[login_count]->ut_host, u->ut_host, UT_HOSTSIZE);
487 logins[login_count]->ut_tv.tv_sec = u->ut_tv.tv_sec;
488 logins[login_count]->ut_pid = u->ut_pid;
489 #endif
490 #else
491 strncpy(logins[login_count]->ut_name, u.ut_name, UT_NAMESIZE);
492 strncpy(logins[login_count]->ut_line, u.ut_line, UT_LINESIZE);
493 strncpy(logins[login_count]->ut_host, u.ut_host, UT_HOSTSIZE);
494 logins[login_count]->ut_host[UT_HOSTSIZE] = '\0';
495 logins[login_count]->ut_time = u.ut_time;
496 #endif
497 logins[++login_count] = NULL;
501 return logins;
504 /* The 'mesg' status of the logged in user. */
505 static char *msgstat(UTMP ** u, int multi)
507 static char line[LINE_MAX];
508 int i;
510 line[0] = '\0';
512 for (i = 0; i < login_count; i++) {
513 char filename[FILENAME_MAX];
514 struct stat st;
515 char m[2] = { multi, '\0' };
517 snprintf(filename, sizeof(filename), "%s%s", _PATH_DEV, u[i]->ut_line);
519 if (stat(filename, &st) == -1)
520 strncat(line, "!", sizeof(line));
521 else
522 strncat(line,
523 (st.st_mode & S_IWGRP || st.st_mode & S_IWOTH) ? "1" : "0",
524 sizeof(line));
526 strncat(line, m, sizeof(line));
529 if (line[0] == '\0')
530 return "!";
532 line[strlen(line) - 1] = '\0';
533 return line;
536 /* Returns the users idle time in seconds. */
537 static char *idle(UTMP ** u, int multi)
539 static char line[LINE_MAX];
540 time_t t;
541 struct stat st;
542 int i;
544 line[0] = '\0';
546 for (i = 0; i < login_count; i++) {
547 char buf[FILENAME_MAX];
548 char m[2] = { multi, '\0' };
550 snprintf(buf, sizeof(buf), "%s%s", _PATH_DEV, u[i]->ut_line);
552 if (stat(buf, &st) == -1) {
553 strncat(line, "!", sizeof(line));
554 strncat(line, m, sizeof(line));
555 continue;
558 #ifdef UTMPX_FORMAT
559 if (u[i]->ut_tv.tv_sec > st.st_atime) {
560 #else
561 if (u[i]->ut_time > st.st_atime) {
562 #endif
563 strncat(line, "-", sizeof(line));
564 strncat(line, m, sizeof(line));
565 continue;
568 t = st.st_atime;
570 #ifdef UTMPX_FORMAT
571 if (t < u[i]->ut_tv.tv_sec)
572 t = u[i]->ut_tv.tv_sec;
573 #else
574 if (t < u[i]->ut_time)
575 t = u[i]->ut_time;
576 #endif
578 snprintf(buf, sizeof(buf), "%lu", (now - t <= 0) ? 0 : now - t);
579 strncat(line, buf, sizeof(line));
580 strncat(line, m, sizeof(line));
583 if (line[0] == '\0')
584 return "!";
586 line[strlen(line) - 1] = '\0';
587 return line;
590 /* This is output if the -h command line option is passed to the main program.
592 void ui_module_help()
594 #ifdef DEBUG
595 fprintf(stderr, "%s: ui_module_help()\n", __FILE__);
596 #endif
598 printf(" Login information [-L (-%s)]:\n", LOGIN_OPTION_ORDER);
599 printf("\t-y tty\t\t\t\t");
600 printf("-m message status\n");
601 printf("\t-t login time stamp\t\t");
602 printf("-d duration in minutes\n");
603 printf("\t-h hostname\t\t\t");
604 printf("-i seconds idle\n");
605 printf("\t-p login process id\n");
606 printf("\t-l lastlog information"
607 " (any of tt[y],[h]ostname,[t]ime, or [a]ll)\n\n");
608 return;
611 /* This is the equivalent to main() only without argc and argv available. */
612 int ui_module_exec(char ***s, const struct passwd *pw, const int multi,
613 const int verbose, char *tf)
615 char *p = options;
616 UTMP **u = NULL, **up;
617 char buf[255];
619 login_count = 0;
620 u = get_utmp(pw->pw_name);
621 strings = *s;
623 while (*p) {
624 char line[LINE_MAX] = { '\0' };
625 int i;
626 char m[2] = { multi, '\0' };
627 #ifdef __NetBSD__
628 #ifdef UTMPX_FORMAT
629 char htmp[UTX_HOSTSIZE + 1];
630 #else
631 char htmp[UT_HOSTSIZE + 1];
632 #endif
633 #else
634 char htmp[UT_HOSTSIZE + 1];
635 #endif
637 switch (*p) {
638 case 'i':
639 add_string(&strings, (u) ? idle(u, multi) : "!");
640 break;
641 case 'l':
642 last_strings(lastlogin(pw->pw_uid, tf));
643 break;
644 case 'h':
645 for (i = 0; i < login_count; i++) {
646 if (u[i]->ut_host[0]
647 && isalnum((unsigned char) u[i]->ut_host[0])) {
648 strcpy(htmp, u[i]->ut_host);
649 htmp[sizeof(htmp) - 1] = '\0';
650 strncat(line, htmp, sizeof(line));
652 else {
654 * If a users tty is tty1-n, it must be a console
655 * * login.
657 if (u[i]->ut_line[0]
658 && isdigit((unsigned char) u[i]->ut_line[3]))
659 strncat(line, "-", sizeof(line));
660 else
661 strncat(line, "!", sizeof(line));
664 strncat(line, m, sizeof(line));
667 if (line[0] == '\0')
668 strncpy(line, "!", sizeof(line));
669 else
670 line[strlen(line) - 1] = '\0';
672 add_string(&strings, line);
673 break;
674 case 'y':
675 for (i = 0; i < login_count; i++) {
676 if (u[i]->ut_line[0])
677 strncat(line, u[i]->ut_line, sizeof(line));
678 else
679 strncat(line, "!", sizeof(line));
681 strncat(line, m, sizeof(line));
684 if (line[0] == '\0')
685 strncpy(line, "!", sizeof(line));
686 else
687 line[strlen(line) - 1] = '\0';
689 add_string(&strings, line);
690 break;
691 case 'm':
692 add_string(&strings, msgstat(u, multi));
693 break;
694 case 't':
695 for (i = 0; i < login_count; i++) {
696 #ifdef UTMPX_FORMAT
697 strncat(line, stamp(u[i]->ut_tv.tv_sec, tf), sizeof(line));
698 #else
699 strncat(line, stamp(u[i]->ut_time, tf), sizeof(line));
700 #endif
701 strncat(line, m, sizeof(line));
704 if (line[0] == '\0')
705 strncpy(line, "!", sizeof(line));
706 else
707 line[strlen(line) - 1] = '\0';
709 add_string(&strings, line);
710 break;
711 case 'd':
712 for (i = 0; i < login_count; i++) {
713 #ifdef UTMPX_FORMAT
714 if ((now - u[i]->ut_tv.tv_sec) > 60) {
715 snprintf(buf, sizeof(buf), "%lu",
716 ((now - u[i]->ut_tv.tv_sec) / 60));
717 #else
718 if ((now - u[i]->ut_time) > 60) {
719 snprintf(buf, sizeof(buf), "%lu",
720 ((now - u[i]->ut_time) / 60));
721 #endif
722 strncat(line, buf, sizeof(line));
724 else
725 strncat(line, "-", sizeof(line));
727 strncat(line, m, sizeof(line));
730 if (line[0] == '\0')
731 strncpy(line, "!", sizeof(line));
732 else
733 line[strlen(line) - 1] = '\0';
735 add_string(&strings, line);
736 break;
737 case 'p':
738 #ifdef UTMPX_FORMAT
739 for (i = 0; i < login_count; i++) {
740 if (u[i]->ut_pid) {
741 snprintf(buf, sizeof(buf), "%li", (long) u[i]->ut_pid);
742 strncat(line, buf, sizeof(line));
744 else
745 strncat(line, "!", sizeof(line));
747 strncat(line, m, sizeof(line));
750 if (line[0] == '\0')
751 strncpy(line, "!", sizeof(line));
752 else
753 line[strlen(line) - 1] = '\0';
755 add_string(&strings, line);
756 #else
757 add_string(&strings, (u) ? ui_module_pid(pw->pw_uid, multi) : "!");
758 #endif
759 break;
760 default:
761 break;
764 p++;
767 if (login_count) {
768 for (up = u; *up; up++)
769 free(*up);
771 free(u);
774 *s = strings;
775 return EXIT_SUCCESS;
778 /* See if the last login options (-l) are valid. */
779 static int parse_last_options(const char *args)
781 int i = 0;
783 for (i = 0; i < strlen(args); i++) {
784 switch (args[i]) {
785 case 'y':
786 case 'h':
787 case 't':
788 case 'a':
789 break;
790 default:
791 return 1;
795 return 0;
798 char *ui_module_options_init(char **defaults)
800 *defaults = "L";
801 return LOGIN_OPTION_STRING;
804 /* Check module option validity. */
805 int ui_module_options(int argc, char **argv)
807 int opt;
808 char *p = options;
810 #ifdef DEBUG
811 fprintf(stderr, "%s: ui_module_options()\n", __FILE__);
812 #endif
814 while ((opt = getopt(argc, argv, LOGIN_OPTION_STRING)) != -1) {
815 switch (opt) {
816 case 'l':
817 last_options = optarg;
818 break;
819 case 'L':
820 case 'p':
821 case 'd':
822 case 'i':
823 case 'm':
824 case 'y':
825 case 'h':
826 case 't':
827 break;
828 case '?':
829 warnx("login: invalid option -- %c", optopt);
830 default:
831 return 1;
834 if (opt == 'l') {
835 if (parse_last_options(last_options))
836 return 1;
839 if (opt == 'L') {
840 strncpy(options, LOGIN_OPTION_ORDER, sizeof(options));
841 last_options = "a";
842 break;
845 *p++ = opt;
846 *p = '\0';
849 return 0;