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