xref: /illumos-gate/usr/src/lib/pam_modules/unix_session/unix_session.c (revision 6249f9725f411468c70516176806c553ac983270)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  * Copyright 2015 Lauri Tirkkonen.
26  */
27 
28 #include <strings.h>
29 #include <sys/types.h>
30 #include <sys/wait.h>
31 #include <sys/stat.h>
32 #include <fcntl.h>
33 #include <stdlib.h>
34 #include <security/pam_appl.h>
35 #include <security/pam_modules.h>
36 #include <security/pam_impl.h>
37 #include <syslog.h>
38 #include <pwd.h>
39 #include <shadow.h>
40 #include <lastlog.h>
41 #include <ctype.h>
42 #include <unistd.h>
43 #include <stdlib.h>
44 #include <stdio.h>
45 #include <libintl.h>
46 #include <signal.h>
47 #include <thread.h>
48 #include <synch.h>
49 #include <errno.h>
50 #include <time.h>
51 #include <string.h>
52 #include <crypt.h>
53 #include <assert.h>
54 #include <nss_dbdefs.h>
55 
56 #define	LASTLOG_LEGACY		"/var/adm/lastlog"
57 struct lastlog_legacy {
58 #ifdef _LP64
59 	time32_t ll_time;
60 #else
61 	time_t	ll_time;
62 #endif
63 	char	ll_line[8];
64 	char	ll_host[16];
65 };
66 
67 /*
68  * pam_sm_close_session	- Terminate a PAM authenticated session
69  */
70 /*ARGSUSED*/
71 int
72 pam_sm_close_session(pam_handle_t *pamh, int flags, int argc,
73 	const char **argv)
74 {
75 	int	i;
76 	int	debug = 0;
77 
78 	for (i = 0; i < argc; i++) {
79 		if (strcasecmp(argv[i], "debug") == 0)
80 			debug = 1;
81 		else
82 			syslog(LOG_ERR, "illegal option %s", argv[i]);
83 	}
84 
85 	if (debug)
86 		syslog(LOG_DEBUG,
87 		    "pam_unix_session: inside pam_sm_close_session()");
88 
89 	return (PAM_SUCCESS);
90 }
91 
92 static int
93 lastlog_seek(int fdl, uid_t uid, boolean_t legacy)
94 {
95 	offset_t	offset;
96 
97 	offset = uid;
98 	if (legacy)
99 		offset *= sizeof (struct lastlog_legacy);
100 	else
101 		offset *= sizeof (struct lastlog);
102 
103 	if (llseek(fdl, offset, SEEK_SET) != offset) {
104 		syslog(LOG_ERR, "pam_unix_session: %slastlog seek failed for "
105 		    "uid %d: %m", (legacy ? "legacy " : ""), uid);
106 		return (-1);
107 	}
108 	return (0);
109 }
110 
111 static int
112 lastlog_read(int fdl, uid_t uid, struct lastlog *out, boolean_t legacy)
113 {
114 	ssize_t			nread = 0;
115 	ssize_t			llsize;
116 	struct lastlog		ll;
117 	struct lastlog_legacy	ll_legacy;
118 	void			*llp;
119 
120 	if (legacy) {
121 		llp = &ll_legacy;
122 		llsize = sizeof (ll_legacy);
123 	} else {
124 		llp = &ll;
125 		llsize = sizeof (ll);
126 	}
127 
128 	if (lastlog_seek(fdl, uid, legacy) == -1)
129 		return (-1);
130 
131 	while (nread < llsize) {
132 		ssize_t ret;
133 reread:
134 		ret = read(fdl, ((char *)llp) + nread, llsize - nread);
135 		if (ret < 0) {
136 			if (errno == EINTR)
137 				goto reread;
138 			syslog(LOG_ERR, "pam_unix_session: read %slastlog "
139 			    "failed for uid %d: %m", (legacy ? "legacy " : ""),
140 			    uid);
141 			return (-1);
142 		} else if (ret == 0) {
143 			if (nread == 0) {
144 				out->ll_time = 0;
145 				return (-1);
146 			}
147 			syslog(LOG_ERR, "pam_unix_session: %slastlog short "
148 			    "read for uid %d", (legacy ? "legacy " : ""), uid);
149 			return (-1);
150 		}
151 		nread += ret;
152 	}
153 	if (legacy) {
154 		out->ll_time = ll_legacy.ll_time;
155 		ll_legacy.ll_line[sizeof (ll_legacy.ll_line) - 1] = '\0';
156 		ll_legacy.ll_host[sizeof (ll_legacy.ll_host) - 1] = '\0';
157 		(void) strlcpy(out->ll_line, ll_legacy.ll_line,
158 		    sizeof (out->ll_line));
159 		(void) strlcpy(out->ll_host, ll_legacy.ll_host,
160 		    sizeof (out->ll_line));
161 	} else {
162 		out->ll_time = ll.ll_time;
163 		ll.ll_line[sizeof (ll.ll_line) - 1] = '\0';
164 		ll.ll_host[sizeof (ll.ll_host) - 1] = '\0';
165 		(void) strlcpy(out->ll_line, ll.ll_line,
166 		    sizeof (out->ll_line));
167 		(void) strlcpy(out->ll_host, ll.ll_host,
168 		    sizeof (out->ll_host));
169 	}
170 	return (0);
171 }
172 
173 static int
174 lastlog_write(int fdl, uid_t uid, const struct lastlog *ll)
175 {
176 	ssize_t		nwritten = 0;
177 	if (lastlog_seek(fdl, uid, B_FALSE))
178 		return (-1);
179 
180 	while (nwritten < sizeof (*ll)) {
181 		ssize_t ret;
182 rewrite:
183 		ret = write(fdl, ((char *)ll) + nwritten,
184 		    sizeof (*ll) - nwritten);
185 		if (ret < 0) {
186 			if (errno == EINTR)
187 				goto rewrite;
188 			syslog(LOG_ERR, "pam_unix_session: write lastlog "
189 			    "failed for uid %d: %m", uid);
190 			return (-1);
191 		} else if (ret == 0) {
192 			syslog(LOG_ERR, "pam_unix_session: lastlog short "
193 			    "write for uid %d", uid);
194 			return (-1);
195 		}
196 		nwritten += ret;
197 	}
198 	return (0);
199 }
200 
201 /*ARGSUSED*/
202 int
203 pam_sm_open_session(pam_handle_t *pamh, int flags, int argc,
204 	const char **argv)
205 {
206 	int	error;
207 	char    *ttyn, *rhost, *user;
208 	int	fdl;
209 	struct lastlog	newll = { 0 };
210 	struct lastlog	legacyll;
211 	struct lastlog	ll;
212 	struct lastlog	*llp = NULL;
213 	struct passwd pwd;
214 	char    buffer[NSS_BUFLEN_PASSWD];
215 	int	i;
216 	int	debug = 0;
217 	time_t	cur_time;
218 
219 	for (i = 0; i < argc; i++) {
220 		if (strcasecmp(argv[i], "debug") == 0)
221 			debug = 1;
222 		else
223 			syslog(LOG_ERR, "illegal option %s", argv[i]);
224 	}
225 
226 	if (debug)
227 		syslog(LOG_DEBUG,
228 		    "pam_unix_session: inside pam_sm_open_session()");
229 
230 	if ((error = pam_get_item(pamh, PAM_TTY, (void **)&ttyn))
231 	    != PAM_SUCCESS ||
232 	    (error = pam_get_item(pamh, PAM_USER, (void **)&user))
233 	    != PAM_SUCCESS ||
234 	    (error = pam_get_item(pamh, PAM_RHOST, (void **)&rhost))
235 	    != PAM_SUCCESS) {
236 		return (error);
237 	}
238 
239 	if (user == NULL || *user == '\0')
240 		return (PAM_USER_UNKNOWN);
241 
242 	/* report error if ttyn not set */
243 	if (ttyn == NULL)
244 		return (PAM_SESSION_ERR);
245 
246 	if (getpwnam_r(user, &pwd, buffer, sizeof (buffer)) == NULL) {
247 		return (PAM_USER_UNKNOWN);
248 	}
249 
250 	ll.ll_time = 0;
251 reopenll_ro:
252 	fdl = open(_PATH_LASTLOG, O_RDONLY);
253 	if (fdl < 0) {
254 		if (errno == EINTR)
255 			goto reopenll_ro;
256 		if (errno != ENOENT)
257 			syslog(LOG_ERR, "pam_unix_session: unable to open "
258 			    "lastlog for uid %d: %m", pwd.pw_uid);
259 	} else {
260 		if (lastlog_read(fdl, pwd.pw_uid, &ll, B_FALSE) == 0)
261 			llp = &ll;
262 		(void) close(fdl);
263 	}
264 
265 	if ((fdl = open(LASTLOG_LEGACY, O_RDONLY)) >= 0) {
266 		if (lastlog_read(fdl, pwd.pw_uid, &legacyll, B_TRUE) == 0 &&
267 		    legacyll.ll_time > ll.ll_time)
268 			llp = &legacyll;
269 		(void) close(fdl);
270 	}
271 
272 	if (llp != NULL && llp->ll_time != 0 && !(flags & PAM_SILENT)) {
273 		char timestr[26];
274 		char msg[PAM_MAX_MSG_SIZE];
275 		int ret;
276 		time_t t = llp->ll_time;
277 		(void) ctime_r(&t, timestr, sizeof (timestr));
278 		timestr[strcspn(timestr, "\n")] = '\0';
279 		if (strcmp(llp->ll_host, "") != 0) {
280 			ret = snprintf(msg, PAM_MAX_MSG_SIZE,
281 			    "Last login: %s from %s", timestr, llp->ll_host);
282 		} else if (strcmp(llp->ll_line, "") != 0) {
283 			ret = snprintf(msg, PAM_MAX_MSG_SIZE,
284 			    "Last login: %s on %s", timestr, llp->ll_line);
285 		} else {
286 			ret = snprintf(msg, PAM_MAX_MSG_SIZE,
287 			    "Last login: %s", timestr);
288 		}
289 		if (!(ret < 0 || ret >= PAM_MAX_MSG_SIZE)) {
290 			(void) __pam_display_msg(pamh, PAM_TEXT_INFO, 1, &msg,
291 			    NULL);
292 		}
293 	}
294 
295 reopenll_rw:
296 	fdl = open(_PATH_LASTLOG, O_RDWR|O_CREAT|O_DSYNC, 0444);
297 	if (fdl < 0) {
298 		if (errno == EINTR)
299 			goto reopenll_rw;
300 		syslog(LOG_ERR, "pam_unix_session: unable to open lastlog for "
301 		    "writing for uid %d: %m", pwd.pw_uid);
302 		return (PAM_SUCCESS);
303 	}
304 
305 	(void) time(&cur_time);
306 
307 	newll.ll_time = cur_time;
308 	if ((strncmp(ttyn, "/dev/", 5) == 0)) {
309 		(void) strlcpy(newll.ll_line,
310 		    (ttyn + sizeof ("/dev/")-1),
311 		    sizeof (newll.ll_line));
312 	} else {
313 		(void) strlcpy(newll.ll_line, ttyn,
314 		    sizeof (newll.ll_line));
315 	}
316 	if (rhost != NULL) {
317 		(void) strlcpy(newll.ll_host, rhost,
318 		    sizeof (newll.ll_host));
319 	}
320 
321 	if (debug) {
322 		char	buf[26];
323 
324 		(void) ctime_r((const time_t *)&cur_time, buf,
325 		    sizeof (buf));
326 		buf[24] = '\000';
327 		syslog(LOG_DEBUG, "pam_unix_session: "
328 		    "user = %s, time = %s, tty = %s, host = %s.",
329 		    user, buf, newll.ll_line, newll.ll_host);
330 	}
331 	(void) lastlog_write(fdl, pwd.pw_uid, &newll);
332 	if (close(fdl) < 0) {
333 		syslog(LOG_ERR, "pam_unix_session: unable to close lastlog for"
334 		    " uid %d: %m", pwd.pw_uid);
335 	}
336 	return (PAM_SUCCESS);
337 }
338