Fix potential buffer overflow.
[userinfo.git] / src / modules / login.c
blob775c75b0996497d685b92b97502f9ffbaea7ce8c
1 /*
2 Copyright (C) 2001-2015 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 (!u)
96 return;
98 if (login_count) {
99 UTMP **up;
101 for (up = u; *up; up++)
102 free(*up);
104 free(u);
108 #ifndef HAVE_UTMPX_H
109 /* This is for *BSD (login process id). */
110 #ifdef BSD_KVM
111 static char *get_pid(uid_t uid, int multi)
113 static int firstrun;
114 static char line[LINE_MAX];
115 int cnt, i;
116 char errbuf[LINE_MAX];
117 struct kinfo_proc *kp;
119 line[0] = '\0';
121 if (!kd && firstrun)
122 return "!";
124 if (!kd) {
125 firstrun = 1;
127 #if defined(__NetBSD__) || defined (__OpenBSD__)
128 if ((kd = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, errbuf)) == NULL) {
129 #else
130 if ((kd = kvm_openfiles(_PATH_DEVNULL, _PATH_DEVNULL, _PATH_DEVNULL,
131 O_RDONLY, errbuf)) == NULL) {
132 #endif
133 warnx("%s", errbuf);
134 return "!";
138 #ifdef __OpenBSD__
139 if ((kp = kvm_getprocs(kd, KERN_PROC_UID, uid, sizeof(struct kinfo_proc),
140 &cnt)) == NULL) {
141 warnx("kvm_getprocs(): %s", kvm_geterr(kd));
142 return "!";
144 #else
145 if ((kp = kvm_getprocs(kd, KERN_PROC_UID, uid, &cnt)) == NULL) {
146 warnx("kvm_getprocs(): %s", kvm_geterr(kd));
147 return "!";
149 #endif
151 for (i = 0; i < cnt; i++) {
152 char buf[32];
153 pid_t pid = 0;
155 #ifdef __OpenBSD__
156 if ((kp[i].p_eflag & EPROC_SLEADER) && kp[i].p_tdev != -1)
157 pid = kp[i].p_pid;
158 #else
159 #if __FreeBSD_version < 500000
160 if (kp[i].kp_eproc.e_flag & EPROC_SLEADER && kp[i].kp_eproc.e_tdev
161 != -1) {
162 pid = kp[i].kp_eproc.e_ppid;
164 * pid = kp[i].kp_proc.p_pid;
167 #else
168 if (kp[i].ki_kiflag & KI_SLEADER && kp[i].ki_tdev != -1) {
169 pid = kp[i].ki_pid;
171 #endif
172 #endif
173 if (!pid || pid == 1)
174 continue;
176 snprintf(buf, sizeof(buf), "%i%c", pid, multi);
177 safe_strncat(line, buf, sizeof(line));
180 if (line[0] == '\0')
181 return "!";
183 line[strlen(line) - 1] = '\0';
184 return line;
187 /* This is for Linux and Solaris. */
188 #elif defined(HAVE_PROCFS)
189 #include <sys/types.h>
190 #include <sys/stat.h>
192 #ifdef HAVE_DIRENT_H
193 #include <dirent.h>
194 #endif
196 #ifdef __sun__
197 #include <unistd.h>
198 #include <procfs.h>
199 #endif
201 static char *get_pid(uid_t uid, int multi)
203 static int firstrun;
204 struct dirent *ent;
205 struct stat st;
206 static char line[LINE_MAX];
207 pid_t *pids = 0, *tpids;
208 int pid_index = 0;
210 #ifdef __sun__
211 int fd;
212 struct pstatus pstat;
213 #else
214 FILE *fp;
215 #endif
217 line[0] = '\0';
219 if (!procdir && firstrun)
220 return "!";
222 if (!procdir) {
223 firstrun = 1;
225 if ((procdir = opendir("/proc")) == NULL) {
226 warn("%s", "/proc");
227 return "!";
231 rewinddir(procdir);
233 again:
234 while ((ent = readdir(procdir)) != NULL) {
235 pid_t pid = -1;
236 char filename[FILENAME_MAX];
237 char buf[LINE_MAX];
238 int i;
240 #ifndef __sun__
241 char *t;
242 #endif
244 if (!isdigit((unsigned char) *ent->d_name))
245 continue;
247 #ifdef __linux__
248 snprintf(filename, sizeof(filename), "/proc/%s/stat", ent->d_name);
249 #else
250 snprintf(filename, sizeof(filename), "/proc/%s/status", ent->d_name);
251 #endif
253 if (stat(filename, &st) == -1)
254 continue;
257 * The current user owns this file (process id).
259 if (st.st_uid == uid) {
260 #ifdef __sun__
261 if ((fd = open(filename, O_RDONLY)) == -1)
262 continue;
264 if (pread(fd, &pstat, sizeof(struct pstatus), 0) !=
265 sizeof(struct pstatus)) {
266 close(fd);
267 continue;
270 pid = pstat.pr_ppid;
271 close(fd);
272 #else
273 if ((fp = fopen(filename, "r")) == NULL)
274 continue;
276 if ((t = fgets(buf, sizeof(buf), fp)) == NULL) {
277 fclose(fp);
278 continue;
281 #ifdef __linux__
282 if ((i = sscanf(buf, "%*i %*s %*c %*i %*i %i", &pid)) < 1) {
283 #endif
285 #else
286 if ((i = sscanf(buf, "%*s %*i %li", &ppid)) < 1) {
287 #endif
289 fclose(fp);
290 continue;
293 fclose(fp);
294 #endif
297 * Skip duplicate pids.
299 for (i = 0; i < pid_index; i++) {
300 if (pids[i] == pid)
301 goto again;
304 snprintf(buf, sizeof(buf), "%li%c", (unsigned long) pid, multi);
305 safe_strncat(line, buf, sizeof(line));
307 if ((tpids =
308 realloc(pids, (pid_index + 2) * sizeof(pid_t *))) == NULL) {
309 warn("realloc()");
310 continue;
313 pids = tpids;
314 pids[pid_index++] = pid;
318 if (pid_index)
319 free(pids);
321 if (line[0] == '\0')
322 return "!";
324 line[strlen(line) - 1] = '\0';
325 return line;
327 #else
328 /* Unsupported OS. */
329 static char *get_pid(uid_t uid, int multi)
331 return "!";
333 #endif
334 #endif
336 /* Break up the last login string into sections and add the sections to the
337 * output string array if needed. */
338 static void last_strings(char *str)
340 int i = 0;
341 char *buf;
342 const char *line, *host, *when;
344 line = host = when = (str) ? "-" : "!";
346 while ((buf = strsep(&str, ",")) != NULL) {
347 if (!buf[0])
348 continue;
350 switch (i++) {
351 case 0:
352 line = buf;
353 break;
354 case 1:
355 host = buf;
356 break;
357 case 2:
358 when = buf;
359 break;
360 default:
361 break;
365 for (i = 0; i < strlen(last_options); i++) {
366 switch (last_options[i]) {
367 case 'y':
368 add_string(&strings, line);
369 break;
370 case 'h':
371 add_string(&strings, host);
372 break;
373 case 't':
374 add_string(&strings, when);
375 break;
376 case 'a':
377 add_string(&strings, line);
378 add_string(&strings, host);
379 add_string(&strings, when);
380 default:
381 break;
386 /* Get the lastlog structure from the lastlog file. */
387 #ifdef HAVE_GETLASTLOGX
388 static char *lastlogin(const struct passwd *pw, char *tf)
390 struct lastlogx *last = getlastlogx(_PATH_LASTLOGX, pw->pw_uid, NULL);
391 static char buf[LINE_MAX];
393 if (!last)
394 return NULL;
396 snprintf(buf, sizeof(buf), "%s,%s,%s",
397 !last->ll_line[0] ? "!" : last->ll_line,
398 (!last->ll_host[0] || !isalnum(last->ll_host[0])) ? "-" : last->ll_host,
399 stamp(last->ll_tv.tv_sec, tf));
400 return buf;
402 #else
403 #if __FreeBSD_version >= 900000
404 static char *lastlogin(const struct passwd *pw, char *tf)
406 struct utmpx *last;
407 static char buf[LINE_MAX];
409 if (setutxdb(UTXDB_LASTLOGIN, NULL) == -1) {
410 warn("lastlog");
411 return NULL;
414 last = getutxuser(pw->pw_name);
416 if (!last)
417 return NULL;
419 snprintf(buf, sizeof(buf), "%s,%s,%s",
420 !last->ut_line[0] ? "!" : last->ut_line,
421 (!last->ut_host[0] || !isalnum(last->ut_host[0])) ? "-" : last->ut_host,
422 stamp(last->ut_tv.tv_sec, tf));
424 return buf;
426 #else
427 static char *lastlogin(const struct passwd *pw, char *tf)
429 int count;
430 long offset;
431 static char buf[LINE_MAX];
432 struct lastlog last;
434 if (lastlogfd < 0)
435 return NULL;
437 if (!lastlogfd) {
438 if ((lastlogfd = open(_PATH_LASTLOG, O_RDONLY)) == -1) {
439 warn("%s", _PATH_LASTLOG);
440 return NULL;
444 offset = (long) pw->pw_uid * sizeof(struct lastlog);
446 if (lseek(lastlogfd, offset, SEEK_SET) == -1) {
447 warn("%s", _PATH_LASTLOG);
448 return NULL;
451 if ((count = read(lastlogfd, &last, sizeof(struct lastlog))) !=
452 sizeof(struct lastlog)) {
453 if (count == -1)
454 warn("%s", _PATH_LASTLOG);
456 return NULL;
459 #ifdef __NetBSD__
460 #ifdef HAVE_UTMPX_H
461 last.ll_host[UTX_HOSTSIZE-1] = '\0';
462 last.ll_line[UTX_LINESIZE-1] = '\0';
463 #else
464 last.ll_host[UT_HOSTSIZE-1] = '\0';
465 last.ll_line[UT_LINESIZE-1] = '\0';
466 #endif
467 #else
468 last.ll_host[UT_HOSTSIZE-1] = '\0';
469 last.ll_line[UT_LINESIZE-1] = '\0';
470 #endif
472 snprintf(buf, sizeof(buf), "%s,%s,%s",
473 !last.ll_line[0] ? "!" : last.ll_line,
474 (!last.ll_host[0] || !isalnum(last.ll_host[0])) ?
475 !isdigit(last.ll_line[3]) ? "!" : "-" : last.ll_host,
476 !last.ll_time ? "!" : stamp(last.ll_time, tf));
477 return buf;
479 #endif
480 #endif
482 /* This will return an array of utmp structures if a user is logged in, NULL
483 * otherwise. We'll try to keep the utmp file descriptor open if possible to
484 * speed things up a bit. */
485 static UTMP **get_utmp(const char *user)
487 UTMP **logins = NULL;
488 #if defined HAVE_UTMPX_H && defined (HAVE_SETUTXENT)
489 UTMP *u;
490 #else
491 UTMP u;
492 int count;
493 static int fd;
495 if (fd < 0)
496 return NULL;
498 if (!fd) {
499 if ((fd = open(_PATH_UTMP, O_RDONLY)) == -1) {
500 warn("%s", _PATH_UTMP);
501 return NULL;
504 #endif
506 login_count = 0;
508 #if defined HAVE_UTMPX_H && defined (HAVE_SETUTXENT)
509 setutxent();
511 while ((u = getutxent()) != NULL) {
512 if (!strncmp(u->ut_user, user, UT_NAMESIZE) && u->ut_type != DEAD_PROCESS) {
513 #else
514 lseek(fd, 0, SEEK_SET);
516 while ((count = read(fd, &u, sizeof(UTMP))) == sizeof(UTMP)) {
517 if (strncmp(u.ut_name, user, UT_NAMESIZE) == 0) {
518 #endif
519 UTMP **tmp;
521 if ((tmp = realloc(logins,
522 (login_count + 2) * sizeof(UTMP *))) ==
523 NULL) {
524 warn("realloc()");
525 free_logins(logins);
526 return NULL;
529 logins = tmp;
531 if ((logins[login_count] = malloc(sizeof(UTMP))) == NULL) {
532 warn("malloc()");
533 free_logins(logins);
534 return NULL;
537 #if defined HAVE_UTMPX_H && defined (HAVE_SETUTXENT)
538 #ifdef __NetBSD__
539 memcpy(logins[login_count]->ut_name, u->ut_name, UTX_NAMESIZE);
540 logins[login_count]->ut_name[UTX_NAMESIZE-1] = 0;
541 memcpy(logins[login_count]->ut_line, u->ut_line, UTX_LINESIZE);
542 logins[login_count]->ut_line[UTX_LINESIZE-1] = 0;
543 memcpy(logins[login_count]->ut_host, u->ut_host, UTX_HOSTSIZE);
544 logins[login_count]->ut_host[UTX_HOSTSIZE-1] = 0;
545 logins[login_count]->ut_pid = u->ut_pid;
546 #else
547 memcpy(logins[login_count]->ut_user, u->ut_user, UT_NAMESIZE);
548 logins[login_count]->ut_user[UT_NAMESIZE-1] = 0;
549 memcpy(logins[login_count]->ut_line, u->ut_line, UT_LINESIZE);
550 logins[login_count]->ut_line[UT_LINESIZE-1] = 0;
551 memcpy(logins[login_count]->ut_host, u->ut_host, UT_HOSTSIZE);
552 logins[login_count]->ut_host[UT_HOSTSIZE-1] = 0;
553 logins[login_count]->ut_tv.tv_sec = u->ut_tv.tv_sec;
554 logins[login_count]->ut_pid = u->ut_pid;
555 #endif
556 #else
557 memcpy(logins[login_count]->ut_name, u.ut_name, UT_NAMESIZE);
558 logins[login_count]->ut_name[UT_NAMESIZE-1] = 0;
559 memcpy(logins[login_count]->ut_line, u.ut_line, UT_LINESIZE);
560 logins[login_count]->ut_line[UT_LINESIZE-1] = 0;
561 memcpy(logins[login_count]->ut_host, u.ut_host, UT_HOSTSIZE);
562 logins[login_count]->ut_host[UT_HOSTSIZE-1] = 0;
563 logins[login_count]->ut_time = u.ut_time;
564 #endif
565 logins[++login_count] = NULL;
569 return logins;
572 /* The 'mesg' status of the logged in user. */
573 static const char *msgstat(UTMP **u, int multi)
575 static char line[LINE_MAX];
576 int i;
578 line[0] = '\0';
580 for (i = 0; i < login_count; i++) {
581 char filename[FILENAME_MAX];
582 struct stat st;
583 char m[2] = { multi, '\0' };
585 snprintf(filename, sizeof(filename), "%s%s", _PATH_DEV, u[i]->ut_line);
587 if (stat(filename, &st) == -1)
588 safe_strncat(line, "!", sizeof(line));
589 else
590 safe_strncat(line,
591 (st.st_mode & S_IWGRP || st.st_mode & S_IWOTH) ? "1" : "0",
592 sizeof(line));
594 safe_strncat(line, m, sizeof(line));
597 if (line[0] == '\0')
598 return "!";
600 line[strlen(line) - 1] = '\0';
601 return line;
604 /* Returns the users idle time in seconds. */
605 static const char *idle(UTMP **u, int multi)
607 static char line[LINE_MAX];
608 time_t t;
609 struct stat st;
610 int i;
612 line[0] = '\0';
614 for (i = 0; i < login_count; i++) {
615 char buf[FILENAME_MAX];
616 char m[2] = { multi, '\0' };
618 snprintf(buf, sizeof(buf), "%s%s", _PATH_DEV, u[i]->ut_line);
620 if (stat(buf, &st) == -1) {
621 safe_strncat(line, "!", sizeof(line));
622 safe_strncat(line, m, sizeof(line));
623 continue;
626 #ifdef HAVE_UTMPX_H
627 if (u[i]->ut_tv.tv_sec > st.st_atime) {
628 #else
629 if (u[i]->ut_time > st.st_atime) {
630 #endif
631 safe_strncat(line, "-", sizeof(line));
632 safe_strncat(line, m, sizeof(line));
633 continue;
636 t = st.st_atime;
638 #ifdef HAVE_UTMPX_H
639 if (t < u[i]->ut_tv.tv_sec)
640 t = u[i]->ut_tv.tv_sec;
641 #else
642 if (t < u[i]->ut_time)
643 t = u[i]->ut_time;
644 #endif
646 snprintf(buf, sizeof(buf), "%lu", (now - t <= 0) ? 0 : now - t);
647 safe_strncat(line, buf, sizeof(line));
648 safe_strncat(line, m, sizeof(line));
651 if (line[0] == '\0')
652 return "!";
654 line[strlen(line) - 1] = '\0';
655 return line;
658 /* This is output if the -h command line option is passed to the main program.
660 void ui_module_help()
662 printf(" Login information [-L (-%s)]:\n", LOGIN_OPTION_ORDER);
663 printf("\t-y tty\t\t\t\t");
664 printf("-m message status\n");
665 printf("\t-t login time stamp\t\t");
666 printf("-d duration in minutes\n");
667 printf("\t-h hostname\t\t\t");
668 printf("-i seconds idle\n");
669 printf("\t-p login process id\n");
670 printf("\t-l lastlog information"
671 " (any of tt[y],[h]ostname,[t]ime, or [a]ll)\n\n");
672 return;
675 /* This is the equivalent to main() only without argc and argv available. */
676 int ui_module_exec(char ***s, const struct passwd *pw, const int multi,
677 const int verbose, char *tf)
679 char *p = options;
680 UTMP **u = NULL;
681 char buf[255];
683 login_count = 0;
684 u = get_utmp(pw->pw_name);
685 strings = *s;
687 while (*p) {
688 char line[LINE_MAX] = { '\0' };
689 int i;
690 char m[2] = { multi, '\0' };
692 switch (*p) {
693 case 'i':
694 add_string(&strings, (u) ? idle(u, multi) : "!");
695 break;
696 case 'l':
697 last_strings(lastlogin(pw, tf));
698 break;
699 case 'h':
700 for (i = 0; i < login_count; i++) {
701 if (u[i]->ut_host[0]
702 && isalnum((unsigned char) u[i]->ut_host[0]))
703 safe_strncat(line, u[i]->ut_host, sizeof(line));
704 else
705 safe_strncat(line, "-", sizeof(line));
707 safe_strncat(line, m, sizeof(line));
710 if (line[0] == '\0')
711 strncpy(line, "!", sizeof(line));
712 else
713 line[strlen(line) - 1] = '\0';
715 add_string(&strings, line);
716 break;
717 case 'y':
718 for (i = 0; i < login_count; i++) {
719 if (u[i]->ut_line[0])
720 safe_strncat(line, u[i]->ut_line, sizeof(line));
721 else
722 safe_strncat(line, "!", sizeof(line));
724 safe_strncat(line, m, sizeof(line));
727 if (line[0] == '\0')
728 strncpy(line, "!", sizeof(line));
729 else
730 line[strlen(line) - 1] = '\0';
732 add_string(&strings, line);
733 break;
734 case 'm':
735 add_string(&strings, u ? msgstat(u, multi) : "!");
736 break;
737 case 't':
738 for (i = 0; i < login_count; i++) {
739 #ifdef HAVE_UTMPX_H
740 safe_strncat(line, stamp(u[i]->ut_tv.tv_sec, tf), sizeof(line));
741 #else
742 safe_strncat(line, stamp(u[i]->ut_time, tf), sizeof(line));
743 #endif
744 safe_strncat(line, m, sizeof(line));
747 if (line[0] == '\0')
748 strncpy(line, "!", sizeof(line));
749 else
750 line[strlen(line) - 1] = '\0';
752 add_string(&strings, line);
753 break;
754 case 'd':
755 for (i = 0; i < login_count; i++) {
756 #ifdef HAVE_UTMPX_H
757 if ((now - u[i]->ut_tv.tv_sec) > 60) {
758 snprintf(buf, sizeof(buf), "%lu",
759 ((now - u[i]->ut_tv.tv_sec) / 60));
760 #else
761 if ((now - u[i]->ut_time) > 60) {
762 snprintf(buf, sizeof(buf), "%lu",
763 ((now - u[i]->ut_time) / 60));
764 #endif
765 safe_strncat(line, buf, sizeof(line));
767 else
768 safe_strncat(line, "-", sizeof(line));
770 safe_strncat(line, m, sizeof(line));
773 if (line[0] == '\0')
774 strncpy(line, "!", sizeof(line));
775 else
776 line[strlen(line) - 1] = '\0';
778 add_string(&strings, line);
779 break;
780 case 'p':
781 #ifdef HAVE_UTMPX_H
782 for (i = 0; i < login_count; i++) {
783 if (u[i]->ut_pid) {
784 snprintf(buf, sizeof(buf), "%li", (long) u[i]->ut_pid);
785 safe_strncat(line, buf, sizeof(line));
787 else
788 safe_strncat(line, "!", sizeof(line));
790 safe_strncat(line, m, sizeof(line));
793 if (line[0] == '\0')
794 strncpy(line, "!", sizeof(line));
795 else
796 line[strlen(line) - 1] = '\0';
798 add_string(&strings, line);
799 #else
800 add_string(&strings, (u) ? get_pid(pw->pw_uid, multi) : "!");
801 #endif
802 break;
803 default:
804 break;
807 p++;
810 free_logins(u);
811 *s = strings;
812 return EXIT_SUCCESS;
815 /* See if the last login options (-l) are valid. */
816 static int parse_last_options(const char *args)
818 int i = 0;
820 for (i = 0; i < strlen(args); i++) {
821 switch (args[i]) {
822 case 'y':
823 case 'h':
824 case 't':
825 case 'a':
826 break;
827 default:
828 return 1;
832 return 0;
835 const char *ui_module_options_init(char **defaults)
837 *defaults = (char *)"L";
838 return LOGIN_OPTION_STRING;
841 /* Check module option validity. */
842 int ui_module_options(int argc, char **argv)
844 int opt;
845 char *p = options;
847 while ((opt = getopt(argc, argv, LOGIN_OPTION_STRING)) != -1) {
848 switch (opt) {
849 case 'l':
850 if (parse_last_options(optarg))
851 return 1;
853 last_options = optarg;
854 break;
855 case 'L':
856 strncpy(options, LOGIN_OPTION_ORDER, sizeof(options));
857 last_options = (char *)"a";
858 return 0;
859 case 'p':
860 case 'd':
861 case 'i':
862 case 'm':
863 case 'y':
864 case 'h':
865 case 't':
866 break;
867 case '?':
868 warnx("login: invalid option -- %c", optopt);
869 default:
870 return 1;
873 *p++ = opt;
874 *p = '\0';
877 return 0;