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 (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 /* 27 * utmp_update - Update the /var/adm/utmpx file 28 * 29 * As of on28, the utmp interface is obsolete, 30 * so we only handle updating the utmpx file now. 31 * The utmpx routines in libc "simulate" calls 32 * to manipulate utmp entries. 33 * 34 * This program runs set uid root on behalf of 35 * non-privileged user programs. Normal programs cannot 36 * write to /var/adm/utmpx. Non-root callers of pututxline 37 * will invoke this program to write the utmpx entry. 38 */ 39 40 /* 41 * Header files 42 */ 43 #include <stdio.h> 44 #include <sys/param.h> 45 #include <sys/types.h> 46 #include <sys/stat.h> 47 #include <utmpx.h> 48 #include <errno.h> 49 #include <fcntl.h> 50 #include <string.h> 51 #include <stdlib.h> 52 #include <unistd.h> 53 #include <pwd.h> 54 #include <ctype.h> 55 #include <stropts.h> 56 #include <syslog.h> 57 58 /* 59 * Invocation argument definitions 60 */ 61 62 #define UTMPX_NARGS 14 63 64 /* 65 * Return codes 66 */ 67 #define NORMAL_EXIT 0 68 #define BAD_ARGS 1 69 #define PUTUTXLINE_FAILURE 2 70 #define FORK_FAILURE 3 71 #define SETSID_FAILURE 4 72 #define ALREADY_DEAD 5 73 #define ENTRY_NOTFOUND 6 74 #define ILLEGAL_ARGUMENT 7 75 #define DEVICE_ERROR 8 76 77 /* 78 * Sizes 79 */ 80 81 #define MAX_SYSLEN 257 /* From utmpx.h host length + nul */ 82 #define BUF_SIZE 256 83 84 /* 85 * Other defines 86 */ 87 #define ROOT_UID 0 88 /* 89 * Debugging support 90 */ 91 #ifdef DEBUG 92 #define dprintf printf 93 #define dprintf3 printf 94 static void display_args(); 95 #else /* DEBUG */ 96 #define dprintf(x, y) 97 #define dprintf3(w, x, y, z) 98 #endif 99 100 /* 101 * Local functions 102 */ 103 104 static void load_utmpx_struct(struct utmpx *, char **); 105 static void usage(void); 106 static void check_utmpx(struct utmpx *); 107 static int bad_hostname(char *, int); 108 static int hex2bin(unsigned char); 109 110 static int invalid_utmpx(struct utmpx *, struct utmpx *); 111 static int bad_line(char *); 112 static void check_id(char *, char *); 113 114 int 115 main(int argc, char *argv[]) 116 { 117 int devfd, err; 118 struct utmpx *rutmpx; 119 struct utmpx entryx; 120 struct stat stat_arg, stat_db; 121 #ifdef DEBUG 122 int debugger = 1; 123 printf("%d\n", getpid()); 124 /* Uncomment the following for attaching with dbx(1) */ 125 /* while (debugger) ; */ 126 display_args(argc, argv); 127 #endif /* DEBUG */ 128 129 /* 130 * We will always be called by pututxline, so simply 131 * verify the correct number of args 132 */ 133 134 if (argc != UTMPX_NARGS) { 135 usage(); 136 return (BAD_ARGS); 137 } 138 /* 139 * we should never be called by root the code in libc already 140 * updates the file for root so no need to do it here. This 141 * assumption simpilfies the rest of code since we nolonger 142 * have to do special processing for the case when we are called 143 * by root 144 * 145 */ 146 if (getuid() == ROOT_UID) { 147 usage(); 148 return (ILLEGAL_ARGUMENT); 149 } 150 /* 151 * Search for matching entry by line name before put operation 152 * (scan over the whole file using getutxent(3C) to ensure 153 * that the line name is the same. We can not use getutline(3C) 154 * because that will return LOGIN_PROCESS and USER_PROCESS 155 * records. Also check that the entry is for either a dead 156 * process or a current process that is valid (see 157 * invalid_utmpx() for details of validation criteria). 158 * 159 * Match entries using the inode number of the device file. 160 */ 161 load_utmpx_struct(&entryx, argv); 162 check_utmpx(&entryx); 163 if ((devfd = open("/dev", O_RDONLY)) < 0) { 164 usage(); 165 return (DEVICE_ERROR); 166 } 167 168 if (fstatat(devfd, entryx.ut_line, &stat_arg, 0) < 0) { 169 (void) close(devfd); 170 usage(); 171 return (DEVICE_ERROR); 172 } 173 174 err = 0; 175 for (rutmpx = getutxent(); rutmpx != (struct utmpx *)NULL; 176 rutmpx = getutxent()) { 177 178 if ((rutmpx->ut_type != USER_PROCESS) && 179 (rutmpx->ut_type != DEAD_PROCESS)) 180 continue; 181 182 if (fstatat(devfd, rutmpx->ut_line, &stat_db, 0) < 0) 183 continue; 184 185 if (stat_arg.st_ino == stat_db.st_ino && 186 stat_arg.st_dev == stat_db.st_dev) { 187 if (rutmpx->ut_type == USER_PROCESS) 188 err = invalid_utmpx(&entryx, rutmpx); 189 break; 190 } 191 } 192 (void) close(devfd); 193 if (err) { 194 usage(); 195 return (ILLEGAL_ARGUMENT); 196 } 197 198 if (pututxline(&entryx) == (struct utmpx *)NULL) { 199 return (PUTUTXLINE_FAILURE); 200 } 201 return (NORMAL_EXIT); 202 } 203 204 static int 205 hex2bin(unsigned char c) 206 { 207 if ('0' <= c && c <= '9') 208 return (c - '0'); 209 else if ('A' <= c && c <= 'F') 210 return (10 + c - 'A'); 211 else if ('a' <= c && c <= 'f') 212 return (10 + c - 'a'); 213 214 dprintf("Bad hex character: 0x%x\n", c); 215 exit(ILLEGAL_ARGUMENT); 216 /* NOTREACHED */ 217 } 218 219 220 /* 221 * load_utmpx_struct - Load up the utmpx structure with information supplied 222 * as arguments in argv. 223 */ 224 225 static void 226 load_utmpx_struct(struct utmpx *entryx, char *argv[]) 227 { 228 char *user, *id, *line, *pid, *type, *term, *time_usec, 229 *exitstatus, *xtime, *session, *pad, *syslen, *host; 230 int temp, i; 231 unsigned char *cp; 232 233 (void) memset(entryx, 0, sizeof (struct utmpx)); 234 235 user = argv[1]; 236 id = argv[2]; 237 line = argv[3]; 238 pid = argv[4]; 239 type = argv[5]; 240 term = argv[6]; 241 exitstatus = argv[7]; 242 xtime = argv[8]; 243 time_usec = argv[9 ]; 244 session = argv[10]; 245 pad = argv[11]; 246 syslen = argv[12]; 247 host = argv[13]; 248 249 (void) strncpy(entryx->ut_user, user, sizeof (entryx->ut_user)); 250 (void) strncpy(entryx->ut_id, id, sizeof (entryx->ut_id)); 251 (void) strncpy(entryx->ut_line, line, sizeof (entryx->ut_line)); 252 253 (void) sscanf(pid, "%d", &temp); 254 entryx->ut_pid = temp; 255 256 (void) sscanf(type, "%d", &temp); 257 entryx->ut_type = temp; 258 259 (void) sscanf(term, "%d", &temp); 260 entryx->ut_exit.e_termination = temp; 261 262 (void) sscanf(exitstatus, "%d", &temp); 263 entryx->ut_exit.e_exit = temp; 264 /* 265 * Here's where we stamp the exit field of a USER_PROCESS 266 * record so that we know it was written by a normal user. 267 */ 268 269 if (entryx->ut_type == USER_PROCESS) 270 setuserx(*entryx); 271 272 (void) sscanf(xtime, "%d", &temp); 273 entryx->ut_tv.tv_sec = temp; 274 275 (void) sscanf(time_usec, "%d", &temp); 276 entryx->ut_tv.tv_usec = temp; 277 278 (void) sscanf(session, "%d", &temp); 279 entryx->ut_session = temp; 280 281 temp = strlen(pad); 282 cp = (unsigned char *)entryx->pad; 283 for (i = 0; i < temp && (i>>1) < sizeof (entryx->pad); i += 2) 284 cp[i>>1] = hex2bin(pad[i]) << 4 | hex2bin(pad[i+1]); 285 286 (void) sscanf(syslen, "%d", &temp); 287 entryx->ut_syslen = temp; 288 289 (void) strlcpy(entryx->ut_host, host, sizeof (entryx->ut_host)); 290 } 291 292 /* 293 * usage - There's no need to say more. This program isn't supposed to 294 * be executed by normal users directly. 295 */ 296 297 static void 298 usage() 299 { 300 syslog(LOG_ERR, "Wrong number of arguments or invalid user \n"); 301 } 302 303 /* 304 * check_utmpx - Verify the utmpx structure 305 */ 306 307 static void 308 check_utmpx(struct utmpx *entryx) 309 { 310 char buf[BUF_SIZE]; 311 char *line = buf; 312 struct passwd *pwd; 313 int uid; 314 int hostlen; 315 char *user; 316 uid_t ruid = getuid(); 317 318 (void) memset(buf, 0, BUF_SIZE); 319 user = malloc(sizeof (entryx->ut_user) +1); 320 (void) strncpy(user, entryx->ut_user, sizeof (entryx->ut_user)); 321 user[sizeof (entryx->ut_user)] = '\0'; 322 pwd = getpwnam(user); 323 (void) free(user); 324 325 (void) strlcat(strcpy(buf, "/dev/"), entryx->ut_line, sizeof (buf)); 326 327 if (pwd != (struct passwd *)NULL) { 328 uid = pwd->pw_uid; 329 /* 330 * We nolonger permit the UID of the caller to be different 331 * the UID to be written to the utmp file. This was thought 332 * necessary to allow the utmp file to be updated when 333 * logging out from an xterm(1) window after running 334 * exec login. Instead we now rely upon utmpd(1) to update 335 * the utmp file for us. 336 * 337 */ 338 339 if (ruid != uid) { 340 dprintf3("Bad uid: user %s = %d uid = %d \n", 341 entryx->ut_user, uid, getuid()); 342 exit(ILLEGAL_ARGUMENT); 343 } 344 345 } else if (entryx->ut_type != DEAD_PROCESS) { 346 dprintf("Bad user name: %s \n", entryx->ut_user); 347 exit(ILLEGAL_ARGUMENT); 348 } 349 350 /* 351 * Only USER_PROCESS and DEAD_PROCESS entries may be updated 352 */ 353 if (!(entryx->ut_type == USER_PROCESS || 354 entryx->ut_type == DEAD_PROCESS)) { 355 dprintf("Bad type type = %d\n", entryx->ut_type); 356 exit(ILLEGAL_ARGUMENT); 357 } 358 359 /* 360 * Verify that the pid of the entry field is the same pid as our 361 * parent, who should be the guy writing the entry. This is commented 362 * out for now because this restriction is overkill. 363 */ 364 #ifdef VERIFY_PID 365 if (entryx->ut_type == USER_PROCESS && entryx->ut_pid != getppid()) { 366 dprintf("Bad pid = %d\n", entryx->ut_pid); 367 exit(ILLEGAL_ARGUMENT); 368 } 369 #endif /* VERIFY_PID */ 370 371 if (bad_line(line) == 1) { 372 dprintf("Bad line = %s\n", line); 373 exit(ILLEGAL_ARGUMENT); 374 } 375 376 hostlen = strlen(entryx->ut_host) + 1; 377 if (entryx->ut_syslen != hostlen) { 378 dprintf3("Bad syslen of \"%s\" = %d - correcting to %d\n", 379 entryx->ut_host, entryx->ut_syslen, hostlen); 380 entryx->ut_syslen = hostlen; 381 } 382 383 if (bad_hostname(entryx->ut_host, entryx->ut_syslen) == 1) { 384 dprintf("Bad hostname name = %s\n", entryx->ut_host); 385 exit(ILLEGAL_ARGUMENT); 386 } 387 check_id(entryx->ut_id, entryx->ut_line); 388 } 389 390 /* 391 * bad_hostname - Previously returned an error if a non alpha numeric 392 * was in the host field, but now just clears those so 393 * cmdtool entries will work. 394 */ 395 396 static int 397 bad_hostname(char *name, int len) 398 { 399 int i; 400 401 if (len < 0 || len > MAX_SYSLEN) 402 return (1); 403 /* 404 * Scan for non-alpha numerics 405 * Per utmpx.h, len includes the nul character. 406 */ 407 for (i = 0; i < len; i++) 408 if (name[i] != '\0' && isprint(name[i]) == 0) 409 name[i] = ' '; 410 return (0); 411 } 412 413 /* 414 * Workaround until the window system gets fixed. Look for id's with 415 * a '/' in them. That means they are probably from libxview. 416 * Then create a new id that is unique using the last 4 chars in the line. 417 */ 418 419 static void 420 check_id(char *id, char *line) 421 { 422 int i, len; 423 424 if (id[1] == '/' && id[2] == 's' && id[3] == 't') { 425 len = strlen(line); 426 if (len > 0) 427 len--; 428 for (i = 0; i < 4; i++) 429 id[i] = len - i < 0 ? 0 : line[len-i]; 430 } 431 } 432 433 434 /* 435 * The function invalid_utmpx() enforces the requirement that the record 436 * being updating in the utmpx file can not have been created by login(1) 437 * or friends. Also that the id and username of the record to be written match 438 * those found in the utmpx file. We need this both for security and to ensure 439 * that pututxline(3C) will NOT reposition the file pointer in the utmpx file, 440 * so that the record is updated in place. 441 * 442 */ 443 static int 444 invalid_utmpx(struct utmpx *eutmpx, struct utmpx *rutmpx) 445 { 446 #define SUTMPX_ID (sizeof (eutmpx->ut_id)) 447 #define SUTMPX_USER (sizeof (eutmpx->ut_user)) 448 449 return (!nonuserx(*rutmpx) || 450 strncmp(eutmpx->ut_id, rutmpx->ut_id, SUTMPX_ID) != 0 || 451 strncmp(eutmpx->ut_user, rutmpx->ut_user, SUTMPX_USER) != 0); 452 } 453 454 static int 455 bad_line(char *line) 456 { 457 struct stat statbuf; 458 int fd; 459 460 /* 461 * The line field must be a device file that we can write to, 462 * it should live under /dev which is enforced by requiring 463 * its name not to contain "../" and opening it as the user for 464 * writing. 465 */ 466 if (strstr(line, "../") != 0) { 467 dprintf("Bad line = %s\n", line); 468 return (1); 469 } 470 471 /* 472 * It has to be a tty. It can't be a bogus file, e.g. ../tmp/bogus. 473 */ 474 if (seteuid(getuid()) != 0) 475 return (1); 476 477 /* 478 * We need to open the line without blocking so that it does not hang 479 */ 480 if ((fd = open(line, O_WRONLY|O_NOCTTY|O_NONBLOCK)) == -1) { 481 dprintf("Bad line (Can't open/write) = %s\n", line); 482 return (1); 483 } 484 485 /* 486 * Check that fd is a tty, if this fails all is not lost see below 487 */ 488 if (isatty(fd) == 1) { 489 /* 490 * It really is a tty, so return success 491 */ 492 (void) close(fd); 493 if (seteuid(ROOT_UID) != 0) 494 return (1); 495 return (0); 496 } 497 498 /* 499 * Check that the line refers to a character 500 * special device. 501 */ 502 if ((fstat(fd, &statbuf) < 0) || !S_ISCHR(statbuf.st_mode)) { 503 dprintf("Bad line (fstat failed) (Not S_IFCHR) = %s\n", line); 504 (void) close(fd); 505 return (1); 506 } 507 508 /* 509 * Check that the line refers to a streams device 510 */ 511 if (isastream(fd) != 1) { 512 dprintf("Bad line (isastream failed) = %s\n", line); 513 (void) close(fd); 514 return (1); 515 } 516 517 /* 518 * if isatty(3C) failed above we assume that the ptem module has 519 * been popped already and that caused the failure, so we push it 520 * and try again 521 */ 522 if (ioctl(fd, I_PUSH, "ptem") == -1) { 523 dprintf("Bad line (I_PUSH of \"ptem\" failed) = %s\n", line); 524 (void) close(fd); 525 return (1); 526 } 527 528 if (isatty(fd) != 1) { 529 dprintf("Bad line (isatty failed) = %s\n", line); 530 (void) close(fd); 531 return (1); 532 } 533 534 if (ioctl(fd, I_POP, 0) == -1) { 535 dprintf("Bad line (I_POP of \"ptem\" failed) = %s\n", line); 536 (void) close(fd); 537 return (1); 538 } 539 540 (void) close(fd); 541 542 if (seteuid(ROOT_UID) != 0) 543 return (1); 544 545 return (0); 546 547 } 548 549 #ifdef DEBUG 550 551 /* 552 * display_args - This code prints out invocation arguments 553 * This is helpful since the program is called with 554 * up to 15 argumments. 555 */ 556 557 static void 558 display_args(argc, argv) 559 int argc; 560 char **argv; 561 { 562 int i = 0; 563 564 while (argc--) { 565 printf("Argument #%d = %s\n", i, argv[i]); 566 i++; 567 } 568 } 569 570 fputmpx(struct utmpx *rutmpx) 571 { 572 printf("ut_user = \"%-32.32s\" \n", rutmpx->ut_user); 573 printf("ut_id = \"%-4.4s\" \n", rutmpx->ut_id); 574 printf("ut_line = \"%-32.32s\" \n", rutmpx->ut_line); 575 printf("ut_pid = \"%d\" \n", rutmpx->ut_pid); 576 printf("ut_type = \"%d\" \n", rutmpx->ut_type); 577 printf("ut_exit.e_termination = \"%d\" \n", 578 rutmpx->ut_exit.e_termination); 579 printf("ut_exit.e_exit = \"%d\" \n", rutmpx->ut_exit.e_exit); 580 } 581 582 #endif /* DEBUG */ 583