Home | History | Annotate | Line # | Download | only in dist
      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