/*-------------------------------------------------------------------------
|   lfd is a lock file server.
|   Copyright 2002-2004 by Trent Jarvi trentjarvi@yahoo.com
|
|   This software is free software; you can redistribute it and/or
|   modify it under the terms of the GNU General Public
|   License as published by the Free Software Foundation; either
|   version 2 of the License, or (at your option) any later version.
|
|   This library is distributed in the hope that it will be useful,
|   but WITHOUT ANY WARRANTY; without even the implied warranty of
|   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
|   Library General Public License for more details.
|
|   You should have received a copy of the GNU General Public
|   License along with this software; if not, write to the Free
|   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
--------------------------------------------------------------------------*/
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <syslog.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <errno.h>
#include <signal.h>
#include <pwd.h>

#define FHS
#define LOCKFILEPREFIX "LCK.."
#define LOCK fhs_lock
#define UNLOCK fhs_unlock
#define LOCKDIR "/var/lock"

char hostname[256];
#define M200 "200 Command okay.\n"
#define M202 "202 Command not implemented\n"
#define M220 "220 %s Lock File Server (Version rxtx-1.5-9) ready\n", hostname
#define M221 "221 Thank you for using the Lock File service on %s\n", hostname
#define M450 "450 : File busy.\n"
#define M500 "500 '%s': command not understood\n", str
#define M550 "550 : Permission denied.\n"

int is_device_locked( const char * );
int check_group_uucp();
int check_lock_status( const char * );
int check_lock_pid( const char *, int  );

#define UNEXPECTED_LOCK_FILE "RXTX Error:  Unexpected lock file: %s\n Please report to the RXTX developers\n"
#define UUCP_ERROR "\n\n\nRXTX WARNING:  This library requires the user running applications to be in\ngroup uucp.  Please consult the INSTALL documentation.  More information is\navaiable under the topic 'How can I use Lock Files with rxtx?'\n" 

extern int errno;

/*----------------------------------------------------------
 fhs_lock

   accept:      The name of the device to try to lock
                termios struct
   perform:     Create a lock file if there is not one already.
   return:      1 on failure 0 on success
   exceptions:  none
   comments:    This is for linux and freebsd only currently.  I see SVR4 does
                this differently and there are other proposed changes to the
		Filesystem Hierachy Standard

		more reading:

----------------------------------------------------------*/
int fhs_lock( const char *filename, int pid )
{
	/*
	 * There is a zoo of lockdir possibilities
	 * Its possible to check for stale processes with most of them.
	 * for now we will just check for the lockfile on most
	 * Problem lockfiles will be dealt with.  Some may not even be in use.
	 *
	 */
	int fd,j;
	char lockinfo[12], message[80];
	char file[80], *p;

	j = strlen( filename );
	p = ( char * ) filename + j;
	/*  FIXME  need to handle subdirectories /dev/cua/... 
	    SCO Unix use lowercase all the time
			taj
	*/
	while( *( p - 1 ) != '/' && j-- != 1 )
	{
#if defined ( __unixware__ )
		*p = tolower( *p );
#endif /* __unixware__ */
		p--;
	}
	sprintf( file, "%s/LCK..%s", LOCKDIR, p );
	if ( check_lock_status( filename ) )
	{
		/* syslog( LOG_INFO, "fhs_lock() lockstatus fail\n" ); */
		return 1;
	}
	fd = open( file, O_CREAT | O_WRONLY | O_EXCL, 0444 );
	if( fd < 0 )
	{
		sprintf( message,
			"RXTX fhs_lock() Error: creating lock file: %s: %s\n",
			file, strerror(errno) );
		syslog( LOG_INFO, message );
		return 1;
	}
	sprintf( lockinfo, "%10d\n", pid );
	sprintf( message, "fhs_lock: creating lockfile: %s\n", lockinfo );
	//syslog( LOG_INFO, message );
	write( fd, lockinfo, 11 );
	close( fd );
	return 0;
}

/*----------------------------------------------------------
 uucp_lock

   accept:     char * filename.  Device to be locked 
   perform:    Try to get a uucp_lock
   return:     int 0 on success
   exceptions: none 
   comments: 
		The File System Hierarchy Standard
		http://www.pathname.com/fhs/

		UUCP Lock Files
		http://docs.freebsd.org/info/uucp/uucp.info.UUCP_Lock_Files.html

		FSSTND
		ftp://tsx-11.mit.edu/pub/linux/docs/linux-standards/fsstnd/

		Proposed Changes to the File System Hierarchy Standard
		ftp://scicom.alphacdc.com/pub/linux/devlock-0.X.tgz

		"UNIX Network Programming", W. Richard Stevens,
		Prentice-Hall, 1990, pages 96-101.

		There is much to do here.

		1) UUCP style locks (done)
			/var/spool/uucp
		2) SVR4 locks
			/var/spool/locks
		3) FSSTND locks (done)
			/var/lock
		4) handle stale locks  (done except kermit locks)
		5) handle minicom lockfile contents (FSSTND?)
			"     16929 minicom root\n"  (done)
		6) there are other Lock conventions that use Major and Minor
		   numbers...
		7) Stevens recommends LCK..<pid>

		most are caught above.  If they turn out to be problematic
		rather than an exercise, we will handle them.

----------------------------------------------------------*/
int uucp_lock( const char *filename, int pid )
{
	char lockfilename[80], lockinfo[12], message[80];
	char name[80];
	int fd;
	struct stat buf;

	sprintf( message, "uucp_lock( %s );\n", filename ); 
	syslog( LOG_INFO, message );

	if ( check_lock_status( filename ) )
	{
		syslog( LOG_INFO, "RXTX uucp check_lock_status true\n" );
		return 1;
	}
	if ( stat( LOCKDIR, &buf ) != 0 )
	{
		syslog( LOG_INFO, "RXTX uucp_lock() could not find lock directory.\n" );
		return 1;
	}
	if ( stat( filename, &buf ) != 0 )
	{
		syslog( LOG_INFO, "RXTX uucp_lock() could not find device.\n" );
		sprintf( message, "uucp_lock: device was %s\n", name );
		syslog( LOG_INFO, message );
		sprintf( message, "Filename is : %s \n", filename );
		syslog( LOG_INFO, message );
		return 1;
	}
	sprintf( lockfilename, "%s/LK.%03d.%03d.%03d",
		LOCKDIR,
		(int) major( buf.st_dev ),
	 	(int) major( buf.st_rdev ),
		(int) minor( buf.st_rdev )
	);
	sprintf( lockinfo, "%10d\n", pid );
	if ( stat( lockfilename, &buf ) == 0 )
	{
		sprintf( message, "RXTX uucp_lock() %s is there\n",
			lockfilename );
		syslog( LOG_INFO, message );
		syslog( LOG_INFO, message );
		return 1;
	}
	fd = open( lockfilename, O_CREAT | O_WRONLY | O_EXCL, 0444 );
	if( fd < 0 )
	{
		sprintf( message,
			"RXTX uucp_lock() Error: creating lock file: %s\n",
			lockfilename );
		syslog( LOG_INFO, message );
		return 1;
	}
	write( fd, lockinfo,11 );
	close( fd );
/*
	setgid( nobody );
	setuid( nobody );
*/
	return 0;
}

/*----------------------------------------------------------
 check_lock_status

   accept:      the lock name in question
   perform:     Make sure everything is sane
   return:      0 on success
   exceptions:  none
   comments:    
----------------------------------------------------------*/
int check_lock_status( const char *filename )
{
	struct stat buf;
	/*  First, can we find the directory? */

	if ( stat( LOCKDIR, &buf ) != 0 )
	{
		syslog( LOG_INFO, "check_lock_status: could not find lock directory.\n" );
		return 1;
	}

	/*  OK.  Are we able to write to it?  If not lets bail */

	if ( check_group_uucp() )
	{
		syslog( LOG_INFO, "check_lock_status: No permission to create lock file.

		please see: How can I use Lock Files with rxtx? in INSTALL\n" );
		return 1;
	}

	/* is the device alread locked */

	if ( is_device_locked( filename ) )
	{
		/* syslog( LOG_INFO, "check_lock_status: device is locked by another application\n" ); */
		return 1;	
	}
	return 0;
	
}

/*----------------------------------------------------------
 fhs_unlock

   accept:      The name of the device to unlock
   perform:     delete the lock file
   return:      none
   exceptions:  none
   comments:    This is for linux only currently.  I see SVR4 does this
                differently and there are other proposed changes to the
		Filesystem Hierachy Standard
----------------------------------------------------------*/
int fhs_unlock( const char *filename, int openpid )
{
	char file[80],*p, msg[80];
	struct stat buf;
	int i;

	i = strlen( filename );
	p = ( char * ) filename + i;
	sprintf( msg, "fhs_unlock %s\n", filename );
	/* syslog( LOG_INFO, msg ); */
	/*  FIXME  need to handle subdirectories /dev/cua/... */
	while( *( p - 1 ) != '/' && i-- != 1 ) p--;
	sprintf( file, "%s/LCK..%s", LOCKDIR, p );
#ifdef asdf
	if ( ! check_lock_status( p ) )
	{
		sprintf( msg, "fhs_unlock %s check_lock_status\n", filename );
		syslog( LOG_INFO, msg );
		return 0;
	}
#endif
	if ( stat( filename, &buf ) != 0 ) 
	{
		/* hmm the file is not there? */
		syslog( LOG_INFO, "uucp_unlock() no such device\n" );
		return(0);
	}

	if( !check_lock_pid( file, openpid ) )
	{
		unlink(file);
		//syslog( LOG_INFO,"fhs_unlock: Removing LockFile\n");
		return( 0 );
	}
	else
	{
		syslog( LOG_INFO,"fhs_unlock: Unable to remove LockFile\n");
		return( 1 );
	}
}

/*----------------------------------------------------------
 uucp_unlock

   accept:     char *filename the device that is locked      
   perform:    remove the uucp lockfile if it exists 
   return:     none 
   exceptions: none 
   comments:   http://docs.freebsd.org/info/uucp/uucp.info.UUCP_Lock_Files.html 
----------------------------------------------------------*/
void uucp_unlock( const char *filename, int openpid )
{
	struct stat buf;
	char file[80], message[80];
	/* FIXME */

	sprintf( message, "uucp_unlock( %s );\n", filename );
	syslog( LOG_INFO, message );

	if ( stat( filename, &buf ) != 0 ) 
	{
		/* hmm the file is not there? */
		syslog( LOG_INFO, "uucp_unlock() no such device\n" );
		return;
	}
	sprintf( file, LOCKDIR"/LK.%03d.%03d.%03d",
		(int) major( buf.st_dev ),
	 	(int) major( buf.st_rdev ),
		(int) minor( buf.st_rdev )
	);
	if ( stat( file, &buf ) != 0 ) 
	{
		/* hmm the file is not there? */
		syslog( LOG_INFO, "uucp_unlock no such lockfile\n" );
		return;
	}
	if( !check_lock_pid( file, openpid ) )
	{ 
		sprintf( message, "uucp_unlock: unlinking %s\n", file );
		syslog( LOG_INFO, message );
		unlink(file);
	}
	else
	{
		sprintf( message, "uucp_unlock: unlinking failed %s\n", file );
		syslog( LOG_INFO, message );
	}
}

/*----------------------------------------------------------
 check_lock_pid

   accept:     the name of the lockfile 
   perform:    make sure the lock file is ours.
   return:     0 on success
   exceptions: none
   comments:   
----------------------------------------------------------*/
int check_lock_pid( const char *file, int openpid )
{
	int fd, lockpid;
	char pid_buffer[12];
	char message[80];

	fd=open( file, O_RDONLY );
	if ( fd < 0 )
	{
		return( 1 );
	}
	if ( read( fd, pid_buffer, 11 ) < 0 )
	{
		close( fd );
		return( 1 );
	}
	close( fd );
	pid_buffer[11] = '\0';
	lockpid = atol( pid_buffer );
	if ( lockpid != openpid )
	{
		if( kill( (pid_t) lockpid, 0 ) && errno==ESRCH )
		{
			return( 0 );
		}
		sprintf(message, "check_lock_pid: lock = %s pid = %i gpid=%i openpid=%i\n",
			pid_buffer, (int) getpid(), (int) getppid(), openpid );
		syslog( LOG_INFO, message );
		return( 1 );
	}
	return( 0 );
}

/*----------------------------------------------------------
 check_group_uucp

   accept:     none
   perform:    check if the user is root or in group uucp
   return:     0 on success 
   exceptions: none 
   comments:   
		This checks if the effective user is in group uucp so we can
		create lock files.  If not we give them a warning and bail.
		If its root we just skip the test.

		if someone really wants to override this they can use the			USER_LOCK_DIRECTORY --not recommended.

		In a recent change RedHat 7.2 decided to use group lock.
		In order to get around this we just check the group id
		of the lock directory.
----------------------------------------------------------*/
int check_group_uucp()
{
#ifndef USER_LOCK_DIRECTORY
	int group_count;
	struct passwd *user = getpwuid( geteuid() );
	struct stat buf;
	char msg[80];
	gid_t list[ NGROUPS_MAX ];

	if( stat( LOCKDIR, &buf) )
	{
		sprintf( msg, "check_group_uucp:  Can not find Lock Directory: %s\n", LOCKDIR );
		syslog( LOG_INFO, msg );
		return( 1 );
	}
	group_count = getgroups( NGROUPS_MAX, list );
	list[ group_count ] = geteuid();

	if( user->pw_gid )
	{
		while( group_count >= 0 && buf.st_gid != list[ group_count ] )
		{
  			group_count--; 
		}
		if( buf.st_gid == list[ group_count ] )
			return 0;
		sprintf( msg, "%i %i\n", buf.st_gid, list[ group_count ] );
		syslog( LOG_INFO, msg );
		syslog( LOG_INFO, UUCP_ERROR );
		return 1;
	}
	return 0;
/*
	if( strcmp( user->pw_name, "root" ) )
	{
		while( *g->gr_mem )
		{
			if( !strcmp( *g->gr_mem, user->pw_name ) )
			{
				break;
			}
			(void) *g->gr_mem++;
		}
		if( !*g->gr_mem )
		{
			syslog( LOG_INFO, UUCP_ERROR );
			return 1;
		}
	}
*/
#endif /* USER_LOCK_DIRECTORY */
	return 0;
}

/*----------------------------------------------------------
 The following should be able to follow symbolic links.  I think the stat
 method used below will work on more systems.  This was found while looking
 for information.

 * realpath() doesn't exist on all of the systems my code has to run
   on (HP-UX 9.x, specifically)
----------------------------------------------------------
int different_from_LOCKDIR(const char* ld)
{
	char real_ld[MAXPATHLEN];
	char real_LOCKDIR[MAXPATHLEN];
	if (strncmp(ld, LOCKDIR, strlen(ld)) == 0)
		return 0;
	if (realpath(ld, real_ld) == NULL)
		return 1;
	if (realpath(LOCKDIR, real_LOCKDIR) == NULL)
		return 1;
	if (strncmp(real_ld, real_LOCKDIR, strlen(real_ld)) == 0)
		return 0;
	else
		return 1;
}
*/

/*----------------------------------------------------------
 is_device_locked

   accept:      char * filename.  The device in question including the path.
   perform:     see if one of the many possible lock files is aready there
		if there is a stale lock, remove it.
   return:      1 if the device is locked or somethings wrong.
		0 if its possible to create our own lock file.
   exceptions:  none
   comments:    check if the device is already locked
----------------------------------------------------------*/
int is_device_locked( const char *port_filename )
{
	const char *lockdirs[] = { "/etc/locks", "/usr/spool/kermit",
		"/usr/spool/locks", "/usr/spool/uucp", "/usr/spool/uucp/",
		"/usr/spool/uucp/LCK", "/var/lock", "/var/lock/modem",
		"/var/spool/lock", "/var/spool/locks", "/var/spool/uucp",
		LOCKDIR, NULL
	};
	const char *lockprefixes[] = { "LCK..", "lk..", "LK.", NULL }; 
	char *p, file[80], pid_buffer[20], message[80];
	int i = 0, j, k, fd , pid;
	struct stat buf;
	struct stat buf2;

	j = strlen( port_filename );
	p = ( char * ) port_filename+j;
	while( *( p-1 ) != '/' && j-- !=1 ) p--;

	while( lockdirs[i] )
	{
		/*
		   Look for lockfiles in all known places other than the
		   defined lock directory for this system
		   report any unexpected lockfiles.

		   Is the suspect lockdir there?
		   if it is there is it not the expected lock dir?
		*/
		if( !stat( lockdirs[i], &buf2 ) &&
			strncmp( lockdirs[i], LOCKDIR, strlen( lockdirs[i] ) ) )
		{
			j = strlen( port_filename );
			p = ( char *  ) port_filename + j;
		/*
		   SCO Unix use lowercase all the time
			taj
		*/
			while( *( p - 1 ) != '/' && j-- != 1 )
			{
#if defined ( __unixware__ )
				*p = tolower( *p );
#endif /* __unixware__ */
				p--;
			}
			k=0;
			while ( lockprefixes[k] )
			{
				/* FHS style */
				sprintf( file, "%s/%s%s", lockdirs[i],
					lockprefixes[k], p );
				if( stat( file, &buf ) == 0 )
				{
					sprintf( message, UNEXPECTED_LOCK_FILE,
						file );
					syslog( LOG_INFO, message );
					return 1;
				}

				/* UUCP style */
				stat(port_filename , &buf );
				sprintf( file, "%s/%s%03d.%03d.%03d",
					lockdirs[i],
					lockprefixes[k],
					(int) major( buf.st_dev ),
					(int) major( buf.st_rdev ),
					(int) minor( buf.st_rdev )
				);
				if( stat( file, &buf ) == 0 )
				{
					sprintf( message, UNEXPECTED_LOCK_FILE,
						file );
					syslog( LOG_INFO, message );
					return 1;
				}
				k++;
			}
		}
		i++;
	}

	/*
		OK.  We think there are no unexpect lock files for this device
		Lets see if there any stale lock files that need to be
		removed.
	*/
		 
#ifdef FHS
	/*  FHS standard locks */
	i = strlen( port_filename );
	p = ( char * ) port_filename + i;
	while( *(p-1) != '/' && i-- != 1)
	{
#if defined ( __unixware__ )
		*p = tolower( *p );
#endif /* __unixware__ */
		p--;
	}
	sprintf( file, "%s/%s%s", LOCKDIR, LOCKFILEPREFIX, p );
#else 
	/*  UUCP standard locks */
	sprintf( file, "%s/LK.%03d.%03d.%03d",
		LOCKDIR,
		(int) major( buf.st_dev ),
 		(int) major( buf.st_rdev ),
		(int) minor( buf.st_rdev )
	);

#endif /* FHS */

	if( stat( file, &buf ) == 0 )
	{

		/* check if its a stale lock */
		fd=open( file, O_RDONLY );
		read( fd, pid_buffer, 11 );
		/* FIXME null terminiate pid_buffer? need to check in Solaris */
		close( fd );
		sscanf( pid_buffer, "%d", &pid );
		sprintf( message, "found lock for %s with pid %i\n", file, pid );
		/* syslog( LOG_INFO, message ); */

		if( kill( (pid_t) pid, 0 ) && errno==ESRCH )
		{
			sprintf( message,
				"RXTX Warning:  Removing stale lock file. %s\n",
				file );
			syslog( LOG_INFO, message );
			if( unlink( file ) != 0 )
			{
				snprintf( message, 80, "RXTX Error:  Unable to \
					remove stale lock file: %s\n",
					file
				);
				syslog( LOG_INFO, message );
				return 0;
			}
		}
		else
		{
			sprintf( message, "could not kill %i\n", pid );
			/* syslog( LOG_INFO, message ); */
			return 1;
		}
	}
	return 0;
}
int init( void )
{
	pid_t pid;

	if(  ( pid = fork() ) < 0 )
	{
		return(-1);
	} 
	else if ( pid != 0 )
	{
		exit( 0 );
	}
	setsid();
	chdir("/");
	umask( 0 );
	return( 0 );
}
int process_requests( )
{
	for(;;)
	{
		char str[80];
		char str2[80];
		char *p;
		int ret;

		ret = read( 1, str, 80 );
		
		if( ret < 80 && ret > 1 )
			str[ret] = '\0';
		else
			str[79] = '\0';
		if ( !strncasecmp( str, "quit", 4 ) )
		{
			sprintf( str, M221 );
			write( 0, str, strlen( str ) );
			return( 1 );
		}
		else if( !strncasecmp( str, "lock ", 4 ) )
		{
			char *q,*r;
			p = str + 5;
			q=p;
			while( *q != ' ' && *q != '\0' )
				q++;
			if ( *q == '\0' )
			{
				write( 0, M450, strlen( M450 ) );
				return(0);
			}
			*q = '\0';
			q++;
			r=q; 
			while( *r != '\n' && *r != '\0' )
				r++;
			if( *r == '\n' )
				*r = '\0';
			if ( LOCK( p, atoi( q ) ) )
			{
				write( 0, M450, strlen( M450 ) );
			}
			else
			{
				write( 0, M200, strlen( M200 ) );
			}
		}
		else if( !strncasecmp( str, "unlock ", 6 ) )
		{
			char *q,*r;
			p = str + 7;
			q=p;
			while( *q != ' ' && *q != '\0' )
				q++;
			if ( *q == '\0' )
			{
				write( 0, "q=0\n", strlen( "q=0\n" ) );
				write( 0, M450, strlen( M450 ) );
				return(0);
			}
			*q = '\0';
			q++;
			r=q; 
			while( *r != '\n' && *r != '\0' )
				r++;
			if( *r == '\n' )
				*r = '\0';
			if ( UNLOCK( (const char *) p, atoi( q ) ) )
			{
				write( 0, M450, strlen( M450 ) );
			}
			else
			{
				write( 0, M200, strlen( M200 ) );
			}
		}
		else
		{
			str[ret-2]='\0';
			sprintf( str2, M500 );
			write( 0, str2, strlen(str2));
		}
		return( 0 );
	}
}
int main( int argc, char **argv )
{
	char str[128];
	char portstr[7];
	struct sockaddr *cliaddr;
	struct sockaddr_in *sin;
	socklen_t len;

	openlog( argv[0], LOG_PID, 0 );
	cliaddr= malloc( 128 );
	len = 128;
	sin = ( struct sockaddr_in * ) cliaddr;
	cliaddr= malloc( 128 );
	gethostname( hostname, 255 );
	if ( !inet_ntop( AF_INET, &(sin->sin_addr), str, sizeof(str) ) )
		str[0] = '\0';
	if ( !ntohs( sin->sin_port ) )
	{
		snprintf( portstr, sizeof( portstr ), ".%d", ntohs( sin->sin_port) );
		strcat( str, portstr);
	}
	sprintf( str,  M220 );
	//syslog( LOG_INFO, str );
	write( 0, str, strlen(str) );
	for(;;)
	{
		if (process_requests( ))
		{
			goto exit;
		}
	}
	syslog( LOG_INFO, "Lock Daemon Shutting down" );
exit:
	closelog();
	exit(0);
}
