Use safe_strncat() to prevent overrunning the destination buffer.
[userinfo.git] / src / modules / login.c
blobad97535e86972315878fc02cc2f4de866e3eec62
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 #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 #ifdef DEBUG
59 fprintf(stderr, "%s: ui_module_init()\n", __FILE__);
60 #endif
62 *chainable = 0;
63 time(&now);
66 void ui_module_exit()
68 #ifdef DEBUG
69 fprintf(stderr, "%s: ui_module_exit()\n", __FILE__);
70 #endif
72 #ifdef HAVE_PROCFS
73 if (procdir)
74 closedir(procdir);
75 #endif
77 #ifdef HAVE_KVM_H
78 if (kd)
79 kvm_close(kd);
80 #endif
82 if (lastlogfd)
83 close(lastlogfd);
86 #ifndef HAVE_UTMPX_H
87 /* This is for *BSD (login process id). */
88 #ifdef BSD_KVM
89 char *ui_module_pid(uid_t uid, int multi)
91 static int firstrun;
92 static char line[LINE_MAX];
93 int cnt, i;
94 pid_t pid = 0;
95 char errbuf[LINE_MAX];
96 struct kinfo_proc *kp;
98 line[0] = '\0';
100 if (!kd && firstrun)
101 return "!";
103 if (!kd) {
104 firstrun = 1;
106 #ifdef __NetBSD__
107 if ((kd = kvm_openfiles(NULL, NULL, NULL,
108 #else
109 if ((kd = kvm_openfiles(_PATH_DEVNULL, _PATH_DEVNULL, _PATH_DEVNULL,
110 #endif
111 O_RDONLY, errbuf)) == NULL) {
112 warnx("%s", errbuf);
113 return "!";
117 if ((kp = kvm_getprocs(kd, KERN_PROC_UID, uid, &cnt)) == NULL) {
118 warnx("kvm_getprocs(): %s", kvm_geterr(kd));
119 return "!";
122 for (i = 0; i < cnt; i++) {
123 char buf[32];
125 #if __FreeBSD_version < 500000
126 if (kp[i].kp_eproc.e_flag & EPROC_SLEADER && kp[i].kp_eproc.e_tdev !=
127 -1) {
128 pid = kp[i].kp_eproc.e_ppid;
130 * pid = kp[i].kp_proc.p_pid;
133 if (pid == 1)
134 continue;
135 #else
136 if (kp[i].ki_kiflag & KI_SLEADER && kp[i].ki_tdev != -1) {
137 pid = kp[i].ki_pid;
138 #endif
139 snprintf(buf, sizeof(buf), "%i%c", pid, multi);
140 safe_strncat(line, buf, sizeof(line));
144 if (line[0] == '\0')
145 return "!";
147 line[strlen(line) - 1] = '\0';
148 return line;
151 /* This is for Linux and Solaris. */
152 #elif defined(HAVE_PROCFS)
153 #include <sys/types.h>
154 #include <sys/stat.h>
156 #ifdef HAVE_DIRENT_H
157 #include <dirent.h>
158 #endif
160 #ifdef __svr4__
161 #include <unistd.h>
162 #include <procfs.h>
163 #endif
165 char *ui_module_pid(uid_t uid, int multi)
167 static int firstrun;
168 struct dirent *ent;
169 struct stat st;
170 static char line[LINE_MAX];
171 pid_t *pids = 0;
172 int pid_index = 0;
174 #ifdef __svr4__
175 int fd;
176 struct pstatus pstat;
177 #else
178 FILE *fp;
179 #endif
181 line[0] = '\0';
183 if (!procdir && firstrun)
184 return "!";
186 if (!procdir) {
187 firstrun = 1;
189 if ((procdir = opendir("/proc")) == NULL) {
190 warn("%s", "/proc");
191 return "!";
195 rewinddir(procdir);
197 again:
198 while ((ent = readdir(procdir)) != NULL) {
199 pid_t pid = -1;
200 char filename[FILENAME_MAX];
201 char buf[LINE_MAX];
202 int i;
204 #ifndef __svr4__
205 char *t;
206 #endif
208 if (!isdigit((unsigned char) *ent->d_name))
209 continue;
211 #ifdef __linux__
212 snprintf(filename, sizeof(filename), "/proc/%s/stat", ent->d_name);
213 #else
214 snprintf(filename, sizeof(filename), "/proc/%s/status", ent->d_name);
215 #endif
217 if (stat(filename, &st) == -1)
218 continue;
221 * The current user owns this file (process id).
223 if (st.st_uid == uid) {
224 #ifdef __svr4__
225 if ((fd = open(filename, O_RDONLY)) == -1)
226 continue;
228 if (pread(fd, &pstat, sizeof(struct pstatus), 0) !=
229 sizeof(struct pstatus)) {
230 close(fd);
231 continue;
234 pid = pstat.pr_ppid;
235 close(fd);
236 #else
237 if ((fp = fopen(filename, "r")) == NULL)
238 continue;
240 if ((t = fgets(buf, sizeof(buf), fp)) == NULL) {
241 fclose(fp);
242 continue;
245 #ifdef __linux__
246 if ((i = sscanf(buf, "%*i %*s %*c %*i %*i %i", &pid)) < 1) {
247 #endif
249 #else
250 if ((i = sscanf(buf, "%*s %*i %li", &ppid)) < 1) {
251 #endif
253 fclose(fp);
254 continue;
257 fclose(fp);
258 #endif
261 * Skip duplicate pids.
263 for (i = 0; i < pid_index; i++) {
264 if (pids[i] == pid)
265 goto again;
268 snprintf(buf, sizeof(buf), "%li%c", (unsigned long) pid, multi);
269 safe_strncat(line, buf, sizeof(line));
271 if ((pids =
272 realloc(pids, (pid_index + 2) * sizeof(pid_t *))) == NULL) {
273 warn("realloc()");
274 continue;
277 pids[pid_index++] = pid;
281 if (pid_index)
282 free(pids);
284 if (line[0] == '\0')
285 return "!";
287 line[strlen(line) - 1] = '\0';
288 return line;
290 #else
291 /* Unsupported OS. */
292 char *ui_module_pid(uid_t uid, int multi)
294 return "!";
296 #endif
297 #endif
299 /* Break up the last login string into sections and add the sections to the
300 * output string array if needed. */
301 static void last_strings(char *str)
303 int i = 0;
304 char *buf;
305 const char *line, *host, *when;
307 line = host = when = (str) ? "-" : "!";
309 while ((buf = strsep(&str, ",")) != NULL) {
310 if (!buf[0])
311 continue;
313 switch (i++) {
314 case 0:
315 line = buf;
316 break;
317 case 1:
318 host = buf;
319 break;
320 case 2:
321 when = buf;
322 break;
323 default:
324 break;
328 for (i = 0; i < strlen(last_options); i++) {
329 switch (last_options[i]) {
330 case 'y':
331 add_string(&strings, line);
332 break;
333 case 'h':
334 add_string(&strings, host);
335 break;
336 case 't':
337 add_string(&strings, when);
338 break;
339 case 'a':
340 add_string(&strings, line);
341 add_string(&strings, host);
342 add_string(&strings, when);
343 default:
344 break;
349 /* Get the lastlog structure from the lastlog file. */
350 #if (defined(__FreeBSD_version) && __FreeBSD_version >= 900000)
351 static char *lastlogin(const struct passwd *pw, char *tf)
353 struct utmpx *last;
354 static char buf[LINE_MAX];
356 if (setutxdb(UTXDB_LASTLOGIN, NULL) == -1) {
357 warn("lastlog");
358 return NULL;
361 last = getutxuser(pw->pw_name);
363 if (!last)
364 return NULL;
366 snprintf(buf, sizeof(buf), "%s,%s,%s",
367 !last->ut_line[0] ? "!" : last->ut_line,
368 (!last->ut_host[0] || !isalnum(last->ut_host[0])) ?
369 !isdigit(last->ut_line[3]) ? "!" : "-" : last->ut_host,
370 !last->ut_time[0] ? "!" : stamp(last->ut_time, tf));
372 return buf;
374 #else
375 static char *lastlogin(const struct passwd *pw, char *tf)
377 int count;
378 long offset;
379 static char buf[LINE_MAX];
380 struct lastlog last;
382 if (lastlogfd < 0)
383 return NULL;
385 if (!lastlogfd) {
386 if ((lastlogfd = open(_PATH_LASTLOG, O_RDONLY)) == -1) {
387 warn("%s", _PATH_LASTLOG);
388 return NULL;
392 offset = (long) pw->pw_uid * sizeof(struct lastlog);
394 if (lseek(lastlogfd, offset, SEEK_SET) == -1) {
395 warn("%s", _PATH_LASTLOG);
396 return NULL;
399 if ((count = read(lastlogfd, &last, sizeof(struct lastlog))) !=
400 sizeof(struct lastlog)) {
401 if (count == -1)
402 warn("%s", _PATH_LASTLOG);
404 return NULL;
407 #ifdef __NetBSD__
408 #ifdef HAVE_UTMPX_H
409 last.ll_host[UTX_HOSTSIZE-1] = '\0';
410 last.ll_line[UTX_LINESIZE-1] = '\0';
411 #else
412 last.ll_host[UT_HOSTSIZE-1] = '\0';
413 last.ll_line[UT_LINESIZE-1] = '\0';
414 #endif
415 #else
416 last.ll_host[UT_HOSTSIZE-1] = '\0';
417 last.ll_line[UT_LINESIZE-1] = '\0';
418 #endif
420 snprintf(buf, sizeof(buf), "%s,%s,%s",
421 !last.ll_line[0] ? "!" : last.ll_line,
422 (!last.ll_host[0] || !isalnum(last.ll_host[0])) ?
423 !isdigit(last.ll_line[3]) ? "!" : "-" : last.ll_host,
424 !last.ll_time ? "!" : stamp(last.ll_time, tf));
425 return buf;
427 #endif
429 /* This will return an array of utmp structures if a user is logged in, NULL
430 * otherwise. We'll try to keep the utmp file descriptor open if possible to
431 * speed things up a bit. */
432 static UTMP **get_utmp(const char *user)
434 UTMP **logins = NULL;
435 #ifdef HAVE_UTMPX_H
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 HAVE_UTMPX_H
456 setutxent();
458 while ((u = getutxent()) != NULL) {
459 if (!strcmp(u->ut_user, user)) {
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 HAVE_UTMPX_H
479 #ifdef __NetBSD__
480 memcpy(logins[login_count]->ut_name, u->ut_name, UTX_NAMESIZE);
481 logins[login_count]->ut_name[UTX_NAMESIZE-1] = 0;
482 memcpy(logins[login_count]->ut_line, u->ut_line, UTX_LINESIZE);
483 logins[login_count]->ut_line[UTX_LINESIZE-1] = 0;
484 memcpy(logins[login_count]->ut_host, u->ut_host, UTX_HOSTSIZE);
485 logins[login_count]->ut_host[UTX_HOSTSIZE-1] = 0;
486 logins[login_count]->ut_pid = u->ut_pid;
487 #else
488 memcpy(logins[login_count]->ut_user, u->ut_user, UT_NAMESIZE);
489 logins[login_count]->ut_user[UT_NAMESIZE-1] = 0;
490 memcpy(logins[login_count]->ut_line, u->ut_line, UT_LINESIZE);
491 logins[login_count]->ut_line[UT_LINESIZE-1] = 0;
492 memcpy(logins[login_count]->ut_host, u->ut_host, UT_HOSTSIZE);
493 logins[login_count]->ut_host[UT_HOSTSIZE-1] = 0;
494 logins[login_count]->ut_tv.tv_sec = u->ut_tv.tv_sec;
495 logins[login_count]->ut_pid = u->ut_pid;
496 #endif
497 #else
498 memcpy(logins[login_count]->ut_name, u.ut_name, UT_NAMESIZE);
499 logins[login_count]->ut_name[UT_NAMESIZE-1] = 0;
500 memcpy(logins[login_count]->ut_line, u.ut_line, UT_LINESIZE);
501 logins[login_count]->ut_line[UT_LINESIZE-1] = 0;
502 memcpy(logins[login_count]->ut_host, u.ut_host, UT_HOSTSIZE);
503 logins[login_count]->ut_host[UT_HOSTSIZE-1] = 0;
504 logins[login_count]->ut_time = u.ut_time;
505 #endif
506 logins[++login_count] = NULL;
510 return logins;
513 /* The 'mesg' status of the logged in user. */
514 static char *msgstat(UTMP **u, int multi)
516 static char line[LINE_MAX];
517 int i;
519 line[0] = '\0';
521 for (i = 0; i < login_count; i++) {
522 char filename[FILENAME_MAX];
523 struct stat st;
524 char m[2] = { multi, '\0' };
526 snprintf(filename, sizeof(filename), "%s%s", _PATH_DEV, u[i]->ut_line);
528 if (stat(filename, &st) == -1)
529 safe_strncat(line, "!", sizeof(line));
530 else
531 safe_strncat(line,
532 (st.st_mode & S_IWGRP || st.st_mode & S_IWOTH) ? "1" : "0",
533 sizeof(line));
535 safe_strncat(line, m, sizeof(line));
538 if (line[0] == '\0')
539 return "!";
541 line[strlen(line) - 1] = '\0';
542 return line;
545 /* Returns the users idle time in seconds. */
546 static char *idle(UTMP **u, int multi)
548 static char line[LINE_MAX];
549 time_t t;
550 struct stat st;
551 int i;
553 line[0] = '\0';
555 for (i = 0; i < login_count; i++) {
556 char buf[FILENAME_MAX];
557 char m[2] = { multi, '\0' };
559 snprintf(buf, sizeof(buf), "%s%s", _PATH_DEV, u[i]->ut_line);
561 if (stat(buf, &st) == -1) {
562 safe_strncat(line, "!", sizeof(line));
563 safe_strncat(line, m, sizeof(line));
564 continue;
567 #ifdef HAVE_UTMPX_H
568 if (u[i]->ut_tv.tv_sec > st.st_atime) {
569 #else
570 if (u[i]->ut_time > st.st_atime) {
571 #endif
572 safe_strncat(line, "-", sizeof(line));
573 safe_strncat(line, m, sizeof(line));
574 continue;
577 t = st.st_atime;
579 #ifdef HAVE_UTMPX_H
580 if (t < u[i]->ut_tv.tv_sec)
581 t = u[i]->ut_tv.tv_sec;
582 #else
583 if (t < u[i]->ut_time)
584 t = u[i]->ut_time;
585 #endif
587 snprintf(buf, sizeof(buf), "%lu", (now - t <= 0) ? 0 : now - t);
588 safe_strncat(line, buf, sizeof(line));
589 safe_strncat(line, m, sizeof(line));
592 if (line[0] == '\0')
593 return "!";
595 line[strlen(line) - 1] = '\0';
596 return line;
599 /* This is output if the -h command line option is passed to the main program.
601 void ui_module_help()
603 #ifdef DEBUG
604 fprintf(stderr, "%s: ui_module_help()\n", __FILE__);
605 #endif
607 printf(" Login information [-L (-%s)]:\n", LOGIN_OPTION_ORDER);
608 printf("\t-y tty\t\t\t\t");
609 printf("-m message status\n");
610 printf("\t-t login time stamp\t\t");
611 printf("-d duration in minutes\n");
612 printf("\t-h hostname\t\t\t");
613 printf("-i seconds idle\n");
614 printf("\t-p login process id\n");
615 printf("\t-l lastlog information"
616 " (any of tt[y],[h]ostname,[t]ime, or [a]ll)\n\n");
617 return;
620 /* This is the equivalent to main() only without argc and argv available. */
621 int ui_module_exec(char ***s, const struct passwd *pw, const int multi,
622 const int verbose, char *tf)
624 char *p = options;
625 UTMP **u = NULL, **up;
626 char buf[255];
628 login_count = 0;
629 u = get_utmp(pw->pw_name);
630 strings = *s;
632 while (*p) {
633 char line[LINE_MAX] = { '\0' };
634 int i;
635 char m[2] = { multi, '\0' };
637 switch (*p) {
638 case 'i':
639 add_string(&strings, (u) ? idle(u, multi) : "!");
640 break;
641 case 'l':
642 last_strings(lastlogin(pw, 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 safe_strncat(line, u[i]->ut_host, sizeof(line));
650 else {
652 * If a users tty is tty1-n, it must be a console
653 * * login.
655 if (u[i]->ut_line[0]
656 && isdigit((unsigned char) u[i]->ut_line[3]))
657 safe_strncat(line, "-", sizeof(line));
658 else
659 safe_strncat(line, "!", sizeof(line));
662 safe_strncat(line, m, sizeof(line));
665 if (line[0] == '\0')
666 strncpy(line, "!", sizeof(line));
667 else
668 line[strlen(line) - 1] = '\0';
670 add_string(&strings, line);
671 break;
672 case 'y':
673 for (i = 0; i < login_count; i++) {
674 if (u[i]->ut_line[0])
675 safe_strncat(line, u[i]->ut_line, sizeof(line));
676 else
677 safe_strncat(line, "!", sizeof(line));
679 safe_strncat(line, m, sizeof(line));
682 if (line[0] == '\0')
683 strncpy(line, "!", sizeof(line));
684 else
685 line[strlen(line) - 1] = '\0';
687 add_string(&strings, line);
688 break;
689 case 'm':
690 add_string(&strings, msgstat(u, multi));
691 break;
692 case 't':
693 for (i = 0; i < login_count; i++) {
694 #ifdef HAVE_UTMPX_H
695 safe_strncat(line, stamp(u[i]->ut_tv.tv_sec, tf), sizeof(line));
696 #else
697 safe_strncat(line, stamp(u[i]->ut_time, tf), sizeof(line));
698 #endif
699 safe_strncat(line, m, sizeof(line));
702 if (line[0] == '\0')
703 strncpy(line, "!", sizeof(line));
704 else
705 line[strlen(line) - 1] = '\0';
707 add_string(&strings, line);
708 break;
709 case 'd':
710 for (i = 0; i < login_count; i++) {
711 #ifdef HAVE_UTMPX_H
712 if ((now - u[i]->ut_tv.tv_sec) > 60) {
713 snprintf(buf, sizeof(buf), "%lu",
714 ((now - u[i]->ut_tv.tv_sec) / 60));
715 #else
716 if ((now - u[i]->ut_time) > 60) {
717 snprintf(buf, sizeof(buf), "%lu",
718 ((now - u[i]->ut_time) / 60));
719 #endif
720 safe_strncat(line, buf, sizeof(line));
722 else
723 safe_strncat(line, "-", sizeof(line));
725 safe_strncat(line, m, sizeof(line));
728 if (line[0] == '\0')
729 strncpy(line, "!", sizeof(line));
730 else
731 line[strlen(line) - 1] = '\0';
733 add_string(&strings, line);
734 break;
735 case 'p':
736 #ifdef HAVE_UTMPX_H
737 for (i = 0; i < login_count; i++) {
738 if (u[i]->ut_pid) {
739 snprintf(buf, sizeof(buf), "%li", (long) u[i]->ut_pid);
740 safe_strncat(line, buf, sizeof(line));
742 else
743 safe_strncat(line, "!", sizeof(line));
745 safe_strncat(line, m, sizeof(line));
748 if (line[0] == '\0')
749 strncpy(line, "!", sizeof(line));
750 else
751 line[strlen(line) - 1] = '\0';
753 add_string(&strings, line);
754 #else
755 add_string(&strings, (u) ? ui_module_pid(pw->pw_uid, multi) : "!");
756 #endif
757 break;
758 default:
759 break;
762 p++;
765 if (login_count) {
766 for (up = u; *up; up++)
767 free(*up);
769 free(u);
772 *s = strings;
773 return EXIT_SUCCESS;
776 /* See if the last login options (-l) are valid. */
777 static int parse_last_options(const char *args)
779 int i = 0;
781 for (i = 0; i < strlen(args); i++) {
782 switch (args[i]) {
783 case 'y':
784 case 'h':
785 case 't':
786 case 'a':
787 break;
788 default:
789 return 1;
793 return 0;
796 char *ui_module_options_init(char **defaults)
798 *defaults = "L";
799 return LOGIN_OPTION_STRING;
802 /* Check module option validity. */
803 int ui_module_options(int argc, char **argv)
805 int opt;
806 char *p = options;
808 #ifdef DEBUG
809 fprintf(stderr, "%s: ui_module_options()\n", __FILE__);
810 #endif
812 while ((opt = getopt(argc, argv, LOGIN_OPTION_STRING)) != -1) {
813 switch (opt) {
814 case 'l':
815 last_options = optarg;
816 break;
817 case 'L':
818 case 'p':
819 case 'd':
820 case 'i':
821 case 'm':
822 case 'y':
823 case 'h':
824 case 't':
825 break;
826 case '?':
827 warnx("login: invalid option -- %c", optopt);
828 default:
829 return 1;
832 if (opt == 'l') {
833 if (parse_last_options(last_options))
834 return 1;
837 if (opt == 'L') {
838 strncpy(options, LOGIN_OPTION_ORDER, sizeof(options));
839 last_options = "a";
840 break;
843 *p++ = opt;
844 *p = '\0';
847 return 0;