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