#define NEED_GETUID #include <p4wp4.h> #include <assert.h> #ifndef OS_NT #include <pwd.h> #include <grp.h> #endif #include "p4wProcess.h" #include "p4wThread.h" #include "p4wCustomVer.h" // for appending a custom version string #define PRINTFBUFSIZE 4096 int ChoosePort(); /* Ident strings */ Ident ident = { IdentMagic "P4Web" "/" ID_OS "/" ID_REL "/" ID_PATCH, ID_Y "/" ID_M "/" ID_D }; /* Security Flags - sec_flags (i.e. SEC_FLAGS) and sec_flg MUST be kept in sync!!! */ char sec_flags[] = SEC_FLAGS; int sec_flg[] = { FLG_DISALLOW_CHG_CLIENTS, // a FLG_ALLOW_UNOWNED_CLIENTS, // B FLG_ALLOW_ALL_CLIENTS, // C FLG_ALLOW_CREATE_CLIENTS, // D FLG_ALLOW_EDIT_CLIENTROOT, // E FLG_SHOW_STARTUP_FLAGS, // F FLG_ALLOW_NOHOST_CLIENTS, // G FLG_DISALLOW_REVHIST_INTEG, // h FLG_DISALLOW_INTEG, // i FLG_ALLOW_CR8CHG_SYMLINKS, // L FLG_ALLOW_NULL_ROOT, // N FLG_ALLOW_DOTDOT_ROOT, // P FLG_DONT_SHOW_REVHIST, // q FLG_DISALLOW_RAW_CMDS, // r FLG_USE_HTTPS, // s FLG_DONT_SHOW_WKSP_LINKS, // w FLG_DONT_SHOW_EMAIL_ADDR, // x 0, FLG_USE_HTTPS, FLG_USE_HTTPS }; // must be 0, FLG_USE_HTTPS, FLG_USE_HTTPS! // last line used to check that there are same # valid entries in both arrays /* Usage info */ static ErrorId UserNotFound = { ErrorOf( ES_SERVER, 0, E_FAILED, EV_USAGE, 1 ), "Unknown user: '%user%'." }; static ErrorId CantSwitchUser = { ErrorOf( ES_SERVER, 0, E_FAILED, EV_USAGE, 1 ), "Can't run as user '%user%': not started by root." }; ErrorId usage = { ErrorOf( 0, 0, 0, 0, 0 ), "p4web -h for usage." }; static const char long_usage[] = "Usage:\n" "\n" " p4web [ options ]\n" "\n" " starts a P4Web assigned to the specified URL port ('-w' option).\n" " Default is 8080 on Mac and Windows, user-specific number on UNIX.\n" "\n" " P4Web options:\n" " -h -? print this message\n" " -w number p4web port number\n" " -v level debug modes\n" " -V print P4Web version\n" " -b run as browse-only 'Viewer' without authentication\n" " -B run as browse-only 'Viewer' using authentication\n" " -d path specify default file filter\n" " -l restrict to local host\n" //" -f custom-file path to customization info file\n" " -T tab-letters first letter of tabs to show (default fpsblcuj)\n" " -r path only allow clients whose roots begin with path\n" " -m MIME-file specify a custom MIME type map file\n" " -s sec-flags security opts: %s (*=remote users only)\n" " a = Don't allow switching clients *\n" " B = Allow unowned clients *\n" " C = Allow all clients *\n" " D = Allow creating clients *\n" " E = Allow editing client's Root/Altroots/Owner *\n" " F = Show startup flags *\n" " G = Allow clients with empty Host field *\n" " h = Don't allow displaying branching history *\n" " i = Don't allow integ *\n" " L = Allow creating/changing symlinks *\n" " N = Allow clients with null roots *\n" " P = Allow clients with .. in their roots *\n" " q = Link directly to file from Path Browser page\n" " r = Don't allow running raw p4 commands *\n" " s = Use https\n" " w = Don't show Workspace links *\n" " x = Don't show Email addresses *\n" // " -J mode javascript mode (0=disabled, 1=netscape, 2=MSIE, 3=Safari\n" // " default enabled, browser auto-detected)\n" #ifndef OS_NT " -U username When started by root, drop privileges and run as\n" " username after opening p4web port\n" #endif "\n" " Perforce configuration options:\n" " -p port server port (default $P4PORT)\n" " -c client client name (default $P4CLIENT)\n" " -u user user name (default $P4USER)\n" " -M allow multiple users (-u and -c ignored)\n" " -g group allow only users in specified group (-M only)\n" " -i run for inetd (socket on stdin/stdout)\n" " -L logfilename log http requests to file (default no log)\n" " -H host host name for client use (default $P4HOST)\n" " -C charset character set (default $P4CHARSET) \n" " -P password password in non-authenticated 'Viewer' mode only\n" " (default $P4PASSWD)\n" "\n"; #ifndef OS_NT int BecomeUser( const StrPtr *user, Error *e ) { StrBuf t; struct passwd *pw = getpwnam( user->Text() ); if( !pw ) { e->Set( UserNotFound ) << user->Text(); return 0; } // // Replace key environment variables with user-specific values. Those // that we replace are those defined by POSIX, plus $USER and $SHELL if // they're already defined. Note that PATH is explicitly _not_ modified. // // Note setenv() isn't available on older platforms, so we're using // the old clunky putenv() here. // StrBuf tev; tev << "HOME=" << pw->pw_dir; putenv( strdup( tev.Text() ) ); tev.Clear(); tev << "LOGNAME=" << pw->pw_name; putenv( strdup( tev.Text() ) ); tev.Clear(); if( getenv( "USER" ) ) { tev << "USER=" << pw->pw_name; putenv( strdup( tev.Text() ) ); tev.Clear(); } if( getenv( "SHELL" ) ) { tev << "SHELL=" << pw->pw_shell; putenv( strdup( tev.Text() ) ); tev.Clear(); } if( initgroups( user->Text(), pw->pw_gid ) < 0 ) { e->Sys( "initgroups", "" ); return 0; } // // Now that the supplementary groups are done, do the main // group, and the user-id // if( setgid( pw->pw_gid ) < 0 ) { t << pw->pw_gid; e->Sys( "setgid", t.Text() ); return 0; } if( setuid( pw->pw_uid ) < 0 ) { t << pw->pw_uid; e->Sys( "setuid", t.Text() ); return 0; } return 1; } #endif Threading * gThreading = NULL; int globalCancel = 0; int ignoreBrowserAuth = 0; ErrorLog *reportLog = 0; StrBuf szInitTime; # ifdef OS_NT int p4wprintf( char * first, ... ); # define printf p4wprintf # define MAIN( argc, argv ) nt_main( DWORD argc, char **argv ) int asService = 0; int nt_main( DWORD argc, char **argv ); static NtService ntService; int main( DWORD argc, char **argv ) { if( argc ) { char *s, *t = argv[0]; if( ( s = strrchr( t, '/' ) ) || ( s = strrchr( t, '\\' ) ) ) t = s + 1; if( strlen( t ) > 5 ) { StrBuf lowered; lowered.Set( t, 6 ); StrOps::Lower( lowered ); if( !strcmp( lowered.Text(), "p4webs" ) ) { Error e; // No logfile defined, begin with the // Windows Event log. AssertLog.SetSyslog(); asService = 1; ntService.Start( nt_main, t, &e ); AssertLog.Abort( &e ); return(0); } } } // Otherwise we are not starting as a service. nt_main( argc, argv ); return(0); } # else # define MAIN( argc, argv ) main( int argc, char **argv ) # endif int MAIN( cargc, cargv ) { // Temporary vars Error e; // General vars. StrBuf port; Options opts; StrBuf p4webserviceflags; int isService = 0; int isMultiUser = 0; /* Arg processing */ int i; StrPtr *s; // xlate args into StrPtrs (ugh) // cargc is a ulong on NT int argc = cargc; StrRef *oargv = new StrRef[ argc ]; StrPtr *argv = oargv; for( int ac = 0; ac < argc; ac++ ) argv[ac] = StrRef( cargv[ac] ); // // host & port is needed for LOCATION directives later by p4wRequest StrBuf hostPort; // // variables set by cmd line flags ClientApi tmpClient; int isHTTPS = 0; int securityFlags = 0; StrBuf logfile; // // Parse up options --argc; ++argv; opts.Parse( argc, argv, OPTSFLAGS, OPT_ANY, usage, &e ); # ifdef OS_NT // // If this is an NT service, pass this along to the // p4wThread who needs to know. Also, we need to check // the service's registry area for the value of // P4WEBSERVICEFLAGS and any overrides in P4USER, // P4CLIENT, P4PORT, etc. whenever we try to access as // it if we are running as an NT service. if( asService ) { Enviro enviro; StrRef service( cargv[0] ); enviro.BeServer( &service ); const char *serviceflags; const char *user; const char *port; const char *client; const char *host; const char *security; const char *grp; const char *log; if( serviceflags = enviro.Get( "P4WEBSERVICEFLAGS" ) ) { p4webserviceflags.Set( serviceflags ); char *cv[20]; int nw = StrOps::Words( p4webserviceflags, serviceflags, cv, 20 ); char ***pcv = (char ***)&cv; opts.Parse( nw, (char **&)pcv, OPTSFLAGS, OPT_ANY, usage, &e ); } if( port = enviro.Get( "P4PORT" ) ) { tmpClient.SetPort( port ); } if( user = enviro.Get( "P4USER" ) ) { tmpClient.SetUser( user ); } if (user && !strcmp(user, "@")) { isMultiUser = 1; tmpClient.SetClient( "(not set)" ); } else if( client = enviro.Get( "P4CLIENT" ) ) { tmpClient.SetClient( client ); } if( host = enviro.Get( "P4HOST" ) ) { tmpClient.SetHost( host ); } if( security = enviro.Get( "P4WEBSECURITY" ) ) { isHTTPS = strchr(security, 's') ? 1 : 0; for (i = -1; sec_flags[++i]; ) securityFlags |= strchr(security, sec_flags[i]) ? sec_flg[i] : 0; } if( log = enviro.Get( "P4WEBLOG" ) ) { logfile << log; } } #endif // Put up the help if there was an error parsing the options // OR they added some non-option text to the command line // if( e.Test() || argc ) { printf( (char *)long_usage, SEC_FLAGS ); return 1; } // // -B & -b are mutually exclusive: flag an error and exit // if both were entered. if( opts[ 'b' ] && opts[ 'B' ] ) { printf( "\nError: Cannot choose both -b and -B options.\n" "\nPlease select '-b' for Viewer mode without\n" "authentication, or '-B' for Viewer mode using\n" "user authentication.\n"); return 1; } if( (opts[ 'M' ] && opts[ 'b' ]) || (opts[ 'M' ] && opts[ 'B' ]) ) { printf( "\nError: -M is incompatible with -b and with -B options.\n" "\nPlease select '-M' for Multiuser mode,\n" "or '-B' or '-b' for Viewer mode.\n"); return 1; } // // -P is only valid with -b or the undocumented -a options. // Generate warning and continue if flag was misused. if( opts[ 'P' ] && !opts[ 'b' ] && !opts[ 'a' ] ) { printf( "\nWarning: -P is invalid in this context" " and will be ignored.\n" "\nThe -P option is only valid when used\n" "with the -b option for Viewer mode without\n" "authentication.\n" ); } // // Process any options we can right here if( opts[ 'V' ] ) { StrBuf s; ident.GetMessage( &s ); #ifdef POSTPENDVER s << " " << POSTPENDVER; #ifdef LASTP4CHANGE s << LASTP4CHANGE; #endif char *p = s.Text(); while (p = strchr(p, '<')) { char *q = strchr(p, '>'); if (q) strcpy(p, q+1); else break; } s.SetLength(); s << "\n"; #endif printf( "%s", s.Text() ); return 1; } if( opts[ 'h' ] || opts[ '?' ] ) { printf( (char *)long_usage, SEC_FLAGS ); return 1; } if ( s = opts[ 's' ] ) { isHTTPS = strchr(s->Text(), 's') ? 1 : 0; for (i = -1; sec_flags[++i]; ) securityFlags |= strchr(s->Text(), sec_flags[i]) ? sec_flg[i] : 0; assert(!sec_flg[i]); if (!securityFlags || strchr(s->Text(), '-')) { printf( "\nError: '-s' must have valid argument(s).\n\n"); printf( (char *)long_usage, SEC_FLAGS ); return 1; } if (securityFlags & FLG_DISALLOW_CHG_CLIENTS) { if (securityFlags & FLG_ALLOW_ALL_CLIENTS || securityFlags & FLG_ALLOW_CREATE_CLIENTS || securityFlags & FLG_ALLOW_EDIT_CLIENTROOT) { printf( "\nError: '-saC' '-saD' and '-saE' are invalid security flag combinations.\n" "\nPlease select '-sa' to disallow switching clients,\n" "or '-sC' '-sD' '-sE' or a combination such as '-sCDE'.\n"); return 1; } } if (securityFlags & FLG_USE_HTTPS) { if (opts[ 'l' ]) { printf( "\nError: '-ss' is incompatible with -l.\n" "\nIn https mode, all connections are considered to be remote," "\nso setting both would mean no connections would be allowed.\n"); return 1; } } } if( opts[ 'M' ] && (securityFlags & FLG_DISALLOW_CHG_CLIENTS) ) { printf( "\nError: -M is incompatible with the -sa option.\n" "\nPlease select '-M' for Multiuser mode,\n" "or '-sa' to disallow switching clients.\n"); return 1; } if( opts[ 'w' ] && opts[ 'i' ] ) { printf( "\nError: -w and -i are mutually exclusive.\n" "\nPlease select '-w' to provide a port number,\n" "or '-i' to run via an inetd.\n"); return 1; } // // If there was a port passed in on the command-line // const char *portno; if ( opts[ 'i' ] ) { // run via inetd port = "rsh"; } else { Enviro enviro; HostEnv h; StrBuf cwd; // // If we are running as a service, get parameters from // the service area of the registry #ifdef OS_NT if( asService ) { isService = 1; StrRef service( cargv[0] ); enviro.BeServer( &service ); } else { h.GetCwd( cwd ); enviro.Config( cwd ); } #else h.GetCwd( cwd ); enviro.Config( cwd ); #endif if ( opts[ 'w' ] ) { // p4web-specific options and arguments port = opts[ 'w' ]->Text(); } else { portno = enviro.Get( "P4WEBPORT" ); if( portno ) port.Set( portno ); } } // // Debugging for( i = 0; s = opts.GetValue( 'v', i ); i++ ) p4debug.SetLevel( s->Text() ); // // Is the port set? // if ( port.Length() == 0 ) { // // Choose a port based first on user id and then username. // #if defined ( OS_NT ) int chosenPort = 8080; #else int chosenPort = ChoosePort(); #endif if ( chosenPort ) { port << chosenPort; } } // // And *still* no port has been set // if ( port.Length() == 0 ) { printf( "Error: Cannot choose a port number based on your uid or your username.\n" "Please specify one by adding -w <port_number> after \'p4web\' on the command line.\n" ); return 1; } if ( !opts[ 'i' ] ) { // // Test to see if we can use this port NetEndPoint *endpoint = NetEndPoint::Create(port.Text(), &e); AssertLog.Abort( &e ); // // If this port is already in use, abort NetTransport *nt = endpoint->Connect(&e); if( !e.Test() ) { delete nt; delete endpoint; printf( "Error: port %s is already in use.\n" "Please restart p4web using a different port via the -w option, as in:\n" " p4web -w <portnumber>\n", port.Text()); return 0; } else { e.Clear(); } delete nt; delete endpoint; } // // Create our endpoint NetEndPoint *endpoint = NetEndPoint::Create(port.Text(), &e); // // Listen on the port endpoint->Listen(&e); AssertLog.Abort( &e ); #ifndef OS_NT // // Change user id if we're root // if( !geteuid() && ( s = opts[ 'U' ] ) ) { BecomeUser( s, &e ); } else if( !geteuid() ) { e.Set( E_FATAL, "Must supply a username to switch to when started by root.\n" "\tPass the username with the -U flag" ); } else if( s = opts[ 'U' ] ) { e.Set( CantSwitchUser ) << s->Text(); } AssertLog.Abort( &e ); #endif // // Get the log file opened if( s = opts[ 'L' ] ) { logfile.Set(s); } else { Enviro enviro; const char *log; if( log = enviro.Get( "P4WEBLOG" ) ) logfile.Set(log); } if (logfile.Length()) { reportLog = new ErrorLog; reportLog->SetTag( "P4Web" ); reportLog->SetLog( logfile.Text() ); } // // Identify this p4web's client, and instruct user // how to connect from browser. if( s = opts[ 'p' ] ) tmpClient.SetPort( s ); if((s = opts[ 'M' ]) || isMultiUser ) { tmpClient.SetUser( "@" ); isMultiUser = 1; } else if( s = opts[ 'u' ] ) { tmpClient.SetUser( s ); } if( isMultiUser || !strcmp(tmpClient.GetUser().Text(), "@") ) { tmpClient.SetClient( "(not set)" ); } else if( s = opts[ 'c' ] ) { tmpClient.SetClient( s ); } if( isMultiUser && (opts[ 'u' ] || opts[ 'c' ]) ) printf( "Starting in multiuser mode: ignoring -u and -c\n" ); // Removed ZeroConf support (job059581) if( opts[ '0' ] || opts.GetValue( 'I', 'n', 0 ) || opts.GetValue( 'I', 'd', 0 ) ) { printf( (char *)long_usage, SEC_FLAGS ); return 1; } StrBuf group; if( s = opts[ 'g' ] ) group << s; if ( !opts[ 'i' ] ) { char printfbuf[PRINTFBUFSIZE]; sprintf(printfbuf, "\nP4Web started on host %s.\n", tmpClient.GetHost().Text()); sprintf(printfbuf + strlen(printfbuf), "User: %s, Client: %s, Port: %s\n", opts[ 'M' ] ? "(Multiple)" : tmpClient.GetUser().Text(), tmpClient.GetClient().Text(), tmpClient.GetPort().Text()); if (group.Length()) sprintf(printfbuf + strlen(printfbuf), "Restricted to group: %s\n", group.Text()); if (logfile.Length()) sprintf(printfbuf + strlen(printfbuf), "Log: %s\n", logfile.Text()); sprintf(printfbuf + strlen(printfbuf), "Version: P4Web/%s/%s/%s\n", ID_REL, ID_OS, ID_PATCH); printf(printfbuf); #ifdef POSTPENDVER StrBuf custver; custver << POSTPENDVER; #ifdef LASTP4CHANGE custver << LASTP4CHANGE; #endif char *p = custver.Text(); while (p = strchr(p, '<')) { char *q = strchr(p, '>'); if (q) strcpy(p, q+1); else break; } custver.SetLength(); printf("\t %s\n", custver.Text()); #endif // // The mac will not work in all browsers if the user types in localhost (Netscape) // if( (s = opts[ 's' ]) && strchr(s->Text(), 's') ) { printf( "\nPlease point your SSL Wrapper to port:\n" " localhost:%s\n" "or to\n" " %s:%s\n", port.Text(), tmpClient.GetHost().Text(), port.Text()); } else if( s = opts[ 'l' ] ) { printf( "\nPlease point your browser to:\n" " http://localhost:%s/\n", port.Text()); } else { printf( "\nIf your web browser is running on %s,\n" " for speed purposes, please point your browser to:\n" " http://localhost:%s/\n" "\nbut if your web browser is running on a machine other than %s,\n" "please point your browser to:\n" " http://%s:%s/\n", tmpClient.GetHost().Text(), port.Text(), tmpClient.GetHost().Text(), tmpClient.GetHost().Text(), port.Text()); } // Flush before fork() fflush( stdout ); } hostPort.Set(tmpClient.GetHost()); hostPort << ":" << port; char *p; szInitTime << (int)time( NULL ); // save startup time for hashing with non-local paswtickets for (p = szInitTime.Text(); *p; ) *p++ |= 0x01; // obsufucate by making all the digits odd // // Mindlessly accept connections. // Single threaded systems implement TmbMulti as TmbSingle p4wProcess * process = new p4wProcess( endpoint ); # if defined ( OS_MACOSX ) || defined ( OS_CYGWIN ) ThreadMode tMode = TmbSingle; # else ThreadMode tMode = ( !opts[ 'i' ] ) ? TmbMulti : TmbSingle; # endif Threading * threading = new Threading(tMode, process); gThreading = threading; # ifdef OS_NT if( asService ) ntService.SetStatus( NtService::running ); # endif // OS_NT StrBuf thread_debug; while( !threading->Cancelled() ) { // // Wait for a connection. NetTransport *t = endpoint->Accept(&e); if( (t == NULL) ) { if ( e.IsWarning() ) { e.Clear(); delete threading; delete process; delete endpoint; endpoint = NetEndPoint::Create(port.Text(), &e); // // Listen on the port endpoint->Listen(&e); AssertLog.Abort( &e ); process = new p4wProcess( endpoint ); threading = new Threading(tMode, process); gThreading = threading; } else { // Instead of breaking out of the loop, we continue on. thread_debug << "DEBUG: Received what's supposed to be a fatal error, but soldiering on anyhow.\n"; reportLog->LogWrite( thread_debug ); reportLog->Report( &e ); thread_debug.Clear(); } } if ( t ) { // // Spawn a thread to handle this connection. threading->Launch(new p4wThread(t, &opts, hostPort.Text(), cargv[0], isService, isHTTPS, securityFlags, reportLog)); if( threading->Cancelled() ) { thread_debug << "DEBUG: threading->Cancelled! Restarting the threading loop.\n"; reportLog->LogWrite( thread_debug ); thread_debug.Clear(); threading->Restart(); } } if ( opts[ 'i' ] ) break; } if( threading->Cancelled() ) { thread_debug << "DEBUG: threading->Cancelled, despite Restart - means we broke out on !e.isWarning()\n"; reportLog->LogWrite( thread_debug ); thread_debug.Clear(); threading->Reap(); } // // Clean up and exit. if (reportLog) delete reportLog; delete threading; delete process; delete endpoint; delete []oargv; # ifdef OS_NT if( asService ) ntService.SetStatus( NtService::stopped ); # endif // OS_NT return 0; } int ChoosePort() { int port = 0; HostEnv hostEnv; int uid = 0; // // Start by hashing something out of the user id. // if ( hostEnv.GetUid( uid ) ) { port = ( uid % 25000 ) + 5000; } else { // // No user id. Make something out of the UserName // StrBuf name; char *text = NULL; if ( hostEnv.GetUser( name ) ) { text = name.Text(); for ( int i = 0; i < name.Length(); i++ ) { port += ((port << 3) + text[i]); } port = ( port % 25000 ) + 5000; } } return port; // is still 0 if not set to anything else } # ifdef OS_NT # undef printf int p4wprintf( char * first, ... ) { char *fmt = first; char *a[20]; long count = 0, i = 0; va_list marker; va_start( marker, first ); /* Initialize variable arguments. */ while( count < 20 ) a[count++] = va_arg( marker, char *); va_end( marker ); /* Reset variable arguments. */ printf(fmt, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15], a[16], a[17], a[18], a[19]); if ( reportLog ) { Error e; char buf[PRINTFBUFSIZE]; sprintf(buf, fmt, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15], a[16], a[17], a[18], a[19]); e.Set( E_INFO, buf ); reportLog->Report( &e ); } return 1; } #endif
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 12234 | Matt Attaway |
Rejigger P4Web project in preparation for official sunsetting The bin directory contains the last official builds of P4Web from the Perforce download site. P4Web is soon to be completely sunsetted; these builds are here for folks who don't want to build their own. To better handle the archived builds the source code has been moved into a separate src directory. |
||
//guest/perforce_software/p4web/Main/main.cpp | |||||
#3 | 10423 | Jan Van Uytven | Fixed brain-dead omission of StrBuf variable to hold threading debug info #review-10424 | ||
#2 | 10105 | Jan Van Uytven |
Fixed a problem with the threading loop that would cause it to unexpectedly exit occasionally on Solaris. #review-10106 |
||
#1 | 8914 | Matt Attaway | Initial add of the P4Web source code |