Fix potential crash found by smatch.
[userinfo.git] / src / modules / login.c
blob3d744a65aef2e04d354249587c6442616fa21df3
1 /*
2 Copyright (C) 2001-2013 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 char errbuf[LINE_MAX];
114 struct kinfo_proc *kp;
116 line[0] = '\0';
118 if (!kd && firstrun)
119 return "!";
121 if (!kd) {
122 firstrun = 1;
124 #if defined(__NetBSD__) || defined (__OpenBSD__)
125 if ((kd = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, errbuf)) == NULL) {
126 #else
127 if ((kd = kvm_openfiles(_PATH_DEVNULL, _PATH_DEVNULL, _PATH_DEVNULL,
128 O_RDONLY, errbuf)) == NULL) {
129 #endif
130 warnx("%s", errbuf);
131 return "!";
135 #ifdef __OpenBSD__
136 if ((kp = kvm_getprocs(kd, KERN_PROC_UID, uid, sizeof(struct kinfo_proc),
137 &cnt)) == NULL) {
138 warnx("kvm_getprocs(): %s", kvm_geterr(kd));
139 return "!";
141 #else
142 if ((kp = kvm_getprocs(kd, KERN_PROC_UID, uid, &cnt)) == NULL) {
143 warnx("kvm_getprocs(): %s", kvm_geterr(kd));
144 return "!";
146 #endif
148 for (i = 0; i < cnt; i++) {
149 char buf[32];
150 pid_t pid = 0;
152 #ifdef __OpenBSD__
153 if ((kp[i].p_eflag & EPROC_SLEADER) && kp[i].p_tdev != -1)
154 pid = kp[i].p_pid;
155 #else
156 #if __FreeBSD_version < 500000
157 if (kp[i].kp_eproc.e_flag & EPROC_SLEADER && kp[i].kp_eproc.e_tdev
158 != -1) {
159 pid = kp[i].kp_eproc.e_ppid;
161 * pid = kp[i].kp_proc.p_pid;
164 #else
165 if (kp[i].ki_kiflag & KI_SLEADER && kp[i].ki_tdev != -1) {
166 pid = kp[i].ki_pid;
168 #endif
169 #endif
170 if (!pid || pid == 1)
171 continue;
173 snprintf(buf, sizeof(buf), "%i%c", pid, multi);
174 safe_strncat(line, buf, sizeof(line));
177 if (line[0] == '\0')
178 return "!";
180 line[strlen(line) - 1] = '\0';
181 return line;
184 /* This is for Linux and Solaris. */
185 #elif defined(HAVE_PROCFS)
186 #include <sys/types.h>
187 #include <sys/stat.h>
189 #ifdef HAVE_DIRENT_H
190 #include <dirent.h>
191 #endif
193 #ifdef __sun__
194 #include <unistd.h>
195 #include <procfs.h>
196 #endif
198 static char *get_pid(uid_t uid, int multi)
200 static int firstrun;
201 struct dirent *ent;
202 struct stat st;
203 static char line[LINE_MAX];
204 pid_t *pids = 0, *tpids;
205 int pid_index = 0;
207 #ifdef __sun__
208 int fd;
209 struct pstatus pstat;
210 #else
211 FILE *fp;
212 #endif
214 line[0] = '\0';
216 if (!procdir && firstrun)
217 return "!";
219 if (!procdir) {
220 firstrun = 1;
222 if ((procdir = opendir("/proc")) == NULL) {
223 warn("%s", "/proc");
224 return "!";
228 rewinddir(procdir);
230 again:
231 while ((ent = readdir(procdir)) != NULL) {
232 pid_t pid = -1;
233 char filename[FILENAME_MAX];
234 char buf[LINE_MAX];
235 int i;
237 #ifndef __sun__
238 char *t;
239 #endif
241 if (!isdigit((unsigned char) *ent->d_name))
242 continue;
244 #ifdef __linux__
245 snprintf(filename, sizeof(filename), "/proc/%s/stat", ent->d_name);
246 #else
247 snprintf(filename, sizeof(filename), "/proc/%s/status", ent->d_name);
248 #endif
250 if (stat(filename, &st) == -1)
251 continue;
254 * The current user owns this file (process id).
256 if (st.st_uid == uid) {
257 #ifdef __sun__
258 if ((fd = open(filename, O_RDONLY)) == -1)
259 continue;
261 if (pread(fd, &pstat, sizeof(struct pstatus), 0) !=
262 sizeof(struct pstatus)) {
263 close(fd);
264 continue;
267 pid = pstat.pr_ppid;
268 close(fd);
269 #else
270 if ((fp = fopen(filename, "r")) == NULL)
271 continue;
273 if ((t = fgets(buf, sizeof(buf), fp)) == NULL) {
274 fclose(fp);
275 continue;
278 #ifdef __linux__
279 if ((i = sscanf(buf, "%*i %*s %*c %*i %*i %i", &pid)) < 1) {
280 #endif
282 #else
283 if ((i = sscanf(buf, "%*s %*i %li", &ppid)) < 1) {
284 #endif
286 fclose(fp);
287 continue;
290 fclose(fp);
291 #endif
294 * Skip duplicate pids.
296 for (i = 0; i < pid_index; i++) {
297 if (pids[i] == pid)
298 goto again;
301 snprintf(buf, sizeof(buf), "%li%c", (unsigned long) pid, multi);
302 safe_strncat(line, buf, sizeof(line));
304 if ((tpids =
305 realloc(pids, (pid_index + 2) * sizeof(pid_t *))) == NULL) {
306 warn("realloc()");
307 continue;
310 pids = tpids;
311 pids[pid_index++] = pid;
315 if (pid_index)
316 free(pids);
318 if (line[0] == '\0')
319 return "!";
321 line[strlen(line) - 1] = '\0';
322 return line;
324 #else
325 /* Unsupported OS. */
326 static char *get_pid(uid_t uid, int multi)
328 return "!";
330 #endif
331 #endif
333 /* Break up the last login string into sections and add the sections to the
334 * output string array if needed. */
335 static void last_strings(char *str)
337 int i = 0;
338 char *buf;
339 const char *line, *host, *when;
341 line = host = when = (str) ? "-" : "!";
343 while ((buf = strsep(&str, ",")) != NULL) {
344 if (!buf[0])
345 continue;
347 switch (i++) {
348 case 0:
349 line = buf;
350 break;
351 case 1:
352 host = buf;
353 break;
354 case 2:
355 when = buf;
356 break;
357 default:
358 break;
362 for (i = 0; i < strlen(last_options); i++) {
363 switch (last_options[i]) {
364 case 'y':
365 add_string(&strings, line);
366 break;
367 case 'h':
368 add_string(&strings, host);
369 break;
370 case 't':
371 add_string(&strings, when);
372 break;
373 case 'a':
374 add_string(&strings, line);
375 add_string(&strings, host);
376 add_string(&strings, when);
377 default:
378 break;
383 /* Get the lastlog structure from the lastlog file. */
384 #ifdef HAVE_GETLASTLOGX
385 static char *lastlogin(const struct passwd *pw, char *tf)
387 struct lastlogx *last = getlastlogx(_PATH_LASTLOGX, pw->pw_uid, NULL);
388 static char buf[LINE_MAX];
390 if (!last)
391 return NULL;
393 snprintf(buf, sizeof(buf), "%s,%s,%s",
394 !last->ll_line[0] ? "!" : last->ll_line,
395 (!last->ll_host[0] || !isalnum(last->ll_host[0])) ? "-" : last->ll_host,
396 stamp(last->ll_tv.tv_sec, tf));
397 return buf;
399 #else
400 #if __FreeBSD_version >= 900000
401 static char *lastlogin(const struct passwd *pw, char *tf)
403 struct utmpx *last;
404 static char buf[LINE_MAX];
406 if (setutxdb(UTXDB_LASTLOGIN, NULL) == -1) {
407 warn("lastlog");
408 return NULL;
411 last = getutxuser(pw->pw_name);
413 if (!last)
414 return NULL;
416 snprintf(buf, sizeof(buf), "%s,%s,%s",
417 !last->ut_line[0] ? "!" : last->ut_line,
418 (!last->ut_host[0] || !isalnum(last->ut_host[0])) ? "-" : last->ut_host,
419 stamp(last->ut_tv.tv_sec, tf));
421 return buf;
423 #else
424 static char *lastlogin(const struct passwd *pw, char *tf)
426 int count;
427 long offset;
428 static char buf[LINE_MAX];
429 struct lastlog last;
431 if (lastlogfd < 0)
432 return NULL;
434 if (!lastlogfd) {
435 if ((lastlogfd = open(_PATH_LASTLOG, O_RDONLY)) == -1) {
436 warn("%s", _PATH_LASTLOG);
437 return NULL;
441 offset = (long) pw->pw_uid * sizeof(struct lastlog);
443 if (lseek(lastlogfd, offset, SEEK_SET) == -1) {
444 warn("%s", _PATH_LASTLOG);
445 return NULL;
448 if ((count = read(lastlogfd, &last, sizeof(struct lastlog))) !=
449 sizeof(struct lastlog)) {
450 if (count == -1)
451 warn("%s", _PATH_LASTLOG);
453 return NULL;
456 #ifdef __NetBSD__
457 #ifdef HAVE_UTMPX_H
458 last.ll_host[UTX_HOSTSIZE-1] = '\0';
459 last.ll_line[UTX_LINESIZE-1] = '\0';
460 #else
461 last.ll_host[UT_HOSTSIZE-1] = '\0';
462 last.ll_line[UT_LINESIZE-1] = '\0';
463 #endif
464 #else
465 last.ll_host[UT_HOSTSIZE-1] = '\0';
466 last.ll_line[UT_LINESIZE-1] = '\0';
467 #endif
469 snprintf(buf, sizeof(buf), "%s,%s,%s",
470 !last.ll_line[0] ? "!" : last.ll_line,
471 (!last.ll_host[0] || !isalnum(last.ll_host[0])) ?
472 !isdigit(last.ll_line[3]) ? "!" : "-" : last.ll_host,
473 !last.ll_time ? "!" : stamp(last.ll_time, tf));
474 return buf;
476 #endif
477 #endif
479 /* This will return an array of utmp structures if a user is logged in, NULL
480 * otherwise. We'll try to keep the utmp file descriptor open if possible to
481 * speed things up a bit. */
482 static UTMP **get_utmp(const char *user)
484 UTMP **logins = NULL;
485 #if defined HAVE_UTMPX_H && defined (HAVE_SETUTXENT)
486 UTMP *u;
487 #else
488 UTMP u;
489 int count;
490 static int fd;
492 if (fd < 0)
493 return NULL;
495 if (!fd) {
496 if ((fd = open(_PATH_UTMP, O_RDONLY)) == -1) {
497 warn("%s", _PATH_UTMP);
498 return NULL;
501 #endif
503 login_count = 0;
505 #if defined HAVE_UTMPX_H && defined (HAVE_SETUTXENT)
506 setutxent();
508 while ((u = getutxent()) != NULL) {
509 if (!strcmp(u->ut_user, user) && u->ut_type != DEAD_PROCESS) {
510 #else
511 lseek(fd, 0, SEEK_SET);
513 while ((count = read(fd, &u, sizeof(UTMP))) == sizeof(UTMP)) {
514 if (strcmp(u.ut_name, user) == 0) {
515 #endif
516 UTMP **tmp;
518 if ((tmp = realloc(logins,
519 (login_count + 2) * sizeof(UTMP *))) ==
520 NULL) {
521 warn("realloc()");
522 free_logins(logins);
523 return NULL;
526 logins = tmp;
528 if ((logins[login_count] = malloc(sizeof(UTMP))) == NULL) {
529 warn("malloc()");
530 free_logins(logins);
531 return NULL;
534 #if defined HAVE_UTMPX_H && defined (HAVE_SETUTXENT)
535 #ifdef __NetBSD__
536 memcpy(logins[login_count]->ut_name, u->ut_name, UTX_NAMESIZE);
537 logins[login_count]->ut_name[UTX_NAMESIZE-1] = 0;
538 memcpy(logins[login_count]->ut_line, u->ut_line, UTX_LINESIZE);
539 logins[login_count]->ut_line[UTX_LINESIZE-1] = 0;
540 memcpy(logins[login_count]->ut_host, u->ut_host, UTX_HOSTSIZE);
541 logins[login_count]->ut_host[UTX_HOSTSIZE-1] = 0;
542 logins[login_count]->ut_pid = u->ut_pid;
543 #else
544 memcpy(logins[login_count]->ut_user, u->ut_user, UT_NAMESIZE);
545 logins[login_count]->ut_user[UT_NAMESIZE-1] = 0;
546 memcpy(logins[login_count]->ut_line, u->ut_line, UT_LINESIZE);
547 logins[login_count]->ut_line[UT_LINESIZE-1] = 0;
548 memcpy(logins[login_count]->ut_host, u->ut_host, UT_HOSTSIZE);
549 logins[login_count]->ut_host[UT_HOSTSIZE-1] = 0;
550 logins[login_count]->ut_tv.tv_sec = u->ut_tv.tv_sec;
551 logins[login_count]->ut_pid = u->ut_pid;
552 #endif
553 #else
554 memcpy(logins[login_count]->ut_name, u.ut_name, UT_NAMESIZE);
555 logins[login_count]->ut_name[UT_NAMESIZE-1] = 0;
556 memcpy(logins[login_count]->ut_line, u.ut_line, UT_LINESIZE);
557 logins[login_count]->ut_line[UT_LINESIZE-1] = 0;
558 memcpy(logins[login_count]->ut_host, u.ut_host, UT_HOSTSIZE);
559 logins[login_count]->ut_host[UT_HOSTSIZE-1] = 0;
560 logins[login_count]->ut_time = u.ut_time;
561 #endif
562 logins[++login_count] = NULL;
566 return logins;
569 /* The 'mesg' status of the logged in user. */
570 static char *msgstat(UTMP **u, int multi)
572 static char line[LINE_MAX];
573 int i;
575 line[0] = '\0';
577 for (i = 0; i < login_count; i++) {
578 char filename[FILENAME_MAX];
579 struct stat st;
580 char m[2] = { multi, '\0' };
582 snprintf(filename, sizeof(filename), "%s%s", _PATH_DEV, u[i]->ut_line);
584 if (stat(filename, &st) == -1)
585 safe_strncat(line, "!", sizeof(line));
586 else
587 safe_strncat(line,
588 (st.st_mode & S_IWGRP || st.st_mode & S_IWOTH) ? "1" : "0",
589 sizeof(line));
591 safe_strncat(line, m, sizeof(line));
594 if (line[0] == '\0')
595 return "!";
597 line[strlen(line) - 1] = '\0';
598 return line;
601 /* Returns the users idle time in seconds. */
602 static char *idle(UTMP **u, int multi)
604 static char line[LINE_MAX];
605 time_t t;
606 struct stat st;
607 int i;
609 line[0] = '\0';
611 for (i = 0; i < login_count; i++) {
612 char buf[FILENAME_MAX];
613 char m[2] = { multi, '\0' };
615 snprintf(buf, sizeof(buf), "%s%s", _PATH_DEV, u[i]->ut_line);
617 if (stat(buf, &st) == -1) {
618 safe_strncat(line, "!", sizeof(line));
619 safe_strncat(line, m, sizeof(line));
620 continue;
623 #ifdef HAVE_UTMPX_H
624 if (u[i]->ut_tv.tv_sec > st.st_atime) {
625 #else
626 if (u[i]->ut_time > st.st_atime) {
627 #endif
628 safe_strncat(line, "-", sizeof(line));
629 safe_strncat(line, m, sizeof(line));
630 continue;
633 t = st.st_atime;
635 #ifdef HAVE_UTMPX_H
636 if (t < u[i]->ut_tv.tv_sec)
637 t = u[i]->ut_tv.tv_sec;
638 #else
639 if (t < u[i]->ut_time)
640 t = u[i]->ut_time;
641 #endif
643 snprintf(buf, sizeof(buf), "%lu", (now - t <= 0) ? 0 : now - t);
644 safe_strncat(line, buf, sizeof(line));
645 safe_strncat(line, m, sizeof(line));
648 if (line[0] == '\0')
649 return "!";
651 line[strlen(line) - 1] = '\0';
652 return line;
655 /* This is output if the -h command line option is passed to the main program.
657 void ui_module_help()
659 printf(" Login information [-L (-%s)]:\n", LOGIN_OPTION_ORDER);
660 printf("\t-y tty\t\t\t\t");
661 printf("-m message status\n");
662 printf("\t-t login time stamp\t\t");
663 printf("-d duration in minutes\n");
664 printf("\t-h hostname\t\t\t");
665 printf("-i seconds idle\n");
666 printf("\t-p login process id\n");
667 printf("\t-l lastlog information"
668 " (any of tt[y],[h]ostname,[t]ime, or [a]ll)\n\n");
669 return;
672 /* This is the equivalent to main() only without argc and argv available. */
673 int ui_module_exec(char ***s, const struct passwd *pw, const int multi,
674 const int verbose, char *tf)
676 char *p = options;
677 UTMP **u = NULL;
678 char buf[255];
680 login_count = 0;
681 u = get_utmp(pw->pw_name);
682 strings = *s;
684 while (*p) {
685 char line[LINE_MAX] = { '\0' };
686 int i;
687 char m[2] = { multi, '\0' };
689 switch (*p) {
690 case 'i':
691 add_string(&strings, (u) ? idle(u, multi) : "!");
692 break;
693 case 'l':
694 last_strings(lastlogin(pw, tf));
695 break;
696 case 'h':
697 for (i = 0; i < login_count; i++) {
698 if (u[i]->ut_host[0]
699 && isalnum((unsigned char) u[i]->ut_host[0]))
700 safe_strncat(line, u[i]->ut_host, sizeof(line));
701 else
702 safe_strncat(line, "-", sizeof(line));
704 safe_strncat(line, m, sizeof(line));
707 if (line[0] == '\0')
708 strncpy(line, "!", sizeof(line));
709 else
710 line[strlen(line) - 1] = '\0';
712 add_string(&strings, line);
713 break;
714 case 'y':
715 for (i = 0; i < login_count; i++) {
716 if (u[i]->ut_line[0])
717 safe_strncat(line, u[i]->ut_line, sizeof(line));
718 else
719 safe_strncat(line, "!", sizeof(line));
721 safe_strncat(line, m, sizeof(line));
724 if (line[0] == '\0')
725 strncpy(line, "!", sizeof(line));
726 else
727 line[strlen(line) - 1] = '\0';
729 add_string(&strings, line);
730 break;
731 case 'm':
732 add_string(&strings, msgstat(u, multi));
733 break;
734 case 't':
735 for (i = 0; i < login_count; i++) {
736 #ifdef HAVE_UTMPX_H
737 safe_strncat(line, stamp(u[i]->ut_tv.tv_sec, tf), sizeof(line));
738 #else
739 safe_strncat(line, stamp(u[i]->ut_time, tf), sizeof(line));
740 #endif
741 safe_strncat(line, m, sizeof(line));
744 if (line[0] == '\0')
745 strncpy(line, "!", sizeof(line));
746 else
747 line[strlen(line) - 1] = '\0';
749 add_string(&strings, line);
750 break;
751 case 'd':
752 for (i = 0; i < login_count; i++) {
753 #ifdef HAVE_UTMPX_H
754 if ((now - u[i]->ut_tv.tv_sec) > 60) {
755 snprintf(buf, sizeof(buf), "%lu",
756 ((now - u[i]->ut_tv.tv_sec) / 60));
757 #else
758 if ((now - u[i]->ut_time) > 60) {
759 snprintf(buf, sizeof(buf), "%lu",
760 ((now - u[i]->ut_time) / 60));
761 #endif
762 safe_strncat(line, buf, sizeof(line));
764 else
765 safe_strncat(line, "-", sizeof(line));
767 safe_strncat(line, m, sizeof(line));
770 if (line[0] == '\0')
771 strncpy(line, "!", sizeof(line));
772 else
773 line[strlen(line) - 1] = '\0';
775 add_string(&strings, line);
776 break;
777 case 'p':
778 #ifdef HAVE_UTMPX_H
779 for (i = 0; i < login_count; i++) {
780 if (u[i]->ut_pid) {
781 snprintf(buf, sizeof(buf), "%li", (long) u[i]->ut_pid);
782 safe_strncat(line, buf, sizeof(line));
784 else
785 safe_strncat(line, "!", sizeof(line));
787 safe_strncat(line, m, sizeof(line));
790 if (line[0] == '\0')
791 strncpy(line, "!", sizeof(line));
792 else
793 line[strlen(line) - 1] = '\0';
795 add_string(&strings, line);
796 #else
797 add_string(&strings, (u) ? get_pid(pw->pw_uid, multi) : "!");
798 #endif
799 break;
800 default:
801 break;
804 p++;
807 free_logins(u);
808 *s = strings;
809 return EXIT_SUCCESS;
812 /* See if the last login options (-l) are valid. */
813 static int parse_last_options(const char *args)
815 int i = 0;
817 for (i = 0; i < strlen(args); i++) {
818 switch (args[i]) {
819 case 'y':
820 case 'h':
821 case 't':
822 case 'a':
823 break;
824 default:
825 return 1;
829 return 0;
832 char *ui_module_options_init(char **defaults)
834 *defaults = "L";
835 return LOGIN_OPTION_STRING;
838 /* Check module option validity. */
839 int ui_module_options(int argc, char **argv)
841 int opt;
842 char *p = options;
844 while ((opt = getopt(argc, argv, LOGIN_OPTION_STRING)) != -1) {
845 switch (opt) {
846 case 'l':
847 if (parse_last_options(optarg))
848 return 1;
850 last_options = optarg;
851 break;
852 case 'L':
853 strncpy(options, LOGIN_OPTION_ORDER, sizeof(options));
854 last_options = "a";
855 return 0;
856 case 'p':
857 case 'd':
858 case 'i':
859 case 'm':
860 case 'y':
861 case 'h':
862 case 't':
863 break;
864 case '?':
865 warnx("login: invalid option -- %c", optopt);
866 default:
867 return 1;
870 *p++ = opt;
871 *p = '\0';
874 return 0;