Compilation fix for Solaris > 4 (untested).
[userinfo.git] / src / modules / login.c
blobb2cbccf2228ba13b116245ec22040a67d4e63bb8
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"
42 #define LOGIN_OPTION_ORDER "pdimyhtl"
43 #define LOGIN_OPTION_STRING "Lpdimyhtl:"
45 static char options[9]; /* NULL terminated. */
46 static char *last_options;
47 static char **strings;
48 static time_t now;
49 static int login_count;
50 #if (!defined(__FreeBSD_version) || __FreeBSD_version < 900000)
51 static int lastlogfd;
52 #endif
54 void add_string(char ***, const char *);
55 char *stamp(time_t, const char *);
56 char *safe_strncat(char *, const char *, size_t);
58 void ui_module_init(int *chainable)
60 *chainable = 0;
61 time(&now);
64 void ui_module_exit()
66 #ifdef HAVE_PROCFS
67 if (procdir)
68 closedir(procdir);
69 #endif
71 #ifdef HAVE_KVM_H
72 if (kd)
73 kvm_close(kd);
74 #endif
76 #if (!defined(__FreeBSD_version) || __FreeBSD_version < 900000)
77 if (lastlogfd)
78 close(lastlogfd);
79 #endif
82 static void free_logins(UTMP **u)
84 if (login_count) {
85 UTMP **up;
87 for (up = u; *up; up++)
88 free(*up);
90 free(u);
94 #ifndef HAVE_UTMPX_H
95 /* This is for *BSD (login process id). */
96 #ifdef BSD_KVM
97 static char *get_pid(uid_t uid, int multi)
99 static int firstrun;
100 static char line[LINE_MAX];
101 int cnt, i;
102 pid_t pid = 0;
103 char errbuf[LINE_MAX];
104 struct kinfo_proc *kp;
106 line[0] = '\0';
108 if (!kd && firstrun)
109 return "!";
111 if (!kd) {
112 firstrun = 1;
114 #ifdef __NetBSD__
115 if ((kd = kvm_openfiles(NULL, NULL, NULL,
116 O_RDONLY, errbuf)) == NULL) {
117 #else
118 if ((kd = kvm_openfiles(_PATH_DEVNULL, _PATH_DEVNULL, _PATH_DEVNULL,
119 O_RDONLY, errbuf)) == NULL) {
120 #endif
121 warnx("%s", errbuf);
122 return "!";
126 if ((kp = kvm_getprocs(kd, KERN_PROC_UID, uid, &cnt)) == NULL) {
127 warnx("kvm_getprocs(): %s", kvm_geterr(kd));
128 return "!";
131 for (i = 0; i < cnt; i++) {
132 char buf[32];
134 #if __FreeBSD_version < 500000
135 if (kp[i].kp_eproc.e_flag & EPROC_SLEADER && kp[i].kp_eproc.e_tdev !=
136 -1) {
137 pid = kp[i].kp_eproc.e_ppid;
139 * pid = kp[i].kp_proc.p_pid;
142 if (pid == 1)
143 continue;
144 #else
145 if (kp[i].ki_kiflag & KI_SLEADER && kp[i].ki_tdev != -1) {
146 pid = kp[i].ki_pid;
147 #endif
148 snprintf(buf, sizeof(buf), "%i%c", pid, multi);
149 safe_strncat(line, buf, sizeof(line));
153 if (line[0] == '\0')
154 return "!";
156 line[strlen(line) - 1] = '\0';
157 return line;
160 /* This is for Linux and Solaris. */
161 #elif defined(HAVE_PROCFS)
162 #include <sys/types.h>
163 #include <sys/stat.h>
165 #ifdef HAVE_DIRENT_H
166 #include <dirent.h>
167 #endif
169 #ifdef __sun__
170 #include <unistd.h>
171 #include <procfs.h>
172 #endif
174 static char *get_pid(uid_t uid, int multi)
176 static int firstrun;
177 struct dirent *ent;
178 struct stat st;
179 static char line[LINE_MAX];
180 pid_t *pids = 0, *tpids;
181 int pid_index = 0;
183 #ifdef __sun__
184 int fd;
185 struct pstatus pstat;
186 #else
187 FILE *fp;
188 #endif
190 line[0] = '\0';
192 if (!procdir && firstrun)
193 return "!";
195 if (!procdir) {
196 firstrun = 1;
198 if ((procdir = opendir("/proc")) == NULL) {
199 warn("%s", "/proc");
200 return "!";
204 rewinddir(procdir);
206 again:
207 while ((ent = readdir(procdir)) != NULL) {
208 pid_t pid = -1;
209 char filename[FILENAME_MAX];
210 char buf[LINE_MAX];
211 int i;
213 #ifndef __sun__
214 char *t;
215 #endif
217 if (!isdigit((unsigned char) *ent->d_name))
218 continue;
220 #ifdef __linux__
221 snprintf(filename, sizeof(filename), "/proc/%s/stat", ent->d_name);
222 #else
223 snprintf(filename, sizeof(filename), "/proc/%s/status", ent->d_name);
224 #endif
226 if (stat(filename, &st) == -1)
227 continue;
230 * The current user owns this file (process id).
232 if (st.st_uid == uid) {
233 #ifdef __sun__
234 if ((fd = open(filename, O_RDONLY)) == -1)
235 continue;
237 if (pread(fd, &pstat, sizeof(struct pstatus), 0) !=
238 sizeof(struct pstatus)) {
239 close(fd);
240 continue;
243 pid = pstat.pr_ppid;
244 close(fd);
245 #else
246 if ((fp = fopen(filename, "r")) == NULL)
247 continue;
249 if ((t = fgets(buf, sizeof(buf), fp)) == NULL) {
250 fclose(fp);
251 continue;
254 #ifdef __linux__
255 if ((i = sscanf(buf, "%*i %*s %*c %*i %*i %i", &pid)) < 1) {
256 #endif
258 #else
259 if ((i = sscanf(buf, "%*s %*i %li", &ppid)) < 1) {
260 #endif
262 fclose(fp);
263 continue;
266 fclose(fp);
267 #endif
270 * Skip duplicate pids.
272 for (i = 0; i < pid_index; i++) {
273 if (pids[i] == pid)
274 goto again;
277 snprintf(buf, sizeof(buf), "%li%c", (unsigned long) pid, multi);
278 safe_strncat(line, buf, sizeof(line));
280 if ((tpids =
281 realloc(pids, (pid_index + 2) * sizeof(pid_t *))) == NULL) {
282 warn("realloc()");
283 continue;
286 pids = tpids;
287 pids[pid_index++] = pid;
291 if (pid_index)
292 free(pids);
294 if (line[0] == '\0')
295 return "!";
297 line[strlen(line) - 1] = '\0';
298 return line;
300 #else
301 /* Unsupported OS. */
302 static char *get_pid(uid_t uid, int multi)
304 return "!";
306 #endif
307 #endif
309 /* Break up the last login string into sections and add the sections to the
310 * output string array if needed. */
311 static void last_strings(char *str)
313 int i = 0;
314 char *buf;
315 const char *line, *host, *when;
317 line = host = when = (str) ? "-" : "!";
319 while ((buf = strsep(&str, ",")) != NULL) {
320 if (!buf[0])
321 continue;
323 switch (i++) {
324 case 0:
325 line = buf;
326 break;
327 case 1:
328 host = buf;
329 break;
330 case 2:
331 when = buf;
332 break;
333 default:
334 break;
338 for (i = 0; i < strlen(last_options); i++) {
339 switch (last_options[i]) {
340 case 'y':
341 add_string(&strings, line);
342 break;
343 case 'h':
344 add_string(&strings, host);
345 break;
346 case 't':
347 add_string(&strings, when);
348 break;
349 case 'a':
350 add_string(&strings, line);
351 add_string(&strings, host);
352 add_string(&strings, when);
353 default:
354 break;
359 /* Get the lastlog structure from the lastlog file. */
360 #if __FreeBSD_version >= 900000
361 static char *lastlogin(const struct passwd *pw, char *tf)
363 struct utmpx *last;
364 static char buf[LINE_MAX];
366 if (setutxdb(UTXDB_LASTLOGIN, NULL) == -1) {
367 warn("lastlog");
368 return NULL;
371 last = getutxuser(pw->pw_name);
373 if (!last)
374 return NULL;
376 snprintf(buf, sizeof(buf), "%s,%s,%s",
377 !last->ut_line[0] ? "!" : last->ut_line,
378 (!last->ut_host[0] || !isalnum(last->ut_host[0])) ? "-" : last->ut_host,
379 stamp(last->ut_tv.tv_sec, tf));
381 return buf;
383 #else
384 static char *lastlogin(const struct passwd *pw, char *tf)
386 int count;
387 long offset;
388 static char buf[LINE_MAX];
389 struct lastlog last;
391 if (lastlogfd < 0)
392 return NULL;
394 if (!lastlogfd) {
395 if ((lastlogfd = open(_PATH_LASTLOG, O_RDONLY)) == -1) {
396 warn("%s", _PATH_LASTLOG);
397 return NULL;
401 offset = (long) pw->pw_uid * sizeof(struct lastlog);
403 if (lseek(lastlogfd, offset, SEEK_SET) == -1) {
404 warn("%s", _PATH_LASTLOG);
405 return NULL;
408 if ((count = read(lastlogfd, &last, sizeof(struct lastlog))) !=
409 sizeof(struct lastlog)) {
410 if (count == -1)
411 warn("%s", _PATH_LASTLOG);
413 return NULL;
416 #ifdef __NetBSD__
417 #ifdef HAVE_UTMPX_H
418 last.ll_host[UTX_HOSTSIZE-1] = '\0';
419 last.ll_line[UTX_LINESIZE-1] = '\0';
420 #else
421 last.ll_host[UT_HOSTSIZE-1] = '\0';
422 last.ll_line[UT_LINESIZE-1] = '\0';
423 #endif
424 #else
425 last.ll_host[UT_HOSTSIZE-1] = '\0';
426 last.ll_line[UT_LINESIZE-1] = '\0';
427 #endif
429 snprintf(buf, sizeof(buf), "%s,%s,%s",
430 !last.ll_line[0] ? "!" : last.ll_line,
431 (!last.ll_host[0] || !isalnum(last.ll_host[0])) ?
432 !isdigit(last.ll_line[3]) ? "!" : "-" : last.ll_host,
433 !last.ll_time ? "!" : stamp(last.ll_time, tf));
434 return buf;
436 #endif
438 /* This will return an array of utmp structures if a user is logged in, NULL
439 * otherwise. We'll try to keep the utmp file descriptor open if possible to
440 * speed things up a bit. */
441 static UTMP **get_utmp(const char *user)
443 UTMP **logins = NULL;
444 #ifdef HAVE_UTMPX_H
445 UTMP *u;
446 #else
447 UTMP u;
448 int count;
449 static int fd;
451 if (fd < 0)
452 return NULL;
454 if (!fd) {
455 if ((fd = open(_PATH_UTMP, O_RDONLY)) == -1) {
456 warn("%s", _PATH_UTMP);
457 return NULL;
460 #endif
462 login_count = 0;
464 #ifdef HAVE_UTMPX_H
465 setutxent();
467 while ((u = getutxent()) != NULL) {
468 if (!strcmp(u->ut_user, user) && u->ut_type != DEAD_PROCESS) {
469 #else
470 lseek(fd, 0, SEEK_SET);
472 while ((count = read(fd, &u, sizeof(UTMP))) == sizeof(UTMP)) {
473 if (strcmp(u.ut_name, user) == 0) {
474 #endif
475 UTMP **tmp;
477 if ((tmp = realloc(logins,
478 (login_count + 2) * sizeof(UTMP *))) ==
479 NULL) {
480 warn("realloc()");
481 free_logins(logins);
482 return NULL;
485 logins = tmp;
487 if ((logins[login_count] = malloc(sizeof(UTMP))) == NULL) {
488 warn("malloc()");
489 free_logins(logins);
490 return NULL;
493 #ifdef HAVE_UTMPX_H
494 #ifdef __NetBSD__
495 memcpy(logins[login_count]->ut_name, u->ut_name, UTX_NAMESIZE);
496 logins[login_count]->ut_name[UTX_NAMESIZE-1] = 0;
497 memcpy(logins[login_count]->ut_line, u->ut_line, UTX_LINESIZE);
498 logins[login_count]->ut_line[UTX_LINESIZE-1] = 0;
499 memcpy(logins[login_count]->ut_host, u->ut_host, UTX_HOSTSIZE);
500 logins[login_count]->ut_host[UTX_HOSTSIZE-1] = 0;
501 logins[login_count]->ut_pid = u->ut_pid;
502 #else
503 memcpy(logins[login_count]->ut_user, u->ut_user, UT_NAMESIZE);
504 logins[login_count]->ut_user[UT_NAMESIZE-1] = 0;
505 memcpy(logins[login_count]->ut_line, u->ut_line, UT_LINESIZE);
506 logins[login_count]->ut_line[UT_LINESIZE-1] = 0;
507 memcpy(logins[login_count]->ut_host, u->ut_host, UT_HOSTSIZE);
508 logins[login_count]->ut_host[UT_HOSTSIZE-1] = 0;
509 logins[login_count]->ut_tv.tv_sec = u->ut_tv.tv_sec;
510 logins[login_count]->ut_pid = u->ut_pid;
511 #endif
512 #else
513 memcpy(logins[login_count]->ut_name, u.ut_name, UT_NAMESIZE);
514 logins[login_count]->ut_name[UT_NAMESIZE-1] = 0;
515 memcpy(logins[login_count]->ut_line, u.ut_line, UT_LINESIZE);
516 logins[login_count]->ut_line[UT_LINESIZE-1] = 0;
517 memcpy(logins[login_count]->ut_host, u.ut_host, UT_HOSTSIZE);
518 logins[login_count]->ut_host[UT_HOSTSIZE-1] = 0;
519 logins[login_count]->ut_time = u.ut_time;
520 #endif
521 logins[++login_count] = NULL;
525 return logins;
528 /* The 'mesg' status of the logged in user. */
529 static char *msgstat(UTMP **u, int multi)
531 static char line[LINE_MAX];
532 int i;
534 line[0] = '\0';
536 for (i = 0; i < login_count; i++) {
537 char filename[FILENAME_MAX];
538 struct stat st;
539 char m[2] = { multi, '\0' };
541 snprintf(filename, sizeof(filename), "%s%s", _PATH_DEV, u[i]->ut_line);
543 if (stat(filename, &st) == -1)
544 safe_strncat(line, "!", sizeof(line));
545 else
546 safe_strncat(line,
547 (st.st_mode & S_IWGRP || st.st_mode & S_IWOTH) ? "1" : "0",
548 sizeof(line));
550 safe_strncat(line, m, sizeof(line));
553 if (line[0] == '\0')
554 return "!";
556 line[strlen(line) - 1] = '\0';
557 return line;
560 /* Returns the users idle time in seconds. */
561 static char *idle(UTMP **u, int multi)
563 static char line[LINE_MAX];
564 time_t t;
565 struct stat st;
566 int i;
568 line[0] = '\0';
570 for (i = 0; i < login_count; i++) {
571 char buf[FILENAME_MAX];
572 char m[2] = { multi, '\0' };
574 snprintf(buf, sizeof(buf), "%s%s", _PATH_DEV, u[i]->ut_line);
576 if (stat(buf, &st) == -1) {
577 safe_strncat(line, "!", sizeof(line));
578 safe_strncat(line, m, sizeof(line));
579 continue;
582 #ifdef HAVE_UTMPX_H
583 if (u[i]->ut_tv.tv_sec > st.st_atime) {
584 #else
585 if (u[i]->ut_time > st.st_atime) {
586 #endif
587 safe_strncat(line, "-", sizeof(line));
588 safe_strncat(line, m, sizeof(line));
589 continue;
592 t = st.st_atime;
594 #ifdef HAVE_UTMPX_H
595 if (t < u[i]->ut_tv.tv_sec)
596 t = u[i]->ut_tv.tv_sec;
597 #else
598 if (t < u[i]->ut_time)
599 t = u[i]->ut_time;
600 #endif
602 snprintf(buf, sizeof(buf), "%lu", (now - t <= 0) ? 0 : now - t);
603 safe_strncat(line, buf, sizeof(line));
604 safe_strncat(line, m, sizeof(line));
607 if (line[0] == '\0')
608 return "!";
610 line[strlen(line) - 1] = '\0';
611 return line;
614 /* This is output if the -h command line option is passed to the main program.
616 void ui_module_help()
618 printf(" Login information [-L (-%s)]:\n", LOGIN_OPTION_ORDER);
619 printf("\t-y tty\t\t\t\t");
620 printf("-m message status\n");
621 printf("\t-t login time stamp\t\t");
622 printf("-d duration in minutes\n");
623 printf("\t-h hostname\t\t\t");
624 printf("-i seconds idle\n");
625 printf("\t-p login process id\n");
626 printf("\t-l lastlog information"
627 " (any of tt[y],[h]ostname,[t]ime, or [a]ll)\n\n");
628 return;
631 /* This is the equivalent to main() only without argc and argv available. */
632 int ui_module_exec(char ***s, const struct passwd *pw, const int multi,
633 const int verbose, char *tf)
635 char *p = options;
636 UTMP **u = NULL;
637 char buf[255];
639 login_count = 0;
640 u = get_utmp(pw->pw_name);
641 strings = *s;
643 while (*p) {
644 char line[LINE_MAX] = { '\0' };
645 int i;
646 char m[2] = { multi, '\0' };
648 switch (*p) {
649 case 'i':
650 add_string(&strings, (u) ? idle(u, multi) : "!");
651 break;
652 case 'l':
653 last_strings(lastlogin(pw, tf));
654 break;
655 case 'h':
656 for (i = 0; i < login_count; i++) {
657 if (u[i]->ut_host[0]
658 && isalnum((unsigned char) u[i]->ut_host[0]))
659 safe_strncat(line, u[i]->ut_host, sizeof(line));
660 else
661 safe_strncat(line, "-", sizeof(line));
663 safe_strncat(line, m, sizeof(line));
666 if (line[0] == '\0')
667 strncpy(line, "!", sizeof(line));
668 else
669 line[strlen(line) - 1] = '\0';
671 add_string(&strings, line);
672 break;
673 case 'y':
674 for (i = 0; i < login_count; i++) {
675 if (u[i]->ut_line[0])
676 safe_strncat(line, u[i]->ut_line, sizeof(line));
677 else
678 safe_strncat(line, "!", sizeof(line));
680 safe_strncat(line, m, sizeof(line));
683 if (line[0] == '\0')
684 strncpy(line, "!", sizeof(line));
685 else
686 line[strlen(line) - 1] = '\0';
688 add_string(&strings, line);
689 break;
690 case 'm':
691 add_string(&strings, msgstat(u, multi));
692 break;
693 case 't':
694 for (i = 0; i < login_count; i++) {
695 #ifdef HAVE_UTMPX_H
696 safe_strncat(line, stamp(u[i]->ut_tv.tv_sec, tf), sizeof(line));
697 #else
698 safe_strncat(line, stamp(u[i]->ut_time, tf), sizeof(line));
699 #endif
700 safe_strncat(line, m, sizeof(line));
703 if (line[0] == '\0')
704 strncpy(line, "!", sizeof(line));
705 else
706 line[strlen(line) - 1] = '\0';
708 add_string(&strings, line);
709 break;
710 case 'd':
711 for (i = 0; i < login_count; i++) {
712 #ifdef HAVE_UTMPX_H
713 if ((now - u[i]->ut_tv.tv_sec) > 60) {
714 snprintf(buf, sizeof(buf), "%lu",
715 ((now - u[i]->ut_tv.tv_sec) / 60));
716 #else
717 if ((now - u[i]->ut_time) > 60) {
718 snprintf(buf, sizeof(buf), "%lu",
719 ((now - u[i]->ut_time) / 60));
720 #endif
721 safe_strncat(line, buf, sizeof(line));
723 else
724 safe_strncat(line, "-", sizeof(line));
726 safe_strncat(line, m, sizeof(line));
729 if (line[0] == '\0')
730 strncpy(line, "!", sizeof(line));
731 else
732 line[strlen(line) - 1] = '\0';
734 add_string(&strings, line);
735 break;
736 case 'p':
737 #ifdef HAVE_UTMPX_H
738 for (i = 0; i < login_count; i++) {
739 if (u[i]->ut_pid) {
740 snprintf(buf, sizeof(buf), "%li", (long) u[i]->ut_pid);
741 safe_strncat(line, buf, sizeof(line));
743 else
744 safe_strncat(line, "!", sizeof(line));
746 safe_strncat(line, m, sizeof(line));
749 if (line[0] == '\0')
750 strncpy(line, "!", sizeof(line));
751 else
752 line[strlen(line) - 1] = '\0';
754 add_string(&strings, line);
755 #else
756 add_string(&strings, (u) ? get_pid(pw->pw_uid, multi) : "!");
757 #endif
758 break;
759 default:
760 break;
763 p++;
766 free_logins(u);
767 *s = strings;
768 return EXIT_SUCCESS;
771 /* See if the last login options (-l) are valid. */
772 static int parse_last_options(const char *args)
774 int i = 0;
776 for (i = 0; i < strlen(args); i++) {
777 switch (args[i]) {
778 case 'y':
779 case 'h':
780 case 't':
781 case 'a':
782 break;
783 default:
784 return 1;
788 return 0;
791 char *ui_module_options_init(char **defaults)
793 *defaults = "L";
794 return LOGIN_OPTION_STRING;
797 /* Check module option validity. */
798 int ui_module_options(int argc, char **argv)
800 int opt;
801 char *p = options;
803 while ((opt = getopt(argc, argv, LOGIN_OPTION_STRING)) != -1) {
804 switch (opt) {
805 case 'l':
806 if (parse_last_options(optarg))
807 return 1;
809 last_options = optarg;
810 break;
811 case 'L':
812 strncpy(options, LOGIN_OPTION_ORDER, sizeof(options));
813 last_options = "a";
814 return 0;
815 case 'p':
816 case 'd':
817 case 'i':
818 case 'm':
819 case 'y':
820 case 'h':
821 case 't':
822 break;
823 case '?':
824 warnx("login: invalid option -- %c", optopt);
825 default:
826 return 1;
829 *p++ = opt;
830 *p = '\0';
833 return 0;