/*
* Copyright 2004 Perforce Software. All rights reserved.
*
* This file is part of Perforce - the FAST SCM System.
*/
/*
* ticket.cc - get/replace/delete stored tickets from ticketfile.
*
* Perforce tickets were introduced in 2003.2 (undoc). Initially only
* NT supported seemless setting/getting of these tickets although this
* was limited to one server through the P4PASSWD registry variable.
*
* 2004.1 introduces a ticket file:
*
* $USERPROFILE/p4tickets.txt -- windows(NT)
* $HOME/.p4tickets -- UNIX
* $P4TICKETS -- user-defined fullpath to file
*
* This code manipulates the users ticket file. Tickets are issued when the
* user runs the 'p4 login' command, they are deleted by running 'p4 logout'
* and they do expire after a default 12 hours or as set by group access.
*
* Any operation on the file will first begin by reading the current ticket
* file and populating an in-memory array. Typically this will only contain
* a few entries. The array is modified and written back out to the file
* using Rename() if a delete/replace/insert operation has occurred.
*
* Currently only one operation is allowed per object creation.
*/
# include <stdhdrs.h>
# include <strbuf.h>
# include <error.h>
# include <vararray.h>
# include <pathsys.h>
# include <filesys.h>
# include <enviro.h>
# include "ticket.h"
# ifdef OS_NT
# include <windows.h>
# endif
struct TicketItem {
StrBuf port;
StrBuf user;
StrBuf ticket;
int deleted;
};
/*
* TicketTable -- array of available tickets
*/
class TicketTable : public VarArray {
public:
~TicketTable();
TicketItem *GetItem( const StrRef &port, const StrRef &user );
void DeleteItem( const StrRef &port, const StrRef &user );
void PutItem( const StrRef &port, const StrRef &user,
const StrRef &ticket );
void AddItem( const StrRef &port, const StrRef &user,
const StrRef &ticket );
};
Ticket::Ticket( const StrPtr *path )
{
ticketTab = 0;
ticketFile = 0;
this->path = path;
}
Ticket::~Ticket()
{
delete ticketTab;
delete ticketFile;
}
int
Ticket::Init()
{
// only one operation allowed, bail if already initialized
if( ticketFile != 0 )
return 1;
if( !ticketTab )
ticketTab = new TicketTable;
if( !path->Length() )
return 1;
ticketFile = FileSys::Create( (FileSysType)(FST_TEXT|FST_L_LFCRLF) );
ticketFile->Set( *path );
// Make sure user-defined ticket is not a directory
int stat = ticketFile->Stat();
if( ( stat & FSF_EXISTS ) && ( stat & FSF_DIRECTORY ) )
return 1;
return 0;
}
void
Ticket::List( StrBuf &b )
{
if( Init() )
return;
Error e;
ReadTicketFile( &e );
if( e.Test() )
return;
TicketItem *t;
for( int i = 0; i < ticketTab->Count(); i++ )
{
t = (TicketItem *)ticketTab->Get( i );
b << t->port << " (" << t->user << ") " << t->ticket << "\n";
}
}
void
Ticket::ListUser( const StrPtr &u, StrBuf &b )
{
if( Init() )
return;
Error e;
ReadTicketFile( &e );
if( e.Test() )
return;
TicketItem *t;
for( int i = 0; i < ticketTab->Count(); i++ )
{
t = (TicketItem *)ticketTab->Get( i );
if( u == t->user )
b << t->port << " " << t->ticket << "\n";
}
}
char *
Ticket::GetTicket( StrPtr &port, StrPtr &user )
{
if( Init() )
return( ( char *) 0 );
Error e;
ReadTicketFile( &e );
if( e.Test() )
return( ( char * ) 0 );
StrBuf validPort;
// prefix local ports with localhost:
if( !strchr( port.Text(), ':' ) )
{
validPort.Set( "localhost:" );
validPort.Append( port.Text() );
}
else
validPort.Set( port.Text() );
TicketItem *t = ticketTab->GetItem( validPort, user );
if( t )
return( t->ticket.Text() );
else
return( ( char *) 0 );
}
void
Ticket::UpdateTicket(
const StrPtr &port,
StrPtr &user,
StrPtr &ticket,
int remove,
Error *e )
{
if( Init() )
return;
FileSys *lock;
lock = FileSys::CreateLock( ticketFile, e );
if( e->Test() )
return;
ReadTicketFile( e );
if( e->Test() )
{
delete lock;
return;
}
StrBuf validPort;
// prefix local ports with localhost:
if( !strchr( port.Text(), ':' ) )
{
validPort.Set( "localhost:" );
validPort.Append( port.Text() );
}
else
validPort.Set( port.Text() );
if( remove )
ticketTab->DeleteItem( validPort, user );
else
ticketTab->PutItem( validPort, user, ticket );
WriteTicketFile( e );
delete lock;
}
void
Ticket::ReadTicketFile( Error *e )
{
if( !( ticketFile->Stat() & FSF_EXISTS ) )
return;
ticketFile->Open( FOM_READ, e );
if( e->Test() )
return;
// Special markers used by trust files.
StrRef dummy( "**++**" );
StrRef alt( "++++++" );
StrRef wild( "******" );
StrBuf line, port, user;
// read contents of ticket file into array
while( ticketFile->ReadLine( &line, e ) )
{
char *equals, *colon;
if( !( equals = strchr( line.Text(), '=' ) ) )
continue;
port.Set( line.Text(), equals++ - line.Text() );
int isTrust = !strncmp( equals, dummy.Text(), dummy.Length() ) ||
!strncmp( equals, alt.Text(), alt.Length() ) ||
!strncmp( equals, wild.Text(), wild.Length() );
colon = isTrust ? strchr( equals, ':' ) : strrchr( equals, ':' );
if( !colon )
continue;
user.Set( equals, colon - equals );
ticketTab->AddItem( port, user, colon + 1 );
}
ticketFile->Close( e );
}
void
Ticket::WriteTicketFile( Error *e )
{
// create a temporary file
FileSys *tmpFile = FileSys::CreateTemp( FST_TEXT );
tmpFile->MakeLocalTemp( path->Text() );
tmpFile->Perms( FPM_RW );
tmpFile->Open( FOM_WRITE, e );
if( e->Test() )
{
delete tmpFile;
return;
}
TicketItem *t;
StrBuf line;
// write out file contents to temporary file
for( int i = 0; i < ticketTab->Count(); i++ )
{
t = (TicketItem *)ticketTab->Get( i );
if( t->deleted )
continue;
line.Clear();
line << t->port << "=" << t->user << ":" << t->ticket << "\n";
tmpFile->Write( &line, e );
if( e->Test() )
break;
}
// rename to the target ticket file
tmpFile->ClearDeleteOnClose();
tmpFile->Close( e );
tmpFile->Rename( ticketFile, e );
// make the file readonly by owner
ticketFile->Chmod( FPM_ROO, e );
delete tmpFile;
}
TicketTable::~TicketTable()
{
for( int i = 0; i < Count(); i++ )
delete (TicketItem *)Get(i);
}
void
TicketTable::AddItem(
const StrRef &port,
const StrRef &user,
const StrRef &ticket )
{
TicketItem *t = new TicketItem;
t->port.Set( port );
t->user.Set( user );
t->ticket.Set( ticket.Text() );
t->deleted = 0;
VarArray::Put( t );
}
void
TicketTable::PutItem(
const StrRef &port,
const StrRef &user,
const StrRef &ticket )
{
TicketItem *t = GetItem( port, user );
if( t )
{
t->ticket.Set( ticket );
// we need to set user just in case source or target user
// was a wild card.
t->user.Set( user );
}
else
AddItem( port, user, ticket );
}
void
TicketTable::DeleteItem(
const StrRef &port,
const StrRef &user )
{
TicketItem *t = GetItem( port, user );
if( t )
t->deleted = 1;
}
TicketItem *
TicketTable::GetItem(
const StrRef &port,
const StrRef &user )
{
TicketItem *t;
StrRef userWild( "******" );
for( int i = 0; i < Count(); ++i )
{
t = (TicketItem * )Get(i);
if( t->port.CCompare( port ) )
continue;
if( !t->user.Compare( user ) ||
!t->user.Compare( userWild ) ||
!user.Compare( userWild ) )
{
return( t );
}
}
return( ( TicketItem * ) 0 );
}