[net.bugs.4bsd] The definitive fix for tips' lock file problem

david@ukma.UUCP (David Herron, NPR Lover) (07/08/85)

Index: /usr/src/usr.bin/tip/{cu.c,tip.c,tip.h,uucplock.c}
Unix-Version: BRL-Unix Release 3 (but not very different from 4.2BSD tip)
Date: 7-July-1985

Summary: The definitive fix for the tip lock file problem.

Problem: As distributed, tip doesn't work.  There are two specific
	problems.  One is that it doesn't remove lock files.  Examining
	the comments in the RCS logs shows that y'all were heading that
	direction but had never made it.  The other problem is that
	it doesn't do wls@astrovax's dialin and out stuff.

	I'm sending the revisions I made which finished the stuff y'all were
	doing for the lock files.  I am assuming you have wls's code and
	will put it in yourselves.  (If not, I *can* forward it).

Explanation: This is being posted to net.bugs.4bsd because the fix
	is needed, and should easily slide into place.  The only
	problem is that in 4.2BSD tip, finish() calls delock().
	The BRL people had deleted this call in preparation
	for code like this but had never installed the rest of it.

Description:  I put a trap in tip (by calling a routine called "Parent()")
	at the point just before it setuid()'s.  Parent() fork()'s and
	the child returns immediately to continue on with setuid()'s
	and all the rest.  In the meantime, the parent is setting up
	two things.  One is a SIGALRM every 10 minutes which will
	touch the lock file (I'm using ultouch() from uucp).  (I'm doing
	this for people who sit in tip for days at a time :-)).  The other
	is a loop which is doing wait3()'s looking for stopped/dead/dying
	children.  For dead children the parent deletes the lock file 
	and exits.  For stopped children it stops itself then when it 
	wakes up sends a SIGCONT to the children.

Something-I-Am-Thinking-About:  There was a suggestion the Will Sebok
	posted about a month ago.  It was for tip to have a set of
	variables which identified the commands to run for file
	transfers.  For instance.  One variable is "sendfile_here"
	(more thought will come up with a better name) which would
	be defined as "kermit -l /dev/ttyxx -b 1200 -s %s".  The
	%s is substituted to be the name of the file to send.  There's
	a corresponding variable to use as the command string for the 
	other side.  Ckermit will have to be modified to have an
	"override the lock file" switch is the biggest problem.
	But, I think you get the idea.


David Herron
cbosgd!ukma!david




RCS file: RCS/cu.c,v
retrieving revision 1.2
retrieving revision 1.3
diff -c -r1.2 -r1.3
*** /tmp/,RCSt1002886	Sun Jul  7 20:04:30 1985
--- /tmp/,RCSt2002886	Sun Jul  7 20:04:32 1985
***************
*** 1,7
  /*
   *			C U . C 
   *
!  * $Revision: 1.2 $
   *
   * $Log:	cu.c,v $
   * Revision 1.2  83/12/13  23:51:47  dpk

--- 1,7 -----
  /*
   *			C U . C 
   *
!  * $Revision: 1.3 $
   *
   * $Log:	cu.c,v $
   * Revision 1.3  85/07/07  14:06:21  root
***************
*** 4,9
   * $Revision: 1.2 $
   *
   * $Log:	cu.c,v $
   * Revision 1.2  83/12/13  23:51:47  dpk
   * Added distinctive RCS header
   * 

--- 4,20 -----
   * $Revision: 1.3 $
   *
   * $Log:	cu.c,v $
+  * Revision 1.3  85/07/07  14:06:21  root
+  * New method to solve the lock file problem.  We keep a process around who
+  * is still setuid.  This is done by fork()ing before the setuid.  The
+  * parent goes into a loop waiting for children to die.  (Also watches
+  * for children SIGSTOP so it can STOP itself, also touches the lock
+  * file (using ultouch() from uucp) every TOUCHTIME seconds.)
+  * 
+  * Look at Parent().
+  * 
+  * 	ukma!david
+  * 
   * Revision 1.2  83/12/13  23:51:47  dpk
   * Added distinctive RCS header
   * 
***************
*** 9,15
   * 
   */
  #ifndef lint
! static char RCSid[] = "@(#)$Header: cu.c,v 1.2 83/12/13 23:51:47 dpk BRL $";
  #endif
  
  #ifndef lint

--- 20,26 -----
   * 
   */
  #ifndef lint
! static char RCSid[] = "@(#)$Header: cu.c,v 1.3 85/07/07 14:06:21 root Exp $";
  #endif
  
  #ifndef lint
***************
*** 20,25
  
  int	cleanup();
  int	timeout();
  
  /*
   * Botch the interface to look like cu's

--- 31,37 -----
  
  int	cleanup();
  int	timeout();
+ int	touchlock();
  
  /*
   * Botch the interface to look like cu's
***************
*** 29,34
  {
  	register int i;
  	static char sbuf[12];
  
  	if (argc < 2) {
  		printf("usage: cu telno [-t] [-s speed] [-a acu] [-l line] [-#]\n");

--- 41,47 -----
  {
  	register int i;
  	static char sbuf[12];
+ 	extern int Parent();
  
  	if (argc < 2) {
  		printf("usage: cu telno [-t] [-s speed] [-a acu] [-l line] [-#]\n");
***************
*** 95,100
  	}
  	setbuf(stdout, NULL);
  	loginit();
  	setuid(getuid());
  	setgid(getgid());
  	vinit();

--- 108,114 -----
  	}
  	setbuf(stdout, NULL);
  	loginit();
+ 	Parent();	/* child returns, parent never returns */
  	setuid(getuid());
  	setgid(getgid());
  	vinit();


RCS file: RCS/tip.c,v
retrieving revision 1.2
retrieving revision 1.3
diff -c -r1.2 -r1.3
*** /tmp/,RCSt1002932	Sun Jul  7 20:06:11 1985
--- /tmp/,RCSt2002932	Sun Jul  7 20:06:17 1985
***************
*** 1,7
  /*
   *			T I P . C 
   *
!  * $Revision: 1.2 $
   *
   * $Log:	tip.c,v $
   * Revision 1.2  83/12/13  23:52:37  dpk

--- 1,7 -----
  /*
   *			T I P . C 
   *
!  * $Revision: 1.3 $
   *
   * $Log:	tip.c,v $
   * Revision 1.3  85/07/07  13:57:25  root
***************
*** 4,9
   * $Revision: 1.2 $
   *
   * $Log:	tip.c,v $
   * Revision 1.2  83/12/13  23:52:37  dpk
   * Added distinctive RCS header
   * 

--- 4,20 -----
   * $Revision: 1.3 $
   *
   * $Log:	tip.c,v $
+  * Revision 1.3  85/07/07  13:57:25  root
+  * New method to solve the lock file problem.  We keep a process around who
+  * is still setuid.  This is done by fork()ing before the setuid.  The
+  * parent goes into a loop waiting for children to die.  (Also watches
+  * for children SIGSTOP so it can STOP itself, also touches the lock
+  * file (using ultouch() from uucp) every TOUCHTIME seconds.)
+  * 
+  * Look at Parent().
+  * 
+  * 	ukma!david
+  * 
   * Revision 1.2  83/12/13  23:52:37  dpk
   * Added distinctive RCS header
   * 
***************
*** 9,15
   * 
   */
  #ifndef lint
! static char RCSid[] = "@(#)$Header: tip.c,v 1.2 83/12/13 23:52:37 dpk BRL $";
  #endif
  
  #ifndef lint

--- 20,26 -----
   * 
   */
  #ifndef lint
! static char RCSid[] = "@(#)$Header: tip.c,v 1.3 85/07/07 13:57:25 root Exp $";
  #endif
  
  #ifndef lint
***************
*** 22,27
   * or
   *  cu phone-number [-s speed] [-l line] [-a acu]
   */
  #include "tip.h"
  
  /*

--- 33,41 -----
   * or
   *  cu phone-number [-s speed] [-l line] [-a acu]
   */
+ #include <sys/time.h>
+ #include <sys/resource.h>
+ #include <sys/wait.h>
  #include "tip.h"
  
  /*
***************
*** 33,38
  };
  
  int	disc = OTTYDISC;		/* tip normally runs this way */
  int	intprompt();
  int	timeout();
  int	cleanup();

--- 47,53 -----
  };
  
  int	disc = OTTYDISC;		/* tip normally runs this way */
+ int	touchlock();
  int	intprompt();
  int	timeout();
  int	cleanup();
***************
*** 47,52
  	register char *p;
  	char sbuf[12];
  
  	if (equal(sname(argv[0]), "cu")) {
  		cumain(argc, argv);
  		cumode = 1;

--- 62,71 -----
  	register char *p;
  	char sbuf[12];
  
+ 	ioctl(0, TIOCGETP, (char *)&defarg);
+ 	ioctl(0, TIOCGETC, (char *)&defchars);
+ 	ioctl(0, TIOCGLTC, (char *)&deflchars);
+ 	ioctl(0, TIOCGETD, (char *)&odisc);
  	if (equal(sname(argv[0]), "cu")) {
  		cumain(argc, argv);
  		cumode = 1;
***************
*** 107,112
  	}
  	setbuf(stdout, NULL);
  	loginit();
  	/*
  	 * Now that we have the logfile and the ACU open
  	 *  return to the real uid and gid.  These things will

--- 126,132 -----
  	}
  	setbuf(stdout, NULL);
  	loginit();
+ 	Parent();	/* It is the CHILD that returns from this ... */
  	/*
  	 * Now that we have the logfile and the ACU open
  	 *  return to the real uid and gid.  These things will
***************
*** 150,159
  	 * From here down the code is shared with
  	 * the "cu" version of tip.
  	 */
- 	ioctl(0, TIOCGETP, (char *)&defarg);
- 	ioctl(0, TIOCGETC, (char *)&defchars);
- 	ioctl(0, TIOCGLTC, (char *)&deflchars);
- 	ioctl(0, TIOCGETD, (char *)&odisc);
  	arg = defarg;
  	arg.sg_flags = ANYP | CBREAK;
  	tchars = defchars;

--- 170,175 -----
  	 * From here down the code is shared with
  	 * the "cu" version of tip.
  	 */
  	arg = defarg;
  	arg.sg_flags = ANYP | CBREAK;
  	tchars = defchars;
***************
*** 508,511
  	}
  	fprintf(stderr, "%s: unknown parity value\n", PA);
  	fflush(stderr);
  }

--- 524,579 -----
  	}
  	fprintf(stderr, "%s: unknown parity value\n", PA);
  	fflush(stderr);
+ }
+ 
+ /*
+  * code for the parent to touch the lock file.
+  */
+ touchlock()
+ {
+ 	ultouch();
+ 	printf("touchlock()\r\n");	/* DEBUG */
+ 	signal(SIGALRM, touchlock);
+ 	alarm(TOUCHTIME);
+ }
+ 
+ /*
+  * Parent() -- Be a big daddy and watch for the children to die off.
+  *
+  * fork() off so the child can change user id's.  The parent
+  * will be up here watching for the child to exit, and when
+  * it does it will remove the lock file and reenable the modem
+  * (if necessary).  It will also touch the lock file from
+  * time to time.
+  */
+ Parent()
+ {
+ 	union wait chstatus;
+ 	struct rusage rusage;
+ 	int child;
+ 
+ 	if ((child = fork()) != 0) {
+ 		/* parent */
+ 		touchlock();
+ /* STRUCTURE ALERT!!! */
+ loop:
+ 		while (wait3(&chstatus, WUNTRACED, &rusage) != child)
+ 			printf("waiting ...\r\n");
+ 		if (chstatus.w_stopval == WSTOPPED) {
+ 			/* 
+ 			 * Child has stopped because of ~^Z command
+ 			 */
+ 			kill(0, SIGTSTP);
+ 			goto loop;
+ 		}
+ 		/* else */
+ 		kill(child, SIGTERM);
+ 		delock(uucplock);
+ 		printf("done\r\n");
+ 		/* make sure ... */
+ 		unraw();
+ 		exit(chstatus);
+ 	}
+ 	else
+ 		return;
  }



RCS file: RCS/tip.h,v
retrieving revision 1.2
retrieving revision 1.3
diff -c -r1.2 -r1.3
*** /tmp/,RCSt1002940	Sun Jul  7 20:06:53 1985
--- /tmp/,RCSt2002940	Sun Jul  7 20:06:54 1985
***************
*** 1,7
  /*
   *			T I P . H 
   *
!  * $Revision: 1.2 $
   *
   * $Log:	tip.h,v $
   * Revision 1.2  83/12/13  23:53:25  dpk

--- 1,7 -----
  /*
   *			T I P . H 
   *
!  * $Revision: 1.3 $
   *
   * $Log:	tip.h,v $
   * Revision 1.3  85/07/07  14:06:25  root
***************
*** 4,9
   * $Revision: 1.2 $
   *
   * $Log:	tip.h,v $
   * Revision 1.2  83/12/13  23:53:25  dpk
   * Added distinctive RCS header
   * 

--- 4,20 -----
   * $Revision: 1.3 $
   *
   * $Log:	tip.h,v $
+  * Revision 1.3  85/07/07  14:06:25  root
+  * New method to solve the lock file problem.  We keep a process around who
+  * is still setuid.  This is done by fork()ing before the setuid.  The
+  * parent goes into a loop waiting for children to die.  (Also watches
+  * for children SIGSTOP so it can STOP itself, also touches the lock
+  * file (using ultouch() from uucp) every TOUCHTIME seconds.)
+  * 
+  * Look at Parent().
+  * 
+  * 	ukma!david
+  * 
   * Revision 1.2  83/12/13  23:53:25  dpk
   * Added distinctive RCS header
   * 
***************
*** 245,247
  extern	char *index();
  extern	char *malloc();
  extern	char *connect();

--- 256,260 -----
  extern	char *index();
  extern	char *malloc();
  extern	char *connect();
+ #define TOUCHTIME	600
+ extern	int   ultouch();



RCS file: RCS/uucplock.c,v
retrieving revision 1.3
retrieving revision 1.4
diff -c -r1.3 -r1.4
*** /tmp/,RCSt1002955	Sun Jul  7 20:07:29 1985
--- /tmp/,RCSt2002955	Sun Jul  7 20:07:35 1985
***************
*** 1,7
  /*
   *			U U C P L O C K . C 
   *
!  * $Revision: 1.3 $
   *
   * $Log:	uucplock.c,v $
   * Revision 1.3  84/10/12  17:05:18  dpk

--- 1,7 -----
  /*
   *			U U C P L O C K . C 
   *
!  * $Revision: 1.4 $
   *
   * $Log:	uucplock.c,v $
   * Revision 1.4  85/07/07  14:06:32  root
***************
*** 4,9
   * $Revision: 1.3 $
   *
   * $Log:	uucplock.c,v $
   * Revision 1.3  84/10/12  17:05:18  dpk
   * Increased sizeof lockfile name array.
   * Size is now 64.

--- 4,20 -----
   * $Revision: 1.4 $
   *
   * $Log:	uucplock.c,v $
+  * Revision 1.4  85/07/07  14:06:32  root
+  * New method to solve the lock file problem.  We keep a process around who
+  * is still setuid.  This is done by fork()ing before the setuid.  The
+  * parent goes into a loop waiting for children to die.  (Also watches
+  * for children SIGSTOP so it can STOP itself, also touches the lock
+  * file (using ultouch() from uucp) every TOUCHTIME seconds.)
+  * 
+  * Look at Parent().
+  * 
+  * 	ukma!david
+  * 
   * Revision 1.3  84/10/12  17:05:18  dpk
   * Increased sizeof lockfile name array.
   * Size is now 64.
***************
*** 13,19
   * 
   */
  #ifndef lint
! static char RCSid[] = "@(#)$Header: uucplock.c,v 1.3 84/10/12 17:05:18 dpk Exp $";
  #endif
  
  #ifndef lint

--- 24,30 -----
   * 
   */
  #ifndef lint
! static char RCSid[] = "@(#)$Header: uucplock.c,v 1.4 85/07/07 14:06:32 root Exp $";
  #endif
  
  #ifndef lint
***************
*** 33,38
  }
  
  #define LOCKPRE "/usr/spool/uucp/LCK."
  
  /*
   * This code is taken almost directly from uucp and follows the same

--- 44,50 -----
  }
  
  #define LOCKPRE "/usr/spool/uucp/LCK."
+ #define LCKMODE	0664
  
  /*
   * This code is taken almost directly from uucp and follows the same
***************
*** 184,190
  {
  	int fd;
  
! 	fd = creat(tempfile, 0444);
  	if (fd < 0)
  		return (-1);
  	write(fd,(char *)&pid, sizeof(int));

--- 196,202 -----
  {
  	int fd;
  
! 	fd = creat(tempfile, LCKMODE);
  	if (fd < 0)
  		return (-1);
  	write(fd,(char *)&pid, sizeof(int));
***************
*** 226,229
  	char lname[30];
  	sprintf(lname, "%s.%s", LOCKPRE, sys);
  	return (ulockf(lname, (time_t) SLCKTIME ) < 0 ? FAIL : 0);
  }

--- 238,281 -----
  	char lname[30];
  	sprintf(lname, "%s.%s", LOCKPRE, sys);
  	return (ulockf(lname, (time_t) SLCKTIME ) < 0 ? FAIL : 0);
+ }
+ 
+ /***
+  *	ultouch()	update 'change' time for lock files
+  *
+  *	-- mod by rti!trt --
+  *	Only update ctime, not mtime or atime.
+  *	The 'chmod' method permits cu(I)-like programs
+  *	to determine how long uucp has been on the line.
+  *	The old "change access, mod, and change time" method
+  *	can be had by defining OLDTOUCH
+  *
+  *	return code - none
+  */
+ ultouch()
+ {
+ 	time_t time();
+ 	static time_t lasttouch = 0;
+ 	register int i;
+ 	struct ut {
+ 		time_t actime;
+ 		time_t modtime;
+ 	} ut;
+ 
+ 	ut.actime = time(&ut.modtime);
+ 	/* Do not waste time touching locking files too often */
+ 	/* (But, defend against backward time changes) */
+ 	if (ut.actime >= lasttouch && ut.actime < lasttouch+60)
+ 		return;
+ 	lasttouch = ut.actime;
+ 
+ 	for (i = 0; i < Nlocks; i++) {
+ 		if (Lockfile[i] == NULL)
+ 			continue;
+ #ifdef	OLDTOUCH
+ 		utime(Lockfile[i], &ut);
+ #else
+ 		chmod(Lockfile[i], LCKMODE);
+ #endif
+ 	}
  }

root@bu-cs.UUCP (Barry Shein) (07/10/85)

>RE: fix for tip involving keeping parent around setuid() to remove lock

Well, I see the fix and there is nothing wrong with it per se *but* I
still prefer my fix I mentioned earlier to this list: To leave the lock
files around always and fix the uucp lock routines to use soft file
locks on those files (lockf() in 4.2 and SYSVR2.)

This solves two and one half problems at once: First, if the process
dies for *any* reason the lock is freed, regardless of UID, sys crash,
etc (suid might only be needed on startup to create the lock or
something in rc could make sure it's there, or it just oughta be there
like /etc/init oughta be there, it's part of installing a line as
available.) Second, it gets rid of the original suid problem, you can
probably run w/o setuid entirely on most systems (give or take tty and
/etc/phones access), and 'half' it eliminates the need for touching the
lock file at all as locked is locked and the only conclusion is there is
an active process out there that will have to be killed or respected to
free it.

Adherence to this fixes all sorts of software which use these psuedo locks
(with the initial nuisance of having to re-compile it all.)

	1. Yes, I realize that not all unices have soft file locks
	yet, but now that SYSV has it as well as 4.2 I think it
	can be accepted as the standard method. Obviously software
	developers should probably #ifdef this for a while, possibly
	using the suggested fine fix for deficient systems.

	2. Yeah, ya gotta have the source. but you have to in order
	to implement the other fix so no objection here.

Isn't this the kind of thing soft locks were added for?

Not a flame, but when something came across as if it should be the
standard fix, I thought I would put my 2c in.

At any rate, here's the relevant fixed code from my uucplock.c in the
tip dir: (yow, have I violated my source license yet? in deference to
this concern most of the code is *not* here, just the two small subrs
involved, I didn't have the old source around to diff.)

For SYSV the fixes (which I have not yet installed) are similar.  As I
suggested, this should probably incorporate the previously suggested
fixes if soft locks are not available and conditionally compile one way
or the other.

Note: Looking this over now a couple of years later it is still not
ideal, there is no reason to unlink and re-create the file which would
still require suid but you get the idea. (ie. not definitive, just
instructive.)

	-Barry Shein, Boston University

(from ../src/usr.bin/tip/uucplock.c):

------------------

#define LOCKPRE "/usr/spool/uucp/LCK."

/*
 * This code is taken almost directly from uucp and follows the same
 * conventions.  This is important since uucp and tip should
 * respect each others locks.
 *
 *	MODIFICATION: (BZS@BU-CS) The way this was written if /usr/spool/uucp
 *	mode did not allow any user to delete files then TIP could
 *	not delete the lock (!!) as it had already done a setuid(getuid()).
 *	The best fix I could think of (which is actually perfect and
 *	solves the vagaries of an 'old' lock) was to keep a soft
 *	lock on the file via flock(2). The problem is it is not
 *	portable to systems that do not support this. For this reason
 *	it is a condtl compilation and the worst case is you get
 *	what everyone else gets and figure out a solution on your
 *	system...at least I warned you of the problem. What UNIX
 *	really needs is temporary files which go
 *	away if the process dies. This is 99% of that except for the
 *	little turd it leaves in the file system.
 */
#define FLOCK	1	/* file locking supported	*/
			/* remember, gotta do this to UUCP also */

	/*  ulockf 3.2  10/26/79  11:40:29  */
/* #include "uucp.h" */
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#ifdef FLOCK
#include <sys/file.h>
#endif

/*******
 *	ulockf(file, atime)
 *	char *file;
 *	time_t atime;
 *
 *	ulockf  -  this routine will create a lock file (file).
 *	If one already exists, the create time is checked for
 *	older than the age time (atime).
 *	If it is older, an attempt will be made to unlink it
 *	and create a new one.
 *
 *	return codes:  0  |  FAIL
 */

static
ulockf(file, atime)
	char *file;
	time_t atime;
{
	struct stat stbuf;
	time_t ptime;
	int ret;
	static int pid = -1;
	static char tempfile[NAMESIZE];
#ifdef FLOCK
	int fd ;
#endif

	if (pid < 0) {
		pid = getpid();
		sprintf(tempfile, "/usr/spool/uucp/LTMP.%d", pid);
	}
	if (onelock(pid, tempfile, file) == -1) {
		/* lock file exists */
#ifndef FLOCK
		/* get status to check age of the lock file */
		ret = stat(file, &stbuf);
		if (ret != -1) {
			time(&ptime);
			if ((ptime - stbuf.st_ctime) < atime) {
				/* file not old enough to delete */
				return (FAIL);
			}
		}
		ret = unlink(file);
#else
		if((fd = open(file,O_RDONLY)) < 0)
			return(FAIL) ;	/* shouldn't happen	*/
		if(flock(fd,LOCK_EX | LOCK_NB)) /* get exclusive lock */
		{
			close(fd) ;
			return(FAIL) ;	/* nope, someone really out there */
		}
		flock(fd,LOCK_UN) ; /* probably not necessary */
		close(fd) ;
		unlink(file) ;
#endif
		ret = onelock(pid, tempfile, file);
		if (ret != 0)
			return (FAIL);
	}
	stlock(file);
	return (0);
}
			...stuff excised...
static
onelock(pid, tempfile, name)
	char *tempfile, *name;
{
	int fd;

	fd = creat(tempfile, 0444);
	...stuff excised...
	unlink(tempfile);
#ifdef FLOCK
	if((fd = open(name,O_RDONLY)) < 0 || flock(fd,LOCK_EX | LOCK_NB))
		return(-1) ;
#endif
	return (0);
}