1/* see LICENSE file for copyright and license details */
2
3#include "config.h"
4#include "passwd.h"
5#include "util.h"
6#include "wexec.h"
7
8#include <grp.h>
9#include <limits.h>
10#include <pwd.h>
11#include <stdio.h>
12#include <stdlib.h>
13#include <string.h>
14#include <termios.h>
15#include <time.h>
16#include <unistd.h>
17#include <utmp.h>
18
19static void
20usage(void)
21{
22 eprintf("usage: %s [-p name] [-f name]\n", argv0);
23}
24
25/* reads one line of input, echoing it or not, into a reusable static
26 * buffer. returns null on eof so a hung-up line doesnt loop forever */
27static char *
28readline(const char *prompt, int echo)
29{
30 static char buf[LOGIN_NAME_MAX + 1];
31 struct termios old, raw;
32 int have_old = 0;
33 size_t n;
34
35 fputs(prompt, stdout);
36 fflush(stdout);
37
38 if (!echo && tcgetattr(STDIN_FILENO, &old) == 0) {
39 have_old = 1;
40 raw = old;
41 raw.c_lflag &= ~ECHO;
42 tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
43 }
44
45 if (!fgets(buf, sizeof(buf), stdin)) {
46 if (have_old)
47 tcsetattr(STDIN_FILENO, TCSAFLUSH, &old);
48 return NULL;
49 }
50
51 if (have_old) {
52 tcsetattr(STDIN_FILENO, TCSAFLUSH, &old);
53 fputc('\n', stdout);
54 }
55
56 n = strlen(buf);
57 if (n && buf[n - 1] == '\n')
58 buf[--n] = '\0';
59
60 return buf;
61}
62
63/* best effort utmp record for the controlling tty: getty already
64 * cleared any stale entry for this line before exec-ing us. only
65 * ut_line, ut_name (musl aliases this to ut_user), and ut_time
66 * (aliased to ut_tv.tv_sec) exist on both linux and bsd utmp
67 * layouts; ut_type/ut_pid do not exist at all on bsd, so those two
68 * fields are the one genuinely platform-specific part of this record */
69static void
70write_utmp(const char *user)
71{
72 struct utmp usr;
73 char *line;
74 FILE *fp;
75
76 memset(&usr, 0, sizeof(usr));
77#if defined(__linux__)
78 usr.ut_type = USER_PROCESS;
79 usr.ut_pid = getpid();
80#endif
81 line = ttyname(STDIN_FILENO);
82 if (line) {
83 if (!strncmp(line, "/dev/", 5))
84 line += 5;
85 strlcpy(usr.ut_line, line, sizeof(usr.ut_line));
86 }
87 strlcpy(usr.ut_name, user, sizeof(usr.ut_name));
88 usr.ut_time = time(NULL);
89
90 /* utmp: a live table, so this overwrites/appends the one entry for
91 * this session */
92 fp = fopen(UTMP_PATH, "r+");
93 if (fp) {
94 fseek(fp, 0, SEEK_END);
95 fwrite(&usr, sizeof(usr), 1, fp);
96 fclose(fp);
97 }
98
99 /* wtmp: an ever-growing append-only log of the same record, same
100 * portable field subset, just opened differently */
101 fp = fopen(WTMP_PATH, "a");
102 if (fp) {
103 fwrite(&usr, sizeof(usr), 1, fp);
104 fclose(fp);
105 }
106}
107
108// ?man login: authenticate a user and start a session
109// ?man arguments: [-p name] [-f name]
110// ?man prompt for a username (unless given) and password, verify against
111// ?man the system password database, then exec the user's login shell
112// ?man invoked by getty with -p name once a username has already been
113// ?man typed at the "login:" prompt
114int
115main(int argc, char *argv[])
116{
117 struct passwd *pw;
118 char namebuf[LOGIN_NAME_MAX + 1];
119 char shellbuf[PATH_MAX];
120 char *preset = NULL, *trusted = NULL;
121 char *name, *pass, *line, *base;
122 char *shargv[2];
123 int tries;
124
125 ARGBEGIN
126 {
127 // ?man -p:name preset the username; still prompts for a password
128 case 'p':
129 preset = EARGF(usage());
130 break;
131 // ?man -f:name log in as name without a password prompt: the
132 // ?man caller has already authenticated this user out-of-band
133 case 'f':
134 trusted = EARGF(usage());
135 break;
136 default:
137 usage();
138 }
139 ARGEND
140
141 pw_init();
142
143 if (trusted) {
144 pw = getpwnam(trusted);
145 if (!pw)
146 eprintf("login: unknown user: %s\n", trusted);
147 } else {
148 pw = NULL;
149 for (tries = 0; tries < 3 && !pw; tries++) {
150 if (preset && tries == 0) {
151 strlcpy(namebuf, preset, sizeof(namebuf));
152 } else {
153 line = readline("login: ", 1);
154 if (!line)
155 exit(1);
156 strlcpy(namebuf, line, sizeof(namebuf));
157 }
158 name = namebuf;
159
160 pass = readline("Password: ", 0);
161 if (!pass)
162 pass = "";
163
164 pw = getpwnam(name);
165 if (!pw || pw_check(pw, pass) != 1) {
166 fputs("Login incorrect\n", stdout);
167 pw = NULL;
168 }
169 }
170 if (!pw)
171 exit(1);
172 }
173
174 if (initgroups(pw->pw_name, pw->pw_gid) < 0)
175 weprintf("initgroups:");
176 if (setgid(pw->pw_gid) < 0)
177 eprintf("setgid:");
178 if (setuid(pw->pw_uid) < 0)
179 eprintf("setuid:");
180
181 write_utmp(pw->pw_name);
182
183 if (chdir(pw->pw_dir) < 0)
184 weprintf("chdir %s:", pw->pw_dir);
185
186 setenv("HOME", pw->pw_dir, 1);
187 setenv("USER", pw->pw_name, 1);
188 setenv("LOGNAME", pw->pw_name, 1);
189 strlcpy(shellbuf, pw->pw_shell[0] ? pw->pw_shell : "/bin/sh", sizeof(shellbuf));
190 setenv("SHELL", shellbuf, 1);
191
192 base = strrchr(shellbuf, '/');
193 base = base ? base + 1 : shellbuf;
194
195 shargv[0] = emalloc(strlen(base) + 2);
196 shargv[0][0] = '-';
197 strcpy(shargv[0] + 1, base);
198 shargv[1] = NULL;
199
200 wexecv_self(shellbuf, shargv);
201 return 1;
202}