More checks for limits.h and LINE_MAX.
[userinfo.git] / src / modules / login.c
blob77964ca22409b06b0e2457db9c496cf8436c3bb1
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 #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 pid_t pid = 0;
114 char errbuf[LINE_MAX];
115 struct kinfo_proc *kp;
117 line[0] = '\0';
119 if (!kd && firstrun)
120 return "!";
122 if (!kd) {
123 firstrun = 1;
125 #ifdef __NetBSD__
126 if ((kd = kvm_openfiles(NULL, NULL, NULL,
127 O_RDONLY, errbuf)) == NULL) {
128 #else
129 if ((kd = kvm_openfiles(_PATH_DEVNULL, _PATH_DEVNULL, _PATH_DEVNULL,
130 O_RDONLY, errbuf)) == NULL) {
131 #endif
132 warnx("%s", errbuf);
133 return "!";
137 if ((kp = kvm_getprocs(kd, KERN_PROC_UID, uid, &cnt)) == NULL) {
138 warnx("kvm_getprocs(): %s", kvm_geterr(kd));
139 return "!";
142 for (i = 0; i < cnt; i++) {
143 char buf[32];
145 #if __FreeBSD_version < 500000
146 if (kp[i].kp_eproc.e_flag & EPROC_SLEADER && kp[i].kp_eproc.e_tdev !=
147 -1) {
148 pid = kp[i].kp_eproc.e_ppid;
150 * pid = kp[i].kp_proc.p_pid;
153 if (pid == 1)
154 continue;
155 #else
156 if (kp[i].ki_kiflag & KI_SLEADER && kp[i].ki_tdev != -1) {
157 pid = kp[i].ki_pid;
158 #endif
159 snprintf(buf, sizeof(buf), "%i%c", pid, multi);
160 safe_strncat(line, buf, sizeof(line));
164 if (line[0] == '\0')
165 return "!";
167 line[strlen(line) - 1] = '\0';
168 return line;
171 /* This is for Linux and Solaris. */
172 #elif defined(HAVE_PROCFS)
173 #include <sys/types.h>
174 #include <sys/stat.h>
176 #ifdef HAVE_DIRENT_H
177 #include <dirent.h>
178 #endif
180 #ifdef __sun__
181 #include <unistd.h>
182 #include <procfs.h>
183 #endif
185 static char *get_pid(uid_t uid, int multi)
187 static int firstrun;
188 struct dirent *ent;
189 struct stat st;
190 static char line[LINE_MAX];
191 pid_t *pids = 0, *tpids;
192 int pid_index = 0;
194 #ifdef __sun__
195 int fd;
196 struct pstatus pstat;
197 #else
198 FILE *fp;
199 #endif
201 line[0] = '\0';
203 if (!procdir && firstrun)
204 return "!";
206 if (!procdir) {
207 firstrun = 1;
209 if ((procdir = opendir("/proc")) == NULL) {
210 warn("%s", "/proc");
211 return "!";
215 rewinddir(procdir);
217 again:
218 while ((ent = readdir(procdir)) != NULL) {
219 pid_t pid = -1;
220 char filename[FILENAME_MAX];
221 char buf[LINE_MAX];
222 int i;
224 #ifndef __sun__
225 char *t;
226 #endif
228 if (!isdigit((unsigned char) *ent->d_name))
229 continue;
231 #ifdef __linux__
232 snprintf(filename, sizeof(filename), "/proc/%s/stat", ent->d_name);
233 #else
234 snprintf(filename, sizeof(filename), "/proc/%s/status", ent->d_name);
235 #endif
237 if (stat(filename, &st) == -1)
238 continue;
241 * The current user owns this file (process id).
243 if (st.st_uid == uid) {
244 #ifdef __sun__
245 if ((fd = open(filename, O_RDONLY)) == -1)
246 continue;
248 if (pread(fd, &pstat, sizeof(struct pstatus), 0) !=
249 sizeof(struct pstatus)) {
250 close(fd);
251 continue;
254 pid = pstat.pr_ppid;
255 close(fd);
256 #else
257 if ((fp = fopen(filename, "r")) == NULL)
258 continue;
260 if ((t = fgets(buf, sizeof(buf), fp)) == NULL) {
261 fclose(fp);
262 continue;
265 #ifdef __linux__
266 if ((i = sscanf(buf, "%*i %*s %*c %*i %*i %i", &pid)) < 1) {
267 #endif
269 #else
270 if ((i = sscanf(buf, "%*s %*i %li", &ppid)) < 1) {
271 #endif
273 fclose(fp);
274 continue;
277 fclose(fp);
278 #endif
281 * Skip duplicate pids.
283 for (i = 0; i < pid_index; i++) {
284 if (pids[i] == pid)
285 goto again;
288 snprintf(buf, sizeof(buf), "%li%c", (unsigned long) pid, multi);
289 safe_strncat(line, buf, sizeof(line));
291 if ((tpids =
292 realloc(pids, (pid_index + 2) * sizeof(pid_t *))) == NULL) {
293 warn("realloc()");
294 continue;
297 pids = tpids;
298 pids[pid_index++] = pid;
302 if (pid_index)
303 free(pids);
305 if (line[0] == '\0')
306 return "!";
308 line[strlen(line) - 1] = '\0';
309 return line;
311 #else
312 /* Unsupported OS. */
313 static char *get_pid(uid_t uid, int multi)
315 return "!";
317 #endif
318 #endif
320 /* Break up the last login string into sections and add the sections to the
321 * output string array if needed. */
322 static void last_strings(char *str)
324 int i = 0;
325 char *buf;
326 const char *line, *host, *when;
328 line = host = when = (str) ? "-" : "!";
330 while ((buf = strsep(&str, ",")) != NULL) {
331 if (!buf[0])
332 continue;
334 switch (i++) {
335 case 0:
336 line = buf;
337 break;
338 case 1:
339 host = buf;
340 break;
341 case 2:
342 when = buf;
343 break;
344 default:
345 break;
349 for (i = 0; i < strlen(last_options); i++) {
350 switch (last_options[i]) {
351 case 'y':
352 add_string(&strings, line);
353 break;
354 case 'h':
355 add_string(&strings, host);
356 break;
357 case 't':
358 add_string(&strings, when);
359 break;
360 case 'a':
361 add_string(&strings, line);
362 add_string(&strings, host);
363 add_string(&strings, when);
364 default:
365 break;
370 /* Get the lastlog structure from the lastlog file. */
371 #if __FreeBSD_version >= 900000
372 static char *lastlogin(const struct passwd *pw, char *tf)
374 struct utmpx *last;
375 static char buf[LINE_MAX];
377 if (setutxdb(UTXDB_LASTLOGIN, NULL) == -1) {
378 warn("lastlog");
379 return NULL;
382 last = getutxuser(pw->pw_name);
384 if (!last)
385 return NULL;
387 snprintf(buf, sizeof(buf), "%s,%s,%s",
388 !last->ut_line[0] ? "!" : last->ut_line,
389 (!last->ut_host[0] || !isalnum(last->ut_host[0])) ? "-" : last->ut_host,
390 stamp(last->ut_tv.tv_sec, tf));
392 return buf;
394 #else
395 static char *lastlogin(const struct passwd *pw, char *tf)
397 int count;
398 long offset;
399 static char buf[LINE_MAX];
400 struct lastlog last;
402 if (lastlogfd < 0)
403 return NULL;
405 if (!lastlogfd) {
406 if ((lastlogfd = open(_PATH_LASTLOG, O_RDONLY)) == -1) {
407 warn("%s", _PATH_LASTLOG);
408 return NULL;
412 offset = (long) pw->pw_uid * sizeof(struct lastlog);
414 if (lseek(lastlogfd, offset, SEEK_SET) == -1) {
415 warn("%s", _PATH_LASTLOG);
416 return NULL;
419 if ((count = read(lastlogfd, &last, sizeof(struct lastlog))) !=
420 sizeof(struct lastlog)) {
421 if (count == -1)
422 warn("%s", _PATH_LASTLOG);
424 return NULL;
427 #ifdef __NetBSD__
428 #ifdef HAVE_UTMPX_H
429 last.ll_host[UTX_HOSTSIZE-1] = '\0';
430 last.ll_line[UTX_LINESIZE-1] = '\0';
431 #else
432 last.ll_host[UT_HOSTSIZE-1] = '\0';
433 last.ll_line[UT_LINESIZE-1] = '\0';
434 #endif
435 #else
436 last.ll_host[UT_HOSTSIZE-1] = '\0';
437 last.ll_line[UT_LINESIZE-1] = '\0';
438 #endif
440 snprintf(buf, sizeof(buf), "%s,%s,%s",
441 !last.ll_line[0] ? "!" : last.ll_line,
442 (!last.ll_host[0] || !isalnum(last.ll_host[0])) ?
443 !isdigit(last.ll_line[3]) ? "!" : "-" : last.ll_host,
444 !last.ll_time ? "!" : stamp(last.ll_time, tf));
445 return buf;
447 #endif
449 /* This will return an array of utmp structures if a user is logged in, NULL
450 * otherwise. We'll try to keep the utmp file descriptor open if possible to
451 * speed things up a bit. */
452 static UTMP **get_utmp(const char *user)
454 UTMP **logins = NULL;
455 #ifdef HAVE_UTMPX_H
456 UTMP *u;
457 #else
458 UTMP u;
459 int count;
460 static int fd;
462 if (fd < 0)
463 return NULL;
465 if (!fd) {
466 if ((fd = open(_PATH_UTMP, O_RDONLY)) == -1) {
467 warn("%s", _PATH_UTMP);
468 return NULL;
471 #endif
473 login_count = 0;
475 #ifdef HAVE_UTMPX_H
476 setutxent();
478 while ((u = getutxent()) != NULL) {
479 if (!strcmp(u->ut_user, user) && u->ut_type != DEAD_PROCESS) {
480 #else
481 lseek(fd, 0, SEEK_SET);
483 while ((count = read(fd, &u, sizeof(UTMP))) == sizeof(UTMP)) {
484 if (strcmp(u.ut_name, user) == 0) {
485 #endif
486 UTMP **tmp;
488 if ((tmp = realloc(logins,
489 (login_count + 2) * sizeof(UTMP *))) ==
490 NULL) {
491 warn("realloc()");
492 free_logins(logins);
493 return NULL;
496 logins = tmp;
498 if ((logins[login_count] = malloc(sizeof(UTMP))) == NULL) {
499 warn("malloc()");
500 free_logins(logins);
501 return NULL;
504 #ifdef HAVE_UTMPX_H
505 #ifdef __NetBSD__
506 memcpy(logins[login_count]->ut_name, u->ut_name, UTX_NAMESIZE);
507 logins[login_count]->ut_name[UTX_NAMESIZE-1] = 0;
508 memcpy(logins[login_count]->ut_line, u->ut_line, UTX_LINESIZE);
509 logins[login_count]->ut_line[UTX_LINESIZE-1] = 0;
510 memcpy(logins[login_count]->ut_host, u->ut_host, UTX_HOSTSIZE);
511 logins[login_count]->ut_host[UTX_HOSTSIZE-1] = 0;
512 logins[login_count]->ut_pid = u->ut_pid;
513 #else
514 memcpy(logins[login_count]->ut_user, u->ut_user, UT_NAMESIZE);
515 logins[login_count]->ut_user[UT_NAMESIZE-1] = 0;
516 memcpy(logins[login_count]->ut_line, u->ut_line, UT_LINESIZE);
517 logins[login_count]->ut_line[UT_LINESIZE-1] = 0;
518 memcpy(logins[login_count]->ut_host, u->ut_host, UT_HOSTSIZE);
519 logins[login_count]->ut_host[UT_HOSTSIZE-1] = 0;
520 logins[login_count]->ut_tv.tv_sec = u->ut_tv.tv_sec;
521 logins[login_count]->ut_pid = u->ut_pid;
522 #endif
523 #else
524 memcpy(logins[login_count]->ut_name, u.ut_name, UT_NAMESIZE);
525 logins[login_count]->ut_name[UT_NAMESIZE-1] = 0;
526 memcpy(logins[login_count]->ut_line, u.ut_line, UT_LINESIZE);
527 logins[login_count]->ut_line[UT_LINESIZE-1] = 0;
528 memcpy(logins[login_count]->ut_host, u.ut_host, UT_HOSTSIZE);
529 logins[login_count]->ut_host[UT_HOSTSIZE-1] = 0;
530 logins[login_count]->ut_time = u.ut_time;
531 #endif
532 logins[++login_count] = NULL;
536 return logins;
539 /* The 'mesg' status of the logged in user. */
540 static char *msgstat(UTMP **u, int multi)
542 static char line[LINE_MAX];
543 int i;
545 line[0] = '\0';
547 for (i = 0; i < login_count; i++) {
548 char filename[FILENAME_MAX];
549 struct stat st;
550 char m[2] = { multi, '\0' };
552 snprintf(filename, sizeof(filename), "%s%s", _PATH_DEV, u[i]->ut_line);
554 if (stat(filename, &st) == -1)
555 safe_strncat(line, "!", sizeof(line));
556 else
557 safe_strncat(line,
558 (st.st_mode & S_IWGRP || st.st_mode & S_IWOTH) ? "1" : "0",
559 sizeof(line));
561 safe_strncat(line, m, sizeof(line));
564 if (line[0] == '\0')
565 return "!";
567 line[strlen(line) - 1] = '\0';
568 return line;
571 /* Returns the users idle time in seconds. */
572 static char *idle(UTMP **u, int multi)
574 static char line[LINE_MAX];
575 time_t t;
576 struct stat st;
577 int i;
579 line[0] = '\0';
581 for (i = 0; i < login_count; i++) {
582 char buf[FILENAME_MAX];
583 char m[2] = { multi, '\0' };
585 snprintf(buf, sizeof(buf), "%s%s", _PATH_DEV, u[i]->ut_line);
587 if (stat(buf, &st) == -1) {
588 safe_strncat(line, "!", sizeof(line));
589 safe_strncat(line, m, sizeof(line));
590 continue;
593 #ifdef HAVE_UTMPX_H
594 if (u[i]->ut_tv.tv_sec > st.st_atime) {
595 #else
596 if (u[i]->ut_time > st.st_atime) {
597 #endif
598 safe_strncat(line, "-", sizeof(line));
599 safe_strncat(line, m, sizeof(line));
600 continue;
603 t = st.st_atime;
605 #ifdef HAVE_UTMPX_H
606 if (t < u[i]->ut_tv.tv_sec)
607 t = u[i]->ut_tv.tv_sec;
608 #else
609 if (t < u[i]->ut_time)
610 t = u[i]->ut_time;
611 #endif
613 snprintf(buf, sizeof(buf), "%lu", (now - t <= 0) ? 0 : now - t);
614 safe_strncat(line, buf, sizeof(line));
615 safe_strncat(line, m, sizeof(line));
618 if (line[0] == '\0')
619 return "!";
621 line[strlen(line) - 1] = '\0';
622 return line;
625 /* This is output if the -h command line option is passed to the main program.
627 void ui_module_help()
629 printf(" Login information [-L (-%s)]:\n", LOGIN_OPTION_ORDER);
630 printf("\t-y tty\t\t\t\t");
631 printf("-m message status\n");
632 printf("\t-t login time stamp\t\t");
633 printf("-d duration in minutes\n");
634 printf("\t-h hostname\t\t\t");
635 printf("-i seconds idle\n");
636 printf("\t-p login process id\n");
637 printf("\t-l lastlog information"
638 " (any of tt[y],[h]ostname,[t]ime, or [a]ll)\n\n");
639 return;
642 /* This is the equivalent to main() only without argc and argv available. */
643 int ui_module_exec(char ***s, const struct passwd *pw, const int multi,
644 const int verbose, char *tf)
646 char *p = options;
647 UTMP **u = NULL;
648 char buf[255];
650 login_count = 0;
651 u = get_utmp(pw->pw_name);
652 strings = *s;
654 while (*p) {
655 char line[LINE_MAX] = { '\0' };
656 int i;
657 char m[2] = { multi, '\0' };
659 switch (*p) {
660 case 'i':
661 add_string(&strings, (u) ? idle(u, multi) : "!");
662 break;
663 case 'l':
664 last_strings(lastlogin(pw, tf));
665 break;
666 case 'h':
667 for (i = 0; i < login_count; i++) {
668 if (u[i]->ut_host[0]
669 && isalnum((unsigned char) u[i]->ut_host[0]))
670 safe_strncat(line, u[i]->ut_host, sizeof(line));
671 else
672 safe_strncat(line, "-", sizeof(line));
674 safe_strncat(line, m, sizeof(line));
677 if (line[0] == '\0')
678 strncpy(line, "!", sizeof(line));
679 else
680 line[strlen(line) - 1] = '\0';
682 add_string(&strings, line);
683 break;
684 case 'y':
685 for (i = 0; i < login_count; i++) {
686 if (u[i]->ut_line[0])
687 safe_strncat(line, u[i]->ut_line, sizeof(line));
688 else
689 safe_strncat(line, "!", sizeof(line));
691 safe_strncat(line, m, sizeof(line));
694 if (line[0] == '\0')
695 strncpy(line, "!", sizeof(line));
696 else
697 line[strlen(line) - 1] = '\0';
699 add_string(&strings, line);
700 break;
701 case 'm':
702 add_string(&strings, msgstat(u, multi));
703 break;
704 case 't':
705 for (i = 0; i < login_count; i++) {
706 #ifdef HAVE_UTMPX_H
707 safe_strncat(line, stamp(u[i]->ut_tv.tv_sec, tf), sizeof(line));
708 #else
709 safe_strncat(line, stamp(u[i]->ut_time, tf), sizeof(line));
710 #endif
711 safe_strncat(line, m, sizeof(line));
714 if (line[0] == '\0')
715 strncpy(line, "!", sizeof(line));
716 else
717 line[strlen(line) - 1] = '\0';
719 add_string(&strings, line);
720 break;
721 case 'd':
722 for (i = 0; i < login_count; i++) {
723 #ifdef HAVE_UTMPX_H
724 if ((now - u[i]->ut_tv.tv_sec) > 60) {
725 snprintf(buf, sizeof(buf), "%lu",
726 ((now - u[i]->ut_tv.tv_sec) / 60));
727 #else
728 if ((now - u[i]->ut_time) > 60) {
729 snprintf(buf, sizeof(buf), "%lu",
730 ((now - u[i]->ut_time) / 60));
731 #endif
732 safe_strncat(line, buf, sizeof(line));
734 else
735 safe_strncat(line, "-", sizeof(line));
737 safe_strncat(line, m, sizeof(line));
740 if (line[0] == '\0')
741 strncpy(line, "!", sizeof(line));
742 else
743 line[strlen(line) - 1] = '\0';
745 add_string(&strings, line);
746 break;
747 case 'p':
748 #ifdef HAVE_UTMPX_H
749 for (i = 0; i < login_count; i++) {
750 if (u[i]->ut_pid) {
751 snprintf(buf, sizeof(buf), "%li", (long) u[i]->ut_pid);
752 safe_strncat(line, buf, sizeof(line));
754 else
755 safe_strncat(line, "!", sizeof(line));
757 safe_strncat(line, m, sizeof(line));
760 if (line[0] == '\0')
761 strncpy(line, "!", sizeof(line));
762 else
763 line[strlen(line) - 1] = '\0';
765 add_string(&strings, line);
766 #else
767 add_string(&strings, (u) ? get_pid(pw->pw_uid, multi) : "!");
768 #endif
769 break;
770 default:
771 break;
774 p++;
777 free_logins(u);
778 *s = strings;
779 return EXIT_SUCCESS;
782 /* See if the last login options (-l) are valid. */
783 static int parse_last_options(const char *args)
785 int i = 0;
787 for (i = 0; i < strlen(args); i++) {
788 switch (args[i]) {
789 case 'y':
790 case 'h':
791 case 't':
792 case 'a':
793 break;
794 default:
795 return 1;
799 return 0;
802 char *ui_module_options_init(char **defaults)
804 *defaults = "L";
805 return LOGIN_OPTION_STRING;
808 /* Check module option validity. */
809 int ui_module_options(int argc, char **argv)
811 int opt;
812 char *p = options;
814 while ((opt = getopt(argc, argv, LOGIN_OPTION_STRING)) != -1) {
815 switch (opt) {
816 case 'l':
817 if (parse_last_options(optarg))
818 return 1;
820 last_options = optarg;
821 break;
822 case 'L':
823 strncpy(options, LOGIN_OPTION_ORDER, sizeof(options));
824 last_options = "a";
825 return 0;
826 case 'p':
827 case 'd':
828 case 'i':
829 case 'm':
830 case 'y':
831 case 'h':
832 case 't':
833 break;
834 case '?':
835 warnx("login: invalid option -- %c", optopt);
836 default:
837 return 1;
840 *p++ = opt;
841 *p = '\0';
844 return 0;