sessreg.c revision 8b8e729c
1/*
2 * Copyright 1990, 1998  The Open Group
3 *
4 * Permission to use, copy, modify, distribute, and sell this software and its
5 * documentation for any purpose is hereby granted without fee, provided that
6 * the above copyright notice appear in all copies and that both that
7 * copyright notice and this permission notice appear in supporting
8 * documentation.
9 *
10 * The above copyright notice and this permission notice shall be included
11 * in all copies or substantial portions of the Software.
12 *
13 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
15 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
16 * IN NO EVENT SHALL THE OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR
17 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
18 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
19 * OTHER DEALINGS IN THE SOFTWARE.
20 *
21 * Except as contained in this notice, the name of The Open Group shall
22 * not be used in advertising or otherwise to promote the sale, use or
23 * other dealings in this Software without prior written authorization
24 * from The Open Group.
25 *
26 */
27
28/*
29 * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved.
30 *
31 * Permission is hereby granted, free of charge, to any person obtaining a
32 * copy of this software and associated documentation files (the "Software"),
33 * to deal in the Software without restriction, including without limitation
34 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
35 * and/or sell copies of the Software, and to permit persons to whom the
36 * Software is furnished to do so, subject to the following conditions:
37 *
38 * The above copyright notice and this permission notice (including the next
39 * paragraph) shall be included in all copies or substantial portions of the
40 * Software.
41 *
42 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
43 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
44 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
45 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
46 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
47 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
48 * DEALINGS IN THE SOFTWARE.
49 */
50
51/*
52 * Author:  Keith Packard, MIT X Consortium
53 * Lastlog support and dynamic utmp entry allocation
54 *   by Andreas Stolcke <stolcke@icsi.berkeley.edu>
55 */
56
57/*
58 * sessreg
59 *
60 * simple wtmp/utmp frobber
61 *
62 * usage: sessreg [ -w <wtmp-file> ] [ -u <utmp-file> ]
63 *		  [ -l <line> ]
64 *		  [ -L <lastlog-file> ]		      / #ifdef USE_LASTLOG
65 *		  [ -h <host-name> ]				/ BSD only
66 *		  [ -s <slot-number> ] [ -x Xservers-file ]	/ BSD only
67 *		  [ -t <ttys-file> ]				/ BSD only
68 *		  [ -a ] [ -d ] user-name
69 *
70 * one of -a or -d must be specified
71 */
72
73#include "sessreg.h"
74
75#include <X11/Xos.h>
76#include <X11/Xfuncs.h>
77#include <X11/Xfuncproto.h>
78#include <stdio.h>
79#include <stdlib.h>
80#include <time.h>
81
82#ifdef USE_UTMP
83static void set_utmp (struct utmp *u, char *line, char *user, char *host,
84		      time_t date, int addp);
85#endif
86
87#ifdef USE_UTMPX
88static void set_utmpx (struct utmpx *u, const char *line, const char *user,
89		       const char *host, time_t date, int addp);
90#endif
91
92static int wflag, uflag, lflag;
93static const char *wtmp_file, *utmp_file;
94#ifdef USE_UTMPX
95#ifdef HAVE_UPDWTMPX
96static const char *wtmpx_file = NULL;
97#endif
98#ifdef HAVE_UTMPXNAME
99static const char *utmpx_file = NULL;
100#endif
101#endif
102
103static int utmp_none, wtmp_none;
104/*
105 * BSD specific variables.  To make life much easier for Xstartup/Xreset
106 * maintainers, these arguments are accepted but ignored for sysV
107 */
108static int hflag, xflag, tflag;
109static char *host_name = NULL;
110#if defined(USE_UTMP) && !defined(HAVE_PUTUTLINE)
111static int sflag;
112static int slot_number;
113#endif
114static char *xservers_file, *ttys_file;
115static char *user_name;
116static int aflag, dflag;
117#ifdef USE_LASTLOG
118static const char *llog_file;
119static int llog_none, Lflag;
120#endif
121
122static char *program_name;
123
124#if defined(USE_UTMP) && !defined(HAVE_PUTUTLINE)
125static int findslot (char *line_name, char *host_name, int addp, int slot);
126static int Xslot (char *ttys_file, char *servers_file, char *tty_line,
127		  char *host_name, int addp);
128#endif
129
130static void _X_NORETURN _X_COLD
131usage (int x)
132{
133	fprintf (stderr,
134		 "%s: usage %s {-a -d} [-w wtmp-file] [-u utmp-file]"
135#ifdef USE_LASTLOG
136		 " [-L lastlog-file]"
137#endif
138		 "\n"
139		 "             [-t ttys-file] [-l line-name] [-h host-name] [-V]\n"
140		 "             [-s slot-number] [-x servers-file] user-name\n",
141		 program_name, program_name);
142	exit (x);
143}
144
145static char *
146getstring (char ***avp, int *flagp)
147{
148	char	**a = *avp;
149	char	*flag = *a;
150
151	if (*flagp != 0) {
152		fprintf (stderr, "%s: cannot give more than one -%s option\n",
153			 program_name, flag);
154		usage (1);
155	}
156	*flagp = 1;
157	/* if the argument is given immediately following the flag,
158	   i.e. "sessreg -hfoo ...", not "sessreg -h foo ...",
159	   then return the rest of the string as the argument value */
160	if (*++*a)
161		return *a;
162	/* else use the next pointer in the argv list as the argument value */
163	++a;
164	if (!*a) {
165		fprintf (stderr, "%s: -%s requires an argument\n",
166			 program_name, flag);
167		usage (1);
168	}
169	*avp = a;
170	return *a;
171}
172
173#if defined(USE_UTMP) && !defined(HAVE_PUTUTLINE)
174static int
175syserr (int x, const char *s)
176{
177	if (x == -1) {
178		perror (s);
179		exit (1);
180	}
181	return x;
182}
183#endif
184
185static int
186sysnerr (int x, const char *s)
187{
188	if (x == 0) {
189		perror (s);
190		exit (1);
191	}
192	return x;
193}
194
195/*
196 * While this looks like it could be replaced with strlcpy() on platforms
197 * that have it, we're sticking with strncpy() so that we zero out the
198 * whole buffer to avoid writing garbage to the fixed length fields in the
199 * utmp/wtmp files, since strlcpy() does not write past the \0 terminator.
200 */
201static void
202safe_strncpy(char *dest, const char *src, size_t n)
203{
204    (void)strncpy(dest, src, n);
205    if (n > 0)
206        dest[n - 1] = '\0';
207}
208
209int
210main (int argc, char **argv)
211{
212#if defined(USE_UTMP) && !defined(HAVE_PUTUTLINE)
213	int		utmp;
214#endif
215#ifndef USE_UTMPX
216	int		wtmp;
217#endif
218	time_t		current_time;
219#ifdef USE_UTMP
220	struct utmp	utmp_entry;
221#endif
222#ifdef USE_UTMPX
223	struct utmpx	utmpx_entry;
224#endif
225	char *		line = NULL;
226
227	program_name = argv[0];
228	while (*++argv && **argv == '-') {
229		switch (*++*argv) {
230		case 'w':
231			wtmp_file = getstring (&argv, &wflag);
232			if (!strcmp (wtmp_file, "none"))
233				wtmp_none = 1;
234#if defined(USE_UTMPX) && defined(HAVE_UPDWTMPX)
235			else
236				wtmpx_file = wtmp_file;
237#endif
238			break;
239		case 'u':
240			utmp_file = getstring (&argv, &uflag);
241			if (!strcmp (utmp_file, "none"))
242				utmp_none = 1;
243#if defined(USE_UTMPX) && defined(HAVE_UTMPXNAME)
244			else
245				utmpx_file = utmp_file;
246#endif
247			break;
248#ifdef USE_LASTLOG
249		case 'L':
250			llog_file = getstring (&argv, &Lflag);
251			if (!strcmp (llog_file, "none"))
252				llog_none = 1;
253			break;
254#endif
255		case 't':
256			ttys_file = getstring (&argv, &tflag);
257			break;
258		case 'l':
259			line = getstring (&argv, &lflag);
260			break;
261		case 'h':
262			host_name = getstring (&argv, &hflag);
263			break;
264		case 's':
265#if defined(USE_UTMP) && !defined(HAVE_PUTUTLINE)
266			slot_number = atoi (getstring (&argv, &sflag));
267#endif
268			break;
269		case 'x':
270			xservers_file = getstring (&argv, &xflag);
271			break;
272		case 'a':
273			aflag++;
274			break;
275		case 'd':
276			dflag++;
277			break;
278		case 'V':
279			printf("%s\n", PACKAGE_STRING);
280			exit (0);
281		default:
282			fprintf (stderr, "%s: unrecognized option '%s'\n",
283				 program_name, argv[0]);
284			usage (1);
285		}
286	}
287	user_name = *argv++;
288	if (user_name == NULL) {
289		fprintf (stderr, "%s: missing required user-name argument\n",
290			 program_name);
291		usage (1);
292	}
293	if (*argv != NULL) {
294		fprintf (stderr, "%s: unrecognized argument '%s'\n",
295			 program_name, argv[0]);
296		usage (1);
297	}
298	/*
299	 * complain if neither aflag nor dflag are set,
300	 * or if both are set.
301	 */
302	if (!(aflag ^ dflag)) {
303		fprintf (stderr, "%s: must specify exactly one of -a or -d\n",
304			 program_name);
305		usage (1);
306	}
307	if (xflag && !lflag) {
308		fprintf (stderr, "%s: must specify -l when -x is used\n",
309			 program_name);
310		usage (1);
311	}
312	/* set up default file names */
313	if (!wflag) {
314		wtmp_file = WTMP_FILE;
315#if defined(USE_UTMPX) && defined(HAVE_UPDWTMPX)
316		wtmpx_file = WTMPX_FILE;
317#endif
318	}
319	if (!uflag) {
320		utmp_file = UTMP_FILE;
321#if defined(USE_UTMPX) && defined(HAVE_UTMPXNAME)
322		utmpx_file = UTMPX_FILE;
323#endif
324	}
325#ifdef USE_LASTLOG
326	if (!Lflag)
327		llog_file = LLOG_FILE;
328#endif
329#if defined(USE_UTMP) && !defined(HAVE_PUTUTLINE)
330	if (!tflag)
331		ttys_file = TTYS_FILE;
332	if (!sflag && !utmp_none) {
333		if (xflag)
334			sysnerr (slot_number = Xslot (ttys_file, xservers_file, line, host_name, aflag), "Xslot");
335		else
336			sysnerr (slot_number = ttyslot (), "ttyslot");
337	}
338#endif
339	if (!lflag) {
340		sysnerr ((line = ttyname (0)) != NULL, "ttyname");
341		if (strncmp(line, "/dev/", 5) == 0)
342			line += 5;
343	}
344	time (&current_time);
345#ifdef USE_UTMP
346	set_utmp (&utmp_entry, line, user_name, host_name, current_time, aflag);
347#endif
348
349#ifdef USE_UTMPX
350	/* need to set utmpxname() before calling set_utmpx() for
351	   UtmpxIdOpen to work */
352# ifdef HAVE_UTMPXNAME
353	if (utmpx_file != NULL) {
354		utmpxname (utmpx_file);
355	}
356# endif
357	set_utmpx (&utmpx_entry, line, user_name,
358		   host_name, current_time, aflag);
359#endif
360
361	if (!utmp_none) {
362#ifdef USE_UTMPX
363# ifdef HAVE_UTMPXNAME
364		if (utmpx_file != NULL)
365# endif
366		{
367			setutxent ();
368			(void) getutxid (&utmpx_entry);
369			pututxline (&utmpx_entry);
370			endutxent ();
371		}
372#endif
373#ifdef USE_UTMP
374# ifdef HAVE_PUTUTLINE
375		utmpname (utmp_file);
376		setutent ();
377		(void) getutid (&utmp_entry);
378		pututline (&utmp_entry);
379		endutent ();
380# else
381		utmp = open (utmp_file, O_RDWR);
382		if (utmp != -1) {
383			syserr ((int) lseek (utmp, (off_t) slot_number * sizeof (struct utmp), 0), "lseek");
384			sysnerr (write (utmp, (char *) &utmp_entry, sizeof (utmp_entry))
385					== sizeof (utmp_entry), "write utmp entry");
386			close (utmp);
387		}
388# endif
389#endif /* USE_UTMP */
390	}
391	if (!wtmp_none) {
392#ifdef USE_UTMPX
393# ifdef HAVE_UPDWTMPX
394		if (wtmpx_file != NULL) {
395			updwtmpx(wtmpx_file, &utmpx_entry);
396		}
397# endif
398#else
399		wtmp = open (wtmp_file, O_WRONLY|O_APPEND);
400		if (wtmp != -1) {
401			sysnerr (write (wtmp, (char *) &utmp_entry, sizeof (utmp_entry))
402					== sizeof (utmp_entry), "write wtmp entry");
403			close (wtmp);
404		}
405#endif
406	}
407#ifdef USE_LASTLOG
408	if (aflag && !llog_none) {
409		int llog;
410		struct passwd *pwd = getpwnam(user_name);
411
412		sysnerr( pwd != NULL, "get user id");
413		llog = open (llog_file, O_RDWR);
414
415		if (llog != -1) {
416			struct lastlog ll;
417
418			sysnerr (lseek(llog, (off_t) (pwd->pw_uid*sizeof(ll)), 0)
419					!= -1, "seeking lastlog entry");
420			memset(&ll, 0, sizeof(ll));
421			ll.ll_time = current_time;
422			if (line)
423			 safe_strncpy (ll.ll_line, line, sizeof (ll.ll_line));
424			if (host_name)
425			 safe_strncpy (ll.ll_host, host_name, sizeof (ll.ll_host));
426
427			sysnerr (write (llog, (char *) &ll, sizeof (ll))
428					== sizeof (ll), "write lastlog entry");
429			close (llog);
430		}
431	}
432#endif
433	return 0;
434}
435
436/*
437 * fill in the appropriate records of the utmp entry
438 */
439
440#ifdef USE_UTMP
441static void
442set_utmp (struct utmp *u, char *line, char *user, char *host, time_t date, int addp)
443{
444	memset (u, 0, sizeof (*u));
445	if (line)
446		safe_strncpy (u->ut_line, line, sizeof (u->ut_line));
447	else
448		memset (u->ut_line, 0, sizeof (u->ut_line));
449	if (addp && user)
450		safe_strncpy (u->ut_name, user, sizeof (u->ut_name));
451	else
452		memset (u->ut_name, 0, sizeof (u->ut_name));
453#ifdef HAVE_STRUCT_UTMP_UT_ID
454	if (line) {
455		size_t	i;
456		/*
457		 * this is a bit crufty, but
458		 * follows the apparent conventions in
459		 * the ttys file.  ut_id is only 4 bytes
460		 * long, and the last 4 bytes of the line
461		 * name are written into it, left justified.
462		 */
463		i = strlen (line);
464		if (i >= sizeof (u->ut_id))
465			i -= sizeof (u->ut_id);
466		else
467			i = 0;
468		safe_strncpy (u->ut_id, line + i, sizeof (u->ut_id));
469	} else
470		memset (u->ut_id, 0, sizeof (u->ut_id));
471#endif
472#ifdef HAVE_STRUCT_UTMP_UT_PID
473	if (addp)
474		u->ut_pid = getppid ();
475	else
476		u->ut_pid = 0;
477#endif
478#ifdef HAVE_STRUCT_UTMP_UT_TYPE
479	if (addp)
480		u->ut_type = USER_PROCESS;
481	else
482		u->ut_type = DEAD_PROCESS;
483#endif
484#ifdef HAVE_STRUCT_UTMP_UT_HOST
485	if (addp && host)
486		safe_strncpy (u->ut_host, host, sizeof (u->ut_host));
487	else
488		memset (u->ut_host, 0, sizeof (u->ut_host));
489#endif
490	u->ut_time = date;
491}
492#endif /* USE_UTMP */
493
494#ifdef USE_UTMPX
495static int
496UtmpxIdOpen( char *utmpId )
497{
498	struct utmpx *u;	/* pointer to entry in utmp file           */
499	int    status = 1;	/* return code                             */
500
501	setutxent();
502
503	while ( (u = getutxent()) != NULL ) {
504
505		if ( (strncmp(u->ut_id, utmpId, 4) == 0 ) &&
506		     u->ut_type != DEAD_PROCESS ) {
507
508			status = 0;
509			break;
510		}
511	}
512
513	endutxent();
514	return (status);
515}
516
517static void
518set_utmpx (struct utmpx *u, const char *line, const char *user,
519	   const char *host, time_t date, int addp)
520{
521	static const char letters[] =
522	       "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
523
524	memset (u, 0, sizeof (*u));
525	if (line)
526	{
527		if(strcmp(line, ":0") == 0)
528			(void) strcpy(u->ut_line, "console");
529		else
530			safe_strncpy (u->ut_line, line, sizeof (u->ut_line));
531
532		safe_strncpy(u->ut_host, line, sizeof(u->ut_host));
533#ifdef HAVE_STRUCT_UTMPX_UT_SYSLEN
534		u->ut_syslen = strlen(line);
535#endif
536	}
537	else
538		memset (u->ut_line, 0, sizeof (u->ut_line));
539	if (addp && user)
540		safe_strncpy (u->ut_user, user, sizeof (u->ut_user));
541	else
542		memset (u->ut_user, 0, sizeof (u->ut_user));
543
544	if (line) {
545		size_t	i;
546		/*
547		 * this is a bit crufty, but
548		 * follows the apparent conventions in
549		 * the ttys file.  ut_id is only 4 bytes
550		 * long, and the last 4 bytes of the line
551		 * name are written into it, left justified.
552		 */
553		i = strlen (line);
554		if (i >= sizeof (u->ut_id))
555			i -= sizeof (u->ut_id);
556		else
557			i = 0;
558		safe_strncpy (u->ut_id, line + i, sizeof (u->ut_id));
559
560		/* make sure there is no entry using identical ut_id */
561		if (!UtmpxIdOpen(u->ut_id) && addp) {
562			int limit = sizeof(letters) - 1;
563			int t = 0;
564
565			u->ut_id[1] = line[i];
566			u->ut_id[2] = line[i+1];
567			u->ut_id[3] = line[i+2];
568			do {
569				u->ut_id[0] = letters[t];
570				t++;
571			} while (!UtmpxIdOpen(u->ut_id) && (t < limit));
572		}
573		if (!addp && strstr(line, ":") != NULL) {
574			struct utmpx *tmpu;
575
576			while ( (tmpu = getutxent()) != NULL ) {
577				if ( (strcmp(tmpu->ut_host, line) == 0 ) &&
578					tmpu->ut_type != DEAD_PROCESS ) {
579					strncpy(u->ut_id, tmpu->ut_id,
580						sizeof(u->ut_id));
581					break;
582				}
583			}
584			endutxent();
585		}
586	} else
587		memset (u->ut_id, 0, sizeof (u->ut_id));
588
589	if (addp) {
590		u->ut_pid = getppid ();
591		u->ut_type = USER_PROCESS;
592	} else {
593		u->ut_pid = 0;
594		u->ut_type = DEAD_PROCESS;
595	}
596	u->ut_tv.tv_sec = date;
597	u->ut_tv.tv_usec = 0;
598}
599#endif /* USE_UTMPX */
600
601#if defined(USE_UTMP) && !defined(HAVE_PUTUTLINE)
602/*
603 * compute the slot-number for an X display.  This is computed
604 * by counting the lines in /etc/ttys and adding the line-number
605 * that the display appears on in Xservers.  This is a poor
606 * design, but is limited by the non-existant interface to utmp.
607 * If host_name is non-NULL, assume it contains the display name,
608 * otherwise use the tty_line argument (i.e., the tty name).
609 */
610
611static int
612Xslot (char *ttys_file, char *servers_file, char *tty_line, char *host_name,
613       int addp)
614{
615	FILE	*ttys, *servers;
616	int	c;
617	int	slot = 1;
618	int	column0 = 1;
619	char	servers_line[1024];
620	char	disp_name[512];
621	int	len;
622	char	*pos;
623
624	/* remove screen number from the display name */
625	memset(disp_name, 0, sizeof(disp_name));
626	strncpy(disp_name, host_name ? host_name : tty_line, sizeof(disp_name)-1);
627	pos = strrchr(disp_name, ':');
628	if (pos) {
629		pos = strchr(pos, '.');
630		if (pos)
631			*pos = '\0';
632	}
633	sysnerr ((int)(long)(ttys = fopen (ttys_file, "r")), ttys_file);
634	while ((c = getc (ttys)) != EOF)
635		if (c == '\n') {
636			++slot;
637			column0 = 1;
638		} else
639			column0 = 0;
640	if (!column0)
641		++slot;
642	(void) fclose (ttys);
643	sysnerr ((int)(long)(servers = fopen (servers_file, "r")), servers_file);
644
645	len = strlen (disp_name);
646	column0 = 1;
647	while (fgets (servers_line, sizeof (servers_line), servers)) {
648		if (column0 && *servers_line != '#') {
649			if (!strncmp (disp_name, servers_line, len) &&
650			    (servers_line[len] == ' ' ||
651			     servers_line[len] == '\t'))
652				return slot;
653			++slot;
654		}
655		if (servers_line[strlen(servers_line)-1] != '\n')
656			column0 = 0;
657		else
658			column0 = 1;
659	}
660	/*
661	 * display not found in Xservers file - allocate utmp entry dinamically
662	 */
663	return findslot (tty_line, host_name, addp, slot);
664}
665
666/*
667 * find a free utmp slot for the X display.  This allocates a new entry
668 * past the regular tty entries if necessary, reusing existing entries
669 * (identified by (line,hostname)) if possible.
670 */
671static int
672findslot (char *line_name, char *host_name, int addp, int slot)
673{
674	int	utmp;
675	struct	utmp entry;
676	int	found = 0;
677	int	freeslot = -1;
678
679	syserr(utmp = open (utmp_file, O_RDONLY), "open utmp");
680
681	/*
682	 * first, try to locate a previous entry for this display
683	 * also record location of a free slots in case we need a new one
684	 */
685	syserr ((int) lseek (utmp, (off_t) slot * sizeof (struct utmp), 0), "lseek");
686
687	if (!host_name)
688		host_name = "";
689
690	while (read (utmp, (char *) &entry, sizeof (entry)) == sizeof (entry)) {
691		if (strncmp(entry.ut_line, line_name,
692			sizeof(entry.ut_line)) == 0
693#ifdef HAVE_STRUCT_UTMP_UT_HOST
694		    &&
695		    strncmp(entry.ut_host, host_name,
696			sizeof(entry.ut_host)) == 0
697#endif
698		   ) {
699			found = 1;
700			break;
701		}
702		if (freeslot < 0 && *entry.ut_name == '\0')
703			freeslot = slot;
704		++slot;
705	}
706
707	close (utmp);
708
709	if (found)
710		return slot;
711	else if (!addp)
712		return 0;	/* trying to delete a non-existing entry */
713	else if (freeslot < 0)
714		return slot;	/* first slot past current entries */
715	else
716		return freeslot;
717}
718#endif
719