1 /* $NetBSD: sshlogin.c,v 1.14 2026/04/08 18:58:41 christos Exp $ */ 2 /* $OpenBSD: sshlogin.c,v 1.37 2026/02/16 23:47:06 jsg Exp $ */ 3 4 /* 5 * Author: Tatu Ylonen <ylo (at) cs.hut.fi> 6 * Copyright (c) 1995 Tatu Ylonen <ylo (at) cs.hut.fi>, Espoo, Finland 7 * All rights reserved 8 * This file performs some of the things login(1) normally does. We cannot 9 * easily use something like login -p -h host -f user, because there are 10 * several different logins around, and it is hard to determined what kind of 11 * login the current system has. Also, we want to be able to execute commands 12 * on a tty. 13 * 14 * As far as I am concerned, the code I have written for this software 15 * can be used freely for any purpose. Any derived versions of this 16 * software must be clearly marked as such, and if the derived work is 17 * incompatible with the protocol description in the RFC file, it must be 18 * called by a name other than "ssh" or "Secure Shell". 19 * 20 * Copyright (c) 1999 Theo de Raadt. All rights reserved. 21 * Copyright (c) 1999 Markus Friedl. All rights reserved. 22 * 23 * Redistribution and use in source and binary forms, with or without 24 * modification, are permitted provided that the following conditions 25 * are met: 26 * 1. Redistributions of source code must retain the above copyright 27 * notice, this list of conditions and the following disclaimer. 28 * 2. Redistributions in binary form must reproduce the above copyright 29 * notice, this list of conditions and the following disclaimer in the 30 * documentation and/or other materials provided with the distribution. 31 * 32 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 33 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 34 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 35 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 36 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 37 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 38 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 39 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 40 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 41 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 42 */ 43 44 #include "includes.h" 45 __RCSID("$NetBSD: sshlogin.c,v 1.14 2026/04/08 18:58:41 christos Exp $"); 46 #include <sys/param.h> 47 #include <sys/types.h> 48 #include <sys/socket.h> 49 50 #include <errno.h> 51 #include <fcntl.h> 52 #include <stdarg.h> 53 #include <stdio.h> 54 #include <stdlib.h> 55 #include <string.h> 56 #include <time.h> 57 #include <unistd.h> 58 #include <util.h> 59 #ifdef SUPPORT_UTMP 60 #include <utmp.h> 61 #endif 62 #ifdef SUPPORT_UTMPX 63 #include <utmpx.h> 64 #endif 65 #include <limits.h> 66 67 #include "sshlogin.h" 68 #include "ssherr.h" 69 #include "log.h" 70 #include "sshbuf.h" 71 #include "misc.h" 72 #include "servconf.h" 73 74 #ifndef HOST_NAME_MAX 75 #define HOST_NAME_MAX MAXHOSTNAMELEN 76 #endif 77 78 extern struct sshbuf *loginmsg; 79 extern ServerOptions options; 80 81 /* 82 * Returns the time when the user last logged in. Returns 0 if the 83 * information is not available. This must be called before record_login. 84 * The host the user logged in from will be returned in buf. 85 */ 86 time_t 87 get_last_login_time(uid_t uid, const char *logname, 88 char *buf, size_t bufsize) 89 { 90 #ifdef SUPPORT_UTMPX 91 struct lastlogx llx, *llxp; 92 #endif 93 #ifdef SUPPORT_UTMP 94 struct lastlog ll; 95 int fd; 96 #endif 97 off_t pos, r; 98 99 buf[0] = '\0'; 100 #ifdef SUPPORT_UTMPX 101 if ((llxp = getlastlogx(_PATH_LASTLOGX, uid, &llx)) != NULL) { 102 if (bufsize > sizeof(llxp->ll_host) + 1) 103 bufsize = sizeof(llxp->ll_host) + 1; 104 strncpy(buf, llxp->ll_host, bufsize - 1); 105 buf[bufsize - 1] = 0; 106 return llxp->ll_tv.tv_sec; 107 } 108 #endif 109 #ifdef SUPPORT_UTMP 110 fd = open(_PATH_LASTLOG, O_RDONLY); 111 if (fd == -1) 112 return 0; 113 114 pos = (off_t)uid * sizeof(ll); 115 r = lseek(fd, pos, SEEK_SET); 116 if (r == -1) { 117 error_f("lseek: %s", strerror(errno)); 118 close(fd); 119 return (0); 120 } 121 if (r != pos) { 122 debug_f("truncated lastlog"); 123 close(fd); 124 return (0); 125 } 126 if (read(fd, &ll, sizeof(ll)) != sizeof(ll)) { 127 close(fd); 128 return 0; 129 } 130 close(fd); 131 if (bufsize > sizeof(ll.ll_host) + 1) 132 bufsize = sizeof(ll.ll_host) + 1; 133 strncpy(buf, ll.ll_host, bufsize - 1); 134 buf[bufsize - 1] = '\0'; 135 return (time_t)ll.ll_time; 136 #else 137 return 0; 138 #endif 139 } 140 141 /* 142 * Generate and store last login message. This must be done before 143 * login_login() is called and lastlog is updated. 144 */ 145 static void 146 store_lastlog_message(const char *user, uid_t uid) 147 { 148 char *time_string, hostname[HOST_NAME_MAX+1] = ""; 149 time_t last_login_time; 150 int r; 151 152 if (!options.print_lastlog) 153 return; 154 155 last_login_time = get_last_login_time(uid, user, hostname, 156 sizeof(hostname)); 157 158 if (last_login_time != 0) { 159 if ((time_string = ctime(&last_login_time)) != NULL) 160 time_string[strcspn(time_string, "\n")] = '\0'; 161 if (strcmp(hostname, "") == 0) 162 r = sshbuf_putf(loginmsg, "Last login: %s\r\n", 163 time_string); 164 else 165 r = sshbuf_putf(loginmsg, "Last login: %s from %s\r\n", 166 time_string, hostname); 167 if (r != 0) 168 fatal_fr(r, "sshbuf_putf"); 169 } 170 } 171 172 /* 173 * Records that the user has logged in. I wish these parts of operating 174 * systems were more standardized. 175 */ 176 void 177 record_login(pid_t pid, const char *tty, const char *user, uid_t uid, 178 const char *host, struct sockaddr *addr, socklen_t addrlen) 179 { 180 #if defined(SUPPORT_UTMP) || defined(SUPPORT_UTMPX) 181 int fd; 182 #endif 183 struct timeval tv; 184 #ifdef SUPPORT_UTMP 185 struct utmp u; 186 struct lastlog ll; 187 #endif 188 #ifdef SUPPORT_UTMPX 189 struct utmpx ux, *uxp = &ux; 190 struct lastlogx llx; 191 #endif 192 (void)gettimeofday(&tv, NULL); 193 /* 194 * XXX: why do we need to handle logout cases here? 195 * Isn't the function below taking care of this? 196 */ 197 /* save previous login details before writing new */ 198 store_lastlog_message(user, uid); 199 200 #ifdef SUPPORT_UTMP 201 /* Construct an utmp/wtmp entry. */ 202 memset(&u, 0, sizeof(u)); 203 strncpy(u.ut_line, tty + 5, sizeof(u.ut_line)); 204 u.ut_time = (time_t)tv.tv_sec; 205 strncpy(u.ut_name, user, sizeof(u.ut_name)); 206 strncpy(u.ut_host, host, sizeof(u.ut_host)); 207 208 login(&u); 209 210 /* Update lastlog unless actually recording a logout. */ 211 if (*user != '\0') { 212 /* 213 * It is safer to memset the lastlog structure first because 214 * some systems might have some extra fields in it (e.g. SGI) 215 */ 216 memset(&ll, 0, sizeof(ll)); 217 218 /* Update lastlog. */ 219 ll.ll_time = time(NULL); 220 strncpy(ll.ll_line, tty + 5, sizeof(ll.ll_line)); 221 strncpy(ll.ll_host, host, sizeof(ll.ll_host)); 222 fd = open(_PATH_LASTLOG, O_RDWR); 223 if (fd >= 0) { 224 lseek(fd, (off_t)uid * sizeof(ll), SEEK_SET); 225 if (write(fd, &ll, sizeof(ll)) != sizeof(ll)) 226 logit("Could not write %.100s: %.100s", _PATH_LASTLOG, strerror(errno)); 227 close(fd); 228 } 229 } 230 #endif 231 #ifdef SUPPORT_UTMPX 232 /* Construct an utmpx/wtmpx entry. */ 233 memset(&ux, 0, sizeof(ux)); 234 strncpy(ux.ut_line, tty + 5, sizeof(ux.ut_line)); 235 if (*user) { 236 ux.ut_pid = pid; 237 ux.ut_type = USER_PROCESS; 238 ux.ut_tv = tv; 239 strncpy(ux.ut_name, user, sizeof(ux.ut_name)); 240 strncpy(ux.ut_host, host, sizeof(ux.ut_host)); 241 /* XXX: need ut_id, use last 4 char of tty */ 242 if (strlen(tty) > sizeof(ux.ut_id)) { 243 strncpy(ux.ut_id, 244 tty + strlen(tty) - sizeof(ux.ut_id), 245 sizeof(ux.ut_id)); 246 } else 247 strncpy(ux.ut_id, tty, sizeof(ux.ut_id)); 248 /* XXX: It would be better if we had sockaddr_storage here */ 249 if (addrlen > sizeof(ux.ut_ss)) 250 addrlen = sizeof(ux.ut_ss); 251 (void)memcpy(&ux.ut_ss, addr, addrlen); 252 if (pututxline(&ux) == NULL) 253 logit("could not add utmpx line: %.100s", 254 strerror(errno)); 255 /* Update lastlog. */ 256 (void)gettimeofday(&llx.ll_tv, NULL); 257 strncpy(llx.ll_line, tty + 5, sizeof(llx.ll_line)); 258 strncpy(llx.ll_host, host, sizeof(llx.ll_host)); 259 (void)memcpy(&llx.ll_ss, addr, addrlen); 260 if (updlastlogx(_PATH_LASTLOGX, uid, &llx) == -1) 261 logit("Could not update %.100s: %.100s", 262 _PATH_LASTLOGX, strerror(errno)); 263 } else { 264 if ((uxp = getutxline(&ux)) == NULL) 265 logit("could not find utmpx line for %.100s", tty); 266 else { 267 uxp->ut_type = DEAD_PROCESS; 268 uxp->ut_tv = tv; 269 /* XXX: we don't record exit info yet */ 270 if (pututxline(&ux) == NULL) 271 logit("could not replace utmpx line: %.100s", 272 strerror(errno)); 273 } 274 } 275 endutxent(); 276 updwtmpx(_PATH_WTMPX, uxp); 277 #endif 278 } 279 280 /* Records that the user has logged out. */ 281 void 282 record_logout(pid_t pid, const char *tty) 283 { 284 #if defined(SUPPORT_UTMP) || defined(SUPPORT_UTMPX) 285 const char *line = tty + 5; /* /dev/ttyq8 -> ttyq8 */ 286 #endif 287 #ifdef SUPPORT_UTMP 288 if (logout(line)) 289 logwtmp(line, "", ""); 290 #endif 291 #ifdef SUPPORT_UTMPX 292 /* XXX: no exit info yet */ 293 if (logoutx(line, 0, DEAD_PROCESS)) 294 logwtmpx(line, "", "", 0, DEAD_PROCESS); 295 #endif 296 } 297