login_skey.c revision 1.30
1/* $OpenBSD: login_skey.c,v 1.30 2023/03/08 04:43:05 guenther Exp $ */ 2 3/* 4 * Copyright (c) 2000, 2001, 2004 Todd C. Miller <millert@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19#include <sys/socket.h> 20#include <sys/stat.h> 21#include <sys/time.h> 22#include <sys/resource.h> 23 24#include <ctype.h> 25#include <errno.h> 26#include <fcntl.h> 27#include <paths.h> 28#include <pwd.h> 29#include <readpassphrase.h> 30#include <signal.h> 31#include <stdio.h> 32#include <stdlib.h> 33#include <string.h> 34#include <syslog.h> 35#include <unistd.h> 36#include <limits.h> 37#include <err.h> 38 39#include <login_cap.h> 40#include <bsd_auth.h> 41#include <skey.h> 42 43#define MODE_LOGIN 0 44#define MODE_CHALLENGE 1 45#define MODE_RESPONSE 2 46 47void quit(int); 48void send_fd(int); 49void suspend(int); 50 51volatile sig_atomic_t resumed; 52struct skey skey; 53 54int 55main(int argc, char *argv[]) 56{ 57 FILE *back = NULL; 58 char *user = NULL, *cp, *ep; 59 char challenge[SKEY_MAX_CHALLENGE+17], response[SKEY_MAX_PW_LEN+1]; 60 const char *errstr; 61 int ch, fd = -1, haskey = 0, mode = MODE_LOGIN; 62 63 (void)signal(SIGINT, quit); 64 (void)signal(SIGQUIT, quit); 65 (void)signal(SIGALRM, quit); 66 (void)signal(SIGTSTP, suspend); 67 (void)setpriority(PRIO_PROCESS, 0, 0); 68 69 if (pledge("stdio rpath wpath flock sendfd proc tty", NULL) == -1) { 70 syslog(LOG_AUTH|LOG_ERR, "pledge: %m"); 71 exit(1); 72 } 73 74 openlog(NULL, LOG_ODELAY, LOG_AUTH); 75 76 while ((ch = getopt(argc, argv, "ds:v:")) != -1) { 77 switch (ch) { 78 case 'd': 79 back = stdout; 80 break; 81 case 's': /* service */ 82 if (strcmp(optarg, "login") == 0) 83 mode = MODE_LOGIN; 84 else if (strcmp(optarg, "challenge") == 0) 85 mode = MODE_CHALLENGE; 86 else if (strcmp(optarg, "response") == 0) 87 mode = MODE_RESPONSE; 88 else { 89 syslog(LOG_ERR, "%s: invalid service", optarg); 90 exit(1); 91 } 92 break; 93 case 'v': 94 if (strncmp(optarg, "fd=", 3) == 0) { 95 fd = strtonum(optarg + 3, 0, INT_MAX, &errstr); 96 if (errstr != NULL) { 97 syslog(LOG_ERR, "fd is %s: %s", 98 errstr, optarg + 3); 99 fd = -1; 100 } 101 } 102 /* silently ignore unsupported variables */ 103 break; 104 default: 105 syslog(LOG_ERR, "usage error"); 106 exit(1); 107 } 108 } 109 argc -= optind; 110 argv += optind; 111 112 switch (argc) { 113 case 2: /* silently ignore class */ 114 case 1: 115 user = *argv; 116 break; 117 default: 118 syslog(LOG_ERR, "usage error"); 119 exit(1); 120 } 121 122 if (back == NULL && (back = fdopen(3, "r+")) == NULL) { 123 syslog(LOG_ERR, "reopening back channel: %m"); 124 exit(1); 125 } 126 127 /* 128 * Note: our skeychallenge2() will always fill in the challenge, 129 * even if it has to create a fake one. 130 */ 131 switch (mode) { 132 case MODE_LOGIN: 133 haskey = (skeychallenge2(fd, &skey, user, challenge) == 0); 134 strlcat(challenge, "\nS/Key Password:", sizeof(challenge)); 135 136 /* time out getting passphrase after 2 minutes to avoid a DoS */ 137 if (haskey) 138 alarm(120); 139 resumed = 0; 140 if (!readpassphrase(challenge, response, sizeof(response), 0)) 141 exit(1); 142 if (response[0] == '\0') 143 readpassphrase("S/Key Password [echo on]: ", 144 response, sizeof(response), RPP_ECHO_ON); 145 alarm(0); 146 if (resumed) { 147 /* 148 * We were suspended by the user. Our lock is 149 * no longer valid so we must regain it so 150 * an attacker cannot do a partial guess of 151 * an S/Key response already in progress. 152 */ 153 haskey = (skeylookup(&skey, user) == 0); 154 } 155 break; 156 157 case MODE_CHALLENGE: 158 haskey = (skeychallenge2(fd, &skey, user, challenge) == 0); 159 strlcat(challenge, "\nS/Key Password:", sizeof(challenge)); 160 cp = auth_mkvalue(challenge); 161 if (cp == NULL) { 162 (void)fprintf(back, BI_VALUE " errormsg %s\n", 163 "unable to allocate memory"); 164 (void)fprintf(back, BI_REJECT "\n"); 165 exit(1); 166 } 167 fprintf(back, BI_VALUE " challenge %s\n", cp); 168 fprintf(back, BI_CHALLENGE "\n"); 169 if (haskey) { 170 fprintf(back, BI_FDPASS "\n"); 171 fflush(back); 172 send_fd(fileno(back)); 173 } 174 exit(0); 175 176 case MODE_RESPONSE: 177 /* read challenge */ 178 mode = -1; 179 cp = challenge; 180 ep = challenge + sizeof(challenge); 181 while (cp < ep && read(fileno(back), cp, 1) == 1) { 182 if (*cp++ == '\0') { 183 mode = MODE_CHALLENGE; 184 break; 185 } 186 } 187 if (mode != MODE_CHALLENGE) { 188 syslog(LOG_ERR, 189 "protocol error: bad/missing challenge"); 190 exit(1); 191 } 192 193 /* read response */ 194 cp = response; 195 ep = response + sizeof(response); 196 while (cp < ep && read(fileno(back), cp, 1) == 1) { 197 if (*cp++ == '\0') { 198 mode = MODE_RESPONSE; 199 break; 200 } 201 } 202 if (mode != MODE_RESPONSE) { 203 syslog(LOG_ERR, 204 "protocol error: bad/missing response"); 205 exit(1); 206 } 207 208 /* 209 * Since the entry is locked we do not need to compare 210 * the passed in challenge to the S/Key database but 211 * maybe we should anyway? 212 */ 213 haskey = (skeychallenge2(fd, &skey, user, challenge) == 0); 214 break; 215 } 216 217 /* 218 * Ignore keyboard interrupt/suspend during database update. 219 */ 220 signal(SIGINT, SIG_IGN); 221 signal(SIGQUIT, SIG_IGN); 222 signal(SIGTSTP, SIG_IGN); 223 224 if (haskey && skeyverify(&skey, response) == 0) { 225 if (mode == MODE_LOGIN) { 226 if (skey.n <= 1) 227 printf("Warning! You MUST change your " 228 "S/Key password now!\n"); 229 else if (skey.n < 5) 230 printf("Warning! Change S/Key password soon\n"); 231 } 232 fprintf(back, BI_AUTH "\n"); 233 fprintf(back, BI_SECURE "\n"); 234 exit(0); 235 } 236 fprintf(back, BI_REJECT "\n"); 237 exit(1); 238} 239 240void 241quit(int signo) 242{ 243 244 _exit(1); 245} 246 247void 248suspend(int signo) 249{ 250 sigset_t nset; 251 int save_errno = errno; 252 253 /* 254 * Unlock the skey record so we don't sleep holding the lock. 255 * Unblock SIGTSTP, set it to the default action and then 256 * resend it so we are suspended properly. 257 * When we resume, reblock SIGTSTP, reset the signal handler, 258 * set a flag and restore errno. 259 */ 260 alarm(0); 261 skey_unlock(&skey); 262 (void)signal(signo, SIG_DFL); 263 (void)sigemptyset(&nset); 264 (void)sigaddset(&nset, signo); 265 (void)sigprocmask(SIG_UNBLOCK, &nset, NULL); 266 (void)kill(getpid(), signo); 267 (void)sigprocmask(SIG_BLOCK, &nset, NULL); 268 (void)signal(signo, suspend); 269 resumed = 1; 270 errno = save_errno; 271} 272 273void 274send_fd(int sock) 275{ 276 struct msghdr msg; 277 struct cmsghdr *cmp; 278 union { 279 struct cmsghdr hdr; 280 char buf[CMSG_SPACE(sizeof(int))]; 281 } cmsgbuf; 282 283 memset(&msg, 0, sizeof(msg)); 284 msg.msg_control = &cmsgbuf.buf; 285 msg.msg_controllen = sizeof(cmsgbuf.buf); 286 287 cmp = CMSG_FIRSTHDR(&msg); 288 cmp->cmsg_len = CMSG_LEN(sizeof(int)); 289 cmp->cmsg_level = SOL_SOCKET; 290 cmp->cmsg_type = SCM_RIGHTS; 291 292 *(int *)CMSG_DATA(cmp) = fileno(skey.keyfile); 293 294 if (sendmsg(sock, &msg, 0) == -1) 295 syslog(LOG_ERR, "sendmsg: %m"); 296} 297