1 /*
2 * pam_issue module - a simple /etc/issue parser to set PAM_USER_PROMPT
3 *
4 * Copyright 1999 by Ben Collins <bcollins@debian.org>
5 *
6 * Needs to be called before any other auth modules so we can setup the
7 * user prompt before it's first used. Allows one argument option, which
8 * is the full path to a file to be used for issue (uses /etc/issue as a
9 * default) such as "issue=/etc/issue.telnet".
10 *
11 * We can also parse escapes within the the issue file (enabled by
12 * default, but can be disabled with the "noesc" option). It's the exact
13 * same parsing as util-linux's agetty program performs.
14 *
15 * Released under the GNU LGPL version 2 or later
16 */
17
18 #include "config.h"
19
20 #include <string.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <fcntl.h>
26 #include <unistd.h>
27 #include <sys/utsname.h>
28 #include <time.h>
29 #include <syslog.h>
30
31 #ifdef USE_LOGIND
32 #include <systemd/sd-login.h>
33 #else
34 #include <utmp.h>
35 #endif
36
37 #include <security/_pam_macros.h>
38 #include <security/pam_modules.h>
39 #include <security/pam_ext.h>
40 #include "pam_inline.h"
41
42 static int _user_prompt_set = 0;
43
44 static int
45 read_issue_raw(pam_handle_t *pamh, FILE *fp, char **prompt)
46 {
47 char *issue;
48 struct stat st;
49
50 *prompt = NULL;
51
52 if (fstat(fileno(fp), &st) < 0) {
53 pam_syslog(pamh, LOG_ERR, "stat error: %m");
54 return PAM_SERVICE_ERR;
55 }
56
57 if ((issue = malloc(st.st_size + 1)) == NULL) {
58 pam_syslog(pamh, LOG_CRIT, "out of memory");
59 return PAM_BUF_ERR;
60 }
61
62 if ((off_t)fread(issue, 1, st.st_size, fp) != st.st_size) {
63 pam_syslog(pamh, LOG_ERR, "read error: %m");
64 _pam_drop(issue);
65 return PAM_SERVICE_ERR;
66 }
67
68 issue[st.st_size] = '\0';
69 *prompt = issue;
70 return PAM_SUCCESS;
71 }
72
73 static int
74 read_issue_quoted(pam_handle_t *pamh, FILE *fp, char **prompt)
75 {
76 int c;
77 size_t size = 1024;
78 size_t issue_len = 0;
79 char *issue;
80 struct utsname uts;
81
82 *prompt = NULL;
83
84 if ((issue = malloc(size)) == NULL) {
85 pam_syslog(pamh, LOG_CRIT, "out of memory");
86 return PAM_BUF_ERR;
87 }
88
89 (void) uname(&uts);
90
91 while ((c = getc(fp)) != EOF) {
92 const char *src = NULL;
93 size_t len = 0;
94 char buf[1024] = "";
95
96 if (c == '\\') {
97 if ((c = getc(fp)) == EOF)
98 break;
99 switch (c) {
100 case 's':
101 src = uts.sysname;
102 len = strnlen(uts.sysname, sizeof(uts.sysname));
103 break;
104 case 'n':
105 src = uts.nodename;
106 len = strnlen(uts.nodename, sizeof(uts.nodename));
107 break;
108 case 'r':
109 src = uts.release;
110 len = strnlen(uts.release, sizeof(uts.release));
111 break;
112 case 'v':
113 src = uts.version;
114 len = strnlen(uts.version, sizeof(uts.version));
115 break;
116 case 'm':
117 src = uts.machine;
118 len = strnlen(uts.machine, sizeof(uts.machine));
119 break;
120 case 'o':
121 #ifdef HAVE_GETDOMAINNAME
122 if (getdomainname(buf, sizeof(buf)) >= 0)
123 buf[sizeof(buf) - 1] = '\0';
124 #endif
125 break;
126 case 'd':
127 case 't':
128 {
129 const char *weekday[] = {
130 "Sun", "Mon", "Tue", "Wed", "Thu",
131 "Fri", "Sat" };
132 const char *month[] = {
133 "Jan", "Feb", "Mar", "Apr", "May",
134 "Jun", "Jul", "Aug", "Sep", "Oct",
135 "Nov", "Dec" };
136 time_t now;
137 struct tm *tm;
138
139 (void) time (&now);
140 tm = localtime(&now);
141
142 if (c == 'd')
143 snprintf (buf, sizeof buf, "%s %s %d %d",
144 weekday[tm->tm_wday], month[tm->tm_mon],
145 tm->tm_mday, tm->tm_year + 1900);
146 else
147 snprintf (buf, sizeof buf, "%02d:%02d:%02d",
148 tm->tm_hour, tm->tm_min, tm->tm_sec);
149 }
150 break;
151 case 'l':
152 {
153 const char *ttyn = ttyname(1);
154 if (ttyn) {
155 const char *str = pam_str_skip_prefix(ttyn, "/dev/");
156 if (str != NULL)
157 ttyn = str;
158 src = ttyn;
159 len = strlen(ttyn);
160 }
161 }
162 break;
163 case 'u':
164 case 'U':
165 {
166 unsigned int users = 0;
167 #ifdef USE_LOGIND
168 int sessions = sd_get_sessions(NULL);
169
170 if (sessions < 0) {
171 pam_syslog(pamh, LOG_ERR, "logind error: %s",
172 strerror(-sessions));
173 _pam_drop(issue);
174 return PAM_SERVICE_ERR;
175 } else {
176 users = sessions;
177 }
178 #else
179 struct utmp *ut;
180 setutent();
181 while ((ut = getutent())) {
182 if (ut->ut_type == USER_PROCESS)
183 ++users;
184 }
185 endutent();
186 #endif
187 if (c == 'U')
188 snprintf (buf, sizeof buf, "%u %s", users,
189 (users == 1) ? "user" : "users");
190 else
191 snprintf (buf, sizeof buf, "%u", users);
192 break;
193 }
194 default:
195 buf[0] = c; buf[1] = '\0';
196 }
197 } else {
198 buf[0] = c; buf[1] = '\0';
199 }
200
201 if (src == NULL) {
202 src = buf;
203 len = strlen(buf);
204 }
205 if (issue_len + len + 1 > size) {
206 char *new_issue;
207
208 size += len + 1;
209 new_issue = realloc (issue, size);
210 if (new_issue == NULL) {
211 _pam_drop(issue);
212 return PAM_BUF_ERR;
213 }
214 issue = new_issue;
215 }
216 memcpy(issue + issue_len, src, len);
217 issue_len += len;
218 }
219
220 issue[issue_len] = '\0';
221
222 if (ferror(fp)) {
223 pam_syslog(pamh, LOG_ERR, "read error: %m");
224 _pam_drop(issue);
225 return PAM_SERVICE_ERR;
226 }
227
228 *prompt = issue;
229 return PAM_SUCCESS;
230 }
231
232 /* --- authentication management functions (only) --- */
233
234 int
235 pam_sm_authenticate(pam_handle_t *pamh, int flags UNUSED,
236 int argc, const char **argv)
237 {
238 int retval = PAM_SERVICE_ERR;
239 FILE *fp;
240 const char *issue_file = NULL;
241 int parse_esc = 1;
242 const void *item = NULL;
243 const char *cur_prompt;
244 char *issue_prompt = NULL;
245
246 /* If we've already set the prompt, don't set it again */
247 if(_user_prompt_set)
248 return PAM_IGNORE;
249
250 /* We set this here so if we fail below, we won't get further
251 than this next time around (only one real failure) */
252 _user_prompt_set = 1;
253
254 for ( ; argc-- > 0 ; ++argv ) {
255 const char *str;
256
257 if ((str = pam_str_skip_prefix(*argv, "issue=")) != NULL) {
258 issue_file = str;
259 D(("set issue_file to: %s", issue_file));
260 } else if (!strcmp(*argv,"noesc")) {
261 parse_esc = 0;
262 D(("turning off escape parsing by request"));
263 } else
264 D(("unknown option passed: %s", *argv));
265 }
266
267 if (issue_file == NULL)
268 issue_file = "/etc/issue";
269
270 if ((fp = fopen(issue_file, "r")) == NULL) {
271 pam_syslog(pamh, LOG_ERR, "error opening %s: %m", issue_file);
272 return PAM_SERVICE_ERR;
273 }
274
275 if ((retval = pam_get_item(pamh, PAM_USER_PROMPT, &item)) != PAM_SUCCESS) {
276 fclose(fp);
277 return retval;
278 }
279
280 cur_prompt = item;
281 if (cur_prompt == NULL)
282 cur_prompt = "";
283
284 if (parse_esc)
285 retval = read_issue_quoted(pamh, fp, &issue_prompt);
286 else
287 retval = read_issue_raw(pamh, fp, &issue_prompt);
288
289 fclose(fp);
290
291 if (retval != PAM_SUCCESS)
292 goto out;
293
294 {
295 size_t size = strlen(issue_prompt) + strlen(cur_prompt) + 1;
296 char *new_prompt = realloc(issue_prompt, size);
297
298 if (new_prompt == NULL) {
299 pam_syslog(pamh, LOG_CRIT, "out of memory");
300 retval = PAM_BUF_ERR;
301 goto out;
302 }
303 issue_prompt = new_prompt;
304 }
305
306 strcat(issue_prompt, cur_prompt);
307 retval = pam_set_item(pamh, PAM_USER_PROMPT,
308 (const void *) issue_prompt);
309 out:
310 _pam_drop(issue_prompt);
311 return (retval == PAM_SUCCESS) ? PAM_IGNORE : retval;
312 }
313
314 int
315 pam_sm_setcred(pam_handle_t *pamh UNUSED, int flags UNUSED,
316 int argc UNUSED, const char **argv UNUSED)
317 {
318 return PAM_IGNORE;
319 }