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.
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.
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]
22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
27 * utmp_update - Update the /var/adm/utmpx file
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.
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.
44 #include <sys/param.h>
45 #include <sys/types.h>
59 * Invocation argument definitions
62 #define UTMPX_NARGS 14
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
81 #define MAX_SYSLEN 257 /* From utmpx.h host length + nul */
92 #define dprintf printf
93 #define dprintf3 printf
94 static void display_args();
97 #define dprintf3(w, x, y, z)
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);
110 static int invalid_utmpx(struct utmpx
*, struct utmpx
*);
111 static int bad_line(char *);
112 static void check_id(char *, char *);
115 main(int argc
, char *argv
[])
118 struct utmpx
*rutmpx
;
120 struct stat stat_arg
, stat_db
;
123 printf("%d\n", getpid());
124 /* Uncomment the following for attaching with dbx(1) */
125 /* while (debugger) ; */
126 display_args(argc
, argv
);
130 * We will always be called by pututxline, so simply
131 * verify the correct number of args
134 if (argc
!= UTMPX_NARGS
) {
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
146 if (getuid() == ROOT_UID
) {
148 return (ILLEGAL_ARGUMENT
);
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).
159 * Match entries using the inode number of the device file.
161 load_utmpx_struct(&entryx
, argv
);
162 check_utmpx(&entryx
);
163 if ((devfd
= open("/dev", O_RDONLY
)) < 0) {
165 return (DEVICE_ERROR
);
168 if (fstatat(devfd
, entryx
.ut_line
, &stat_arg
, 0) < 0) {
171 return (DEVICE_ERROR
);
175 for (rutmpx
= getutxent(); rutmpx
!= (struct utmpx
*)NULL
;
176 rutmpx
= getutxent()) {
178 if ((rutmpx
->ut_type
!= USER_PROCESS
) &&
179 (rutmpx
->ut_type
!= DEAD_PROCESS
))
182 if (fstatat(devfd
, rutmpx
->ut_line
, &stat_db
, 0) < 0)
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
);
195 return (ILLEGAL_ARGUMENT
);
198 if (pututxline(&entryx
) == (struct utmpx
*)NULL
) {
199 return (PUTUTXLINE_FAILURE
);
201 return (NORMAL_EXIT
);
205 hex2bin(unsigned char c
)
207 if ('0' <= c
&& c
<= '9')
209 else if ('A' <= c
&& c
<= 'F')
210 return (10 + c
- 'A');
211 else if ('a' <= c
&& c
<= 'f')
212 return (10 + c
- 'a');
214 dprintf("Bad hex character: 0x%x\n", c
);
215 exit(ILLEGAL_ARGUMENT
);
221 * load_utmpx_struct - Load up the utmpx structure with information supplied
222 * as arguments in argv.
226 load_utmpx_struct(struct utmpx
*entryx
, char *argv
[])
228 char *user
, *id
, *line
, *pid
, *type
, *term
, *time_usec
,
229 *exitstatus
, *xtime
, *session
, *pad
, *syslen
, *host
;
233 (void) memset(entryx
, 0, sizeof (struct utmpx
));
241 exitstatus
= argv
[7];
243 time_usec
= argv
[9 ];
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
));
253 (void) sscanf(pid
, "%d", &temp
);
254 entryx
->ut_pid
= temp
;
256 (void) sscanf(type
, "%d", &temp
);
257 entryx
->ut_type
= temp
;
259 (void) sscanf(term
, "%d", &temp
);
260 entryx
->ut_exit
.e_termination
= temp
;
262 (void) sscanf(exitstatus
, "%d", &temp
);
263 entryx
->ut_exit
.e_exit
= temp
;
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.
269 if (entryx
->ut_type
== USER_PROCESS
)
272 (void) sscanf(xtime
, "%d", &temp
);
273 entryx
->ut_tv
.tv_sec
= temp
;
275 (void) sscanf(time_usec
, "%d", &temp
);
276 entryx
->ut_tv
.tv_usec
= temp
;
278 (void) sscanf(session
, "%d", &temp
);
279 entryx
->ut_session
= temp
;
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]);
286 (void) sscanf(syslen
, "%d", &temp
);
287 entryx
->ut_syslen
= temp
;
289 (void) strlcpy(entryx
->ut_host
, host
, sizeof (entryx
->ut_host
));
293 * usage - There's no need to say more. This program isn't supposed to
294 * be executed by normal users directly.
300 syslog(LOG_ERR
, "Wrong number of arguments or invalid user \n");
304 * check_utmpx - Verify the utmpx structure
308 check_utmpx(struct utmpx
*entryx
)
316 uid_t ruid
= getuid();
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
);
325 (void) strlcat(strcpy(buf
, "/dev/"), entryx
->ut_line
, sizeof (buf
));
327 if (pwd
!= (struct passwd
*)NULL
) {
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.
340 dprintf3("Bad uid: user %s = %d uid = %d \n",
341 entryx
->ut_user
, uid
, getuid());
342 exit(ILLEGAL_ARGUMENT
);
345 } else if (entryx
->ut_type
!= DEAD_PROCESS
) {
346 dprintf("Bad user name: %s \n", entryx
->ut_user
);
347 exit(ILLEGAL_ARGUMENT
);
351 * Only USER_PROCESS and DEAD_PROCESS entries may be updated
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
);
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.
365 if (entryx
->ut_type
== USER_PROCESS
&& entryx
->ut_pid
!= getppid()) {
366 dprintf("Bad pid = %d\n", entryx
->ut_pid
);
367 exit(ILLEGAL_ARGUMENT
);
369 #endif /* VERIFY_PID */
371 if (bad_line(line
) == 1) {
372 dprintf("Bad line = %s\n", line
);
373 exit(ILLEGAL_ARGUMENT
);
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
;
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
);
387 check_id(entryx
->ut_id
, entryx
->ut_line
);
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.
397 bad_hostname(char *name
, int len
)
401 if (len
< 0 || len
> MAX_SYSLEN
)
404 * Scan for non-alpha numerics
405 * Per utmpx.h, len includes the nul character.
407 for (i
= 0; i
< len
; i
++)
408 if (name
[i
] != '\0' && isprint(name
[i
]) == 0)
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.
420 check_id(char *id
, char *line
)
424 if (id
[1] == '/' && id
[2] == 's' && id
[3] == 't') {
428 for (i
= 0; i
< 4; i
++)
429 id
[i
] = len
- i
< 0 ? 0 : line
[len
-i
];
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.
444 invalid_utmpx(struct utmpx
*eutmpx
, struct utmpx
*rutmpx
)
446 #define SUTMPX_ID (sizeof (eutmpx->ut_id))
447 #define SUTMPX_USER (sizeof (eutmpx->ut_user))
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);
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
466 if (strstr(line
, "../") != 0) {
467 dprintf("Bad line = %s\n", line
);
472 * It has to be a tty. It can't be a bogus file, e.g. ../tmp/bogus.
474 if (seteuid(getuid()) != 0)
478 * We need to open the line without blocking so that it does not hang
480 if ((fd
= open(line
, O_WRONLY
|O_NOCTTY
|O_NONBLOCK
)) == -1) {
481 dprintf("Bad line (Can't open/write) = %s\n", line
);
486 * Check that fd is a tty, if this fails all is not lost see below
488 if (isatty(fd
) == 1) {
490 * It really is a tty, so return success
493 if (seteuid(ROOT_UID
) != 0)
499 * Check that the line refers to a character
502 if ((fstat(fd
, &statbuf
) < 0) || !S_ISCHR(statbuf
.st_mode
)) {
503 dprintf("Bad line (fstat failed) (Not S_IFCHR) = %s\n", line
);
509 * Check that the line refers to a streams device
511 if (isastream(fd
) != 1) {
512 dprintf("Bad line (isastream failed) = %s\n", line
);
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
522 if (ioctl(fd
, I_PUSH
, "ptem") == -1) {
523 dprintf("Bad line (I_PUSH of \"ptem\" failed) = %s\n", line
);
528 if (isatty(fd
) != 1) {
529 dprintf("Bad line (isatty failed) = %s\n", line
);
534 if (ioctl(fd
, I_POP
, 0) == -1) {
535 dprintf("Bad line (I_POP of \"ptem\" failed) = %s\n", line
);
542 if (seteuid(ROOT_UID
) != 0)
552 * display_args - This code prints out invocation arguments
553 * This is helpful since the program is called with
554 * up to 15 argumments.
558 display_args(argc
, argv
)
565 printf("Argument #%d = %s\n", i
, argv
[i
]);
570 fputmpx(struct utmpx
*rutmpx
)
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
);