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 */ 26 27 /* Copyright (c) 1983-1989 AT&T */ 28 /* All Rights Reserved */ 29 30 /* 31 * Portions of this source code were derived from Berkeley 4.3 BSD 32 * under license from the Regents of the University of California. 33 */ 34 35 #pragma ident "%Z%%M% %I% %E% SMI" 36 37 #include <sys/types.h> 38 #include <sys/ioctl.h> 39 #include <sys/param.h> 40 #include <sys/socket.h> 41 #include <sys/time.h> 42 #include <sys/filio.h> 43 44 #include <netinet/in.h> 45 #include <arpa/inet.h> 46 47 #include <unistd.h> 48 #include <string.h> 49 #include <stdlib.h> 50 #include <stdio.h> 51 #include <stdarg.h> 52 #include <errno.h> 53 #include <pwd.h> 54 #include <grp.h> 55 #include <signal.h> 56 #include <netdb.h> 57 #include <syslog.h> 58 #include <security/pam_appl.h> 59 60 #ifdef SYSV 61 #include <shadow.h> 62 #endif /* SYSV */ 63 64 #ifndef NCARGS 65 #define NCARGS 5120 66 #endif /* NCARGS */ 67 68 #ifdef SYSV 69 #define rindex strrchr 70 #define killpg(a, b) kill(-(a), (b)) 71 #else 72 char *sprintf(); 73 #endif /* SYSV */ 74 75 #define MAXFD(A, B) ((A) > (B) ? (A) : (B)) 76 77 static void error(char *fmt, ...); 78 static void doit(int f, struct sockaddr_storage *fromp); 79 static void getstr(char *buf, int cnt, char *err); 80 81 static int legalenvvar(char *s); 82 83 /* Function decls. for functions not in any header file. (Grrrr.) */ 84 extern int audit_rexecd_setup(void); 85 extern int audit_rexecd_success(char *, char *, char *); 86 extern int audit_rexecd_fail(char *, char *, char *, char *); 87 extern int audit_settid(int); /* set termnal ID */ 88 89 /* PAM conversation function */ 90 static int rexec_conv(int, struct pam_message **, 91 struct pam_response **, void *); 92 93 static pam_handle_t *pamh; /* authentication handle */ 94 static struct pam_conv conv = { 95 rexec_conv, 96 NULL 97 }; 98 99 /* 100 * remote execute server: 101 * username\0 102 * password\0 103 * command\0 104 * data 105 * 106 * in.rexecd has been modified to run as the user invoking it. Hence there is no 107 * need to limit any privileges. 108 */ 109 /*ARGSUSED*/ 110 int 111 main(int argc, char **argv) 112 { 113 struct sockaddr_storage from; 114 socklen_t fromlen; 115 116 openlog("rexec", LOG_PID | LOG_ODELAY, LOG_DAEMON); 117 (void) audit_rexecd_setup(); /* BSM */ 118 fromlen = (socklen_t)sizeof (from); 119 if (getpeername(0, (struct sockaddr *)&from, &fromlen) < 0) { 120 (void) fprintf(stderr, "%s: ", argv[0]); 121 perror("getpeername"); 122 exit(1); 123 } 124 125 if (audit_settid(0) != 0) { 126 perror("settid"); 127 exit(1); 128 } 129 130 doit(0, &from); 131 return (0); 132 } 133 134 static char username[20] = "USER="; 135 static char homedir[64] = "HOME="; 136 static char shell[64] = "SHELL="; 137 138 static char *envinit[] = 139 #ifdef SYSV 140 {homedir, shell, (char *)0, username, 141 (char *)0, (char *)0, (char *)0, (char *)0, 142 (char *)0, (char *)0, (char *)0, (char *)0, 143 (char *)0, (char *)0, (char *)0, (char *)0, 144 (char *)0, (char *)0, (char *)0, (char *)0, 145 (char *)0}; 146 #define ENVINIT_PATH 2 /* position of PATH in envinit[] */ 147 #define PAM_ENV_ELIM 16 /* max PAM environment variables */ 148 149 /* 150 * See PSARC opinion 1992/025 151 */ 152 static char userpath[] = "PATH=/usr/bin:"; 153 static char rootpath[] = "PATH=/usr/sbin:/usr/bin"; 154 #else 155 {homedir, shell, "PATH=:/usr/ucb:/bin:/usr/bin", username, 0}; 156 #endif /* SYSV */ 157 158 static struct sockaddr_storage asin; 159 static char pass[16]; 160 161 static void 162 doit(int f, struct sockaddr_storage *fromp) 163 { 164 char cmdbuf[NCARGS+1], *cp; 165 char user[16]; 166 char hostname [MAXHOSTNAMELEN + 1]; 167 struct passwd *pwd; 168 int s; 169 ushort_t port; 170 pid_t pid; 171 int pv[2], cc; 172 fd_set readfrom, ready; 173 char buf[BUFSIZ], sig; 174 int one = 1; 175 int idx = 0, end_env = 0; 176 char **pam_env; 177 int status = PAM_AUTH_ERR; 178 char abuf[INET6_ADDRSTRLEN]; 179 struct in_addr v4dst; 180 socklen_t fromplen; 181 struct sockaddr_in *sin; 182 struct sockaddr_in6 *sin6; 183 184 (void) signal(SIGINT, SIG_DFL); 185 (void) signal(SIGQUIT, SIG_DFL); 186 (void) signal(SIGTERM, SIG_DFL); 187 #ifdef DEBUG 188 { 189 int t = open("/dev/tty", 2); 190 if (t >= 0) { 191 #ifdef SYSV 192 (void) setsid(); 193 #else 194 (void) ioctl(t, TIOCNOTTY, (char *)0); 195 #endif /* SYSV */ 196 (void) close(t); 197 } 198 } 199 #endif 200 if (fromp->ss_family == AF_INET) { 201 sin = (struct sockaddr_in *)fromp; 202 fromplen = sizeof (struct sockaddr_in); 203 asin.ss_family = AF_INET; /* used for bind */ 204 } else if (fromp->ss_family == AF_INET6) { 205 sin6 = (struct sockaddr_in6 *)fromp; 206 fromplen = sizeof (struct sockaddr_in6); 207 asin.ss_family = AF_INET6; /* used for bind */ 208 } else { 209 syslog(LOG_ERR, "unknown address family %d\n", 210 fromp->ss_family); 211 exit(1); 212 } 213 /* 214 * store common info. for audit record 215 */ 216 217 if (getnameinfo((const struct sockaddr *) fromp, fromplen, hostname, 218 sizeof (hostname), NULL, 0, 0) != 0) { 219 if (fromp->ss_family == AF_INET6) { 220 if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { 221 struct in_addr ipv4_addr; 222 223 IN6_V4MAPPED_TO_INADDR(&sin6->sin6_addr, 224 &ipv4_addr); 225 inet_ntop(AF_INET, &ipv4_addr, abuf, 226 sizeof (abuf)); 227 } else { 228 inet_ntop(AF_INET6, &sin6->sin6_addr, 229 abuf, sizeof (abuf)); 230 } 231 } else if (fromp->ss_family == AF_INET) { 232 inet_ntop(AF_INET, &sin->sin_addr, 233 abuf, sizeof (abuf)); 234 } 235 (void) strncpy(hostname, abuf, sizeof (hostname)); 236 } 237 (void) dup2(f, 0); 238 (void) dup2(f, 1); 239 (void) dup2(f, 2); 240 (void) alarm(60); 241 port = 0; 242 for (;;) { 243 char c; 244 if (read(f, &c, 1) != 1) 245 exit(1); 246 if (c == 0) 247 break; 248 port = port * 10 + c - '0'; 249 } 250 (void) alarm(0); 251 if (port != 0) { 252 s = socket(fromp->ss_family, SOCK_STREAM, 0); 253 if (s < 0) 254 exit(1); 255 if (bind(s, (struct sockaddr *)&asin, fromplen) < 0) 256 exit(1); 257 (void) alarm(60); 258 if (fromp->ss_family == AF_INET) { 259 sin->sin_port = htons((ushort_t)port); 260 } else if (fromp->ss_family == AF_INET6) { 261 sin6->sin6_port = htons((ushort_t)port); 262 } 263 if (connect(s, (struct sockaddr *)fromp, fromplen) < 0) 264 exit(1); 265 (void) alarm(0); 266 } 267 getstr(user, sizeof (user), "username"); 268 getstr(pass, sizeof (pass), "password"); 269 getstr(cmdbuf, sizeof (cmdbuf), "command"); 270 271 setpwent(); 272 pwd = getpwnam(user); 273 if (pwd == NULL) { 274 (void) audit_rexecd_fail("Login incorrect", hostname, user, 275 cmdbuf); /* BSM */ 276 error("Login incorrect.\n"); 277 exit(1); 278 } 279 endpwent(); 280 281 if (pam_start("rexec", user, &conv, &pamh) != PAM_SUCCESS) { 282 exit(1); 283 } 284 if (pam_set_item(pamh, PAM_RHOST, hostname) != PAM_SUCCESS) { 285 exit(1); 286 } 287 288 if ((status = pam_authenticate(pamh, 0)) != PAM_SUCCESS) { 289 switch (status) { 290 case PAM_USER_UNKNOWN: 291 (void) audit_rexecd_fail("Login incorrect", hostname, 292 user, cmdbuf); /* BSM */ 293 error("Login incorrect.\n"); 294 break; 295 default: 296 (void) audit_rexecd_fail("Password incorrect", hostname, 297 user, cmdbuf); /* BSM */ 298 error("Password incorrect.\n"); 299 } 300 pam_end(pamh, status); 301 exit(1); 302 } 303 if ((status = pam_acct_mgmt(pamh, 0)) != PAM_SUCCESS) { 304 (void) audit_rexecd_fail("Account or Password Expired", 305 hostname, user, cmdbuf); 306 switch (status) { 307 case PAM_NEW_AUTHTOK_REQD: 308 error("Password Expired.\n"); 309 break; 310 case PAM_PERM_DENIED: 311 error("Account Expired.\n"); 312 break; 313 case PAM_AUTHTOK_EXPIRED: 314 error("Password Expired.\n"); 315 break; 316 default: 317 error("Login incorrect.\n"); 318 break; 319 } 320 pam_end(pamh, status); 321 exit(1); 322 } 323 324 (void) write(2, "\0", 1); 325 326 if (setgid((gid_t)pwd->pw_gid) < 0) { 327 (void) audit_rexecd_fail("Can't setgid", hostname, 328 user, cmdbuf); /* BSM */ 329 error("setgid"); 330 pam_end(pamh, PAM_ABORT); 331 exit(1); 332 } 333 (void) initgroups(pwd->pw_name, pwd->pw_gid); 334 335 if ((status = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS) { 336 (void) audit_rexecd_fail("Unable to establish credentials", 337 hostname, user, cmdbuf); /* BSM */ 338 error("Unable to establish credentials.\n"); 339 pam_end(pamh, PAM_SUCCESS); 340 } 341 342 (void) audit_rexecd_success(hostname, user, cmdbuf); /* BSM */ 343 344 if (setuid((uid_t)pwd->pw_uid) < 0) { 345 (void) audit_rexecd_fail("Can't setuid", hostname, 346 user, cmdbuf); /* BSM */ 347 error("setuid"); 348 pam_end(pamh, PAM_ABORT); 349 exit(1); 350 } 351 352 353 if (port) { 354 (void) pipe(pv); 355 pid = fork(); 356 if (pid == (pid_t)-1) { 357 error("Try again.\n"); 358 pam_end(pamh, PAM_ABORT); 359 exit(1); 360 } 361 if (pid) { 362 /* 363 * since the daemon is running as the user no need 364 * to prune privileges. 365 */ 366 (void) close(0); (void) close(1); (void) close(2); 367 (void) close(f); (void) close(pv[1]); 368 FD_ZERO(&readfrom); 369 FD_SET(s, &readfrom); 370 FD_SET(pv[0], &readfrom); 371 (void) ioctl(pv[0], FIONBIO, (char *)&one); 372 /* should set s nbio! */ 373 do { 374 ready = readfrom; 375 if (select(MAXFD(s, pv[0])+1, &ready, NULL, 376 NULL, NULL) < 0) { 377 perror("select:"); 378 exit(1); 379 } 380 if (FD_ISSET(s, &ready)) { 381 if (read(s, &sig, 1) <= 0) 382 FD_CLR(s, &readfrom); 383 else 384 (void) killpg(pid, sig); 385 } 386 if (FD_ISSET(pv[0], &ready)) { 387 cc = read(pv[0], buf, sizeof (buf)); 388 if (cc <= 0) { 389 (void) shutdown(s, 1+1); 390 FD_CLR(pv[0], &readfrom); 391 } else 392 (void) write(s, buf, cc); 393 } 394 } while (FD_ISSET(s, &readfrom) || 395 FD_ISSET(pv[0], &readfrom)); 396 exit(0); 397 } 398 /* setpgrp(0, getpid()); */ 399 (void) setsid(); /* Should be the same as above. */ 400 (void) close(s); (void)close(pv[0]); 401 (void) dup2(pv[1], 2); 402 } 403 404 if (*pwd->pw_shell == '\0') 405 pwd->pw_shell = "/bin/sh"; 406 if (f > 2) 407 (void) close(f); 408 /* Change directory only after becoming the appropriate user. */ 409 if (chdir(pwd->pw_dir) < 0) { 410 error("No remote directory.\n"); 411 pam_end(pamh, PAM_ABORT); 412 exit(1); 413 } 414 #ifdef SYSV 415 if (pwd->pw_uid) 416 envinit[ENVINIT_PATH] = userpath; 417 else 418 envinit[ENVINIT_PATH] = rootpath; 419 #endif /* SYSV */ 420 (void) strncat(homedir, pwd->pw_dir, sizeof (homedir) - 6); 421 (void) strncat(shell, pwd->pw_shell, sizeof (shell) - 7); 422 (void) strncat(username, pwd->pw_name, sizeof (username) - 6); 423 424 /* 425 * add PAM environment variables set by modules 426 * -- only allowed 16 (PAM_ENV_ELIM) 427 * -- check to see if the environment variable is legal 428 */ 429 for (end_env = 0; envinit[end_env] != 0; end_env++) 430 ; 431 if ((pam_env = pam_getenvlist(pamh)) != 0) { 432 while (pam_env[idx] != 0) { 433 if (idx < PAM_ENV_ELIM && 434 legalenvvar(pam_env[idx])) { 435 envinit[end_env + idx] = pam_env[idx]; 436 } 437 idx++; 438 } 439 } 440 441 pam_end(pamh, PAM_SUCCESS); 442 443 cp = rindex(pwd->pw_shell, '/'); 444 if (cp) 445 cp++; 446 else 447 cp = pwd->pw_shell; 448 (void) execle(pwd->pw_shell, cp, "-c", cmdbuf, (char *)0, envinit); 449 perror(pwd->pw_shell); 450 exit(1); 451 } 452 453 static void 454 getstr(char *buf, int cnt, char *err) 455 { 456 char c; 457 458 do { 459 if (read(0, &c, 1) != 1) 460 exit(1); 461 *buf++ = c; 462 if (--cnt == 0) { 463 error("%s too long\n", err); 464 exit(1); 465 } 466 } while (c != 0); 467 } 468 469 static void 470 error(char *fmt, ...) 471 { 472 va_list ap; 473 char buf[BUFSIZ]; 474 475 buf[0] = 1; 476 va_start(ap, fmt); 477 (void) vsprintf(buf+1, fmt, ap); 478 va_end(ap); 479 (void) write(2, buf, strlen(buf)); 480 } 481 482 static char *illegal[] = { 483 "SHELL=", 484 "HOME=", 485 "LOGNAME=", 486 #ifndef NO_MAIL 487 "MAIL=", 488 #endif 489 "CDPATH=", 490 "IFS=", 491 "PATH=", 492 "USER=", 493 0 494 }; 495 496 /* 497 * legalenvvar - can PAM insert this environmental variable? 498 */ 499 500 static int 501 legalenvvar(char *s) 502 { 503 register char **p; 504 505 for (p = illegal; *p; p++) 506 if (strncmp(s, *p, strlen(*p)) == 0) 507 return (0); 508 509 if (s[0] == 'L' && s[1] == 'D' && s[2] == '_') 510 return (0); 511 512 return (1); 513 } 514 515 /* 516 * rexec_conv - This is the conv (conversation) function called from 517 * a PAM authentication module to print error messages 518 * or garner information from the user. 519 */ 520 521 /* ARGSUSED3 */ 522 static int 523 rexec_conv(int num_msg, struct pam_message **msg, 524 struct pam_response **response, void *appdata_ptr) 525 { 526 struct pam_message *m; 527 struct pam_response *r; 528 int i; 529 530 if (num_msg <= 0) 531 return (PAM_CONV_ERR); 532 533 *response = calloc(num_msg, sizeof (struct pam_response)); 534 if (*response == NULL) 535 return (PAM_BUF_ERR); 536 537 m = *msg; 538 r = *response; 539 540 if (m->msg_style == PAM_PROMPT_ECHO_OFF) { 541 if (pass[0] != '\0') { 542 r->resp = strdup(pass); 543 if (r->resp == NULL) { 544 /* free responses */ 545 r = *response; 546 for (i = 0; i < num_msg; i++, r++) { 547 if (r->resp) 548 free(r->resp); 549 } 550 free(*response); 551 *response = NULL; 552 return (PAM_BUF_ERR); 553 } 554 } 555 } 556 557 return (PAM_SUCCESS); 558 } 559