1 /* Copyright © 2007, 2008 Red Hat, Inc. All rights reserved.
2 Red Hat author: Miloslav Trmač <mitr@redhat.com>
3
4 Redistribution and use in source and binary forms of Linux-PAM, with
5 or without modification, are permitted provided that the following
6 conditions are met:
7
8 1. Redistributions of source code must retain any existing copyright
9 notice, and this entire permission notice in its entirety,
10 including the disclaimer of warranties.
11
12 2. Redistributions in binary form must reproduce all prior and current
13 copyright notices, this list of conditions, and the following
14 disclaimer in the documentation and/or other materials provided
15 with the distribution.
16
17 3. The name of any author may not be used to endorse or promote
18 products derived from this software without their specific prior
19 written permission.
20
21 ALTERNATIVELY, this product may be distributed under the terms of the
22 GNU General Public License, in which case the provisions of the GNU
23 GPL are required INSTEAD OF the above restrictions. (This clause is
24 necessary due to a potential conflict between the GNU GPL and the
25 restrictions contained in a BSD-style copyright.)
26
27 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
28 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
29 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
30 IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
31 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
32 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
33 OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
34 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
35 TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
36 USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
37 DAMAGE. */
38
39 #include "config.h"
40 #include <errno.h>
41 #include <fnmatch.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <syslog.h>
45 #include <sys/socket.h>
46 #include <unistd.h>
47
48 #include <libaudit.h>
49 #include <linux/netlink.h>
50
51 #include <security/pam_ext.h>
52 #include <security/pam_modules.h>
53 #include <security/pam_modutil.h>
54
55 #include "pam_cc_compat.h"
56 #include "pam_inline.h"
57
58 #define DATANAME "pam_tty_audit_last_state"
59
60 /* Open an audit netlink socket */
61 static int
62 nl_open (void)
63 {
64 return socket (AF_NETLINK, SOCK_RAW, NETLINK_AUDIT);
65 }
66
67 static int
68 nl_send (int fd, unsigned type, unsigned flags, const void *data, size_t size)
69 {
70 struct sockaddr_nl addr;
71 struct msghdr msg;
72 struct nlmsghdr nlm;
73 struct iovec iov[2];
74 ssize_t res;
75
76 nlm.nlmsg_len = NLMSG_LENGTH (size);
77 nlm.nlmsg_type = type;
78 nlm.nlmsg_flags = NLM_F_REQUEST | flags;
79 nlm.nlmsg_seq = 0;
80 nlm.nlmsg_pid = 0;
81 iov[0].iov_base = &nlm;
82 iov[0].iov_len = sizeof (nlm);
83 DIAG_PUSH_IGNORE_CAST_QUAL;
84 iov[1].iov_base = (void *)data;
85 DIAG_POP_IGNORE_CAST_QUAL;
86 iov[1].iov_len = size;
87 addr.nl_family = AF_NETLINK;
88 addr.nl_pid = 0;
89 addr.nl_groups = 0;
90 msg.msg_name = &addr;
91 msg.msg_namelen = sizeof (addr);
92 msg.msg_iov = iov;
93 msg.msg_iovlen = 2;
94 msg.msg_control = NULL;
95 msg.msg_controllen = 0;
96 msg.msg_flags = 0;
97 res = sendmsg (fd, &msg, 0);
98 if (res == -1)
99 return -1;
100 if ((size_t)res != nlm.nlmsg_len)
101 {
102 errno = EIO;
103 return -1;
104 }
105 return 0;
106 }
107
108 static int
109 nl_recv (int fd, unsigned type, void *buf, size_t size)
110 {
111 struct sockaddr_nl addr;
112 struct msghdr msg;
113 struct nlmsghdr nlm;
114 struct iovec iov[2];
115 ssize_t res, resdiff;
116
117 again:
118 iov[0].iov_base = &nlm;
119 iov[0].iov_len = sizeof (nlm);
120 msg.msg_name = &addr;
121 msg.msg_namelen = sizeof (addr);
122 msg.msg_iov = iov;
123 msg.msg_iovlen = 1;
124 msg.msg_control = NULL;
125 msg.msg_controllen = 0;
126 msg.msg_flags = 0;
127 if (type != NLMSG_ERROR)
128 {
129 res = recvmsg (fd, &msg, MSG_PEEK);
130 if (res == -1)
131 return -1;
132 if (res != NLMSG_LENGTH (0))
133 {
134 errno = EIO;
135 return -1;
136 }
137 if (nlm.nlmsg_type == NLMSG_ERROR)
138 {
139 struct nlmsgerr err;
140
141 iov[1].iov_base = &err;
142 iov[1].iov_len = sizeof (err);
143 msg.msg_iovlen = 2;
144 res = recvmsg (fd, &msg, 0);
145 if (res == -1)
146 return -1;
147 if ((size_t)res != NLMSG_LENGTH (sizeof (err))
148 || nlm.nlmsg_type != NLMSG_ERROR)
149 {
150 errno = EIO;
151 return -1;
152 }
153 if (err.error == 0)
154 goto again;
155 errno = -err.error;
156 return -1;
157 }
158 }
159 if (size != 0)
160 {
161 iov[1].iov_base = buf;
162 iov[1].iov_len = size;
163 msg.msg_iovlen = 2;
164 }
165 res = recvmsg (fd, &msg, 0);
166 if (res == -1)
167 return -1;
168 resdiff = NLMSG_LENGTH(size) - (size_t)res;
169 if (resdiff < 0
170 || nlm.nlmsg_type != type)
171 {
172 errno = EIO;
173 return -1;
174 }
175 else if (resdiff > 0)
176 {
177 memset((char *)buf + size - resdiff, 0, resdiff);
178 }
179 return 0;
180 }
181
182 static int
183 nl_recv_ack (int fd)
184 {
185 struct nlmsgerr err;
186
187 if (nl_recv (fd, NLMSG_ERROR, &err, sizeof (err)) != 0)
188 return -1;
189 if (err.error != 0)
190 {
191 errno = -err.error;
192 return -1;
193 }
194 return 0;
195 }
196
197 static void
198 cleanup_old_status (pam_handle_t *pamh, void *data, int error_status)
199 {
200 (void)pamh;
201 (void)error_status;
202 free (data);
203 }
204
205 enum uid_range { UID_RANGE_NONE, UID_RANGE_MM, UID_RANGE_MIN,
206 UID_RANGE_ONE, UID_RANGE_ERR };
207
208 static enum uid_range
209 parse_uid_range(pam_handle_t *pamh, const char *s,
210 uid_t *min_uid, uid_t *max_uid)
211 {
212 const char *range = s;
213 const char *pmax;
214 char *endptr;
215 enum uid_range rv = UID_RANGE_MM;
216
217 if ((pmax=strchr(range, ':')) == NULL)
218 return UID_RANGE_NONE;
219 ++pmax;
220
221 if (range[0] == ':')
222 rv = UID_RANGE_ONE;
223 else {
224 errno = 0;
225 *min_uid = strtoul (range, &endptr, 10);
226 if (errno != 0 || (range == endptr) || *endptr != ':') {
227 pam_syslog(pamh, LOG_DEBUG,
228 "wrong min_uid value in '%s'", s);
229 return UID_RANGE_ERR;
230 }
231 }
232
233 if (*pmax == '\0') {
234 if (rv == UID_RANGE_ONE)
235 return UID_RANGE_ERR;
236
237 return UID_RANGE_MIN;
238 }
239
240 errno = 0;
241 *max_uid = strtoul (pmax, &endptr, 10);
242 if (errno != 0 || (pmax == endptr) || *endptr != '\0') {
243 pam_syslog(pamh, LOG_DEBUG,
244 "wrong max_uid value in '%s'", s);
245 return UID_RANGE_ERR;
246 }
247
248 if (rv == UID_RANGE_ONE)
249 *min_uid = *max_uid;
250 return rv;
251 }
252
253 int
254 pam_sm_open_session (pam_handle_t *pamh, int flags, int argc, const char **argv)
255 {
256 enum command { CMD_NONE, CMD_ENABLE, CMD_DISABLE };
257
258 enum command command;
259 struct audit_tty_status *old_status, new_status;
260 const char *user;
261 int i, fd, open_only;
262 struct passwd *pwd;
263 #ifdef HAVE_STRUCT_AUDIT_TTY_STATUS_LOG_PASSWD
264 int log_passwd;
265 #endif /* HAVE_STRUCT_AUDIT_TTY_STATUS_LOG_PASSWD */
266
267 (void)flags;
268
269 if (pam_get_user (pamh, &user, NULL) != PAM_SUCCESS)
270 {
271 pam_syslog(pamh, LOG_NOTICE, "cannot determine user name");
272 return PAM_SESSION_ERR;
273 }
274
275 pwd = pam_modutil_getpwnam(pamh, user);
276 if (pwd == NULL)
277 {
278 pam_syslog(pamh, LOG_NOTICE,
279 "open_session unknown user '%s'", user);
280 return PAM_SESSION_ERR;
281 }
282
283 command = CMD_NONE;
284 open_only = 0;
285 #ifdef HAVE_STRUCT_AUDIT_TTY_STATUS_LOG_PASSWD
286 log_passwd = 0;
287 #endif /* HAVE_STRUCT_AUDIT_TTY_STATUS_LOG_PASSWD */
288 for (i = 0; i < argc; i++)
289 {
290 const char *str;
291
292 if ((str = pam_str_skip_prefix(argv[i], "enable=")) != NULL
293 || (str = pam_str_skip_prefix(argv[i], "disable=")) != NULL)
294 {
295 enum command this_command;
296 char *copy, *tok_data, *tok;
297
298 this_command = *argv[i] == 'e' ? CMD_ENABLE : CMD_DISABLE;
299 copy = strdup (str);
300 if (copy == NULL)
301 return PAM_SESSION_ERR;
302 for (tok = strtok_r (copy, ",", &tok_data);
303 tok != NULL && command != this_command;
304 tok = strtok_r (NULL, ",", &tok_data))
305 {
306 uid_t min_uid = 0, max_uid = 0;
307 switch (parse_uid_range(pamh, tok, &min_uid, &max_uid))
308 {
309 case UID_RANGE_NONE:
310 if (fnmatch (tok, user, 0) == 0)
311 command = this_command;
312 break;
313 case UID_RANGE_MM:
314 if (pwd->pw_uid >= min_uid && pwd->pw_uid <= max_uid)
315 command = this_command;
316 break;
317 case UID_RANGE_MIN:
318 if (pwd->pw_uid >= min_uid)
319 command = this_command;
320 break;
321 case UID_RANGE_ONE:
322 if (pwd->pw_uid == max_uid)
323 command = this_command;
324 break;
325 case UID_RANGE_ERR:
326 break;
327 }
328 }
329 free (copy);
330 }
331 else if (strcmp (argv[i], "open_only") == 0)
332 open_only = 1;
333 else if (strcmp (argv[i], "log_passwd") == 0)
334 #ifdef HAVE_STRUCT_AUDIT_TTY_STATUS_LOG_PASSWD
335 log_passwd = 1;
336 #else /* HAVE_STRUCT_AUDIT_TTY_STATUS_LOG_PASSWD */
337 pam_syslog (pamh, LOG_WARNING,
338 "The log_passwd option was not available at compile time.");
339 #warning "pam_tty_audit: The log_passwd option is not available. Please upgrade your headers/kernel."
340 #endif /* HAVE_STRUCT_AUDIT_TTY_STATUS_LOG_PASSWD */
341 else
342 {
343 pam_syslog (pamh, LOG_ERR, "unknown option `%s'", argv[i]);
344 }
345 }
346 if (command == CMD_NONE)
347 return PAM_SUCCESS;
348
349 old_status = malloc (sizeof (*old_status));
350 if (old_status == NULL)
351 return PAM_SESSION_ERR;
352
353 fd = nl_open ();
354 if (fd == -1
355 && errno == EPROTONOSUPPORT)
356 {
357 pam_syslog (pamh, LOG_WARNING, "unable to open audit socket, audit not "
358 "supported; tty_audit skipped");
359 free (old_status);
360 return PAM_IGNORE;
361 }
362 else if (fd == -1
363 || nl_send (fd, AUDIT_TTY_GET, 0, NULL, 0) != 0
364 || nl_recv (fd, AUDIT_TTY_GET, old_status, sizeof (*old_status)) != 0)
365 {
366 pam_syslog (pamh, LOG_ERR, "error reading current audit status: %m");
367 if (fd != -1)
368 close (fd);
369 free (old_status);
370 return PAM_SESSION_ERR;
371 }
372
373 memcpy(&new_status, old_status, sizeof(new_status));
374
375 new_status.enabled = (command == CMD_ENABLE ? 1 : 0);
376 #ifdef HAVE_STRUCT_AUDIT_TTY_STATUS_LOG_PASSWD
377 new_status.log_passwd = log_passwd;
378 #endif /* HAVE_STRUCT_AUDIT_TTY_STATUS_LOG_PASSWD */
379 if (old_status->enabled == new_status.enabled
380 #ifdef HAVE_STRUCT_AUDIT_TTY_STATUS_LOG_PASSWD
381 && old_status->log_passwd == new_status.log_passwd
382 #endif /* HAVE_STRUCT_AUDIT_TTY_STATUS_LOG_PASSWD */
383 )
384 {
385 open_only = 1; /* to clean up old_status */
386 goto ok_fd;
387 }
388
389 if (open_only == 0
390 && pam_set_data (pamh, DATANAME, old_status, cleanup_old_status)
391 != PAM_SUCCESS)
392 {
393 pam_syslog (pamh, LOG_ERR, "error saving old audit status");
394 close (fd);
395 free (old_status);
396 return PAM_SESSION_ERR;
397 }
398
399 if (nl_send (fd, AUDIT_TTY_SET, NLM_F_ACK, &new_status,
400 sizeof (new_status)) != 0
401 || nl_recv_ack (fd) != 0)
402 {
403 pam_syslog (pamh, LOG_ERR, "error setting current audit status: %m");
404 close (fd);
405 if (open_only != 0)
406 free (old_status);
407 return PAM_SESSION_ERR;
408 }
409 /* Fall through */
410 ok_fd:
411 close (fd);
412 pam_syslog (pamh, LOG_DEBUG, "changed status from %d to %d",
413 old_status->enabled, new_status.enabled);
414 if (open_only != 0)
415 free (old_status);
416 return PAM_SUCCESS;
417 }
418
419 int
420 pam_sm_close_session (pam_handle_t *pamh, int flags, int argc,
421 const char **argv)
422 {
423 const void *status_;
424
425 (void)flags;
426 (void)argc;
427 (void)argv;
428 if (pam_get_data (pamh, DATANAME, &status_) == PAM_SUCCESS)
429 {
430 const struct audit_tty_status *status;
431 int fd;
432
433 status = status_;
434
435 fd = nl_open ();
436 if (fd == -1
437 || nl_send (fd, AUDIT_TTY_SET, NLM_F_ACK, status,
438 sizeof (*status)) != 0
439 || nl_recv_ack (fd) != 0)
440 {
441 pam_syslog (pamh, LOG_ERR, "error restoring audit status: %m");
442 if (fd != -1)
443 close (fd);
444 return PAM_SESSION_ERR;
445 }
446 close (fd);
447 pam_syslog (pamh, LOG_DEBUG, "restored status to %d", status->enabled);
448 }
449 return PAM_SUCCESS;
450 }