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 = ≪ 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 = ≪ 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