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
main(int argc,char * argv[])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
hex2bin(unsigned char c)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
load_utmpx_struct(struct utmpx * entryx,char * argv[])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
usage()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
check_utmpx(struct utmpx * entryx)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
bad_hostname(char * name,int len)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
check_id(char * id,char * line)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
invalid_utmpx(struct utmpx * eutmpx,struct utmpx * rutmpx)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
bad_line(char * line)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
display_args(argc,argv)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 
fputmpx(struct utmpx * rutmpx)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