/**************************************************************************/ /* */ /* Copyright (c) 2001, 2007 NoMachine, http://www.nomachine.com/. */ /* */ /* NXCOMP, NX protocol compression and NX extensions to this software */ /* are copyright of NoMachine. Redistribution and use of the present */ /* software is allowed according to terms specified in the file LICENSE */ /* which comes in the source distribution. */ /* */ /* Check http://www.nomachine.com/licensing.html for applicability. */ /* */ /* NX and NoMachine are trademarks of NoMachine S.r.l. */ /* */ /* All rights reserved. */ /* */ /**************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __sun #include #endif // // MacOSX 10.4 defines socklen_t. This is // intended to ensure compatibility with // older versions. // #ifdef __APPLE__ #include #if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_3 typedef int socklen_t; #endif #endif #ifdef _AIX #include #include #endif #ifndef __CYGWIN32__ #include #include #endif // // NX include files. // #include "NX.h" #include "NXalert.h" #include "Misc.h" #include "Control.h" #include "Socket.h" #include "Statistics.h" #include "Auth.h" #include "Keeper.h" #include "Agent.h" #include "ClientProxy.h" #include "ServerProxy.h" #include "Message.h" // // System specific defines. // #if defined(__EMX__) || defined(__CYGWIN32__) struct sockaddr_un { u_short sun_family; char sun_path[108]; }; #endif // // HP-UX hides this define. // #if defined(hpux) && !defined(RLIM_INFINITY) #define RLIM_INFINITY 0x7fffffff #endif // // Set the verbosity level. // #define PANIC #define WARNING #undef TEST #undef DEBUG #undef DUMP // // Enable log output in signal handler. // This is likely to hang the proxy at // random, at least on Linux. // #undef UNSAFE // // Let all logs go to the standard error. // This is useful to interleave the Xlib // log output with the proxy output in a // single file. // #undef MIXED // // Define this to check if the client and // server caches match at shutdown. This // is a test facility as it requires that // both proxies are running on the same // host. // #undef MATCH // // If defined, reduce the size of the log // file and be sure it never exceeds the // limit. // #undef QUOTA // // If defined, force very strict limits for // the proxy tokens and force the proxy to // enter often in congestion state. // #undef STRICT // // Print a line in the log if the time we // spent inside the select or handling the // messages exceeded a given time value. // #undef TIME // // This can be useful when testing the forwarding // of the SSHD connection by nxssh to the agent. // The debug output will go to a well known file // that will be opened also by nxssh when BINDER // is enabled there. // #undef BINDER // // Define this to override the limits on // the core dump size. // #define COREDUMPS // // Upper limit of pre-allocated buffers // for string parameters. // #define DEFAULT_STRING_LENGTH 256 // // Maximum length of remote options data // passed by peer proxy at startup. // #define DEFAULT_REMOTE_OPTIONS_LENGTH 512 // // Maximum length of NX display string. // #define DEFAULT_DISPLAY_OPTIONS_LENGTH 1024 // // Maximum number of cache file names to // send to the server side. // #define DEFAULT_REMOTE_CACHE_ENTRIES 100 // // Maximum length of remote options string // that can be received from the peer proxy. // #define MAXIMUM_REMOTE_OPTIONS_LENGTH 4096 // // Macro is true if we determined our proxy // mode. // #define WE_SET_PROXY_MODE (control -> ProxyMode != proxy_undefined) // // Macro is true if our side is the one that // should connect to remote. // #define WE_INITIATE_CONNECTION (*connectHost != '\0') // // Is true if we must provide our credentials // to the remote peer. // #define WE_PROVIDE_CREDENTIALS (control -> ProxyMode == proxy_server) // // Is true if we listen for a local forwarder // that will tunnel the traffic through a SSH // or HTTP link. // #define WE_LISTEN_FORWARDER (control -> ProxyMode == proxy_server && \ listenPort != -1) // // You must define FLUSH in Misc.h if // you want an immediate flush of the // log output. // ostream *logofs = NULL; // // Other stream destriptors used for // logging. // ostream *statofs = NULL; ostream *errofs = NULL; // // Save standard error's rdbuf here // and restore it when exiting. // static streambuf *errsbuf = NULL; // // Allow faults to be recovered by // jumping back into the main loop. // jmp_buf context; // // Provide operational parameters. // Control *control = NULL; // // Collect and print statistics. // Statistics *statistics = NULL; // // Keep data for X11 authentication. // Auth *auth = NULL; // // This class makes the hard work. // Proxy *proxy = NULL; // // Used to handle memory-to-memory // transport to the X agent. // Agent *agent = NULL; // // The image cache house-keeping class. // Keeper *keeper = NULL; // // Callback set by the child process // to be notified about signals. // int (*handler)(int) = NULL; // // Signal handling functions. // void DisableSignals(); void EnableSignals(); void InstallSignals(); static void RestoreSignals(); static void HandleSignal(int signal); // // Signal handling utilities. // static void InstallSignal(int signal, int action); static void RestoreSignal(int signal); static int HandleChildren(); static int HandleChild(int child); static int CheckChild(int pid, int status); static int WaitChild(int child, const char *label, int force); int CheckParent(char *name, char *type, int parent); void RegisterChild(int child); static int CheckAbort(); // // Timer handling utilities. // void SetTimer(int timeout); void ResetTimer(); static void HandleTimer(int signal); // // Kill or check a running child. // static int KillProcess(int pid, const char *label, int signal, int wait); static int CheckProcess(int pid, const char *label); // // Macros used to test the pid of a child. // #define IsFailed(pid) ((pid) < 0) #define IsRunning(pid) ((pid) > 1) #define IsNotRunning(pid) ((pid) == 0) #define IsRestarting(pid) ((pid) == 1) #define SetNotRunning(pid) ((pid) = 0) #define SetRestarting(pid) ((pid) = 1) // // Start or restart the house-keeper process. // static int StartKeeper(); // // Cleanup functions. // void CleanupConnections(); void CleanupListeners(); void CleanupSockets(); void CleanupGlobal(); static void CleanupChildren(); static void CleanupLocal(); static void CleanupKeeper(); static void CleanupStreams(); // // Loop forever until the connections // to the peer proxy is dropped. // static void WaitCleanup(); // // Initialization functions. // static int InitBeforeNegotiation(); static int SetupProxyConnection(); static int InitAfterNegotiation(); static int SetupProxyInstance(); static int SetupAuthInstance(); static int SetupAgentInstance(); static int SetupTcpSocket(); static int SetupUnixSocket(); static int SetupServiceSockets(); static int SetupDisplaySocket(int &xServerAddrFamily, sockaddr *&xServerAddr, unsigned int &xServerAddrLength); // // Setup a listening socket and accept // a new connection. // static int ListenConnection(int port, const char *label); static int AcceptConnection(int fd, int domain, const char *label); // // Other convenience functions. // static int WaitForRemote(int portNum); static int ConnectToRemote(const char *const hostName, int portNum); static int SendProxyOptions(int fd); static int SendProxyCaches(int fd); static int ReadProxyVersion(int fd); static int ReadProxyOptions(int fd); static int ReadProxyCaches(int fd); static int ReadForwarderVersion(int fd); static int ReadForwarderOptions(int fd); static int ReadRemoteData(int fd, char *buffer, int size, char stop); static int WriteLocalData(int fd, const char *buffer, int size); static void PrintVersionInfo(); static void PrintProcessInfo(); static void PrintConnectionInfo(); static void PrintUsageInfo(const char *option, const int error); static void PrintOptionIgnored(const char *type, const char *name, const char *value); // // This is not static to avoid a warning. // void PrintCopyrightInfo(); static const char *GetOptions(const char *options); static const char *GetArg(int &argi, int argc, const char **argv); static int CheckArg(const char *type, const char *name, const char *value); static int ParseArg(const char *type, const char *name, const char *value); static int ValidateArg(const char *type, const char *name, const char *value); static int LowercaseArg(const char *type, const char *name, char *value); static int CheckSignal(int signal); extern "C" { int ParseCommandLineOptions(int argc, const char **argv); int ParseEnvironmentOptions(const char *env, int force); int ParseBindOptions(char **host, int *port); } static int ParseFileOptions(const char *file); static int ParseRemoteOptions(char *opts); static int ParseForwarderOptions(char *opts); // // These functions are used to parse literal // values provided by the user and set the // control parameters accordingly. // static int ParseLinkOption(const char *opt); static int ParseBitrateOption(const char *opt); static int ParseCacheOption(const char *opt); static int ParseShmemOption(const char *opt); static int ParseImagesOption(const char *opt); static int ParsePackOption(const char *opt); // // Set host and port where NX proxy is supposed // to be listening in case such parameters are // given on the command line. // static int ParseHostOption(const char *opt, char *host, int &port); // // Translate a font server port specification // to the corresponding Unix socket path. // static int ParseFontPath(char *path); // // Determine the interface where to listen for // the remote proxy connection or the local // forwarder. // static int ParseListenOption(int &interface); // // Translate a pack method id in a literal. // static int ParsePackMethod(const int method, const int quality); // // Try to increase the size of the allowed // core dumps. // static int SetCore(); // // Set the proxy mode to either client or // server. // static int SetMode(int mode); // // Determine the path of the NX_* directories // from the environment. // static int SetDirectories(); // // Set the defaults used for the log file and // statistics. // static int SetLogs(); // // Check if local and remote protocol versions // are compatible and, eventually, downgrade // local version to the minimum level that is // known to work. // static int SetVersion(); // // Setup the listening TCP ports used for the // additional channels according to user's // wishes. // static int SetPorts(); // // Set the maximum number of open descriptors. // static int SetDescriptors(); // // Set the path used for choosing the cache. // It must be selected after determining the // session type. // static int SetCaches(); // // Initialize, one after the other, all the // configuration parameters. // static int SetParameters(); // // Set the specific configuration parameter. // static int SetSession(); static int SetStorage(); static int SetShmem(); static int SetPack(); static int SetImages(); static int SetLimits(); // // Set up the control parameters based on // the link speed negotiated between the // proxies. // static int SetLink(); static int SetLinkModem(); static int SetLinkIsdn(); static int SetLinkAdsl(); static int SetLinkWan(); static int SetLinkLan(); // // Adjust the compression parameters. // static int SetCompression(); static int SetCompressionModem(); static int SetCompressionIsdn(); static int SetCompressionAdsl(); static int SetCompressionWan(); static int SetCompressionLan(); // // Determine the NX paths based on the // user's parameters or the environment. // char *GetClientPath(); static char *GetSystemPath(); static char *GetHomePath(); static char *GetTempPath(); static char *GetRootPath(); static char *GetCachePath(); static char *GetImagesPath(); static char *GetSessionPath(); static char *GetLastCache(char *list, const char *path); static int OpenLogFile(char *name, ostream *&stream); static int ReopenLogFile(char *name, ostream *&stream, int limit); // // Perform operations on the managed // descriptors in the main loop. // static void handleCheckSessionInLoop(); static void handleCheckBitrateInLoop(); #if defined(TEST) || defined(INFO) static void handleCheckSelectInLoop(int &setFDs, fd_set &readSet, fd_set &writeSet, T_timestamp selectTs); static void handleCheckResultInLoop(int &resultFDs, int &errorFDs, int &setFDs, fd_set &readSet, fd_set &writeSet, struct timeval &selectTs, struct timeval &startTs); static void handleCheckStateInLoop(int &setFDs); #endif static void handleCheckSessionInConnect(); static inline void handleSetReadInLoop(fd_set &readSet, int &setFDs, struct timeval &selectTs); static inline void handleSetWriteInLoop(fd_set &writeSet, int &setFDs, struct timeval &selectTs); static inline void handleSetListenersInLoop(fd_set &writeSet, int &setFDs); static inline void handleSetAgentInLoop(int &setFDs, fd_set &readSet, fd_set &writeSet, struct timeval &selectTs); static void handleAlertInLoop(); static void handleStatisticsInLoop(); static inline void handleAgentInLoop(int &resultFDs, int &errorFDs, int &setFDs, fd_set &readSet, fd_set &writeSet, struct timeval &selectTs); static inline void handleAgentLateInLoop(int &resultFDs, int &errorFDs, int &setFDs, fd_set &readSet, fd_set &writeSet, struct timeval &selectTs); static inline void handleReadableInLoop(int &resultFDs, fd_set &readSet); static inline void handleWritableInLoop(int &resultFDs, fd_set &writeSet); static inline void handleRotateInLoop(); static inline void handleEventsInLoop(); static inline void handleFlushInLoop(); // // Manage the proxy link during the negotiation // phase. // static void handleNegotiationInLoop(int &setFDs, fd_set &readSet, fd_set &writeSet, T_timestamp &selectTs); // // Print the 'terminating' messages in the // session log. // static inline void handleTerminatingInLoop(); static inline void handleTerminatedInLoop(); // // Monitor the size of the log file. // static void handleLogReopenInLoop(T_timestamp &logsTs, T_timestamp &nowTs); // // Directory where the NX binaries and libraries reside. // static char systemDir[DEFAULT_STRING_LENGTH] = { 0 }; // // Directory used for temporary files. // static char tempDir[DEFAULT_STRING_LENGTH] = { 0 }; // // Actually the full path to the client. // static char clientDir[DEFAULT_STRING_LENGTH] = { 0 }; // // User's home directory. // static char homeDir[DEFAULT_STRING_LENGTH] = { 0 }; // // Root of directory structure to be created by proxy. // static char rootDir[DEFAULT_STRING_LENGTH] = { 0 }; // // Root of statistics and log files to be created by proxy. // static char sessionDir[DEFAULT_STRING_LENGTH] = { 0 }; // // Log files for errors and statistics. Error log is // the place where we print also debug informations. // Both files are closed, deleted and reopened as // their size exceed the limit set in control class. // The session log is not reopened, as it is used by // the NX client and server to track the advance of // the session. // static char errorsFileName[DEFAULT_STRING_LENGTH] = { 0 }; static char statsFileName[DEFAULT_STRING_LENGTH] = { 0 }; static char sessionFileName[DEFAULT_STRING_LENGTH] = { 0 }; static char optionsFileName[DEFAULT_STRING_LENGTH] = { 0 }; // // String literal representing selected link speed // parameter. Value is translated in control values // used by proxies to stay synchronized. // static char linkSpeedName[DEFAULT_STRING_LENGTH] = { 0 }; // // String literal representing selected // cache size. // static char cacheSizeName[DEFAULT_STRING_LENGTH] = { 0 }; // // String literal representing selected // shared memory segment size. // static char shsegSizeName[DEFAULT_STRING_LENGTH] = { 0 }; // // String literal of images cache size. // static char imagesSizeName[DEFAULT_STRING_LENGTH] = { 0 }; // // String literal for bandwidth limit. // static char bitrateLimitName[DEFAULT_STRING_LENGTH] = { 0 }; // // String literal for image packing method. // static char packMethodName[DEFAULT_STRING_LENGTH] = { 0 }; // // Product name provided by the server or client. // static char productName[DEFAULT_STRING_LENGTH] = { 0 }; // // Its corresponding value from NXpack.h. // static int packMethod = -1; static int packQuality = -1; // // String literal for session type. Persistent caches // are searched in directory whose name matches this // parameter. // static char sessionType[DEFAULT_STRING_LENGTH] = { 0 }; // // Unique id assigned to session. It is used as // name of directory where all files are placed. // static char sessionId[DEFAULT_STRING_LENGTH] = { 0 }; // // Set if we already parsed the options. // static int parsedOptions = 0; static int parsedCommand = 0; // // Buffer data received from the remote proxy at // session negotiation. // static char remoteData[MAXIMUM_REMOTE_OPTIONS_LENGTH] = { 0 }; static int remotePosition = 0; // // Main loop file descriptors. // static int tcpFD = -1; static int unixFD = -1; static int cupsFD = -1; static int auxFD = -1; static int smbFD = -1; static int mediaFD = -1; static int httpFD = -1; static int fontFD = -1; static int slaveFD = -1; static int proxyFD = -1; // // Used for internal communication // with the X agent. // static int agentFD[2] = { -1, -1 }; // // Flags determining which protocols and // ports are forwarded. // int useUnixSocket = 1; static int useTcpSocket = 1; static int useCupsSocket = 0; static int useAuxSocket = 0; static int useSmbSocket = 0; static int useMediaSocket = 0; static int useHttpSocket = 0; static int useFontSocket = 0; static int useSlaveSocket = 0; static int useAgentSocket = 0; // // Set by user if he/she wants to modify // the default TCP_NODELAY option as set // in control. // static int useNoDelay = -1; // // Set if user wants to override default // flush timeout set according to link. // static int usePolicy = -1; // // Set if user wants to hide the RENDER // extension or wants to short-circuit // some simple replies at client side. // static int useRender = -1; static int useTaint = -1; // // Set if the user wants to reduce the // nominal size of the token messages // exchanged between the proxies. // static int useStrict = -1; // // Set if the proxy is running as part // of SSH on the client. // static int useEncryption = -1; // // Name of Unix socket created by the client proxy to // accept client connections. File must be unlinked // by cleanup function. // static char unixSocketName[DEFAULT_STRING_LENGTH] = { 0 }; // // Other parameters. // static char connectHost[DEFAULT_STRING_LENGTH] = { 0 }; static char acceptHost[DEFAULT_STRING_LENGTH] = { 0 }; static char listenHost[DEFAULT_STRING_LENGTH] = { 0 }; static char displayHost[DEFAULT_STRING_LENGTH] = { 0 }; static char authCookie[DEFAULT_STRING_LENGTH] = { 0 }; static int proxyPort = DEFAULT_NX_PROXY_PORT; static int xPort = DEFAULT_NX_X_PORT; // // Used to setup the connection the real // X display socket. // static int xServerAddrFamily = -1; static sockaddr *xServerAddr = NULL; static unsigned int xServerAddrLength = 0; // // The port where the local proxy will await // the peer connection or where the remote // proxy will be contacted. // static int listenPort = -1; static int connectPort = -1; // // Helper channels are disabled by default. // static int cupsPort = -1; static int auxPort = -1; static int smbPort = -1; static int mediaPort = -1; static int httpPort = -1; static int slavePort = -1; // // Can be either a port number or a Unix // socket. // static char fontPort[DEFAULT_STRING_LENGTH] = { 0 }; // // Host and port where the existing proxy // is running. // static char bindHost[DEFAULT_STRING_LENGTH] = { 0 }; static int bindPort = -1; // // Pointers to the callback functions and // parameter set by the agent // static void (*flushCallback)(void *, int) = NULL; static void *flushParameter = NULL; static void (*statisticsCallback)(void *, int) = NULL; static void *statisticsParameter = NULL; // // State variables shared between the init // function and the main loop. // T_timestamp initTs; T_timestamp startTs; T_timestamp logsTs; T_timestamp nowTs; int diffTs; // // This is set to the main proxy process id. // int lastProxy = 0; // // Set to last dialog process launched by proxy. // int lastDialog = 0; // // Set to watchdog process launched by proxy. // int lastWatchdog = 0; // // Set if a cache house-keeper process is running. // int lastKeeper = 0; // // Let an inner routine register the pid of a slave // process. // static int lastChild = 0; // // Exit code of the last child process exited. // static int lastStatus = 0; // // Set if shutdown was requested through a signal. // static int lastKill = 0; // // Set if the agent confirmed the destruction of // the NX transport. // static int lastDestroy = 0; // // This is set to the code and local flag of the // last requested alert. // static struct { int code; int local; } lastAlert; // // Manage the current signal masks. // static struct { sigset_t saved; int blocked; int installed; int enabled[32]; int forward[32]; struct sigaction action[32]; } lastMasks; // // Manage the current timer. // static struct { struct sigaction action; struct itimerval value; struct timeval start; struct timeval next; } lastTimer; // // This is set to last signal received in handler. // static int lastSignal = 0; // // Set to the last time bytes readable were queried // by the agent. // static T_timestamp lastReadableTs = nullTimestamp(); // // Here are interfaces declared in NX.h. // int NXTransProxy(int fd, int mode, const char* options) { // // Let the log temporarily go to the standard // error. Be also sure we have a jump context, // in the case any subsequent operation will // cause a cleanup. // if (logofs == NULL) { logofs = &cerr; } if (setjmp(context) == 1) { #ifdef TEST *logofs << "NXTransProxy: Out of the long jump with pid '" << lastProxy << "'.\n" << logofs_flush; #endif return -1; } // // Check if have already performed a parsing of // parameters, as in the case we are running as // a stand-alone process. If needed create the // parameters repository // if (control == NULL) { control = new Control(); } lastProxy = getpid(); #ifdef TEST *logofs << "NXTransProxy: Main process started with pid '" << lastProxy << "'.\n" << logofs_flush; #endif SetMode(mode); if (mode == NX_MODE_CLIENT) { if (fd != NX_FD_ANY) { #ifdef TEST *logofs << "NXTransProxy: Agent descriptor for X client connections is FD#" << fd << ".\n" << logofs_flush; *logofs << "NXTransProxy: Disabling listening on further X client connections.\n" << logofs_flush; #endif useTcpSocket = 0; useUnixSocket = 0; useAgentSocket = 1; agentFD[1] = fd; } } else if (mode == NX_MODE_SERVER) { if (fd != NX_FD_ANY) { #ifdef TEST *logofs << "NXTransProxy: PANIC! Agent descriptor for X server connections " << "not supported yet.\n" << logofs_flush; #endif cerr << "Error" << ": Agent descriptor for X server connections " << "not supported yet.\n"; return -1; } } const char *env = GetOptions(options); if (ParseEnvironmentOptions(env, 0) < 0) { cerr << "Error" << ": Parsing of NX transport options failed.\n"; return -1; } // // Set the path of the NX directories. // SetDirectories(); // // Open the log files. // SetLogs(); #ifdef TEST *logofs << "NXTransProxy: Going to run the NX transport loop.\n" << logofs_flush; #endif WaitCleanup(); // // This function should never return. // exit(0); } void NXTransExit(int code) { if (logofs == NULL) { logofs = &cerr; } static int recurse; if (++recurse > 1) { #ifdef TEST *logofs << "NXTransExit: Aborting process with pid '" << getpid() << "' due to recursion through " << "exit.\n" << logofs_flush; #endif abort(); } #ifdef TEST *logofs << "NXTransExit: Process with pid '" << getpid() << "' called exit with code '" << code << "'.\n" << logofs_flush; #endif if (control != NULL) { // // Be sure that there we can detect the // termination of the watchdog. // EnableSignals(); // // Close the NX transport if it was not // shut down already. // NXTransDestroy(NX_FD_ANY); } exit(code); } int NXTransParseCommandLine(int argc, const char **argv) { return ParseCommandLineOptions(argc, argv); } int NXTransParseEnvironment(const char *env, int force) { return ParseEnvironmentOptions(env, force); } void NXTransCleanup() { HandleCleanup(); } // // Check the parameters for subsequent // initialization of the NX transport. // int NXTransCreate(int fd, int mode, const char* options) { if (logofs == NULL) { logofs = &cerr; } // // Be sure we have a jump context, in the // case a subsequent operation will cause // a cleanup. // if (setjmp(context) == 1) { return -1; } // // Create the parameters repository // if (control != NULL) { #ifdef PANIC *logofs << "NXTransCreate: PANIC! The NX transport seems " << "to be already running.\n" << logofs_flush; #endif cerr << "Error" << ": The NX transport seems " << "to be already running.\n"; return -1; } control = new Control(); if (control == NULL) { #ifdef PANIC *logofs << "Loop: PANIC! Error creating the NX transport.\n" << logofs_flush; #endif cerr << "Error" << ": Error creating the NX transport.\n"; return -1; } lastProxy = getpid(); #ifdef TEST *logofs << "NXTransCreate: Caller process running with pid '" << lastProxy << "'.\n" << logofs_flush; #endif // // Set the local proxy mode an parse the // display NX options. // SetMode(mode); const char *env = GetOptions(options); if (ParseEnvironmentOptions(env, 0) < 0) { cerr << "Error" << ": Parsing of NX transport options failed.\n"; return -1; } // // Set the path of the NX directories. // SetDirectories(); // // Open the log files. // SetLogs(); // // Use the provided descriptor. // proxyFD = fd; #ifdef TEST *logofs << "NXTransCreate: Called with NX proxy descriptor '" << proxyFD << "'.\n" << logofs_flush; #endif #ifdef TEST *logofs << "NXTransCreate: Creation of the NX transport completed.\n" << logofs_flush; #endif return 1; } // // Tell the proxy to use the descriptor as the internal // connection to the X client side NX agent. This will // have the side effect of disabling listening for add- // itional X client connections. // int NXTransAgent(int fd[2]) { // // Be sure we have a jump context, in the // case a subsequent operation will cause // a cleanup. // if (logofs == NULL) { logofs = &cerr; } if (setjmp(context) == 1) { return -1; } if (control == NULL) { cerr << "Error" << ": Can't set the NX agent without a NX transport.\n"; return -1; } else if (control -> ProxyMode != proxy_client) { #ifdef PANIC *logofs << "NXTransAgent: Invalid mode while setting the NX agent.\n" << logofs_flush; #endif cerr << "Error" << ": Invalid mode while setting the NX agent.\n\n"; return -1; } useTcpSocket = 0; useUnixSocket = 0; useAgentSocket = 1; agentFD[0] = fd[0]; agentFD[1] = fd[1]; #ifdef TEST *logofs << "NXTransAgent: Internal descriptors for agent are FD#" << agentFD[0] << " and FD#" << agentFD[1] << ".\n" << logofs_flush; *logofs << "NXTransAgent: Disabling listening for further X client " << "connections.\n" << logofs_flush; #endif agent = new Agent(agentFD); if (agent == NULL || agent -> isValid() != 1) { #ifdef PANIC *logofs << "Loop: PANIC! Error creating the NX memory transport .\n" << logofs_flush; #endif cerr << "Error" << ": Error creating the NX memory transport.\n"; HandleCleanup(); } #ifdef TEST *logofs << "NXTransAgent: Enabling memory-to-memory transport.\n" << logofs_flush; #endif return 1; } int NXTransClose(int fd) { if (logofs == NULL) { logofs = &cerr; } /* * Only handle the proxy connection. The X * transport will take care of closing its * end of the socket pair. */ if (control != NULL && (agent != NULL && (fd == agentFD[0] || fd == NX_FD_ANY)) || (fd == proxyFD || fd == NX_FD_ANY)) { if (proxy != NULL) { #ifdef TEST *logofs << "NXTransClose: Closing down all the X connections.\n" << logofs_flush; #endif CleanupConnections(); } } #ifdef TEST else { *logofs << "NXTransClose: The NX transport is not running.\n" << logofs_flush; } #endif return 1; } // // Close down the transport and free the // allocated NX resources. // int NXTransDestroy(int fd) { if (logofs == NULL) { logofs = &cerr; } if (control != NULL && (agent != NULL && (fd == agentFD[0] || fd == NX_FD_ANY)) || (fd == proxyFD || fd == NX_FD_ANY)) { // // Shut down the X connections and // wait the cleanup to complete. // if (proxy != NULL) { #ifdef TEST *logofs << "NXTransDestroy: Closing down all the X connections.\n" << logofs_flush; #endif CleanupConnections(); } #ifdef TEST *logofs << "NXTransDestroy: Waiting for the NX transport to terminate.\n" << logofs_flush; #endif lastDestroy = 1; WaitCleanup(); } #ifdef TEST else { *logofs << "NXTransDestroy: The NX transport is not running.\n" << logofs_flush; } #endif return 1; } // // Assume that the NX transport is valid // as long as the control class has not // been destroyed. // int NXTransRunning(int fd) { return (control != NULL); } int NXTransContinue(struct timeval *selectTs) { if (control != NULL) { // // If no timeout is provided use // the default. // T_timestamp newTs; if (selectTs == NULL) { setTimestamp(newTs, control -> PingTimeout); selectTs = &newTs; } // // Use empty masks and only get the // descriptors set by the proxy. // fd_set readSet; fd_set writeSet; int setFDs; int errorFDs; int resultFDs; setFDs = 0; FD_ZERO(&readSet); FD_ZERO(&writeSet); // // Run a new loop. If the transport // is gone avoid sleeping until the // timeout. // if (NXTransPrepare(&setFDs, &readSet, &writeSet, selectTs) != 0) { NXTransSelect(&resultFDs, &errorFDs, &setFDs, &readSet, &writeSet, selectTs); NXTransExecute(&resultFDs, &errorFDs, &setFDs, &readSet, &writeSet, selectTs); } } return (control != NULL); } int NXTransSignal(int signal, int action) { if (logofs == NULL) { logofs = &cerr; } if (control == NULL) { return 0; } if (action == NX_SIGNAL_RAISE) { #ifdef TEST *logofs << "NXTransSignal: Raising signal '" << DumpSignal(signal) << "' in the proxy handler.\n" << logofs_flush; #endif HandleSignal(signal); return 1; } else if (signal == NX_SIGNAL_ANY) { #ifdef TEST *logofs << "NXTransSignal: Setting action of all signals to '" << action << "'.\n" << logofs_flush; #endif for (int i = 0; i < 32; i++) { if (CheckSignal(i) == 1) { NXTransSignal(i, action); } } return 1; } else if (CheckSignal(signal) == 1) { #ifdef TEST *logofs << "NXTransSignal: Setting action of signal '" << DumpSignal(signal) << "' to '" << action << "'.\n" << logofs_flush; #endif if (action == NX_SIGNAL_ENABLE || action == NX_SIGNAL_FORWARD) { InstallSignal(signal, action); return 1; } else if (action == NX_SIGNAL_DISABLE) { RestoreSignal(signal); return 1; } } #ifdef WARNING *logofs << "NXTransSignal: WARNING! Unable to perform action '" << action << "' on signal '" << DumpSignal(signal) << "'.\n" << logofs_flush; #endif cerr << "Warning" << ": Unable to perform action '" << action << "' on signal '" << DumpSignal(signal) << "'.\n"; return -1; } int NXTransCongestion(int fd) { if (control != NULL && proxy != NULL) { #ifdef DUMP int congestion = proxy -> getCongestion(proxyFD); *logofs << "NXTransCongestion: Returning " << congestion << " as current congestion level.\n" << logofs_flush; return congestion; #endif return (proxy -> getCongestion(proxyFD)); } return 0; } int NXTransHandler(int fd, int type, void (*handler)(void *parameter, int reason), void *parameter) { if (logofs == NULL) { logofs = &cerr; } switch (type) { case NX_HANDLER_FLUSH: { flushCallback = handler; flushParameter = parameter; break; } case NX_HANDLER_STATISTICS: { // // Reporting of statistics by the agent // still needs to be implemented. // statisticsCallback = handler; statisticsParameter = parameter; break; } default: { #ifdef TEST *logofs << "NXTransHandler: WARNING! Failed to set " << "the NX callback for event '" << type << "' to '" << (void *) handler << "' and parameter '" << parameter << "'.\n" << logofs_flush; #endif return 0; } } #ifdef TEST *logofs << "NXTransHandler: Set the NX " << "callback for event '" << type << "' to '" << (void *) handler << "' and parameter '" << parameter << "'.\n" << logofs_flush; #endif return 1; } int NXTransRead(int fd, char *data, int size) { if (logofs == NULL) { logofs = &cerr; } if (control != NULL && agent != NULL && fd == agentFD[0]) { #ifdef DUMP *logofs << "NXTransRead: Dequeuing " << size << " bytes " << "from FD#" << agentFD[0] << ".\n" << logofs_flush; #endif int result = agent -> dequeueData(data, size); #ifdef DUMP if (result < 0 && EGET() == EAGAIN) { *logofs << "NXTransRead: WARNING! Dequeuing from FD#" << agentFD[0] << " would block.\n" << logofs_flush; } else { *logofs << "NXTransRead: Dequeued " << result << " bytes " << "to FD#" << agentFD[0] << ".\n" << logofs_flush; } #endif return result; } else { #ifdef DUMP *logofs << "NXTransRead: Reading " << size << " bytes " << "from FD#" << fd << ".\n" << logofs_flush; #endif return read(fd, data, size); } } int NXTransReadVector(int fd, struct iovec *iovdata, int iovsize) { if (logofs == NULL) { logofs = &cerr; } if (control != NULL && agent != NULL && fd == agentFD[0]) { #if defined(DUMP) if (control -> ProxyStage >= stage_operational && agent -> localReadable() > 0) { *logofs << "NXTransReadVector: WARNING! Agent has data readable.\n" << logofs_flush; } #endif char *base; int length; int result; struct iovec *vector = iovdata; int count = iovsize; ESET(0); int i = 0; int total = 0; for (; i < count; i++, vector++) { length = vector -> iov_len; base = (char *) vector -> iov_base; while (length > 0) { #ifdef DUMP *logofs << "NXTransReadVector: Dequeuing " << length << " bytes " << "from FD#" << agentFD[0] << ".\n" << logofs_flush; #endif result = agent -> dequeueData(base, length); #ifdef DUMP if (result < 0 && EGET() == EAGAIN) { *logofs << "NXTransReadVector: WARNING! Dequeuing from FD#" << agentFD[0] << " would block.\n" << logofs_flush; } else { *logofs << "NXTransReadVector: Dequeued " << result << " bytes " << "from FD#" << agentFD[0] << ".\n" << logofs_flush; } #endif if (result < 0 && total == 0) { return result; } else if (result <= 0) { return total; } ESET(0); length -= result; total += result; base += result; } } return total; } else { #ifdef DUMP *logofs << "NXTransReadVector: Reading vector with " << iovsize << " elements from FD#" << fd << ".\n" << logofs_flush; #endif return readv(fd, iovdata, iovsize); } } int NXTransReadable(int fd, int *readable) { if (logofs == NULL) { logofs = &cerr; } if (control == NULL || agent == NULL || fd != agentFD[0]) { #ifdef DUMP int result = GetBytesReadable(fd, readable); if (result == -1) { *logofs << "NXTransReadable: Error detected on FD#" << fd << ".\n" << logofs_flush; } else { *logofs << "NXTransReadable: Returning " << *readable << " bytes as readable from FD#" << fd << ".\n" << logofs_flush; } return result; #else return GetBytesReadable(fd, readable); #endif } int result = agent -> dequeuableData(); switch (result) { case 0: { // // The client might have enqueued data to our side // and is now checking for the available events. As // _XEventsQueued() may omit to call _XSelect(), we // handle here the new data that is coming from the // proxy to avoid spinning through this function // again. // if (proxy != NULL && proxy -> canRead() == 1) { #if defined(TEST) || defined(INFO) *logofs << "NXTransReadable: WARNING! Trying to " << "read to generate new agent data.\n" << logofs_flush; #endif // // Set the context as the function // can cause a cleanup. // if (setjmp(context) == 1) { return -1; } if (proxy -> handleRead() < 0) { #if defined(TEST) || defined(INFO) *logofs << "NXTransReadable: Failure reading " << "messages from proxy FD#" << proxyFD << ".\n" << logofs_flush; #endif HandleShutdown(); } // // Call again the routine. By reading // new control messages from the proxy // the agent channel may be gone. // return NXTransReadable(fd, readable); } #ifdef DUMP *logofs << "NXTransReadable: Returning " << 0 << " bytes as readable from FD#" << fd << " with result 0.\n" << logofs_flush; #endif *readable = 0; return 0; } case -1: { #ifdef DUMP *logofs << "NXTransReadable: Returning " << 0 << " bytes as readable from FD#" << fd << " with result -1.\n" << logofs_flush; #endif *readable = 0; return -1; } default: { #ifdef DUMP *logofs << "NXTransReadable: Returning " << result << " bytes as readable from FD#" << fd << " with result 0.\n" << logofs_flush; #endif *readable = result; return 0; } } } int NXTransWrite(int fd, char *data, int size) { // // Be sure we have a valid log file. // if (logofs == NULL) { logofs = &cerr; } if (control != NULL && agent != NULL && fd == agentFD[0]) { int result; if (proxy != NULL) { if (proxy -> canRead(agentFD[1]) == 0) { #if defined(DUMP) || defined(TEST) *logofs << "NXTransWrite: WARNING! Delayed enqueuing to FD#" << agentFD[0] << " with proxy unable to read.\n" << logofs_flush; #endif ESET(EAGAIN); return -1; } // // Set the context as the function // can cause a cleanup. // if (setjmp(context) == 1) { return -1; } // // Don't enqueue the data to the transport // but let the channel borrow the buffer. // #ifdef DUMP *logofs << "NXTransWrite: Letting the channel borrow " << size << " bytes from FD#" << agentFD[0] << ".\n" << logofs_flush; #endif result = proxy -> handleRead(agentFD[1], data, size); if (result == 1) { result = size; } else { if (result == 0) { ESET(EAGAIN); } else { ESET(EPIPE); } result = -1; } } else { // // We don't have a proxy connection, yet. // Enqueue the data to the agent transport. // #ifdef DUMP *logofs << "NXTransWrite: Enqueuing " << size << " bytes " << "to FD#" << agentFD[0] << ".\n" << logofs_flush; #endif result = agent -> enqueueData(data, size); } #ifdef DUMP if (result < 0) { if (EGET() == EAGAIN) { *logofs << "NXTransWrite: WARNING! Enqueuing to FD#" << agentFD[0] << " would block.\n" << logofs_flush; } else { *logofs << "NXTransWrite: WARNING! Error enqueuing to FD#" << agentFD[0] << ".\n" << logofs_flush; } } else { *logofs << "NXTransWrite: Enqueued " << result << " bytes " << "to FD#" << agentFD[0] << ".\n" << logofs_flush; } #endif return result; } else { #ifdef DUMP *logofs << "NXTransWrite: Writing " << size << " bytes " << "to FD#" << fd << ".\n" << logofs_flush; #endif return write(fd, data, size); } } int NXTransWriteVector(int fd, struct iovec *iovdata, int iovsize) { // // Be sure we have a valid log file and a // jump context because we will later call // functions that can perform a cleanup. // if (logofs == NULL) { logofs = &cerr; } int result = 0; if (control != NULL && agent != NULL && fd == agentFD[0]) { // // See the comment in NXTransWrite(). // if (proxy != NULL) { if (proxy -> canRead(agentFD[1]) == 0) { #if defined(DUMP) || defined(TEST) *logofs << "NXTransWriteVector: WARNING! Delayed enqueuing to FD#" << agentFD[0] << " with proxy unable to read.\n" << logofs_flush; #endif ESET(EAGAIN); return -1; } } // // Set the context as the function // can cause a cleanup. // if (setjmp(context) == 1) { return -1; } char *base; int length; struct iovec *vector = iovdata; int count = iovsize; ESET(0); int i = 0; int total = 0; for (; i < count; i++, vector++) { length = vector -> iov_len; base = (char *) vector -> iov_base; while (length > 0) { if (proxy != NULL) { // // Don't enqueue the data to the transport // but let the channel borrow the buffer. // #ifdef DUMP *logofs << "NXTransWriteVector: Letting the channel borrow " << length << " bytes from FD#" << agentFD[0] << ".\n" << logofs_flush; #endif result = proxy -> handleRead(agentFD[1], base, length); if (result == 1) { result = length; } else { if (result == 0) { ESET(EAGAIN); } else { ESET(EPIPE); } result = -1; } } else { // // We don't have a proxy connection, yet. // Enqueue the data to the agent transport. // #ifdef DUMP *logofs << "NXTransWriteVector: Enqueuing " << length << " bytes " << "to FD#" << agentFD[0] << ".\n" << logofs_flush; #endif result = agent -> enqueueData(base, length); } #ifdef DUMP if (result < 0) { if (EGET() == EAGAIN) { *logofs << "NXTransWriteVector: WARNING! Enqueuing to FD#" << agentFD[0] << " would block.\n" << logofs_flush; } else { *logofs << "NXTransWriteVector: WARNING! Error enqueuing to FD#" << agentFD[0] << ".\n" << logofs_flush; } } else { *logofs << "NXTransWriteVector: Enqueued " << result << " bytes " << "to FD#" << agentFD[0] << ".\n" << logofs_flush; } #endif if (result < 0 && total == 0) { return result; } else if (result <= 0) { return total; } ESET(0); length -= result; total += result; base += result; } } return total; } else { #ifdef DUMP *logofs << "NXTransWriteVector: Writing vector with " << iovsize << " elements to FD#" << fd << ".\n" << logofs_flush; #endif return writev(fd, iovdata, iovsize); } } int NXTransPolicy(int fd, int type) { if (control != NULL) { if (usePolicy == -1) { #if defined(TEST) || defined(INFO) *logofs << "NXTransPolicy: Setting flush policy on " << "proxy FD#" << proxyFD << " to '" << DumpPolicy(type == NX_POLICY_DEFERRED ? policy_deferred : policy_immediate) << "'.\n" << logofs_flush; #endif control -> FlushPolicy = (type == NX_POLICY_DEFERRED ? policy_deferred : policy_immediate); if (proxy != NULL) { proxy -> handleFlush(); } return 1; } else { #if defined(TEST) || defined(INFO) *logofs << "NXTransPolicy: Ignoring the agent " << "setting with user policy set to '" << DumpPolicy(control -> FlushPolicy) << "'.\n" << logofs_flush; #endif return 0; } } return 0; } int NXTransFlushable(int fd) { if (proxy == NULL || agent == NULL || fd != agentFD[0]) { #ifdef DUMP *logofs << "NXTransFlushable: Returning 0 bytes as " << "flushable for unrecognized FD#" << fd << ".\n" << logofs_flush; #endif return 0; } else { #if defined(DUMP) || defined(INFO) *logofs << "NXTransFlushable: Returning " << proxy -> getFlushable(proxyFD) << " as bytes flushable on " << "proxy FD#" << proxyFD << ".\n" << logofs_flush; #endif return proxy -> getFlushable(proxyFD); } } int NXTransFlush(int fd) { if (proxy != NULL) { #if defined(TEST) || defined(INFO) *logofs << "NXTransFlush: Requesting an immediate flush of " << "proxy FD#" << proxyFD << ".\n" << logofs_flush; #endif return proxy -> handleFlush(); } return 0; } int NXTransChannel(int fd, int channelFd, int type) { if (proxy != NULL) { // // Set the context as the function // can cause a cleanup. // if (setjmp(context) == 1) { return -1; } #if defined(TEST) || defined(INFO) *logofs << "NXTransChannel: Going to create a new channel " << "with type '" << type << "' on FD#" << channelFd << ".\n" << logofs_flush; #endif int result = -1; switch (type) { case NX_CHANNEL_X11: { if (useUnixSocket == 1 || useTcpSocket == 1 || useAgentSocket == 1 || useAuxSocket == 1) { result = proxy -> handleNewConnection(channel_x11, channelFd); } break; } case NX_CHANNEL_CUPS: { if (useCupsSocket == 1) { result = proxy -> handleNewConnection(channel_cups, channelFd); } break; } case NX_CHANNEL_SMB: { if (useSmbSocket == 1) { result = proxy -> handleNewConnection(channel_smb, channelFd); } break; } case NX_CHANNEL_MEDIA: { if (useMediaSocket == 1) { result = proxy -> handleNewConnection(channel_media, channelFd); } break; } case NX_CHANNEL_HTTP: { if (useHttpSocket == 1) { result = proxy -> handleNewConnection(channel_http, channelFd); } break; } case NX_CHANNEL_FONT: { if (useFontSocket == 1) { result = proxy -> handleNewConnection(channel_font, channelFd); } break; } case NX_CHANNEL_SLAVE: { if (useSlaveSocket == 1) { result = proxy -> handleNewConnection(channel_slave, channelFd); } break; } default: { #ifdef WARNING *logofs << "NXTransChannel: WARNING! Unrecognized channel " << "type '" << type << "'.\n" << logofs_flush; #endif break; } } #ifdef WARNING if (result != 1) { *logofs << "NXTransChannel: WARNING! Could not create the " << "new channel with type '" << type << "' on FD#" << channelFd << ".\n" << logofs_flush; } #endif return result; } return 0; } const char *NXTransFile(int type) { char *name = NULL; switch (type) { case NX_FILE_SESSION: { name = sessionFileName; break; } case NX_FILE_ERRORS: { name = errorsFileName; break; } case NX_FILE_OPTIONS: { name = optionsFileName; break; } case NX_FILE_STATS: { name = statsFileName; break; } } if (name != NULL && *name != '\0') { return name; } return NULL; } long NXTransTime() { static T_timestamp last = getTimestamp(); T_timestamp now = getTimestamp(); long diff = diffTimestamp(last, now); last = now; return diff; } int NXTransAlert(int code, int local) { if (proxy != NULL) { #if defined(DUMP) || defined(INFO) *logofs << "NXTransAlert: Requesting a NX dialog with code " << code << " and local " << local << ".\n" << logofs_flush; #endif if (local == 0) { // // Set the context as the function // can cause a cleanup. // if (setjmp(context) == 1) { return -1; } proxy -> handleAlert(code); } else { // // Show the alert at the next loop. // HandleAlert(code, local); } return 1; } #if defined(DUMP) || defined(INFO) else { if (logofs == NULL) { logofs = &cerr; } *logofs << "NXTransAlert: Can't request an alert without " << "a valid NX transport.\n" << logofs_flush; } #endif return 0; } // // Prepare the file sets and the timeout // for a later execution of the select(). // int NXTransPrepare(int *setFDs, fd_set *readSet, fd_set *writeSet, struct timeval *selectTs) { if (logofs == NULL) { logofs = &cerr; } // // Control is NULL if the NX transport was // reset or was never created. If control // is valid then prepare to jump back when // the transport is destroyed. // if (control == NULL || setjmp(context) == 1) { return 0; } #if defined(TEST) || defined(INFO) *logofs << "\nNXTransPrepare: Going to prepare the NX transport.\n" << logofs_flush; #endif if (control -> ProxyStage < stage_operational) { handleNegotiationInLoop(*setFDs, *readSet, *writeSet, *selectTs); } else { #if defined(TEST) || defined(INFO) if (isTimestamp(*selectTs) == 0) { *logofs << "Loop: WARNING! Preparing the select with requested " << "timeout of " << selectTs -> tv_sec << " S and " << (double) selectTs -> tv_usec / 1000 << " Ms.\n" << logofs_flush; } else { *logofs << "Loop: Preparing the select with requested " << "timeout of " << selectTs -> tv_sec << " S and " << (double) selectTs -> tv_usec / 1000 << " Ms.\n" << logofs_flush; } #endif // // Set descriptors of listening sockets. // handleSetListenersInLoop(*readSet, *setFDs); // // Set descriptors of both proxy and X // connections. // handleSetReadInLoop(*readSet, *setFDs, *selectTs); // // Find out which file descriptors have // data to write. // handleSetWriteInLoop(*writeSet, *setFDs, *selectTs); } // // Prepare the masks for handling the memory- // to-memory transport. This is required even // during session negotiation. // if (agent != NULL) { handleSetAgentInLoop(*setFDs, *readSet, *writeSet, *selectTs); } // // Register time spent handling messages. // nowTs = getNewTimestamp(); diffTs = diffTimestamp(startTs, nowTs); #ifdef TEST *logofs << "Loop: Mark - 0 - at " << strMsTimestamp() << " with " << diffTs << " Ms elapsed.\n" << logofs_flush; #endif // // TODO: Should add the read time in two // parts otherwise the limits are checked // before the counters are updated with // time spent in the last loop. // if (control -> ProxyStage >= stage_operational) { statistics -> addReadTime(diffTs); } startTs = nowTs; #ifdef DEBUG *logofs << "Loop: New timestamp is " << strMsTimestamp(startTs) << ".\n" << logofs_flush; #endif return 1; } // // Let's say that we call select() to find out // if any of the handled descriptors has data, // but actually things are a bit more complex // than that. // int NXTransSelect(int *resultFDs, int *errorFDs, int *setFDs, fd_set *readSet, fd_set *writeSet, struct timeval *selectTs) { #ifdef TIME static T_timestamp lastTs; #endif if (logofs == NULL) { logofs = &cerr; } // // Control is NULL if the NX transport was // reset or never created. If control is // valid then prepare for jumping back in // the case of an error. // if (control == NULL || setjmp(context) == 1) { *resultFDs = select(*setFDs, readSet, writeSet, NULL, selectTs); *errorFDs = errno; return 0; } #if defined(TEST) || defined(INFO) *logofs << "\nNXTransSelect: Going to select the NX descriptors.\n" << logofs_flush; #endif #if defined(TEST) || defined(INFO) handleCheckSelectInLoop(*setFDs, *readSet, *writeSet, *selectTs); #endif #ifdef TIME diffTs = diffTimestamp(lastTs, getNewTimestamp()); if (diffTs > 20) { *logofs << "Loop: TIME! Spent " << diffTs << " Ms handling messages for proxy FD#" << proxyFD << ".\n" << logofs_flush; } lastTs = getNewTimestamp(); #endif #if defined(TEST) || defined(INFO) if (isTimestamp(*selectTs) == 0) { *logofs << "Loop: WARNING! Executing the select with requested " << "timeout of " << selectTs -> tv_sec << " S and " << (double) selectTs -> tv_usec / 1000 << " Ms.\n" << logofs_flush; } else if (proxy != NULL && proxy -> getFlushable(proxyFD) > 0) { *logofs << "Loop: WARNING! Proxy FD#" << proxyFD << " has " << proxy -> getFlushable(proxyFD) << " bytes to write but timeout is " << selectTs -> tv_sec << " S and " << selectTs -> tv_usec / 1000 << " Ms.\n" << logofs_flush; } #endif // // Wait for the selected sockets // or the timeout. // ESET(0); *resultFDs = select(*setFDs, readSet, writeSet, NULL, selectTs); *errorFDs = EGET(); #ifdef TIME diffTs = diffTimestamp(lastTs, getNewTimestamp()); if (diffTs > 100) { *logofs << "Loop: TIME! Spent " << diffTs << " Ms waiting for new data for proxy FD#" << proxyFD << ".\n" << logofs_flush; } lastTs = getNewTimestamp(); #endif // // Check the result of the select. // #if defined(TEST) || defined(INFO) handleCheckResultInLoop(*resultFDs, *errorFDs, *setFDs, *readSet, *writeSet, *selectTs, startTs); #endif // // Get time spent in select. The accouting is done // in milliseconds. This is a real problem on fast // machines where each loop is unlikely to take // more than 500 us, so consider that the results // can be inaccurate. // nowTs = getNewTimestamp(); diffTs = diffTimestamp(startTs, nowTs); #ifdef TEST *logofs << "Loop: Out of select after " << diffTs << " Ms " << "at " << strMsTimestamp(nowTs) << " with result " << *resultFDs << ".\n" << logofs_flush; #endif startTs = nowTs; #ifdef DEBUG *logofs << "Loop: New timestamp is " << strMsTimestamp(startTs) << ".\n" << logofs_flush; #endif if (control -> ProxyStage >= stage_operational) { statistics -> addIdleTime(diffTs); } if (*resultFDs < 0) { // // Check if the call was interrupted or if any of the // managed descriptors has become invalid. This can // happen to the X11 code, before the descriptor is // removed from the managed set. // #ifdef __sun if (*errorFDs == EINTR || *errorFDs == EBADF || *errorFDs == EINVAL) #else if (*errorFDs == EINTR || *errorFDs == EBADF) #endif { #ifdef TEST if (*errorFDs == EINTR) { *logofs << "Loop: Select failed due to EINTR error.\n" << logofs_flush; } else { *logofs << "Loop: WARNING! Call to select failed. Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; } #endif } else { #ifdef PANIC *logofs << "Loop: PANIC! Call to select failed. Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Call to select failed. Error is " << EGET() << " '" << ESTR() << "'.\n"; HandleCleanup(); } } return 1; } // // Perform the required actions on all // the descriptors having I/O pending. // int NXTransExecute(int *resultFDs, int *errorFDs, int *setFDs, fd_set *readSet, fd_set *writeSet, struct timeval *selectTs) { if (logofs == NULL) { logofs = &cerr; } // // Control is NULL if the NX transport was // reset or never created. If control is // valid then prepare for jumping back in // the case of an error. // if (control == NULL || setjmp(context) == 1) { return 0; } #if defined(TEST) || defined(INFO) *logofs << "\nNXTransExecute: Going to execute I/O on the NX descriptors.\n" << logofs_flush; #endif if (control -> ProxyStage >= stage_operational) { // // Check if I/O is possible on the proxy and // local agent descriptors. // if (agent != NULL) { handleAgentInLoop(*resultFDs, *errorFDs, *setFDs, *readSet, *writeSet, *selectTs); } #ifdef TEST *logofs << "Loop: Mark - 1 - at " << strMsTimestamp() << " with " << diffTimestamp(startTs, getTimestamp()) << " Ms elapsed.\n" << logofs_flush; #endif // // Rotate the channel that will be handled // first. // handleRotateInLoop(); // // Flush any data on newly writable sockets. // handleWritableInLoop(*resultFDs, *writeSet); #ifdef TEST *logofs << "Loop: Mark - 2 - at " << strMsTimestamp() << " with " << diffTimestamp(startTs, getTimestamp()) << " Ms elapsed.\n" << logofs_flush; #endif // // Check if any socket has become readable. // handleReadableInLoop(*resultFDs, *readSet); #ifdef TEST *logofs << "Loop: Mark - 3 - at " << strMsTimestamp() << " with " << diffTimestamp(startTs, getTimestamp()) << " Ms elapsed.\n" << logofs_flush; #endif // // Handle the scheduled events on channels. // // - Restart, if possible, any client that was // put to sleep. // // - Check if there are pointer motion events to // flush. This applies only to X server side. // // - Check if any channel has exited the conges- // tion state. // // - Check if there are images that are currently // being streamed. // handleEventsInLoop(); #ifdef TEST *logofs << "Loop: Mark - 4 - at " << strMsTimestamp() << " with " << diffTimestamp(startTs, getTimestamp()) << " Ms elapsed.\n" << logofs_flush; #endif // // Check if user sent a signal to produce // statistics. // handleStatisticsInLoop(); // // We may have flushed the proxy link or // handled data coming from the remote. // Post-process the masks and set the // selected agent descriptors as ready. // if (agent != NULL) { handleAgentLateInLoop(*resultFDs, *errorFDs, *setFDs, *readSet, *writeSet, *selectTs); } #ifdef TEST *logofs << "Loop: Mark - 5 - at " << strMsTimestamp() << " with " << diffTimestamp(startTs, getTimestamp()) << " Ms elapsed.\n" << logofs_flush; #endif // // Check if there is any data to flush. // Agents should flush the proxy link // explicitly. // handleFlushInLoop(); #ifdef TEST *logofs << "Loop: Mark - 6 - at " << strMsTimestamp() << " with " << diffTimestamp(startTs, getTimestamp()) << " Ms elapsed.\n" << logofs_flush; #endif } // // Check if we have an alert to show. // handleAlertInLoop(); if (control -> ProxyStage >= stage_operational) { // // Check if it's time to give up. // handleCheckSessionInLoop(); // // Check if local proxy is consuming // too many resources. // handleCheckBitrateInLoop(); // // Check coherency of internal state. // #if defined(TEST) || defined(INFO) handleCheckStateInLoop(*setFDs); #endif #ifdef TEST *logofs << "Loop: Mark - 7 - at " << strMsTimestamp() << " with " << diffTimestamp(startTs, getTimestamp()) << " Ms elapsed.\n" << logofs_flush; #endif } // // Truncate the logs if needed. // handleLogReopenInLoop(logsTs, nowTs); return 1; } // // Initialize the connection parameters and // prepare for negotiating the link with the // remote proxy. // int InitBeforeNegotiation() { // // Disable limits on core dumps. // SetCore(); // // Install the signal handlers. // InstallSignals(); // // Track how much time we spent in initialization. // nowTs = getNewTimestamp(); startTs = nowTs; initTs = nowTs; #ifdef TEST *logofs << "Loop: INIT! Taking mark for initialization at " << strMsTimestamp(initTs) << ".\n" << logofs_flush; #endif // // If not explicitly specified, determine if local // mode is client or server according to parameters // provided so far. // if (WE_SET_PROXY_MODE == 0) { cerr << "Error" << ": Please specify either the -C or -S option.\n"; HandleCleanup(); } // // Start a watchdog. If initialization cannot // be completed before timeout, then clean up // everything and exit. // if (control -> ProxyMode == proxy_client) { #ifdef TEST *logofs << "Loop: Starting watchdog process with timeout of " << control -> InitTimeout / 1000 << " seconds.\n" << logofs_flush; #endif lastWatchdog = NXTransWatchdog(control -> InitTimeout); if (IsFailed(lastWatchdog)) { #ifdef PANIC *logofs << "Loop: PANIC! Can't start the NX watchdog process.\n" << logofs_flush; #endif SetNotRunning(lastWatchdog); } #ifdef TEST else { *logofs << "Loop: Watchdog started with pid '" << lastWatchdog << "'.\n" << logofs_flush; } #endif } // // Print preliminary info. // PrintProcessInfo(); // // Set cups, multimedia and other // auxiliary ports. // SetPorts(); // // Increase the number of maximum open // file descriptors for this process. // SetDescriptors(); // // Set local endianess. // unsigned int test = 1; setHostBigEndian(*((unsigned char *) (&test)) == 0); #ifdef TEST *logofs << "Loop: Local host is " << (hostBigEndian() ? "big endian" : "little endian") << ".\n" << logofs_flush; #endif if (control -> ProxyMode == proxy_client) { // // Listen on sockets that mimic an X display to // which X clients will be able to connect (e.g. // unix:8 and/or localhost:8). // if (useTcpSocket == 1) { SetupTcpSocket(); } if (useUnixSocket == 1) { SetupUnixSocket(); } } else { // // Don't listen for X connections. // useUnixSocket = 0; useTcpSocket = 0; useAgentSocket = 0; // // Get ready to open the local display. // SetupDisplaySocket(xServerAddrFamily, xServerAddr, xServerAddrLength); } // // If we are the NX server-side proxy we need to // complete our initializazion. We will mandate // our parameters at the time the NX client will // connect. // if (control -> ProxyMode == proxy_client) { SetParameters(); } return 1; } int SetupProxyConnection() { if (proxyFD == -1) { if (WE_INITIATE_CONNECTION) { if (connectPort < 0) { connectPort = DEFAULT_NX_PROXY_PORT_OFFSET + proxyPort; } #ifdef TEST *logofs << "Loop: Going to connect to " << connectHost << ":" << connectPort << ".\n" << logofs_flush; #endif proxyFD = ConnectToRemote(connectHost, connectPort); #ifdef TEST *logofs << "Loop: Connected to remote proxy on FD#" << proxyFD << ".\n" << logofs_flush; #endif cerr << "Info" << ": Connection to remote proxy '" << connectHost << ":" << connectPort << "' established.\n"; } else { if (listenPort < 0) { listenPort = DEFAULT_NX_PROXY_PORT_OFFSET + proxyPort; } #ifdef TEST *logofs << "Loop: Going to wait for connection on port " << listenPort << ".\n" << logofs_flush; #endif proxyFD = WaitForRemote(listenPort); #ifdef TEST if (WE_LISTEN_FORWARDER) { *logofs << "Loop: Connected to remote forwarder on FD#" << proxyFD << ".\n" << logofs_flush; } else { *logofs << "Loop: Connected to remote proxy on FD#" << proxyFD << ".\n" << logofs_flush; } #endif } } #ifdef TEST else { *logofs << "Loop: Using the inherited connection on FD#" << proxyFD << ".\n" << logofs_flush; } #endif // // Set TCP_NODELAY on proxy descriptor // to reduce startup time. Option will // later be disabled if needed. // SetNoDelay(proxyFD, 1); // // We need non-blocking input since the // negotiation phase. // SetNonBlocking(proxyFD, 1); return 1; } // // Create the required proxy and channel classes // and get ready for handling the encoded traffic. // int InitAfterNegotiation() { #ifdef TEST *logofs << "Loop: Connection with remote proxy completed.\n" << logofs_flush; #endif cerr << "Info" << ": Connection with remote proxy completed.\n" << logofs_flush; // // If we are the server proxy we completed // our initializazion phase according to // the values provided by the client side. // if (control -> ProxyMode == proxy_server) { SetParameters(); } // // Set up the listeners for the additional // services. // SetupServiceSockets(); // // Create the proxy class and the statistics // repository and pass all the configuration // data we negotiated with the remote peer. // SetupProxyInstance(); // // We completed both parsing of user's parameters // and handlshaking with remote proxy. Now print // a brief summary including the most significant // control values. // PrintConnectionInfo(); // // Cancel the initialization watchdog. // if (IsRunning(lastWatchdog)) { KillProcess(lastWatchdog, "watchdog", SIGTERM, 1); SetNotRunning(lastWatchdog); lastSignal = 0; } // // Start the house-keeper process. It will // remove the oldest persistent caches, if // the amount of storage exceed the limits // set by the user. // StartKeeper(); // // Set the log size check timestamp. // nowTs = getNewTimestamp(); logsTs = nowTs; // // TODO: Due to the way the new NX transport is working, // the accounting of time spent handling messages must // be rewritten. In particular, at the moment it only // shows the time spent encoding and decoding messages // in the main loop, after executing a select. It doesn't // take into account the time spent in the NXTrans* calls // where messages can be encoded and decoded implicitly, // on demand of the agent. When the agent transport is // in use, these calls constitute the vast majority of // the encoding activity. The result is that the number // of KB encoded per second shown by the proxy statistics // is actually much lower than the real throughput gene- // rated by the proxy. // #ifdef TEST *logofs << "Loop: INIT! Completed initialization at " << strMsTimestamp(nowTs) << " with " << diffTimestamp(initTs, nowTs) << " Ms " << "since the init mark.\n" << logofs_flush; #endif initTs = getNewTimestamp(); // // We can now start handling binary data from // our peer proxy. // if (agent == NULL) { cerr << "Session" << ": Session started at '" << strTimestamp() << "'.\n"; } return 1; } int SetMode(int mode) { // // Set the local proxy mode. // if (control -> ProxyMode == proxy_undefined) { if (mode == NX_MODE_CLIENT) { #ifdef TEST *logofs << "Loop: INIT! Initializing with mode " << "NX_MODE_CLIENT at " << strMsTimestamp() << ".\n" << logofs_flush; #endif control -> ProxyMode = proxy_client; } else if (mode == NX_MODE_SERVER) { #ifdef TEST *logofs << "Loop: INIT! Initializing with mode " << "NX_MODE_SERVER at " << strMsTimestamp() << ".\n" << logofs_flush; #endif control -> ProxyMode = proxy_server; } else { cerr << "Error" << ": Please specify either " << "the -C or -S option.\n"; HandleCleanup(); } } return 1; } int SetupProxyInstance() { if (control -> ProxyMode == proxy_client) { proxy = new ClientProxy(proxyFD); } else { proxy = new ServerProxy(proxyFD); } if (proxy == NULL) { #ifdef PANIC *logofs << "Loop: PANIC! Error creating the NX proxy.\n" << logofs_flush; #endif cerr << "Error" << ": Error creating the NX proxy.\n"; HandleCleanup(); } // // Create the statistics repository. // statistics = new Statistics(proxy); if (statistics == NULL) { #ifdef PANIC *logofs << "Loop: PANIC! Error creating the NX statistics.\n" << logofs_flush; #endif cerr << "Error" << ": Error creating the NX statistics.\n"; HandleCleanup(); } // // If user gave us a proxy cookie than create the // X11 authorization repository and find the real // cookie to be used for our X display. // SetupAuthInstance(); // // Reset the static members in channels. // proxy -> handleChannelConfiguration(); // // Inform the proxies about the ports where they // will have to forward the network connections. // proxy -> handleDisplayConfiguration(displayHost, xServerAddrFamily, xServerAddr, xServerAddrLength); proxy -> handlePortConfiguration(cupsPort, smbPort, mediaPort, httpPort, fontPort); // // We handed over the sockaddr structure we // created when we set up the display socket // to the proxy. // xServerAddr = NULL; // // Set socket options on proxy link, then propagate link // configuration to proxy. This includes translating some // control parameters in 'local' and 'remote'. Finally // adjust cache parameters according to pack method and // session type selected by user. // if (proxy -> handleSocketConfiguration() < 0 || proxy -> handleLinkConfiguration() < 0 || proxy -> handleCacheConfiguration() < 0) { #ifdef PANIC *logofs << "Loop: PANIC! Error configuring the NX transport.\n" << logofs_flush; #endif cerr << "Error" << ": Error configuring the NX transport.\n"; HandleCleanup(); } // // Load the message stores from the persistent // cache. // proxy -> handleLoad(load_if_first); // // Inform the proxy that from now on it can // start handling the encoded data. // proxy -> setOperational(); // // Are we going to use an internal IPC connection // with an agent? In this case create the channel // by using the socket descriptor provided by the // caller at the proxy initialization. // SetupAgentInstance(); // // Check if we need to verify the existence of // a matching client cache at shutdown. // #ifdef MATCH control -> PersistentCacheCheckOnShutdown = 1; #endif // // Flush any data produced so far. // proxy -> handleFlush(); #if defined(TEST) || defined(INFO) if (proxy -> getFlushable(proxyFD) > 0) { *logofs << "Loop: WARNING! Proxy FD#" << proxyFD << " has data " << "to flush after setup of the NX transport.\n" << logofs_flush; } #endif return 1; } int SetupAuthInstance() { // // If user gave us a proxy cookie, then create the // X11 authorization repository and find the real // cookie to be used for our X display. // if (control -> ProxyMode == proxy_server) { if (authCookie != NULL && *authCookie != '\0') { auth = new Auth(displayHost, authCookie); if (auth == NULL || auth -> isValid() != 1) { #ifdef PANIC *logofs << "Loop: PANIC! Error creating the X authorization.\n" << logofs_flush; #endif cerr << "Error" << ": Error creating the X authorization.\n"; HandleCleanup(); } } else { #ifdef TEST *logofs << "Loop: No proxy cookie was provided for " << "authentication.\n" << logofs_flush; #endif cerr << "Info" << ": No proxy cookie was provided for " << "authentication.\n"; #ifdef TEST *logofs << "Loop: Forwarding the real X authorization " << "cookie.\n" << logofs_flush; #endif cerr << "Info" << ": Forwarding the real X authorization " << "cookie.\n"; } } return 1; } int SetupAgentInstance() { if (control -> ProxyMode == proxy_client && useAgentSocket == 1) { // // This will temporarily disable signals to safely // load the cache, then will send a control packet // to the remote end, telling that cache has to be // loaded, so it's important that proxy is already // set in operational state. // int result; if (agent != NULL) { result = proxy -> handleNewAgentConnection(agent); } else { result = proxy -> handleNewConnection(channel_x11, agentFD[1]); } if (result < 0) { #ifdef PANIC *logofs << "Loop: PANIC! Error creating the NX agent connection.\n" << logofs_flush; #endif cerr << "Error" << ": Error creating the NX agent connection.\n"; HandleCleanup(); } } return 1; } int SetupTcpSocket() { // // Open TCP socket emulating local display. // tcpFD = socket(AF_INET, SOCK_STREAM, PF_UNSPEC); if (tcpFD == -1) { #ifdef PANIC *logofs << "Loop: PANIC! Call to socket failed for TCP socket" << ". Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Call to socket failed for TCP socket" << ". Error is " << EGET() << " '" << ESTR() << "'.\n"; HandleCleanup(); } else if (SetReuseAddress(tcpFD) < 0) { HandleCleanup(); } unsigned int proxyPortTCP = X_TCP_PORT + proxyPort; sockaddr_in tcpAddr; tcpAddr.sin_family = AF_INET; tcpAddr.sin_port = htons(proxyPortTCP); tcpAddr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(tcpFD, (sockaddr *) &tcpAddr, sizeof(tcpAddr)) == -1) { #ifdef PANIC *logofs << "Loop: PANIC! Call to bind failed for TCP port " << proxyPortTCP << ". Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Call to bind failed for TCP port " << proxyPortTCP << ". Error is " << EGET() << " '" << ESTR() << "'.\n"; HandleCleanup(); } if (listen(tcpFD, 8) == -1) { #ifdef PANIC *logofs << "Loop: PANIC! Call to listen failed for TCP port " << proxyPortTCP << ". Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Call to listen failed for TCP port " << proxyPortTCP << ". Error is " << EGET() << " '" << ESTR() << "'.\n"; HandleCleanup(); } return 1; } int SetupUnixSocket() { // // Open UNIX domain socket for display. // unixFD = socket(AF_UNIX, SOCK_STREAM, PF_UNSPEC); if (unixFD == -1) { #ifdef PANIC *logofs << "Loop: PANIC! Call to socket failed for UNIX domain" << ". Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Call to socket failed for UNIX domain" << ". Error is " << EGET() << " '" << ESTR() << "'.\n"; HandleCleanup(); } sockaddr_un unixAddr; unixAddr.sun_family = AF_UNIX; char dirName[DEFAULT_STRING_LENGTH]; snprintf(dirName, DEFAULT_STRING_LENGTH - 1, "%s/.X11-unix", control -> TempPath); *(dirName + DEFAULT_STRING_LENGTH - 1) = '\0'; struct stat dirStat; if ((stat(dirName, &dirStat) == -1) && (EGET() == ENOENT)) { mkdir(dirName, (0777 | S_ISVTX)); chmod(dirName, (0777 | S_ISVTX)); } snprintf(unixSocketName, DEFAULT_STRING_LENGTH - 1, "%s/X%d", dirName, proxyPort); strncpy(unixAddr.sun_path, unixSocketName, 108); #ifdef TEST *logofs << "Loop: Assuming Unix socket with name '" << unixAddr.sun_path << "'.\n" << logofs_flush; #endif *(unixAddr.sun_path + 107) = '\0'; if (bind(unixFD, (sockaddr *) &unixAddr, sizeof(unixAddr)) == -1) { #ifdef PANIC *logofs << "Loop: PANIC! Call to bind failed for UNIX domain socket " << unixSocketName << ". Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Call to bind failed for UNIX domain socket " << unixSocketName << ". Error is " << EGET() << " '" << ESTR() << "'.\n"; HandleCleanup(); } if (listen(unixFD, 8) == -1) { #ifdef PANIC *logofs << "Loop: PANIC! Call to listen failed for UNIX domain socket " << unixSocketName << ". Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Call to listen failed for UNIX domain socket " << unixSocketName << ". Error is " << EGET() << " '" << ESTR() << "'.\n"; HandleCleanup(); } // // Let any local user to gain access to socket. // chmod(unixSocketName, 0777); return 1; } // // The following is a dumb copy-paste. The // nxcompsh library should offer a better // implementation. // int SetupDisplaySocket(int &xServerAddrFamily, sockaddr *&xServerAddr, unsigned int &xServerAddrLength) { xServerAddrFamily = AF_INET; xServerAddr = NULL; xServerAddrLength = 0; char *display; if (*displayHost == '\0') { // // Assume DISPLAY as the X server to which // we will forward the proxied connections. // This means that NX parameters have been // passed through other means. // display = getenv("DISPLAY"); if (display == NULL || *display == '\0') { #ifdef PANIC *logofs << "Loop: PANIC! Host X server DISPLAY is not set.\n" << logofs_flush; #endif cerr << "Error" << ": Host X server DISPLAY is not set.\n"; HandleCleanup(); } else if (strncasecmp(display, "nx/nx,", 6) == 0 || strncasecmp(display, "nx,", 3) == 0 || strncasecmp(display, "nx:", 3) == 0) { #ifdef PANIC *logofs << "Loop: PANIC! NX transport on host X server '" << display << "' not supported.\n" << logofs_flush; #endif cerr << "Error" << ": NX transport on host X server '" << display << "' not supported.\n"; cerr << "Error" << ": Please run the local proxy specifying " << "the host X server to connect to.\n"; HandleCleanup(); } else if (strlen(display) >= DEFAULT_STRING_LENGTH) { #ifdef PANIC *logofs << "Loop: PANIC! Host X server DISPLAY cannot exceed " << DEFAULT_STRING_LENGTH << " characters.\n" << logofs_flush; #endif cerr << "Error" << ": Host X server DISPLAY cannot exceed " << DEFAULT_STRING_LENGTH << " characters.\n"; HandleCleanup(); } strcpy(displayHost, display); } display = new char[strlen(displayHost) + 1]; if (display == NULL) { #ifdef PANIC *logofs << "Loop: PANIC! Out of memory handling DISPLAY variable.\n" << logofs_flush; #endif cerr << "Error" << ": Out of memory handling DISPLAY variable.\n"; HandleCleanup(); } strcpy(display, displayHost); char *separator = rindex(display, ':'); if ((separator == NULL) || !isdigit(*(separator + 1))) { #ifdef PANIC *logofs << "Loop: PANIC! Invalid display '" << display << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Invalid display '" << display << "'.\n"; HandleCleanup(); } *separator = '\0'; xPort = atoi(separator + 1); #ifdef TEST *logofs << "Loop: Using local X display '" << displayHost << "' with host '" << display << "' and port '" << xPort << "'.\n" << logofs_flush; #endif if (separator == display || strcmp(display, "unix") == 0) { // // UNIX domain port. // #ifdef TEST *logofs << "Loop: Using real X server on UNIX domain socket.\n" << logofs_flush; #endif sockaddr_un *xServerAddrUNIX = new sockaddr_un; xServerAddrFamily = AF_UNIX; xServerAddrUNIX -> sun_family = AF_UNIX; // // The scope of this function is to fill either the sockaddr_un // (when the display is set to the Unix Domain socket) or the // sockaddr_in structure (when connecting by TCP) only once, so // that the structure will be later used at the time the server // proxy will try to forward the connection to the X server. We // don't need to verify that the socket does exist at the pre- // sent moment. The method that forwards the connection will // perform the required checks and will retry, if needed. Anyway // we need to select the name of the socket, so we check if the // well-known directory exists and take that as an indication of // where the socket will be created. // struct stat statInfo; char unixSocketDir[DEFAULT_STRING_LENGTH]; snprintf(unixSocketDir, DEFAULT_STRING_LENGTH - 1, "%s/.X11-unix", control -> TempPath); *(unixSocketDir + DEFAULT_STRING_LENGTH - 1) = '\0'; #ifdef TEST *logofs << "Loop: Assuming X socket in directory '" << unixSocketDir << "'.\n" << logofs_flush; #endif if (stat(unixSocketDir, &statInfo) < 0) { #ifdef PANIC *logofs << "Loop: PANIC! Can't determine the location of " << "the X display socket.\n" << logofs_flush; #endif cerr << "Error" << ": Can't determine the location of " << "the X display socket.\n"; #ifdef PANIC *logofs << "Loop: PANIC! Error " << EGET() << " '" << ESTR() << "' checking '" << unixSocketDir << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Error " << EGET() << " '" << ESTR() << "' checking '" << unixSocketDir << "'.\n"; HandleCleanup(); } sprintf(unixSocketName, "%s/X%d", unixSocketDir, xPort); #ifdef TEST *logofs << "Loop: Assuming X socket name '" << unixSocketName << "'.\n" << logofs_flush; #endif strcpy(xServerAddrUNIX -> sun_path, unixSocketName); xServerAddr = (sockaddr *) xServerAddrUNIX; xServerAddrLength = sizeof(sockaddr_un); } else { // // TCP port. // #ifdef TEST *logofs << "Loop: Using real X server on TCP port.\n" << logofs_flush; #endif xServerAddrFamily = AF_INET; int xServerIPAddr = GetHostAddress(display); if (xServerIPAddr == 0) { #ifdef PANIC *logofs << "Loop: PANIC! Unknown display host '" << display << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Unknown display host '" << display << "'.\n"; HandleCleanup(); } sockaddr_in *xServerAddrTCP = new sockaddr_in; xServerAddrTCP -> sin_family = AF_INET; xServerAddrTCP -> sin_port = htons(X_TCP_PORT + xPort); xServerAddrTCP -> sin_addr.s_addr = xServerIPAddr; xServerAddr = (sockaddr *) xServerAddrTCP; xServerAddrLength = sizeof(sockaddr_in); } delete [] display; return 1; } int SetupServiceSockets() { if (control -> ProxyMode == proxy_client) { if (useCupsSocket) { if ((cupsFD = ListenConnection(cupsPort, "CUPS")) < 0) { useCupsSocket = 0; } } if (useAuxSocket) { if ((auxFD = ListenConnection(auxPort, "auxiliary X11")) < 0) { useAuxSocket = 0; } } if (useSmbSocket) { if ((smbFD = ListenConnection(smbPort, "SMB")) < 0) { useSmbSocket = 0; } } if (useMediaSocket) { if ((mediaFD = ListenConnection(mediaPort, "media")) < 0) { useMediaSocket = 0; } } if (useHttpSocket) { if ((httpFD = ListenConnection(httpPort, "http")) < 0) { useHttpSocket = 0; } } useFontSocket = 0; } else { // // Disable the font server connections if // they are not supported by the remote // proxy. // if (useFontSocket) { if (control -> isProtoStep7() == 1) { int port = atoi(fontPort); if ((fontFD = ListenConnection(port, "font")) < 0) { useFontSocket = 0; } } else { #ifdef WARNING *logofs << "Loop: WARNING! Font server connections not supported " << "by the remote proxy.\n" << logofs_flush; #endif cerr << "Warning" << ": Font server connections not supported " << "by the remote proxy.\n"; useFontSocket = 0; } } useCupsSocket = 0; useAuxSocket = 0; useSmbSocket = 0; useMediaSocket = 0; useHttpSocket = 0; } // // Slave channels can be originated // by both sides. // if (useSlaveSocket) { if (control -> isProtoStep7() == 1) { if ((slaveFD = ListenConnection(slavePort, "slave")) < 0) { useSlaveSocket = 0; } } else { #ifdef WARNING *logofs << "Loop: WARNING! Slave connections not supported " << "by the remote proxy.\n" << logofs_flush; #endif cerr << "Warning" << ": Slave connections not supported " << "by the remote proxy.\n"; useSlaveSocket = 0; } } return 1; } int ListenConnection(int port, const char *label) { sockaddr_in tcpAddr; unsigned int portTCP = port; int newFD = socket(AF_INET, SOCK_STREAM, PF_UNSPEC); if (newFD == -1) { #ifdef PANIC *logofs << "Loop: PANIC! Call to socket failed for " << label << " TCP socket. Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Call to socket failed for " << label << " TCP socket. Error is " << EGET() << " '" << ESTR() << "'.\n"; goto SetupSocketError; } else if (SetReuseAddress(newFD) < 0) { goto SetupSocketError; } tcpAddr.sin_family = AF_INET; tcpAddr.sin_port = htons(portTCP); tcpAddr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(newFD, (sockaddr *) &tcpAddr, sizeof(tcpAddr)) == -1) { #ifdef PANIC *logofs << "Loop: PANIC! Call to bind failed for " << label << " TCP port " << port << ". Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Call to bind failed for " << label << " TCP port " << port << ". Error is " << EGET() << " '" << ESTR() << "'.\n"; goto SetupSocketError; } if (listen(newFD, 8) == -1) { #ifdef PANIC *logofs << "Loop: PANIC! Call to bind failed for " << label << " TCP port " << port << ". Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Call to bind failed for " << label << " TCP port " << port << ". Error is " << EGET() << " '" << ESTR() << "'.\n"; goto SetupSocketError; } return newFD; SetupSocketError: if (newFD != -1) { close(newFD); } // // May optionally return. The session would // continue without the service. The problem // with this approach is that it would make // harder to track problems with allocation // of ports in clients and server. // HandleCleanup(); } static int AcceptConnection(int fd, int domain, const char *label) { struct sockaddr newAddr; socklen_t addrLen = sizeof(newAddr); #ifdef TEST if (domain == AF_UNIX) { *logofs << "Loop: Going to accept new Unix " << label << " connection on FD#" << fd << ".\n" << logofs_flush; } else { *logofs << "Loop: Going to accept new TCP " << label << " connection on FD#" << fd << ".\n" << logofs_flush; } #endif int newFD = accept(fd, &newAddr, &addrLen); if (newFD < 0) { #ifdef PANIC *logofs << "Loop: PANIC! Call to accept failed for " << label << " connection. Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Call to accept failed for " << label << " connection. Error is " << EGET() << " '" << ESTR() << "'.\n"; } return newFD; } void HandleShutdown() { if (proxy -> getShutdown() == 0) { #ifdef PANIC *logofs << "Loop: PANIC! No shutdown of proxy link " << "performed by remote proxy.\n" << logofs_flush; #endif // // Close the socket before showing the alert. // It seems that the closure of the socket can // sometimes take several seconds, even after // the connection is broken. The result is that // the dialog can be shown long after the user // has gone after the failed session. Note that // disabling the linger timeout does not seem // to make any difference. // CleanupSockets(); cerr << "Error" << ": Connection with remote peer broken.\n"; #ifdef TEST *logofs << "Loop: Bytes received so far are " << (unsigned long long) statistics -> getBytesIn() << ".\n" << logofs_flush; #endif cerr << "Error" << ": Please check the state of your " << "network and retry.\n"; handleTerminatingInLoop(); if (control -> ProxyMode == proxy_server) { #ifdef TEST *logofs << "Loop: Showing the proxy abort dialog.\n" << logofs_flush; #endif HandleAlert(ABORT_PROXY_CONNECTION_ALERT, 1); handleAlertInLoop(); } } #ifdef TEST else { *logofs << "Loop: Finalized the remote proxy shutdown.\n" << logofs_flush; } #endif HandleCleanup(); } void WaitCleanup() { T_timestamp selectTs; while (NXTransRunning(NX_FD_ANY)) { setTimestamp(selectTs, control -> PingTimeout); NXTransContinue(&selectTs); } } int KillProcess(int pid, const char *label, int signal, int wait) { if (pid > 0) { #if defined(TEST) || defined(INFO) *logofs << "Loop: Killing the " << label << " process '" << pid << "' from process with pid '" << getpid() << "' with signal '" << DumpSignal(signal) << "'.\n" << logofs_flush; #endif signal = (signal == 0 ? SIGTERM : signal); if (kill(pid, signal) < 0 && EGET() != ESRCH) { #ifdef PANIC *logofs << "Loop: PANIC! Couldn't kill the " << label << " process with pid '" << pid << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Couldn't kill the " << label << " process with pid '" << pid << "'.\n"; } if (wait == 1) { WaitChild(pid, label, 1); } return 1; } else { #ifdef TEST *logofs << "Loop: No " << label << " process " << "to kill with pid '" << pid << "'.\n" << logofs_flush; #endif return 0; } } int CheckProcess(int pid, const char *label) { #if defined(TEST) || defined(INFO) *logofs << "Loop: Checking the " << label << " process '" << pid << "' from process with pid '" << getpid() << "'.\n" << logofs_flush; #endif if (kill(pid, SIGCONT) < 0 && EGET() == ESRCH) { #ifdef WARNING *logofs << "Loop: WARNING! The " << label << " process " << "with pid '" << pid << "' has exited.\n" << logofs_flush; #endif cerr << "Warning" << ": The " << label << " process " << "with pid '" << pid << "' has exited.\n"; return 0; } return 1; } int StartKeeper() { #if defined(TEST) || defined(INFO) if (IsRunning(lastKeeper) == 1 || IsRestarting(lastKeeper) == 1) { #ifdef PANIC *logofs << "Loop: PANIC! The house-keeping process is " << "alreay running with pid '" << lastKeeper << "'.\n" << logofs_flush; #endif HandleCleanup(); } #endif // // Don't care harvesting the persistent caches if // the memory cache is not enabled. If the memory // cache is not enabled neither we produced per- // sistent caches. The user can still delete any // persistent cache produced by the previous runs // by using the client GUI. // // TODO: At the moment the user doesn't have a way // to specify the amount of disk space to use for // the persistent caches, but only the amount of // space to use for images. // if (control -> LocalTotalStorageSize > 0) { #ifdef TEST *logofs << "Loop: Starting the house-keeping process with " << "storage size " << control -> PersistentCacheDiskLimit << ".\n" << logofs_flush; #endif lastKeeper = NXTransKeeper(control -> PersistentCacheDiskLimit, 0, control -> RootPath); if (IsFailed(lastKeeper)) { #ifdef WARNING *logofs << "Loop: WARNING! Failed to start the NX keeper process.\n" << logofs_flush; #endif cerr << "Warning" << ": Failed to start the NX keeper process.\n"; SetNotRunning(lastKeeper); } #ifdef TEST else { *logofs << "Loop: Keeper started with pid '" << lastKeeper << "'.\n" << logofs_flush; } #endif } #ifdef TEST else { *logofs << "Loop: Nothing to do for the keeper process " << "with persistent cache not enabled.\n" << logofs_flush; } #endif return 1; } void HandleCleanup(int code) { #ifdef TEST *logofs << "Loop: Going to clean up system resources " << "in process '" << getpid() << "'.\n" << logofs_flush; #endif handleTerminatedInLoop(); // // Suspend any signal while cleaning up. // DisableSignals(); if (getpid() == lastProxy) { // // Terminate all the children. // CleanupChildren(); // // Close all listeners. // CleanupListeners(); // // Close all sockets. // CleanupSockets(); // // Release the global objects. // CleanupGlobal(); // // Restore the original signal handlers. // RestoreSignals(); } // // This is our last chance to print a message. If this // is the process which created the transport we will // jump back into the loop, letting the caller find out // that the connection is broken, otherwise we assume // that this is a child of the proxy and so we will // safely exit. // #ifdef TEST if (getpid() == lastProxy) { *logofs << "Loop: Reverting to loop context in process with " << "pid '" << getpid() << "' at " << strMsTimestamp() << ".\n" << logofs_flush; } else { *logofs << "Loop: Exiting from child process with pid '" << getpid() << "' at " << strMsTimestamp() << ".\n" << logofs_flush; } #endif if (getpid() == lastProxy) { // // Reset all values to their default. // CleanupLocal(); CleanupStreams(); longjmp(context, 1); } else { // // Give a last chance to the process // to cleanup the ancillary classes. // CleanupKeeper(); CleanupStreams(); exit(code); } } void CleanupKeeper() { if (keeper != NULL) { #ifdef TEST *logofs << "Loop: Freeing up keeper in process " << "with pid '" << getpid() << "'.\n" << logofs_flush; #endif delete keeper; keeper = NULL; } } void CleanupStreams() { // // TODO: The cleanup procedure skips deletion of // the I/O streams under Windows. This is intended // to avoid a strange segfault occurring randomly, // at the time the proxy is being shut down. // #ifndef __CYGWIN32__ #ifdef TEST *logofs << "Loop: Freeing up streams in process " << "with pid '" << getpid() << "'.\n" << logofs_flush; #endif if (logofs != NULL && logofs != &cerr && *errorsFileName != '\0') { *logofs << flush; delete logofs; // // Let the log go again to the standard // error. // logofs = &cerr; } if (statofs != NULL && statofs != &cerr && *statsFileName != '\0') { *statofs << flush; delete statofs; statofs = NULL; } if (errofs != NULL) { *errofs << flush; if (errofs == &cerr) { errofs = NULL; } else if (errsbuf != NULL) { cerr.rdbuf(errsbuf); errsbuf = NULL; delete errofs; } errofs = NULL; } #endif /* #ifndef __CYGWIN32__ */ // // Reset these as they can't be reset // in CleanupLocal(). // *sessionFileName = '\0'; *errorsFileName = '\0'; *optionsFileName = '\0'; *statsFileName = '\0'; } void CleanupChildren() { // // Remove any watchdog. // if (IsRunning(lastWatchdog)) { KillProcess(lastWatchdog, "watchdog", SIGTERM, 1); SetNotRunning(lastWatchdog); lastSignal = 0; } // // Kill the cache house-keeping process. // if (IsRunning(lastKeeper)) { KillProcess(lastKeeper, "house-keeping", SIGTERM, 1); SetNotRunning(lastKeeper); } // // Let any running dialog to continue until it is // closed by the user. In general this is the exp- // ected behaviour, as for example when we are // exiting because the link was abrouptedly shut // down. // if (IsRunning(lastDialog)) { #if defined(TEST) || defined(INFO) *logofs << "Loop: WARNING! Leaving the dialog process '" << lastDialog << "' running in process " << "with pid '" << getpid() << "'.\n" << logofs_flush; #endif SetNotRunning(lastDialog); } // // Give user a chance to start a new session. // if (control -> EnableRestartOnShutdown == 1) { #ifdef WARNING *logofs << "Loop: WARNING! Respawning the NX client " << "on display '" << displayHost << "'.\n" << logofs_flush; #endif NXTransClient(displayHost); } for (int i = 0; i < control -> KillDaemonOnShutdownNumber; i++) { #ifdef WARNING *logofs << "Loop: WARNING! Killing the NX daemon with " << "pid '" << control -> KillDaemonOnShutdown[i] << "'.\n" << logofs_flush; #endif KillProcess(control -> KillDaemonOnShutdown[i], "daemon", SIGTERM, 0); } } void CleanupGlobal() { if (proxy != NULL) { #ifdef TEST *logofs << "Loop: Freeing up proxy in process " << "with pid '" << getpid() << "'.\n" << logofs_flush; #endif delete proxy; proxy = NULL; } if (agent != NULL) { #ifdef TEST *logofs << "Loop: Freeing up agent in process " << "with pid '" << getpid() << "'.\n" << logofs_flush; #endif delete agent; agent = NULL; } if (auth != NULL) { #ifdef TEST *logofs << "Loop: Freeing up auth data in process " << "with pid '" << getpid() << "'.\n" << logofs_flush; #endif delete auth; auth = NULL; } if (statistics != NULL) { #ifdef TEST *logofs << "Loop: Freeing up statistics in process " << "with pid '" << getpid() << "'.\n" << logofs_flush; #endif delete statistics; statistics = NULL; } if (control != NULL) { #ifdef TEST *logofs << "Loop: Freeing up control in process " << "with pid '" << getpid() << "'.\n" << logofs_flush; #endif delete control; control = NULL; } } void CleanupConnections() { if (proxy -> getChannels(channel_x11) != 0) { #ifdef TEST *logofs << "Loop: Closing any remaining X connections.\n" << logofs_flush; #endif proxy -> handleCloseAllXConnections(); #ifdef TEST *logofs << "Loop: Closing any remaining listener.\n" << logofs_flush; #endif proxy -> handleCloseAllListeners(); } proxy -> handleFinish(); } void CleanupListeners() { if (useTcpSocket == 1) { if (tcpFD != -1) { #ifdef TEST *logofs << "Loop: Closing TCP listener in process " << "with pid '" << getpid() << "'.\n" << logofs_flush; #endif close(tcpFD); tcpFD = -1; } useTcpSocket = 0; } if (useUnixSocket == 1) { if (unixFD != -1) { #ifdef TEST *logofs << "Loop: Closing UNIX listener in process " << "with pid '" << getpid() << "'.\n" << logofs_flush; #endif close(unixFD); unixFD = -1; } if (*unixSocketName != '\0') { #ifdef TEST *logofs << "Loop: Going to remove the Unix domain socket '" << unixSocketName << "' in process " << "with pid '" << getpid() << "'.\n" << logofs_flush; #endif unlink(unixSocketName); } useUnixSocket = 0; } if (useAgentSocket == 1) { // // There is no listener for the // agent descriptor. // useAgentSocket = 0; } if (useCupsSocket == 1) { if (cupsFD != -1) { #ifdef TEST *logofs << "Loop: Closing CUPS listener in process " << "with pid '" << getpid() << "'.\n" << logofs_flush; #endif close(cupsFD); cupsFD = -1; } useCupsSocket = 0; } if (useAuxSocket == 1) { if (auxFD != -1) { #ifdef TEST *logofs << "Loop: Closing auxiliary X11 listener " << "in process " << "with pid '" << getpid() << "'.\n" << logofs_flush; #endif close(auxFD); auxFD = -1; } useAuxSocket = 0; } if (useSmbSocket == 1) { if (smbFD != -1) { #ifdef TEST *logofs << "Loop: Closing SMB listener in process " << "with pid '" << getpid() << "'.\n" << logofs_flush; #endif close(smbFD); smbFD = -1; } useSmbSocket = 0; } if (useMediaSocket == 1) { if (mediaFD != -1) { #ifdef TEST *logofs << "Loop: Closing multimedia listener in process " << "with pid '" << getpid() << "'.\n" << logofs_flush; #endif close(mediaFD); mediaFD = -1; } useMediaSocket = 0; } if (useHttpSocket == 1) { if (httpFD != -1) { #ifdef TEST *logofs << "Loop: Closing http listener in process " << "with pid '" << getpid() << "'.\n" << logofs_flush; #endif close(httpFD); httpFD = -1; } useHttpSocket = 0; } if (useFontSocket == 1) { if (fontFD != -1) { #ifdef TEST *logofs << "Loop: Closing font server listener in process " << "with pid '" << getpid() << "'.\n" << logofs_flush; #endif close(fontFD); fontFD = -1; } useFontSocket = 0; } if (useSlaveSocket == 1) { if (slaveFD != -1) { #ifdef TEST *logofs << "Loop: Closing slave listener in process " << "with pid '" << getpid() << "'.\n" << logofs_flush; #endif close(slaveFD); slaveFD = -1; } useSlaveSocket = 0; } } void CleanupSockets() { if (proxyFD != -1) { #ifdef TEST *logofs << "Loop: Closing proxy FD in process " << "with pid '" << getpid() << "'.\n" << logofs_flush; #endif close(proxyFD); proxyFD = -1; } if (agentFD[1] != -1) { #ifdef TEST *logofs << "Loop: Closing agent FD in process " << "with pid '" << getpid() << "'.\n" << logofs_flush; #endif close(agentFD[1]); agentFD[0] = -1; agentFD[1] = -1; } } void CleanupLocal() { *homeDir = '\0'; *rootDir = '\0'; *tempDir = '\0'; *systemDir = '\0'; *sessionDir = '\0'; *linkSpeedName = '\0'; *cacheSizeName = '\0'; *shsegSizeName = '\0'; *imagesSizeName = '\0'; *bitrateLimitName = '\0'; *packMethodName = '\0'; *productName = '\0'; packMethod = -1; packQuality = -1; *sessionType = '\0'; *sessionId = '\0'; parsedOptions = 0; parsedCommand = 0; *remoteData = '\0'; remotePosition = 0; tcpFD = -1; unixFD = -1; cupsFD = -1; auxFD = -1; smbFD = -1; mediaFD = -1; httpFD = -1; fontFD = -1; slaveFD = -1; proxyFD = -1; agentFD[0] = -1; agentFD[1] = -1; useUnixSocket = 1; useTcpSocket = 1; useCupsSocket = 0; useAuxSocket = 0; useSmbSocket = 0; useMediaSocket = 0; useHttpSocket = 0; useFontSocket = 0; useSlaveSocket = 0; useAgentSocket = 0; useNoDelay = -1; usePolicy = -1; useRender = -1; useTaint = -1; *unixSocketName = '\0'; *connectHost = '\0'; *acceptHost = '\0'; *listenHost = '\0'; *displayHost = '\0'; *authCookie = '\0'; proxyPort = DEFAULT_NX_PROXY_PORT; xPort = DEFAULT_NX_X_PORT; xServerAddrFamily = -1; xServerAddrLength = 0; delete xServerAddr; xServerAddr = NULL; listenPort = -1; connectPort = -1; cupsPort = -1; auxPort = -1; smbPort = -1; mediaPort = -1; httpPort = -1; slavePort = -1; *fontPort = '\0'; *bindHost = '\0'; bindPort = -1; initTs = nullTimestamp(); startTs = nullTimestamp(); logsTs = nullTimestamp(); nowTs = nullTimestamp(); diffTs = 0; lastProxy = 0; lastDialog = 0; lastWatchdog = 0; lastKeeper = 0; lastStatus = 0; lastKill = 0; lastDestroy = 0; lastReadableTs = nullTimestamp(); lastAlert.code = 0; lastAlert.local = 0; lastMasks.blocked = 0; lastMasks.installed = 0; memset(&lastMasks.saved, 0, sizeof(sigset_t)); for (int i = 0; i < 32; i++) { lastMasks.enabled[i] = 0; lastMasks.forward[i] = 0; memset(&lastMasks.action[i], 0, sizeof(struct sigaction)); } lastSignal = 0; memset(&lastTimer.action, 0, sizeof(struct sigaction)); memset(&lastTimer.value, 0, sizeof(struct itimerval)); lastTimer.start = nullTimestamp(); lastTimer.next = nullTimestamp(); } int CheckAbort() { if (lastSignal != 0) { #ifdef TEST *logofs << "Loop: Aborting the procedure due to signal '" << lastSignal << "', '" << DumpSignal(lastSignal) << "'.\n" << logofs_flush; #endif cerr << "Info" << ": Aborting the procedure due to signal '" << lastSignal << "'.\n"; lastSignal = 0; return 1; } return 0; } void HandleAbort() { if (logofs == NULL) { logofs = &cerr; } *logofs << flush; handleTerminatingInLoop(); if (lastSignal == SIGHUP) { lastSignal = 0; } // // The current default is to just quit the program. // Code has not been updated to deal with the new // NX transport loop. // if (control -> EnableCoreDumpOnAbort == 1) { if (agent != NULL) { cerr << "Session" << ": Terminating session at '" << strTimestamp() << "'.\n"; } cerr << "Error" << ": Generating a core file to help " << "the investigations.\n"; cerr << "Session" << ": Session terminated at '" << strTimestamp() << "'.\n"; cerr << flush; signal(SIGABRT, SIG_DFL); raise(SIGABRT); } #ifdef TEST *logofs << "Loop: Showing the proxy abort dialog.\n" << logofs_flush; #endif if (control -> ProxyMode == proxy_server) { // // Close the socket before showing the alert. // It seems that the closure of the socket can // sometimes take several seconds, even after // the connection is broken. // CleanupSockets(); if (lastKill == 0) { HandleAlert(ABORT_PROXY_CONNECTION_ALERT, 1); } else { HandleAlert(ABORT_PROXY_SHUTDOWN_ALERT, 1); } handleAlertInLoop(); } HandleCleanup(); } void HandleAlert(int code, int local) { if (lastAlert.code == 0) { #if defined(TEST) || defined(INFO) *logofs << "Loop: Requesting an alert dialog with code " << code << " and local " << local << ".\n" << logofs_flush; #endif lastAlert.code = code; lastAlert.local = local; } #if defined(TEST) || defined(INFO) else { *logofs << "Loop: WARNING! Alert dialog already requested " << "with code " << lastAlert.code << ".\n" << logofs_flush; } #endif return; } void FlushCallback(int length) { if (flushCallback != NULL) { #if defined(TEST) || defined(INFO) *logofs << "Loop: Reporting a flush request at " << strMsTimestamp() << " with " << length << " bytes written.\n" << logofs_flush; #endif (*flushCallback)(flushParameter, length); } #if defined(TEST) || defined(INFO) else if (control -> ProxyMode == proxy_client) { *logofs << "Loop: WARNING! Can't find a flush " << "callback in process with pid '" << getpid() << "'.\n" << logofs_flush; } #endif } void KeeperCallback() { if (IsRunning(lastKeeper) == 0) { // // Let the house-keeping process take care // of the persistent image cache. // if (control -> ImageCacheEnableLoad == 1 || control -> ImageCacheEnableSave == 1) { #ifdef TEST *logofs << "Loop: Starting the house-keeping process with " << "image storage size " << control -> ImageCacheDiskLimit << ".\n" << logofs_flush; #endif lastKeeper = NXTransKeeper(0, control -> ImageCacheDiskLimit, control -> RootPath); if (IsFailed(lastKeeper)) { #ifdef WARNING *logofs << "Loop: WARNING! Can't start the NX keeper process.\n" << logofs_flush; #endif SetNotRunning(lastKeeper); } #ifdef TEST else { *logofs << "Loop: Keeper started with pid '" << lastKeeper << "'.\n" << logofs_flush; } #endif } #ifdef TEST else { *logofs << "Loop: Nothing to do for the keeper process " << "with image cache not enabled.\n" << logofs_flush; } #endif } #ifdef TEST else { *logofs << "Loop: Nothing to do with the keeper process " << "already running.\n" << logofs_flush; } #endif } void InstallSignals() { #ifdef TEST *logofs << "Loop: Installing signals in process with pid '" << getpid() << "'.\n" << logofs_flush; #endif for (int i = 0; i < 32; i++) { if (CheckSignal(i) == 1 && lastMasks.enabled[i] == 0) { InstallSignal(i, NX_SIGNAL_ENABLE); } } lastMasks.installed = 1; } void RestoreSignals() { #ifdef TEST *logofs << "Loop: Restoring signals in process with pid '" << getpid() << "'.\n" << logofs_flush; #endif if (lastMasks.installed == 1) { // // Need to keep monitoring the children. // for (int i = 0; i < 32; i++) { if (lastMasks.enabled[i] == 1) { RestoreSignal(i); } } } lastMasks.installed = 0; if (lastMasks.blocked == 1) { EnableSignals(); } } void DisableSignals() { if (lastMasks.blocked == 0) { sigset_t newMask; sigemptyset(&newMask); // // Block also the other signals that may be // installed by the agent, that are those // signals for which the function returns 2. // for (int i = 0; i < 32; i++) { if (CheckSignal(i) > 0) { #ifdef DUMP *logofs << "Loop: Disabling signal " << i << " '" << DumpSignal(i) << "' in process with pid '" << getpid() << "'.\n" << logofs_flush; #endif sigaddset(&newMask, i); } } sigprocmask(SIG_BLOCK, &newMask, &lastMasks.saved); lastMasks.blocked++; } #ifdef TEST else { *logofs << "Loop: WARNING! Signals were already blocked in " << "process with pid '" << getpid() << "'.\n" << logofs_flush; } #endif } void EnableSignals() { if (lastMasks.blocked == 1) { #ifdef TEST *logofs << "Loop: Enabling signals in process with pid '" << getpid() << "'.\n" << logofs_flush; #endif sigprocmask(SIG_SETMASK, &lastMasks.saved, NULL); lastMasks.blocked = 0; } else { #ifdef WARNING *logofs << "Loop: WARNING! Signals were not blocked in " << "process with pid '" << getpid() << "'.\n" << logofs_flush; #endif cerr << "Warning" << ": Signals were not blocked in " << "process with pid '" << getpid() << "'.\n"; } } void InstallSignal(int signal, int action) { if (lastMasks.enabled[signal] == 1) { if (action == NX_SIGNAL_FORWARD) { #ifdef TEST *logofs << "Loop: Forwarding handler for signal " << signal << " '" << DumpSignal(signal) << "' in process " << "with pid '" << getpid() << "'.\n" << logofs_flush; #endif lastMasks.forward[signal] = 1; return; } #ifdef TEST else { *logofs << "Loop: Reinstalling handler for signal " << signal << " '" << DumpSignal(signal) << "' in process " << "with pid '" << getpid() << "'.\n" << logofs_flush; } #endif } #ifdef TEST else { *logofs << "Loop: Installing handler for signal " << signal << " '" << DumpSignal(signal) << "' in process " << "with pid '" << getpid() << "'.\n" << logofs_flush; } #endif if (signal == SIGALRM && isTimestamp(lastTimer.start)) { ResetTimer(); } struct sigaction newAction; newAction.sa_handler = HandleSignal; // // This field doesn't exist on most OSes except // Linux. We keep setting the field to NULL to // avoid side-effects in the case the field is // a value return. // #if defined(__linux__) newAction.sa_restorer = NULL; #endif sigemptyset(&(newAction.sa_mask)); if (signal == SIGCHLD) { newAction.sa_flags = SA_NOCLDSTOP; } else { newAction.sa_flags = 0; } sigaction(signal, &newAction, &lastMasks.action[signal]); lastMasks.enabled[signal] = 1; if (action == NX_SIGNAL_FORWARD) { lastMasks.forward[signal] = 1; } } void RestoreSignal(int signal) { if (lastMasks.enabled[signal] == 0) { #ifdef WARNING *logofs << "Loop: WARNING! Signal '" << DumpSignal(signal) << " not installed in process with pid '" << getpid() << "'.\n" << logofs_flush; #endif cerr << "Warning" << ": Signal '" << DumpSignal(signal) << " not installed in process with pid '" << getpid() << "'.\n"; return; } #ifdef TEST *logofs << "Loop: Restoring handler for signal " << signal << " '" << DumpSignal(signal) << "' in process " << "with pid '" << getpid() << "'.\n" << logofs_flush; #endif if (signal == SIGALRM && isTimestamp(lastTimer.start)) { ResetTimer(); } sigaction(signal, &lastMasks.action[signal], NULL); lastMasks.enabled[signal] = 0; lastMasks.forward[signal] = 0; } void HandleSignal(int signal) { if (logofs == NULL) { logofs = &cerr; } #if defined(UNSAFE) && (defined(TEST) || defined(INFO)) if (lastSignal != 0) { *logofs << "Loop: WARNING! Last signal is '" << lastSignal << "', '" << DumpSignal(signal) << "' and not zero " << "in process with pid '" << getpid() << "'.\n" << logofs_flush; } *logofs << "Loop: Signal '" << signal << "', '" << DumpSignal(signal) << "' received in process " << "with pid '" << getpid() << "'.\n" << logofs_flush; #endif if (getpid() != lastProxy && handler != NULL) { #if defined(UNSAFE) && (defined(TEST) || defined(INFO)) *logofs << "Loop: Calling slave handler in process " << "with pid '" << getpid() << "'.\n" << logofs_flush; #endif if ((*handler)(signal) == 0) { return; } } switch (signal) { case SIGUSR1: { if (proxy != NULL && lastSignal == 0) { lastSignal = SIGUSR1; } break; } case SIGUSR2: { if (proxy != NULL && lastSignal == 0) { lastSignal = SIGUSR2; } break; } case SIGPIPE: { // // It can happen that SIGPIPE is delivered // to the proxy even in the case some other // descriptor is unexpectedly closed. // // if (agentFD[1] != -1) // { // cerr << "Info" << ": Received signal 'SIGPIPE'. " // << "Closing agent conection.\n"; // // shutdown(agentFD[1], SHUT_RDWR); // } // break; } case SIGALRM: { // // Nothing to do. Just wake up the // process on blocking operations. // break; } case SIGCHLD: { // // Check if any of our children has exited. // if (HandleChildren() != 0) { signal = 0; } // // Don't save this signal or it will override // any previous signal sent by child before // exiting. // break; } #ifdef __CYGWIN32__ case 12: { // // Nothing to do. This signal is what is delivered // by the Cygwin library when trying use a shared // memory function if the daemon is not running. // #ifdef TEST *logofs << "Loop: WARNING! Received signal '12' in " << "process with pid '" << getpid() << "'.\n" << logofs_flush; *logofs << "Loop: WARNING! Please check that the " << "cygserver daemon is running.\n" << logofs_flush; #endif break; } #endif default: { // // Register the signal so we can handle it // inside the main loop. We will probably // dispose any resource and exit. // if (getpid() == lastProxy) { #if defined(UNSAFE) && defined(TEST) *logofs << "Loop: Registering end of session request " << "due to signal '" << signal << "', '" << DumpSignal(signal) << "'.\n" << logofs_flush; #endif lastSignal = signal; } else { // // This is a child, so exit immediately. // HandleCleanup(); } } } if (signal != 0 && lastMasks.forward[signal] == 1) { if (lastMasks.action[signal].sa_handler != NULL && lastMasks.action[signal].sa_handler != HandleSignal) { #if defined(UNSAFE) && defined(TEST) *logofs << "Loop: Forwarding signal '" << signal << "', '" << DumpSignal(signal) << "' to previous handler.\n" << logofs_flush; #endif lastMasks.action[signal].sa_handler(signal); } #ifdef WARNING else if (lastMasks.action[signal].sa_handler == NULL) { *logofs << "Loop: WARNING! Parent requested to forward " << "signal '" << signal << "', '" << DumpSignal(signal) << "' but didn't set a handler.\n" << logofs_flush; } #endif } } int HandleChildren() { // // Try to waitpid() for each child because the // call might have return ECHILD and so we may // have lost any of the processes. // if (IsRunning(lastDialog) && HandleChild(lastDialog) == 1) { #if defined(UNSAFE) && defined(TEST) *logofs << "Loop: Resetting pid of last dialog process " << "in handler.\n" << logofs_flush; #endif SetNotRunning(lastDialog); if (proxy != NULL) { proxy -> handleResetAlert(); } return 1; } if (IsRunning(lastWatchdog) && HandleChild(lastWatchdog) == 1) { #if defined(UNSAFE) && defined(TEST) *logofs << "Loop: Watchdog is gone. Setting the last " << "signal to SIGHUP.\n" << logofs_flush; #endif lastSignal = SIGHUP; #if defined(UNSAFE) && defined(TEST) *logofs << "Loop: Resetting pid of last watchdog process " << "in handler.\n" << logofs_flush; #endif SetNotRunning(lastWatchdog); return 1; } // // The house-keeping process exits after a // number of iterations to keep the memory // pollution low. It is restarted on demand // by the lower layers, using the callback // function. // if (IsRunning(lastKeeper) && HandleChild(lastKeeper) == 1) { #if defined(UNSAFE) && defined(TEST) *logofs << "Loop: Resetting pid of last house-keeping " << "process in handler.\n" << logofs_flush; #endif SetNotRunning(lastKeeper); return 1; } // // The pid will be checked by the code // that registered the child. // if (IsRunning(lastChild)) { #if defined(UNSAFE) && defined(TEST) *logofs << "Loop: Resetting pid of last child process " << "in handler.\n" << logofs_flush; #endif SetNotRunning(lastChild); return 1; } // // This can actually happen either because we // reset the pid of the child process as soon // as we kill it, or because of a child process // of our parent. // #if defined(UNSAFE) && (defined(TEST) || defined(INFO)) *logofs << "Loop: Ignoring signal received for the " << "unregistered child.\n" << logofs_flush; #endif return 0; } int HandleChild(int child) { int pid; int status = 0; int options = WNOHANG | WUNTRACED; while ((pid = waitpid(child, &status, options)) && pid == -1 && EGET() == EINTR); return CheckChild(pid, status); } int WaitChild(int child, const char* label, int force) { int pid; int status = 0; int options = WUNTRACED; for (;;) { #if defined(TEST) || defined(INFO) *logofs << "Loop: Waiting for the " << label << " process '" << child << "' to die.\n" << logofs_flush; #endif pid = waitpid(child, &status, options); if (pid == -1 && EGET() == EINTR) { if (force == 0) { return 0; } #ifdef WARNING *logofs << "Loop: WARNING! Ignoring signal while " << "waiting for the " << label << " process '" << child << "' to die.\n" << logofs_flush; #endif continue; } break; } return (EGET() == ECHILD ? 1 : CheckChild(pid, status)); } int CheckChild(int pid, int status) { lastStatus = 0; if (pid > 0) { if (WIFSTOPPED(status)) { #if defined(UNSAFE) && defined(TEST) *logofs << "Loop: Child process '" << pid << "' was stopped " << "with signal " << (WSTOPSIG(status)) << ".\n" << logofs_flush; #endif return 0; } else { if (WIFEXITED(status)) { #if defined(UNSAFE) && defined(TEST) *logofs << "Loop: Child process '" << pid << "' exited " << "with status '" << (WEXITSTATUS(status)) << "'.\n" << logofs_flush; #endif lastStatus = WEXITSTATUS(status); } else if (WIFSIGNALED(status)) { if (CheckSignal(WTERMSIG(status)) != 1) { #ifdef WARNING *logofs << "Loop: WARNING! Child process '" << pid << "' died because of signal " << (WTERMSIG(status)) << ", '" << DumpSignal(WTERMSIG(status)) << "'.\n" << logofs_flush; #endif cerr << "Warning" << ": Child process '" << pid << "' died because of signal " << (WTERMSIG(status)) << ", '" << DumpSignal(WTERMSIG(status)) << "'.\n"; } #if defined(UNSAFE) && defined(TEST) else { *logofs << "Loop: Child process '" << pid << "' died because of signal " << (WTERMSIG(status)) << ", '" << DumpSignal(WTERMSIG(status)) << "'.\n" << logofs_flush; } #endif lastStatus = 1; } return 1; } } else if (pid < 0) { if (EGET() != ECHILD) { #ifdef PANIC *logofs << "Loop: PANIC! Call to waitpid failed. " << "Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Call to waitpid failed. " << "Error is " << EGET() << " '" << ESTR() << "'.\n"; HandleCleanup(); } // // This can happen when the waitpid() is // blocking, as the SIGCHLD is received // within the call. // #ifdef TEST *logofs << "Loop: No more children processes running.\n" << logofs_flush; #endif return 1; } return 0; } void RegisterChild(int child) { #if defined(TEST) || defined(INFO) if (IsNotRunning(lastChild)) { *logofs << "Loop: Registering child process '" << child << "' in process with pid '" << getpid() << "'.\n" << logofs_flush; } else { *logofs << "Loop: WARNING! Overriding registered child '" << lastChild << "' with new child '" << child << "' in process with pid '" << getpid() << "'.\n" << logofs_flush; } #endif lastChild = child; } int CheckParent(char *name, char *type, int parent) { if (parent != getppid() || parent == 1) { #ifdef WARNING *logofs << name << ": WARNING! Parent process appears " << "to be dead. Exiting " << type << ".\n" << logofs_flush; #endif cerr << "Warning" << ": Parent process appears " << "to be dead. Exiting " << type << ".\n"; return 0; } return 1; } void HandleTimer(int signal) { if (signal == SIGALRM) { if (isTimestamp(lastTimer.start)) { #if defined(UNSAFE) && defined(TEST) *logofs << "Loop: Timer expired at " << strMsTimestamp() << " in process with pid '" << getpid() << "'.\n" << logofs_flush; #endif if (proxy != NULL) { proxy -> handleTimer(); } ResetTimer(); } else { #ifdef PANIC *logofs << "Loop: PANIC! Inconsistent timer state " << " in process with pid '" << getpid() << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Inconsistent timer state " << " in process with pid '" << getpid() << "'.\n"; } } else { #ifdef PANIC *logofs << "Loop: PANIC! Inconsistent signal '" << signal << "', '" << DumpSignal(signal) << "' received in process with pid '" << getpid() << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Inconsistent signal '" << signal << "', '" << DumpSignal(signal) << "' received in process with pid '" << getpid() << "'.\n"; } } void SetTimer(int value) { getNewTimestamp(); if (isTimestamp(lastTimer.start)) { int diffTs = diffTimestamp(lastTimer.start, getTimestamp()); if (diffTs > lastTimer.next.tv_usec / 1000 * 2) { #ifdef WARNING *logofs << "Loop: WARNING! Timer missed to expire at " << strMsTimestamp() << " in process with pid '" << getpid() << "'.\n" << logofs_flush; #endif cerr << "Warning" << ": Timer missed to expire at " << strMsTimestamp() << " in process with pid '" << getpid() << "'.\n"; HandleTimer(SIGALRM); } else { #ifdef TEST *logofs << "Loop: Timer already running at " << strMsTimestamp() << " in process with pid '" << getpid() << "'.\n" << logofs_flush; #endif return; } } // // Save the former handler. // struct sigaction action; action.sa_handler = HandleTimer; #if defined(__linux__) action.sa_restorer = NULL; #endif sigemptyset(&action.sa_mask); action.sa_flags = 0; sigaction(SIGALRM, &action, &lastTimer.action); // // Start the timer. // lastTimer.next = getTimestamp(value); struct itimerval timer; timer.it_interval = lastTimer.next; timer.it_value = lastTimer.next; #ifdef TEST *logofs << "Loop: Timer set to " << lastTimer.next.tv_sec << " S and " << lastTimer.next.tv_usec / 1000 << " Ms at " << strMsTimestamp() << " in process " << "with pid '" << getpid() << "'.\n" << logofs_flush; #endif if (setitimer(ITIMER_REAL, &timer, &lastTimer.value) < 0) { #ifdef PANIC *logofs << "Loop: PANIC! Call to setitimer failed. " << "Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Call to setitimer failed. " << "Error is " << EGET() << " '" << ESTR() << "'.\n"; lastTimer.next = nullTimestamp(); return; } lastTimer.start = getTimestamp(); } void ResetTimer() { if (isTimestamp(lastTimer.start) == 0) { #if defined(UNSAFE) && defined(TEST) *logofs << "Loop: Timer not running in process " << "with pid '" << getpid() << "'.\n" << logofs_flush; #endif return; } #if defined(UNSAFE) && defined(TEST) *logofs << "Loop: Timer reset at " << strMsTimestamp() << " in process with pid '" << getpid() << "'.\n" << logofs_flush; #endif // // Restore the old signal mask and timer. // if (setitimer(ITIMER_REAL, &lastTimer.value, NULL) < 0) { #ifdef PANIC *logofs << "Loop: PANIC! Call to setitimer failed. " << "Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Call to setitimer failed. " << "Error is " << EGET() << " '" << ESTR() << "'.\n"; } if (sigaction(SIGALRM, &lastTimer.action, NULL) < 0) { #ifdef PANIC *logofs << "Loop: PANIC! Call to sigaction failed. " << "Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Call to sigaction failed. " << "Error is " << EGET() << " '" << ESTR() << "'.\n"; } lastTimer.start = lastTimer.next = nullTimestamp(); } // // Open TCP socket to listen for remote proxy and // block until remote connects. If successful close // the listening socket and return FD on which the // other party is connected. // int WaitForRemote(int portNum) { char hostLabel[DEFAULT_STRING_LENGTH] = { 0 }; int retryAccept = -1; int listenIPAddr = -1; int proxyFD = -1; int newFD = -1; // // Get IP address of host to be awaited. // int acceptIPAddr = 0; if (*acceptHost != '\0') { acceptIPAddr = GetHostAddress(acceptHost); if (acceptIPAddr == 0) { #ifdef PANIC *logofs << "Loop: PANIC! Cannot accept connections from unknown host '" << acceptHost << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Cannot accept connections from unknown host '" << acceptHost << "'.\n"; goto WaitForRemoteError; } } proxyFD = socket(AF_INET, SOCK_STREAM, PF_UNSPEC); if (proxyFD == -1) { #ifdef PANIC *logofs << "Loop: PANIC! Call to socket failed for TCP socket. " << "Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Call to socket failed for TCP socket. " << "Error is " << EGET() << " '" << ESTR() << "'.\n"; goto WaitForRemoteError; } else if (SetReuseAddress(proxyFD) < 0) { goto WaitForRemoteError; } listenIPAddr = 0; ParseListenOption(listenIPAddr); sockaddr_in tcpAddr; tcpAddr.sin_family = AF_INET; tcpAddr.sin_port = htons(portNum); // // Quick patch to run on MacOS/X where inet_addr("127.0.0.1") // alone seems to fail to return a valid interface. It probably // just needs a htonl() or something like that. // // TODO: We have to give another look at inet_addr("127.0.0.1") // on the Mac. // #ifdef __APPLE__ tcpAddr.sin_addr.s_addr = htonl(INADDR_ANY); #else tcpAddr.sin_addr.s_addr = listenIPAddr; #endif if (bind(proxyFD, (sockaddr *) &tcpAddr, sizeof(tcpAddr)) == -1) { #ifdef PANIC *logofs << "Loop: PANIC! Call to bind failed for TCP port " << portNum << ". Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Call to bind failed for TCP port " << portNum << ". Error is " << EGET() << " '" << ESTR() << "'.\n"; goto WaitForRemoteError; } if (listen(proxyFD, 4) == -1) { #ifdef PANIC *logofs << "Loop: PANIC! Call to listen failed for TCP port " << portNum << ". Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Call to listen failed for TCP port " << portNum << ". Error is " << EGET() << " '" << ESTR() << "'.\n"; goto WaitForRemoteError; } if (*acceptHost != '\0') { strcat(hostLabel, "'"); strcat(hostLabel, acceptHost); strcat(hostLabel, "'"); } else { strcpy(hostLabel, "any host"); } #ifdef TEST *logofs << "Loop: Waiting for connection from " << hostLabel << " on port '" << portNum << "'.\n" << logofs_flush; #endif cerr << "Info" << ": Waiting for connection from " << hostLabel << " on port '" << portNum << "'.\n"; // // How many times to loop waiting for connections // from the selected host? Each loop wait for at // most 20 seconds so a default value of 3 gives // a timeout of 1 minute. // // TODO: Handling of timeouts and retry attempts // must be rewritten. // retryAccept = control -> OptionProxyRetryAccept; for (;;) { fd_set readSet; FD_ZERO(&readSet); FD_SET(proxyFD, &readSet); T_timestamp selectTs; selectTs.tv_sec = 20; selectTs.tv_usec = 0; int result = select(proxyFD + 1, &readSet, NULL, NULL, &selectTs); getNewTimestamp(); if (result == -1) { if (EGET() == EINTR) { if (CheckAbort() != 0) { goto WaitForRemoteError; } continue; } #ifdef PANIC *logofs << "Loop: PANIC! Call to select failed. Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Call to select failed. Error is " << EGET() << " '" << ESTR() << "'.\n"; goto WaitForRemoteError; } else if (result > 0 && FD_ISSET(proxyFD, &readSet)) { sockaddr_in newAddr; size_t addrLen = sizeof(sockaddr_in); newFD = accept(proxyFD, (sockaddr *) &newAddr, (socklen_t *) &addrLen); if (newFD == -1) { #ifdef PANIC *logofs << "Loop: PANIC! Call to accept failed. Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Call to accept failed. Error is " << EGET() << " '" << ESTR() << "'.\n"; goto WaitForRemoteError; } char *connectedHost = inet_ntoa(newAddr.sin_addr); if (*acceptHost == '\0' || (int) newAddr.sin_addr.s_addr == acceptIPAddr) { #ifdef TEST unsigned int connectedPort = ntohs(newAddr.sin_port); *logofs << "Loop: Accepted connection from '" << connectedHost << "' with port '" << connectedPort << "'.\n" << logofs_flush; #endif cerr << "Info" << ": Accepted connection from '" << connectedHost << "'.\n"; break; } else { #ifdef PANIC *logofs << "Loop: WARNING! Refusing connection from '" << connectedHost << "' on port '" << portNum << "'.\n" << logofs_flush; #endif cerr << "Warning" << ": Refusing connection from '" << connectedHost << "'.\n"; } // // Not the best way to elude a DOS attack... // sleep(5); close(newFD); } if (--retryAccept == 0) { if (*acceptHost == '\0') { #ifdef PANIC *logofs << "Loop: PANIC! Connection with remote host " << "could not be established.\n" << logofs_flush; #endif cerr << "Error" << ": Connection with remote host " << "could not be established.\n"; } else { #ifdef PANIC *logofs << "Loop: PANIC! Connection with remote host '" << acceptHost << "' could not be established.\n" << logofs_flush; #endif cerr << "Error" << ": Connection with remote host '" << acceptHost << "' could not be established.\n"; } goto WaitForRemoteError; } else { handleCheckSessionInConnect(); } } close(proxyFD); return newFD; WaitForRemoteError: close(proxyFD); HandleCleanup(); } // // Connect to remote proxy. If successful // return FD of connection, else return -1. // int ConnectToRemote(const char *const hostName, int portNum) { int proxyFD = -1; int remoteIPAddr = GetHostAddress(hostName); if (remoteIPAddr == 0) { #ifdef PANIC *logofs << "Loop: PANIC! Unknown remote host '" << hostName << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Unknown remote host '" << hostName << "'.\n"; HandleCleanup(); } #ifdef TEST *logofs << "Loop: Connecting to remote host '" << hostName << ":" << portNum << "'.\n" << logofs_flush; #endif cerr << "Info" << ": Connecting to remote host '" << hostName << ":" << portNum << "'.\n" << logofs_flush; // // How many times we retry to connect to remote // host in case of failure? // int retryConnect = control -> OptionProxyRetryConnect; // // Show an alert after 20 seconds and use the // same timeout to interrupt the connect. The // retry timeout is incremental, starting from // 100 miliseconds up to 1 second. // int alertTimeout = 20000; int connectTimeout = 20000; int retryTimeout = 100; T_timestamp lastRetry = getNewTimestamp(); sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(portNum); addr.sin_addr.s_addr = remoteIPAddr; for (;;) { proxyFD = socket(AF_INET, SOCK_STREAM, PF_UNSPEC); if (proxyFD == -1) { #ifdef PANIC *logofs << "Loop: PANIC! Call to socket failed. " << "Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Call to socket failed. " << "Error is " << EGET() << " '" << ESTR() << "'.\n"; goto ConnectToRemoteError; } else if (SetReuseAddress(proxyFD) < 0) { goto ConnectToRemoteError; } // // Ensure operation is timed out // if there is a network problem. // #ifdef DEBUG *logofs << "Loop: Timer set to " << connectTimeout / 1000 << " S " << "with retry set to " << retryConnect << " in process with pid '" << getpid() << "'.\n" << logofs_flush; #endif SetTimer(connectTimeout); int result = connect(proxyFD, (sockaddr *) &addr, sizeof(sockaddr_in)); int reason = EGET(); ResetTimer(); if (result < 0) { close(proxyFD); if (CheckAbort() != 0) { goto ConnectToRemoteError; } else if (--retryConnect == 0) { ESET(reason); #ifdef PANIC *logofs << "Loop: PANIC! Connection to '" << hostName << ":" << portNum << "' failed. Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Connection to '" << hostName << ":" << portNum << "' failed. Error is " << EGET() << " '" << ESTR() << "'.\n"; goto ConnectToRemoteError; } else { #ifdef TEST *logofs << "Loop: Sleeping " << retryTimeout << " Ms before retrying.\n" << logofs_flush; #endif usleep(retryTimeout * 1000); retryTimeout <<= 1; if (retryTimeout > 1000 * 1000) { retryTimeout = 1000 * 1000; } } // // Check if it is time to show an alert dialog. // if (diffTimestamp(lastRetry, getNewTimestamp()) >= (alertTimeout - control -> LatencyTimeout)) { if (IsNotRunning(lastDialog)) { handleCheckSessionInConnect(); // // Wait for the dialog process to die // unless a signal is received. // while (IsRunning(lastDialog)) { WaitChild(lastDialog, "dialog", 0); if (CheckAbort() != 0) { // // The client ignores the TERM signal // on Windows. // #ifdef __CYGWIN32__ KillProcess(lastDialog, "dialog", SIGKILL, 1); #else KillProcess(lastDialog, "dialog", SIGTERM, 1); #endif goto ConnectToRemoteError; } } lastRetry = getTimestamp(); } } #ifdef TEST { *logofs << "Loop: Not showing the dialog with " << (diffTimestamp(lastRetry, getTimestamp()) / 1000) << " seconds elapsed.\n" << logofs_flush; } #endif ESET(reason); #ifdef TEST *logofs << "Loop: Connection to '" << hostName << ":" << portNum << "' failed with error '" << ESTR() << "'. Retrying.\n" << logofs_flush; #endif } else { // // Connection was successful. // break; } } return proxyFD; ConnectToRemoteError: if (proxyFD != -1) { close(proxyFD); } HandleCleanup(); } // // Make a string of options for the remote // proxy and write it to the descriptor. // The string includes the local version. // int SendProxyOptions(int fd) { char options[DEFAULT_REMOTE_OPTIONS_LENGTH]; // // Send the "compatibility" version first, then our // actual version. Old proxies will take the first // value and ignore the second. // sprintf(options, "NXPROXY-1.5.0-%i.%i.%i", control -> LocalVersionMajor, control -> LocalVersionMinor, control -> LocalVersionPatch); // // If you want to send options from proxy // initiating the connection use something // like this: // // if (WE_PROVIDE_CREDENTIALS) // { // sprintf(options + strlen(options), "%s=%s", option, value); // } // // If you want to send options according to // local proxy mode use something like this: // // if (control -> ProxyMode == proxy_client) // { // sprintf(options + strlen(options), "%s=%s", option, value); // } // // // Send the authorization cookie if any. We assume // user can choose to not provide any auth cookie // and allow any connection to be accepted. // if (WE_PROVIDE_CREDENTIALS && *authCookie != '\0') { sprintf(options + strlen(options), " cookie=%s,", authCookie); } else { sprintf(options + strlen(options), " "); } // // Now link characteristics and compression // options. Delta compression, as well as // preferred pack method, are imposed by // client proxy. // if (control -> ProxyMode == proxy_client) { sprintf(options + strlen(options), "link=%s,pack=%s,cache=%s,", linkSpeedName, packMethodName, cacheSizeName); if (*bitrateLimitName != '\0') { sprintf(options + strlen(options), "limit=%s,", bitrateLimitName); } // // Let the user disable the render extension // and let the X client proxy know if it can // short-circuit the X replies. Also pass // along the session type to ensure that the // remote proxy gets the right value. // sprintf(options + strlen(options), "render=%d,taint=%d,", (control -> HideRender == 0), control -> TaintReplies); if (*sessionType != '\0') { sprintf(options + strlen(options), "type=%s,", sessionType); } else { sprintf(options + strlen(options), "type=default,"); } // // Add the 'strict' option, if needed. // if (control -> isProtoStep7() == 1 && useStrict != -1) { sprintf(options + strlen(options), "strict=%d,", useStrict); } // // Tell the remote the size of the shared // memory segment. // if (control -> isProtoStep7() == 1 && *shsegSizeName != '\0') { sprintf(options + strlen(options), "shseg=%s,", shsegSizeName); } // // Send image cache parameters. // sprintf(options + strlen(options), "images=%s,", imagesSizeName); sprintf(options + strlen(options), "delta=%d,stream=%d,data=%d ", control -> LocalDeltaCompression, control -> LocalStreamCompressionLevel, control -> LocalDataCompressionLevel); } else { // // If no special compression level was selected, // server side will use compression levels set // by client. // if (control -> LocalStreamCompressionLevel < 0) { sprintf(options + strlen(options), "stream=default,"); } else { sprintf(options + strlen(options), "stream=%d,", control -> LocalStreamCompressionLevel); } if (control -> LocalDataCompressionLevel < 0) { sprintf(options + strlen(options), "data=default "); } else { sprintf(options + strlen(options), "data=%d ", control -> LocalDataCompressionLevel); } } #ifdef TEST *logofs << "Loop: Sending remote options '" << options << "'.\n" << logofs_flush; #endif return WriteLocalData(fd, options, strlen(options)); } int ReadProxyVersion(int fd) { #ifdef TEST *logofs << "Loop: Going to read the remote proxy version " << "from FD#" << fd << ".\n" << logofs_flush; #endif // // Read until the first space in string. // We expect the remote version number. // char options[DEFAULT_REMOTE_OPTIONS_LENGTH]; int result = ReadRemoteData(fd, options, sizeof(options), ' '); if (result <= 0) { if (result < 0) { if (control -> ProxyMode == proxy_server) { HandleAlert(ABORT_PROXY_NEGOTIATION_ALERT, 1); } handleAlertInLoop(); } return result; } #ifdef TEST *logofs << "Loop: Received remote version string '" << options << "' from FD#" << fd << ".\n" << logofs_flush; #endif if (strncmp(options, "NXPROXY-", strlen("NXPROXY-")) != 0) { #ifdef PANIC *logofs << "Loop: PANIC! Parse error in remote options string '" << options << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Parse error in remote options string '" << options << "'.\n"; return -1; } // // Try to determine if this is a pre-2.0.0 // version advertising itself as compatible // with the 1.2.2. // int major = -1; int minor = -1; int patch = -1; sscanf(options, "NXPROXY-%i.%i.%i-%i.%i.%i", &(control -> RemoteVersionMajor), &(control -> RemoteVersionMinor), &(control -> RemoteVersionPatch), &major, &minor, &patch); if (control -> RemoteVersionMajor == 1 && control -> RemoteVersionMinor == 2 && control -> RemoteVersionPatch == 2 && major != -1 && minor != -1 && patch != -1) { #ifdef TEST *logofs << "Loop: Read trailing remote version '" << major << "." << minor << "." << patch << "'.\n" << logofs_flush; #endif control -> CompatVersionMajor = major; control -> CompatVersionMinor = minor; control -> CompatVersionPatch = patch; control -> RemoteVersionMajor = major; control -> RemoteVersionMinor = minor; control -> RemoteVersionPatch = patch; } else { // // We read the remote version at the first // round. If the second version is missing, // we will retain the values read before. // sscanf(options, "NXPROXY-%i.%i.%i-%i.%i.%i", &(control -> CompatVersionMajor), &(control -> CompatVersionMinor), &(control -> CompatVersionPatch), &(control -> RemoteVersionMajor), &(control -> RemoteVersionMinor), &(control -> RemoteVersionPatch)); } #ifdef TEST *logofs << "Loop: Identified remote version '" << control -> RemoteVersionMajor << "." << control -> RemoteVersionMinor << "." << control -> RemoteVersionPatch << "'.\n" << logofs_flush; *logofs << "Loop: Remote compatibility version '" << control -> CompatVersionMajor << "." << control -> CompatVersionMinor << "." << control -> CompatVersionPatch << "'.\n" << logofs_flush; *logofs << "Loop: Local version '" << control -> LocalVersionMajor << "." << control -> LocalVersionMinor << "." << control -> LocalVersionPatch << "'.\n" << logofs_flush; #endif if (SetVersion() < 0) { if (control -> ProxyMode == proxy_server) { HandleAlert(WRONG_PROXY_VERSION_ALERT, 1); } handleAlertInLoop(); return -1; } return 1; } int ReadProxyOptions(int fd) { #ifdef TEST *logofs << "Loop: Going to read the remote proxy options " << "from FD#" << fd << ".\n" << logofs_flush; #endif char options[DEFAULT_REMOTE_OPTIONS_LENGTH]; int result = ReadRemoteData(fd, options, sizeof(options), ' '); if (result <= 0) { return result; } #ifdef TEST *logofs << "Loop: Received remote options string '" << options << "' from FD#" << fd << ".\n" << logofs_flush; #endif // // Get the remote options, delimited by a space character. // Note that there will be a further initialization phase // at the time proxies negotiate cache file to restore. // if (ParseRemoteOptions(options) < 0) { #ifdef PANIC *logofs << "Loop: PANIC! Couldn't negotiate a valid " << "session with remote NX proxy.\n" << logofs_flush; #endif cerr << "Error" << ": Couldn't negotiate a valid " << "session with remote NX proxy.\n"; return -1; } return 1; } int SendProxyCaches(int fd) { #ifdef TEST *logofs << "Loop: Synchronizing local and remote caches.\n" << logofs_flush; #endif if (control -> ProxyMode == proxy_client) { // // Prepare a list of caches matching this // session type and send it to the remote. // #ifdef TEST *logofs << "Loop: Going to send the list of local caches.\n" << logofs_flush; #endif SetCaches(); int entries = DEFAULT_REMOTE_CACHE_ENTRIES; const char prefix = 'C'; if (control -> LocalDeltaCompression == 0 || control -> PersistentCacheEnableLoad == 0) { #ifdef TEST *logofs << "Loop: Writing an empty list to FD#" << fd << ".\n" << logofs_flush; #endif return WriteLocalData(fd, "cachelist=none ", strlen("cachelist=none ")); } int count = 0; #ifdef TEST *logofs << "Loop: Looking for cache files in directory '" << control -> PersistentCachePath << "'.\n" << logofs_flush; #endif DIR *cacheDir = opendir(control -> PersistentCachePath); if (cacheDir != NULL) { dirent *dirEntry; int prologue = 0; while (((dirEntry = readdir(cacheDir)) != NULL) && (count < entries)) { if (*dirEntry -> d_name == prefix && strlen(dirEntry -> d_name) == (MD5_LENGTH * 2 + 2)) { if (prologue == 0) { WriteLocalData(fd, "cachelist=", strlen("cachelist=")); prologue = 1; } else { WriteLocalData(fd, ",", strlen(",")); } #ifdef TEST *logofs << "Loop: Writing entry '" << control -> PersistentCachePath << "/" << dirEntry -> d_name << "' to FD#" << fd << ".\n" << logofs_flush; #endif // // Write cache file name to the socket, // including leading 'C-' or 'S-'. // WriteLocalData(fd, dirEntry -> d_name, MD5_LENGTH * 2 + 2); count++; } } closedir(cacheDir); } if (count == 0) { #ifdef TEST *logofs << "Loop: Writing an empty list to FD#" << fd << ".\n" << logofs_flush; #endif return WriteLocalData(fd, "cachelist=none ", strlen("cachelist=none ")); } else { return WriteLocalData(fd, " ", 1); } } else { // // Send back the selected cache name. // #ifdef TEST *logofs << "Loop: Going to send the selected cache.\n" << logofs_flush; #endif char buffer[DEFAULT_STRING_LENGTH]; if (control -> PersistentCacheName != NULL) { #ifdef TEST *logofs << "Loop: Name of selected cache file is '" << control -> PersistentCacheName << "'.\n" << logofs_flush; #endif sprintf(buffer, "cachefile=%s%s ", *(control -> PersistentCacheName) == 'C' ? "S-" : "C-", control -> PersistentCacheName + 2); } else { #ifdef TEST *logofs << "Loop: No valid cache file was selected.\n" << logofs_flush; #endif sprintf(buffer, "cachefile=none "); } #ifdef TEST *logofs << "Loop: Sending string '" << buffer << "' as selected cache file.\n" << logofs_flush; #endif return WriteLocalData(fd, buffer, strlen(buffer)); } } int ReadProxyCaches(int fd) { if (control -> ProxyMode == proxy_client) { #ifdef TEST *logofs << "Loop: Going to receive the selected proxy cache.\n" << logofs_flush; #endif // // We will read the name of cache plus the stop character. // char buffer[DEFAULT_STRING_LENGTH]; // // Leave space for a trailing null. // int result = ReadRemoteData(fd, buffer, sizeof("cachefile=") + MD5_LENGTH * 2 + 3, ' '); if (result <= 0) { return result; } char *cacheName = strstr(buffer, "cachefile="); if (cacheName == NULL) { #ifdef PANIC *logofs << "Loop: PANIC! Invalid cache file option '" << buffer << "' provided by remote proxy.\n" << logofs_flush; #endif cerr << "Error" << ": Invalid cache file option '" << buffer << "' provided by remote proxy.\n"; HandleCleanup(); } cacheName += strlen("cachefile="); if (control -> PersistentCacheName != NULL) { delete [] control -> PersistentCacheName; } control -> PersistentCacheName = NULL; if (strncasecmp(cacheName, "none", strlen("none")) == 0) { #ifdef TEST *logofs << "Loop: No cache file selected by remote proxy.\n" << logofs_flush; #endif } else if (strlen(cacheName) != MD5_LENGTH * 2 + 3 || *(cacheName + MD5_LENGTH * 2 + 2) != ' ') { #ifdef PANIC *logofs << "Loop: PANIC! Invalid cache file name '" << cacheName << "' provided by remote proxy.\n" << logofs_flush; #endif cerr << "Error" << ": Invalid cache file name '" << cacheName << "' provided by remote proxy.\n"; HandleCleanup(); } else { // // It is "C-" + 32 + "\0". // control -> PersistentCacheName = new char[MD5_LENGTH * 2 + 3]; *(cacheName + MD5_LENGTH * 2 + 2) = '\0'; strcpy(control -> PersistentCacheName, cacheName); #ifdef TEST *logofs << "Loop: Cache file '" << control -> PersistentCacheName << "' selected by remote proxy.\n" << logofs_flush; #endif } } else { #ifdef TEST *logofs << "Loop: Going to receive the list of remote caches.\n" << logofs_flush; #endif SetCaches(); int size = ((MD5_LENGTH * 2 + 2) + strlen(",")) * DEFAULT_REMOTE_CACHE_ENTRIES + strlen("cachelist=") + strlen(" ") + 1; char *buffer = new char[size]; int result = ReadRemoteData(fd, buffer, size - 1, ' '); if (result <= 0) { delete [] buffer; return result; } #ifdef TEST *logofs << "Loop: Read list of caches from remote side as '" << buffer << "'.\n" << logofs_flush; #endif // // Prepare the buffer. What we want is a list // like "cache1,cache2,cache2" terminated by // null. // *(buffer + strlen(buffer) - 1) = '\0'; if (strncasecmp(buffer, "cachelist=", strlen("cachelist=")) != 0) { #ifdef PANIC *logofs << "Loop: Wrong format for list of cache files " << "read from FD#" << fd << ".\n" << logofs_flush; #endif cerr << "Error" << ": Wrong format for list of cache files.\n"; delete [] buffer; return -1; } control -> PersistentCacheName = GetLastCache(buffer, control -> PersistentCachePath); // // Get rid of list of caches. // delete [] buffer; } return 1; } int ReadForwarderVersion(int fd) { #ifdef TEST *logofs << "Loop: Going to negotiate the forwarder version.\n" << logofs_flush; #endif // // Check if we actually expect the session cookie. // if (*authCookie == '\0') { #ifdef TEST *logofs << "Loop: No authentication cookie required " << "from FD#" << fd << ".\n" << logofs_flush; #endif return 1; } char options[DEFAULT_REMOTE_OPTIONS_LENGTH]; int result = ReadRemoteData(fd, options, sizeof(options), ' '); if (result <= 0) { return result; } #ifdef TEST *logofs << "Loop: Received forwarder version string '" << options << "' from FD#" << fd << ".\n" << logofs_flush; #endif if (strncmp(options, "NXSSH-", strlen("NXSSH-")) != 0) { #ifdef PANIC *logofs << "Loop: PANIC! Parse error in forwarder options string '" << options << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Parse error in forwarder options string '" << options << "'.\n"; return -1; } // // Accept whatever forwarder version. // sscanf(options, "NXSSH-%i.%i.%i", &(control -> RemoteVersionMajor), &(control -> RemoteVersionMinor), &(control -> RemoteVersionPatch)); #ifdef TEST *logofs << "Loop: Read forwarder version '" << control -> RemoteVersionMajor << "." << control -> RemoteVersionMinor << "." << control -> RemoteVersionPatch << "'.\n" << logofs_flush; #endif return 1; } int ReadForwarderOptions(int fd) { // // Get the forwarder cookie. // if (*authCookie == '\0') { #ifdef TEST *logofs << "Loop: No authentication cookie required " << "from FD#" << fd << ".\n" << logofs_flush; #endif return 1; } char options[DEFAULT_REMOTE_OPTIONS_LENGTH]; int result = ReadRemoteData(fd, options, sizeof(options), ' '); if (result <= 0) { return result; } #ifdef TEST *logofs << "Loop: Received forwarder options string '" << options << "' from FD#" << fd << ".\n" << logofs_flush; #endif if (ParseForwarderOptions(options) < 0) { #ifdef PANIC *logofs << "Loop: PANIC! Couldn't negotiate a valid " << "cookie with the NX forwarder.\n" << logofs_flush; #endif cerr << "Error" << ": Couldn't negotiate a valid " << "cookie with the NX forwarder.\n"; return -1; } return 1; } int ReadRemoteData(int fd, char *buffer, int size, char stop) { #ifdef TEST *logofs << "Loop: Going to read remote data from FD#" << fd << ".\n" << logofs_flush; #endif if (size >= MAXIMUM_REMOTE_OPTIONS_LENGTH) { #ifdef PANIC *logofs << "Loop: PANIC! Maximum remote options buffer " << "limit exceeded.\n" << logofs_flush; #endif cerr << "Error" << ": Maximum remote options buffer " << "limit exceeded.\n"; HandleCleanup(); } while (remotePosition < (size - 1)) { int result = read(fd, remoteData + remotePosition, 1); getNewTimestamp(); if (result <= 0) { if (result == -1) { if (EGET() == EAGAIN) { #ifdef TEST *logofs << "Loop: Reading data from FD#" << fd << " would block.\n" << logofs_flush; #endif return 0; } else if (EGET() == EINTR) { if (CheckAbort() != 0) { return -1; } continue; } } #ifdef PANIC *logofs << "Loop: PANIC! The remote NX proxy closed " << "the connection.\n" << logofs_flush; #endif cerr << "Error" << ": The remote NX proxy closed " << "the connection.\n"; return -1; } else if (*(remoteData + remotePosition) == stop) { #ifdef TEST *logofs << "Loop: Read stop character from FD#" << fd << ".\n" << logofs_flush; #endif remotePosition++; // // Copy the fake terminating null // in the buffer. // *(remoteData + remotePosition) = '\0'; memcpy(buffer, remoteData, remotePosition + 1); #ifdef TEST *logofs << "Loop: Remote string '" << remoteData << "' read from FD#" << fd << ".\n" << logofs_flush; #endif int t = remotePosition; remotePosition = 0; return t; } else { // // Make sure string received // from far end is printable. // if (isgraph(*(remoteData + remotePosition)) == 0) { #ifdef WARNING *logofs << "Loop: WARNING! Non printable character decimal '" << (unsigned int) *(remoteData + remotePosition) << "' received in remote data from FD#" << fd << ".\n" << logofs_flush; #endif cerr << "Warning" << ": Non printable character decimal '" << (unsigned int) *(remoteData + remotePosition) << "' received in remote data from FD#" << fd << ".\n" << logofs_flush; *(remoteData + remotePosition) = ' '; } #ifdef DEBUG *logofs << "Loop: Read a further character " << "from FD#" << fd << ".\n" << logofs_flush; #endif remotePosition++; } } *(remoteData + remotePosition) = '\0'; #ifdef PANIC *logofs << "Loop: PANIC! Stop character missing " << "from FD#" << fd << " after " << remotePosition << " characters read in string '" << remoteData << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Stop character missing " << "from FD#" << fd << " after " << remotePosition << " characters read in string '" << remoteData << "'.\n"; memcpy(buffer, remoteData, remotePosition); remotePosition = 0; return -1; } int WriteLocalData(int fd, const char *buffer, int size) { int position = 0; while (position < size) { int result = write(fd, buffer + position, size - position); getNewTimestamp(); if (result <= 0) { if (result < 0 && EGET() == EINTR) { continue; } #ifdef TEST *logofs << "Loop: Error writing data to FD#" << fd << ".\n" << logofs_flush; #endif return -1; } position += result; } return position; } // // Parse the string passed by calling process in // the environment. This is not necessarily the // content of DISPLAY variable, but can be the // parameters passed when creating the process // or thread. // int ParseEnvironmentOptions(const char *env, int force) { // // Be sure log file is valid. // if (logofs == NULL) { logofs = &cerr; } // // Be sure we have a parameters repository // and a context to jump into because this // can be called before creating the proxy. // if (control == NULL) { control = new Control(); } if (setjmp(context) == 1) { #ifdef TEST *logofs << "Loop: Out of the long jump while parsing " << "the environment options.\n" << logofs_flush; #endif return -1; } if (force == 0 && parsedOptions == 1) { #ifdef TEST *logofs << "Loop: Skipping a further parse of environment " << "options string '" << (env != NULL ? env : "") << "'.\n" << logofs_flush; #endif return 1; } if (env == NULL || *env == '\0') { #ifdef TEST *logofs << "Loop: Nothing to do with empty environment " << "options string '" << (env != NULL ? env : "") << "'.\n" << logofs_flush; #endif return 0; } #ifdef TEST *logofs << "Loop: Going to parse the environment options " << "string '" << env << "'.\n" << logofs_flush; #endif parsedOptions = 1; // // Copy the string passed as parameter // because we need to modify it. // char opts[DEFAULT_DISPLAY_OPTIONS_LENGTH]; #ifdef VALGRIND memset(opts, '\0', DEFAULT_DISPLAY_OPTIONS_LENGTH); #endif if (strlen(env) >= DEFAULT_DISPLAY_OPTIONS_LENGTH) { #ifdef PANIC *logofs << "Loop: PANIC! Environment options string '" << env << "' exceeds length of " << DEFAULT_DISPLAY_OPTIONS_LENGTH << " characters.\n" << logofs_flush; #endif cerr << "Error" << ": Environment options string '" << env << "' exceeds length of " << DEFAULT_DISPLAY_OPTIONS_LENGTH << " characters.\n"; return -1; } strcpy(opts, env); char *nextOpts = opts; // // Ensure that DISPLAY environment variable // (roughly) follows the X convention for // transport notation. // if (strncasecmp(opts, "nx/nx,:", 7) == 0 || strncasecmp(opts, "nx,:", 4) == 0) { #ifdef PANIC *logofs << "Loop: PANIC! Parse error in options string '" << opts << "' at 'nx,:'.\n" << logofs_flush; #endif cerr << "Error" << ": Parse error in options string '" << opts << "' at 'nx,:'.\n"; return -1; } else if (strncasecmp(opts, "nx/nx,", 6) == 0) { nextOpts += 6; } else if (strncasecmp(opts, "nx,", 3) == 0) { nextOpts += 3; } else if (strncasecmp(opts, "nx:", 3) == 0) { nextOpts += 3; } else if (force == 0) { #ifdef TEST *logofs << "Loop: Ignoring host X server display string '" << opts << "'.\n" << logofs_flush; #endif return 0; } // // Save here the name of the options file and // parse it after all the other options. // char fileOptions[DEFAULT_STRING_LENGTH] = { 0 }; // // The options string is intended to be a series // of name/value tuples in the form name=value // separated by the ',' character ended by a ':' // followed by remote NX proxy port. // char *name; char *value; value = rindex(nextOpts, ':'); if (value != NULL) { char *check = value + 1; if (*check == '\0' || isdigit(*check) == 0) { #ifdef PANIC *logofs << "Loop: PANIC! Can't identify NX port in string '" << value << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Can't identify NX port in string '" << value << "'.\n"; return -1; } proxyPort = atoi(check); // // Get rid of the port specification. // *value = '\0'; } else if (proxyPort == DEFAULT_NX_PROXY_PORT && force == 0) { // // Complain only if user didn't specify // the port on the command line. // #ifdef PANIC *logofs << "Loop: PANIC! Can't identify NX port in string '" << opts << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Can't identify NX port in string '" << opts << "'.\n"; return -1; } #ifdef TEST *logofs << "Loop: Parsing options string '" << nextOpts << "'.\n" << logofs_flush; #endif // // Now all the other optional parameters. // name = strtok(nextOpts, "="); while (name) { value = strtok(NULL, ","); if (CheckArg("environment", name, value) < 0) { return -1; } if (strcasecmp(name, "options") == 0) { strncpy(fileOptions, value, DEFAULT_STRING_LENGTH - 1); } else if (strcasecmp(name, "display") == 0) { strncpy(displayHost, value, DEFAULT_STRING_LENGTH - 1); } else if (strcasecmp(name, "link") == 0) { if (control -> ProxyMode == proxy_server) { PrintOptionIgnored("local", name, value); } else if (ParseLinkOption(value) < 0) { #ifdef PANIC *logofs << "Loop: PANIC! Can't identify 'link' option in string '" << value << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Can't identify 'link' option in string '" << value << "'.\n"; return -1; } } else if (strcasecmp(name, "limit") == 0) { if (control -> ProxyMode == proxy_server) { PrintOptionIgnored("local", name, value); } else if (ParseBitrateOption(value) < 0) { #ifdef PANIC *logofs << "Loop: PANIC! Can't identify option 'limit' in string '" << value << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Can't identify option 'limit' in string '" << value << "'.\n"; return -1; } } else if (strcasecmp(name, "type") == 0) { // // Type of session, for example "desktop", // "application", "windows", etc. // if (control -> ProxyMode == proxy_server) { PrintOptionIgnored("local", name, value); } else { if (strcasecmp(value, "default") == 0) { *sessionType = '\0'; } else { strncpy(sessionType, value, DEFAULT_STRING_LENGTH - 1); } } } else if (strcasecmp(name, "listen") == 0) { if (*connectHost != '\0') { #ifdef PANIC *logofs << "Loop: PANIC! Can't handle 'listen' and 'connect' parameters " << "at the same time.\n" << logofs_flush; *logofs << "Loop: PANIC! Refusing 'listen' parameter with 'connect' being '" << connectHost << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Can't handle 'listen' and 'connect' parameters " << "at the same time.\n"; cerr << "Error" << ": Refusing 'listen' parameter with 'connect' being '" << connectHost << "'.\n"; return -1; } listenPort = ValidateArg("local", name, value); } else if (strcasecmp(name, "accept") == 0) { if (*connectHost != '\0') { #ifdef PANIC *logofs << "Loop: PANIC! Can't handle 'accept' and 'connect' parameters " << "at the same time.\n" << logofs_flush; *logofs << "Loop: PANIC! Refusing 'accept' parameter with 'connect' being '" << connectHost << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Can't handle 'accept' and 'connect' parameters " << "at the same time.\n"; cerr << "Error" << ": Refusing 'accept' parameter with 'connect' being '" << connectHost << "'.\n"; return -1; } strncpy(acceptHost, value, DEFAULT_STRING_LENGTH - 1); } else if (strcasecmp(name, "connect") == 0) { if (*acceptHost != '\0') { #ifdef PANIC *logofs << "Loop: PANIC! Can't handle 'connect' and 'accept' parameters " << "at the same time.\n" << logofs_flush; *logofs << "Loop: PANIC! Refusing 'connect' parameter with 'accept' being '" << acceptHost << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Can't handle 'connect' and 'accept' parameters " << "at the same time.\n"; cerr << "Error" << ": Refusing 'connect' parameter with 'accept' being '" << acceptHost << "'.\n"; return -1; } strncpy(connectHost, value, DEFAULT_STRING_LENGTH - 1); } else if (strcasecmp(name, "port") == 0) { connectPort = ValidateArg("local", name, value); } else if (strcasecmp(name, "retry") == 0) { control -> OptionProxyRetryConnect = ValidateArg("local", name, value); control -> OptionServerRetryConnect = ValidateArg("local", name, value); } else if (strcasecmp(name, "session") == 0) { strncpy(sessionFileName, value, DEFAULT_STRING_LENGTH - 1); } else if (strcasecmp(name, "errors") == 0) { // // The old name of the parameter was 'log' // but the default name for the file is // 'errors' so it is more logical to use // the same name. // strncpy(errorsFileName, value, DEFAULT_STRING_LENGTH - 1); } else if (strcasecmp(name, "root") == 0) { strncpy(rootDir, value, DEFAULT_STRING_LENGTH - 1); } else if (strcasecmp(name, "id") == 0) { strncpy(sessionId, value, DEFAULT_STRING_LENGTH - 1); } else if (strcasecmp(name, "stats") == 0) { control -> EnableStatistics = 1; strncpy(statsFileName, value, DEFAULT_STRING_LENGTH - 1); } else if (strcasecmp(name, "cookie") == 0) { LowercaseArg("local", name, value); strncpy(authCookie, value, DEFAULT_STRING_LENGTH - 1); } else if (strcasecmp(name, "nodelay") == 0) { useNoDelay = ValidateArg("local", name, value); } else if (strcasecmp(name, "policy") == 0) { if (control -> ProxyMode == proxy_server) { PrintOptionIgnored("local", name, value); } else { usePolicy = ValidateArg("local", name, value); } } else if (strcasecmp(name, "render") == 0) { if (control -> ProxyMode == proxy_server) { PrintOptionIgnored("local", name, value); } else { useRender = ValidateArg("local", name, value); } } else if (strcasecmp(name, "taint") == 0) { if (control -> ProxyMode == proxy_server) { PrintOptionIgnored("local", name, value); } else { useTaint = ValidateArg("local", name, value); } } else if (strcasecmp(name, "delta") == 0) { if (control -> ProxyMode == proxy_server) { PrintOptionIgnored("local", name, value); } else { control -> LocalDeltaCompression = ValidateArg("local", name, value); } } else if (strcasecmp(name, "data") == 0) { control -> LocalDataCompressionLevel = ValidateArg("local", name, value); if (control -> LocalDataCompressionLevel == 0) { control -> LocalDataCompression = 0; } else { control -> LocalDataCompression = 1; } } else if (strcasecmp(name, "stream") == 0) { control -> LocalStreamCompressionLevel = ValidateArg("local", name, value); if (control -> LocalStreamCompressionLevel == 0) { control -> LocalStreamCompression = 0; } else { control -> LocalStreamCompression = 1; } } else if (strcasecmp(name, "memory") == 0) { control -> LocalMemoryLevel = ValidateArg("local", name, value); } else if (strcasecmp(name, "cache") == 0) { if (control -> ProxyMode == proxy_server) { PrintOptionIgnored("local", name, value); } else if (ParseCacheOption(value) < 0) { #ifdef PANIC *logofs << "Loop: PANIC! Can't identify cache size for string '" << value << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Can't identify cache size for string '" << value << "'.\n"; return -1; } } else if (strcasecmp(name, "images") == 0) { if (control -> ProxyMode == proxy_server) { PrintOptionIgnored("local", name, value); } else if (ParseImagesOption(value) < 0) { #ifdef PANIC *logofs << "Loop: PANIC! Can't identify images cache size for string '" << value << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Can't identify images cache size for string '" << value << "'.\n"; return -1; } } else if (strcasecmp(name, "shseg") == 0) { // // The 'shmem' option is used by the agent, together // with 'shpix' literal. We make the 'shseg' option // specific to the proxy and use it to determine the // size of the shared memory segment, or otherwise 0, // if the use of the shared memory extension should // not be enabled on the real X server. // if (control -> ProxyMode == proxy_server) { PrintOptionIgnored("local", name, value); } else if (ParseShmemOption(value) < 0) { #ifdef PANIC *logofs << "Loop: PANIC! Can't identify size of shared memory " << "segment in string '" << value << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Can't identify size of shared memory " << "segment in string '" << value << "'.\n"; return -1; } } else if (strcasecmp(name, "load") == 0) { if (control -> ProxyMode == proxy_server) { PrintOptionIgnored("local", name, value); } else { control -> PersistentCacheEnableLoad = ValidateArg("local", name, value); if (control -> PersistentCacheEnableLoad > 0) { control -> PersistentCacheEnableLoad = 1; } else { if (control -> PersistentCacheName != NULL) { delete [] control -> PersistentCacheName; } control -> PersistentCacheName = NULL; control -> PersistentCacheEnableLoad = 0; } } } else if (strcasecmp(name, "save") == 0) { if (control -> ProxyMode == proxy_server) { PrintOptionIgnored("local", name, value); } else { control -> PersistentCacheEnableSave = ValidateArg("local", name, value); if (control -> PersistentCacheEnableSave > 0) { control -> PersistentCacheEnableSave = 1; } else { if (control -> PersistentCacheName != NULL) { delete [] control -> PersistentCacheName; } control -> PersistentCacheName = NULL; control -> PersistentCacheEnableSave = 0; } } } else if (strcasecmp(name, "cups") == 0) { cupsPort = ValidateArg("local", name, value); } else if (strcasecmp(name, "sync") == 0) { #ifdef WARNING *logofs << "Loop: WARNING! No 'sync' channel in current version. " << "Assuming 'cups' channel.\n" << logofs_flush; #endif cerr << "Warning" << ": No 'sync' channel in current version. " << "Assuming 'cups' channel.\n"; cupsPort = ValidateArg("local", name, value); } else if (strcasecmp(name, "keybd") == 0 || strcasecmp(name, "aux") == 0) { auxPort = ValidateArg("local", name, value); } else if (strcasecmp(name, "samba") == 0 || strcasecmp(name, "smb") == 0) { smbPort = ValidateArg("local", name, value); } else if (strcasecmp(name, "media") == 0) { mediaPort = ValidateArg("local", name, value); } else if (strcasecmp(name, "http") == 0) { httpPort = ValidateArg("local", name, value); } else if (strcasecmp(name, "font") == 0) { strncpy(fontPort, value, DEFAULT_STRING_LENGTH - 1); } else if (strcasecmp(name, "slave") == 0) { slavePort = ValidateArg("local", name, value); } else if (strcasecmp(name, "mask") == 0) { control -> ChannelMask = ValidateArg("local", name, value); } else if (strcasecmp(name, "timeout") == 0) { int timeout = ValidateArg("local", name, value); if (timeout == 0) { #ifdef TEST *logofs << "Loop: Disabling timeout on broken " << "proxy connection.\n" << logofs_flush; #endif control -> ProxyTimeout = 0; } else { control -> ProxyTimeout = timeout * 1000; } } else if (strcasecmp(name, "cleanup") == 0) { int cleanup = ValidateArg("local", name, value); if (cleanup == 0) { #ifdef TEST *logofs << "Loop: Disabling grace timeout on " << "proxy shutdown.\n" << logofs_flush; #endif control -> CleanupTimeout = 0; } else { control -> CleanupTimeout = cleanup * 1000; } } else if (strcasecmp(name, "pack") == 0) { if (control -> ProxyMode == proxy_server) { PrintOptionIgnored("local", name, value); } else if (ParsePackOption(value) < 0) { #ifdef PANIC *logofs << "Loop: PANIC! Can't identify pack method for string '" << value << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Can't identify pack method for string '" << value << "'.\n"; return -1; } } else if (strcasecmp(name, "core") == 0) { control -> EnableCoreDumpOnAbort = ValidateArg("local", name, value); } else if (strcasecmp(name, "kill") == 0) { if (control -> KillDaemonOnShutdownNumber < control -> KillDaemonOnShutdownLimit) { #ifdef TEST *logofs << "Loop: WARNING! Adding process with pid '" << ValidateArg("local", name, value) << " to the " << "daemons to kill at shutdown.\n" << logofs_flush; #endif control -> KillDaemonOnShutdown[control -> KillDaemonOnShutdownNumber] = ValidateArg("local", name, value); control -> KillDaemonOnShutdownNumber++; } else { #ifdef WARNING *logofs << "Loop: WARNING! Number of daemons to kill " << "at shutdown exceeded.\n" << logofs_flush; #endif cerr << "Warning" << ": Number of daemons to kill " << "at shutdown exceeded.\n"; } } else if (strcasecmp(name, "strict") == 0) { if (control -> ProxyMode == proxy_server) { PrintOptionIgnored("local", name, value); } else { useStrict = ValidateArg("local", name, value); } } else if (strcasecmp(name, "encryption") == 0) { useEncryption = ValidateArg("local", name, value); } else if (strcasecmp(name, "product") == 0) { strncpy(productName, value, DEFAULT_STRING_LENGTH - 1); } else if (strcasecmp(name, "rootless") == 0 || strcasecmp(name, "geometry") == 0 || strcasecmp(name, "resize") == 0 || strcasecmp(name, "fullscreen") == 0 || strcasecmp(name, "keyboard") == 0 || strcasecmp(name, "clipboard") == 0 || strcasecmp(name, "streaming") == 0 || strcasecmp(name, "backingstore") == 0) { #ifdef DEBUG *logofs << "Loop: Ignoring agent option '" << name << "' with value '" << value << "'.\n" << logofs_flush; #endif } else if (strcasecmp(name, "composite") == 0 || strcasecmp(name, "shmem") == 0 || strcasecmp(name, "shpix") == 0 || strcasecmp(name, "kbtype") == 0 || strcasecmp(name, "client") == 0 || strcasecmp(name, "shadow") == 0 || strcasecmp(name, "shadowuid") == 0 || strcasecmp(name, "shadowmode") == 0 || strcasecmp(name, "clients") == 0) { #ifdef DEBUG *logofs << "Loop: Ignoring agent option '" << name << "' with value '" << value << "'.\n" << logofs_flush; #endif } else if (strcasecmp(name, "defer") == 0 || strcasecmp(name, "tile") == 0 || strcasecmp(name, "menu") == 0) { #ifdef DEBUG *logofs << "Loop: Ignoring agent option '" << name << "' with value '" << value << "'.\n" << logofs_flush; #endif } else { #ifdef WARNING *logofs << "Loop: WARNING! Ignoring unknown option '" << name << "' with value '" << value << "'.\n" << logofs_flush; #endif cerr << "Warning" << ": Ignoring unknown option '" << name << "' with value '" << value << "'.\n"; } name = strtok(NULL, "="); } // End of while (name) ... #ifdef TEST *logofs << "Loop: Completed parsing of string '" << env << "'.\n" << logofs_flush; #endif if (*fileOptions != '\0') { if (strcmp(fileOptions, optionsFileName) != 0) { #ifdef TEST *logofs << "Loop: Reading options from '" << fileOptions << "'.\n" << logofs_flush; #endif if (ParseFileOptions(fileOptions) < 0) { return -1; } } #ifdef WARNING else { *logofs << "Loop: WARNING! Name of the options file " << "specified multiple times. Not parsing " << "again.\n" << logofs_flush; } #endif if (*optionsFileName == '\0') { strncpy(optionsFileName, value, DEFAULT_STRING_LENGTH - 1); #ifdef TEST *logofs << "Loop: Assuming name of options file '" << optionsFileName << "'.\n" << logofs_flush; #endif } } // // If port where proxy is acting as an X server // was not specified assume the same port where // proxy is listening for the remote peer. // if (xPort == DEFAULT_NX_X_PORT) { xPort = proxyPort; } return 1; } // // Parse the command line options passed by user when // running proxy in stand alone mode. Note that passing // parameters this way is strongly discouraged. These // command line switch can change (and they do often). // Please, use the form "option=value" instead and set // the DISPLAY environment variable. // int ParseCommandLineOptions(int argc, const char **argv) { // // Be sure log file is valid. // if (logofs == NULL) { logofs = &cerr; } if (setjmp(context) == 1) { #ifdef TEST *logofs << "Loop: Out of the long jump while parsing " << "the command line options.\n" << logofs_flush; #endif return -1; } // // Be sure we have a parameters repository // if (control == NULL) { control = new Control(); } if (parsedCommand == 1) { #ifdef TEST *logofs << "Loop: Skipping a further parse of command line options.\n" << logofs_flush; #endif return 1; } #ifdef TEST *logofs << "Loop: Going to parse the command line options.\n" << logofs_flush; #endif parsedCommand = 1; // // Print out arguments. // #ifdef TEST *logofs << "Loop: Argc is " << argc << ".\n" << logofs_flush; for (int argi = 0; argi < argc; argi++) { *logofs << "Loop: Argv[" << argi << "] is " << argv[argi] << ".\n" << logofs_flush; } #endif // // Shall use getopt here. // for (int argi = 1; argi < argc; argi++) { const char *nextArg = argv[argi]; if (*nextArg == '-') { switch (*(nextArg + 1)) { case 'h': { PrintUsageInfo(nextArg, 0); return -1; } case 'C': { // // Start proxy in CLIENT mode. // if (WE_SET_PROXY_MODE == 0) { #ifdef TEST *logofs << "Loop: Setting local proxy mode to proxy_client.\n" << logofs_flush; #endif control -> ProxyMode = proxy_client; } else if (control -> ProxyMode != proxy_client) { #ifdef PANIC *logofs << "Loop: PANIC! Can't redefine local proxy to " << "client mode.\n" << logofs_flush; #endif cerr << "Error" << ": Can't redefine local proxy to " << "client mode.\n"; return -1; } break; } case 'S': { // // Start proxy in SERVER mode. // if (WE_SET_PROXY_MODE == 0) { #ifdef TEST *logofs << "Loop: Setting local proxy mode to proxy_server.\n" << logofs_flush; #endif control -> ProxyMode = proxy_server; } else if (control -> ProxyMode != proxy_server) { #ifdef PANIC *logofs << "Loop: PANIC! Can't redefine local proxy to " << "server mode.\n" << logofs_flush; #endif cerr << "Error" << ": Can't redefine local proxy to " << "server mode.\n"; return -1; } break; } case 'v': { PrintVersionInfo(); return -1; } default: { PrintUsageInfo(nextArg, 1); // // Function GetArg() is not used anymore. // Add a dummy call to avoid the warning. // if (0) { GetArg(argi, argc, argv); } return -1; } } } else { if (nextArg) { // // Try to parse the option as a remote host:port // specification as in 'localhost:8'. Such a // parameter can be specified at the end of the // command line at the connecting side. // if (ParseHostOption(nextArg, connectHost, connectPort) > 0) { // // Assume port is at a proxied display offset. // proxyPort = connectPort; connectPort += DEFAULT_NX_PROXY_PORT_OFFSET; } else if (ParseEnvironmentOptions(nextArg, 1) < 0) { return -1; } } } } return 1; } // // Set the variable to the values of host and // port where this proxy is going to hook to // an existing proxy. // int ParseBindOptions(char **host, int *port) { if (*bindHost != '\0') { *host = bindHost; *port = bindPort; return 1; } else { return 0; } } // // Read options from file and merge with environment. // int ParseFileOptions(const char *file) { char *fileName; if (*file != '/' && *file != '.') { char *filePath = GetSessionPath(); if (filePath == NULL) { cerr << "Error" << ": Cannot determine directory for NX option file.\n"; HandleCleanup(); } fileName = new char[strlen(filePath) + strlen("/") + strlen(file) + 1]; strcpy(fileName, filePath); strcat(fileName, "/"); strcat(fileName, file); delete [] filePath; } else { fileName = new char[strlen(file) + 1]; strcpy(fileName, file); } #ifdef TEST *logofs << "Loop: Going to read options from file '" << fileName << "'.\n" << logofs_flush; #endif FILE *filePtr = fopen(fileName, "r"); if (filePtr == NULL) { #ifdef PANIC *logofs << "Loop: PANIC! Can't open options file '" << fileName << "'. Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Can't open options file '" << fileName << "'. Error is " << EGET() << " '" << ESTR() << "'.\n"; delete [] fileName; return -1; } char options[DEFAULT_DISPLAY_OPTIONS_LENGTH]; #ifdef VALGRIND memset(options, '\0', DEFAULT_DISPLAY_OPTIONS_LENGTH); #endif if (fgets(options, DEFAULT_DISPLAY_OPTIONS_LENGTH, filePtr) == NULL) { #ifdef PANIC *logofs << "Loop: PANIC! Can't read options from file '" << fileName << "'. Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Can't read options from file '" << fileName << "'. Error is " << EGET() << " '" << ESTR() << "'.\n"; fclose(filePtr); delete [] fileName; return -1; } fclose(filePtr); // // Purge the newline and the other non- // printable characters in the string. // char *next = options; while (*next != '\0') { if (isprint(*next) == 0) { *next = '\0'; } next++; } #ifdef TEST *logofs << "Loop: Read options '" << options << "' from file '" << fileName << "'.\n" << logofs_flush; #endif if (ParseEnvironmentOptions(options, 1) < 0) { delete [] fileName; return -1; } delete [] fileName; return 1; } // // Parse the option string passed from the // remote proxy at startup. // int ParseRemoteOptions(char *opts) { #ifdef TEST *logofs << "Loop: Going to parse the remote options " << "string '" << opts << "'.\n" << logofs_flush; #endif char *name; char *value; // // The options string is intended to be a series // of name/value tuples in the form name=value // separated by the ',' character. // int hasCookie = 0; int hasLink = 0; int hasPack = 0; int hasCache = 0; int hasImages = 0; int hasDelta = 0; int hasStream = 0; int hasData = 0; int hasLimit = 0; int hasRender = 0; int hasTaint = 0; int hasType = 0; int hasStrict = 0; int hasShseg = 0; // // Get rid of the terminating space. // if (*(opts + strlen(opts) - 1) == ' ') { *(opts + strlen(opts) - 1) = '\0'; } name = strtok(opts, "="); while (name) { value = strtok(NULL, ","); if (CheckArg("remote", name, value) < 0) { return -1; } if (strcasecmp(name, "cookie") == 0) { if (WE_PROVIDE_CREDENTIALS) { #ifdef WARNING *logofs << "Loop: WARNING! Ignoring remote option 'cookie' " << "with value '" << value << "' when initiating " << "connection.\n" << logofs_flush; #endif cerr << "Warning" << ": Ignoring remote option 'cookie' " << "with value '" << value << "' when initiating " << "connection.\n"; } else if (strncasecmp(authCookie, value, strlen(authCookie)) != 0) { #ifdef PANIC *logofs << "Loop: PANIC! Authentication cookie '" << value << "' doesn't match '" << authCookie << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Authentication cookie '" << value << "' doesn't match '" << authCookie << "'.\n"; return -1; } hasCookie = 1; } else if (strcasecmp(name, "link") == 0) { if (control -> ProxyMode == proxy_client) { PrintOptionIgnored("remote", name, value); } else { if (*linkSpeedName != '\0' && strcasecmp(linkSpeedName, value) != 0) { #ifdef WARNING *logofs << "Loop: WARNING! Overriding option 'link' " << "with new value '" << value << "'.\n" << logofs_flush; #endif cerr << "Warning" << ": Overriding option 'link' " << "with new value '" << value << "'.\n"; } if (ParseLinkOption(value) < 0) { #ifdef PANIC *logofs << "Loop: PANIC! Can't identify remote 'link' " << "option in string '" << value << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Can't identify remote 'link' " << "option in string '" << value << "'.\n"; return -1; } } hasLink = 1; } else if (strcasecmp(name, "pack") == 0) { if (control -> ProxyMode == proxy_client) { PrintOptionIgnored("remote", name, value); } else { if (*packMethodName != '\0' && strcasecmp(packMethodName, value) != 0) { #ifdef WARNING *logofs << "Loop: WARNING! Overriding option 'pack' " << "with remote value '" << value << "'.\n" << logofs_flush; #endif cerr << "Warning" << ": Overriding option 'pack' " << "with remote value '" << value << "'.\n"; } if (ParsePackOption(value) < 0) { #ifdef PANIC *logofs << "Loop: PANIC! Invalid pack option '" << value << "' requested by remote.\n" << logofs_flush; #endif cerr << "Error" << ": Invalid pack option '" << value << "' requested by remote.\n"; return -1; } } hasPack = 1; } else if (strcasecmp(name, "cache") == 0) { if (control -> ProxyMode == proxy_client) { PrintOptionIgnored("remote", name, value); } else { // // Cache size is sent as a hint of how much memory // the remote proxy is going to consume. A very low // powered thin client could choose to refuse the // connection. // if (ParseCacheOption(value) < 0) { #ifdef PANIC *logofs << "Loop: PANIC! Can't identify remote 'cache' " << "option in string '" << value << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Can't identify remote 'cache' " << "option in string '" << value << "'.\n"; return -1; } } hasCache = 1; } else if (strcasecmp(name, "images") == 0) { if (control -> ProxyMode == proxy_client) { PrintOptionIgnored("remote", name, value); } else { // // Images cache size is sent as a hint. // There is no obbligation for the local // proxy to use the persistent cache. // if (ParseImagesOption(value) < 0) { #ifdef PANIC *logofs << "Loop: PANIC! Can't identify remote 'images' " << "option in string '" << value << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Can't identify remote 'images' " << "option in string '" << value << "'.\n"; return -1; } } hasImages = 1; } else if (strcasecmp(name, "limit") == 0) { if (control -> ProxyMode == proxy_client) { PrintOptionIgnored("remote", name, value); } else { if (*bitrateLimitName != '\0' && strcasecmp(bitrateLimitName, value) != 0) { #ifdef WARNING *logofs << "Loop: WARNING! Overriding option 'limit' " << "with new value '" << value << "'.\n" << logofs_flush; #endif cerr << "Warning" << ": Overriding option 'limit' " << "with new value '" << value << "'.\n"; } if (ParseBitrateOption(value) < 0) { #ifdef PANIC *logofs << "Loop: PANIC! Can't identify 'limit' " << "option in string '" << value << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Can't identify 'limit' " << "option in string '" << value << "'.\n"; return -1; } } hasLimit = 1; } else if (strcasecmp(name, "render") == 0) { if (control -> ProxyMode == proxy_client) { PrintOptionIgnored("remote", name, value); } else { useRender = ValidateArg("remote", name, value); } hasRender = 1; } else if (strcasecmp(name, "taint") == 0) { if (control -> ProxyMode == proxy_client) { PrintOptionIgnored("remote", name, value); } else { useTaint = ValidateArg("remote", name, value); } hasTaint = 1; } else if (strcasecmp(name, "type") == 0) { if (control -> ProxyMode == proxy_client) { PrintOptionIgnored("remote", name, value); } else { if (strcasecmp(value, "default") == 0) { *sessionType = '\0'; } else { strncpy(sessionType, value, DEFAULT_STRING_LENGTH - 1); } } hasType = 1; } else if (strcasecmp(name, "strict") == 0) { if (control -> ProxyMode == proxy_client) { PrintOptionIgnored("remote", name, value); } else { useStrict = ValidateArg("remote", name, value); } hasStrict = 1; } else if (strcasecmp(name, "shseg") == 0) { if (control -> ProxyMode == proxy_client) { PrintOptionIgnored("remote", name, value); } else if (ParseShmemOption(value) < 0) { #ifdef PANIC *logofs << "Loop: PANIC! Can't identify size of shared memory " << "segment in string '" << value << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Can't identify size of shared memory " << "segment in string '" << value << "'.\n"; return -1; } hasShseg = 1; } else if (strcasecmp(name, "delta") == 0) { if (control -> ProxyMode == proxy_client) { PrintOptionIgnored("remote", name, value); } else { control -> RemoteDeltaCompression = ValidateArg("remote", name, value); // // Follow for delta compression the // same settings as the client proxy. // control -> LocalDeltaCompression = control -> RemoteDeltaCompression; } hasDelta = 1; } else if (strcasecmp(name, "stream") == 0) { // // If remote side didn't choose its own // stream compression level then assume // local settings. // if (strcasecmp(value, "default") == 0) { // // This applies only at client side. // control -> RemoteStreamCompression = control -> LocalStreamCompression; control -> RemoteStreamCompressionLevel = control -> LocalStreamCompressionLevel; } else { control -> RemoteStreamCompressionLevel = ValidateArg("remote", name, value); if (control -> RemoteStreamCompressionLevel > 0) { control -> RemoteStreamCompression = 1; } else { control -> RemoteStreamCompression = 0; } if (control -> LocalStreamCompressionLevel < 0) { control -> LocalStreamCompressionLevel = ValidateArg("remote", name, value); if (control -> LocalStreamCompressionLevel > 0) { control -> LocalStreamCompression = 1; } else { control -> LocalStreamCompression = 0; } } } hasStream = 1; } else if (strcasecmp(name, "data") == 0) { // // Apply the same to data compression level. // if (strcasecmp(value, "default") == 0) { control -> RemoteDataCompression = control -> LocalDataCompression; control -> RemoteDataCompressionLevel = control -> LocalDataCompressionLevel; } else { control -> RemoteDataCompressionLevel = ValidateArg("remote", name, value); if (control -> RemoteDataCompressionLevel > 0) { control -> RemoteDataCompression = 1; } else { control -> RemoteDataCompression = 0; } if (control -> LocalDataCompressionLevel < 0) { control -> LocalDataCompressionLevel = ValidateArg("remote", name, value); if (control -> LocalDataCompressionLevel > 0) { control -> LocalDataCompression = 1; } else { control -> LocalDataCompression = 0; } } } hasData = 1; } else if (strcasecmp(name, "flush") == 0) { // // This option has no effect in recent // versions. // #ifdef DEBUG *logofs << "Loop: Ignoring obsolete remote option '" << name << "' with value '" << value << "'.\n" << logofs_flush; #endif } else { #ifdef WARNING *logofs << "Loop: WARNING! Ignoring unknown remote option '" << name << "' with value '" << value << "'.\n" << logofs_flush; #endif cerr << "Warning" << ": Ignoring unknown remote option '" << name << "' with value '" << value << "'.\n"; } name = strtok(NULL, "="); } // End of while (name) ... // // If we are client side, we need remote 'stream' // and 'data' options. If we are server, we need // all the above plus 'link' and some others. // char missing[DEFAULT_STRING_LENGTH]; *missing = '\0'; if (control -> ProxyMode == proxy_client) { if (hasStream == 0) { strcpy(missing, "stream"); } else if (hasData == 0) { strcpy(missing, "data"); } } else { // // Don't complain if the optional 'flush', // 'render' and 'taint' options are not // provided. // if (hasLink == 0) { strcpy(missing, "link"); } else if (hasCache == 0) { strcpy(missing, "cache"); } else if (hasPack == 0) { strcpy(missing, "pack"); } else if (hasDelta == 0) { strcpy(missing, "delta"); } else if (hasStream == 0) { strcpy(missing, "stream"); } else if (hasData == 0) { strcpy(missing, "data"); } else if (hasType == 0) { strcpy(missing, "type"); } else if (hasImages == 0) { strcpy(missing, "images"); } } if (WE_PROVIDE_CREDENTIALS == 0) { // // Can be that user doesn't have requested to // check the authorization cookie provided by // the connecting peer. // if (hasCookie == 0 && *authCookie != '\0') { strcpy(missing, "cookie"); } } if (*missing != '\0') { #ifdef PANIC *logofs << "Loop: PANIC! The remote peer didn't specify the option '" << missing << "'.\n" << logofs_flush; #endif cerr << "Error" << ": The remote peer didn't specify the option '" << missing << "'.\n"; return -1; } return 1; } // // Parse the cookie provided by the NX proxy // connection forwarder. // int ParseForwarderOptions(char *opts) { #ifdef TEST *logofs << "Loop: Going to parse the forwarder options " << "string '" << opts << "'.\n" << logofs_flush; #endif char *name; char *value; int hasCookie = 0; // // Get rid of the terminating space. // if (*(opts + strlen(opts) - 1) == ' ') { *(opts + strlen(opts) - 1) = '\0'; } name = strtok(opts, "="); while (name) { value = strtok(NULL, ","); if (CheckArg("forwarder", name, value) < 0) { return -1; } if (strcasecmp(name, "cookie") == 0) { if (strncasecmp(authCookie, value, strlen(authCookie)) != 0) { #ifdef PANIC *logofs << "Loop: PANIC! The NX forwarder cookie '" << value << "' doesn't match '" << authCookie << "'.\n" << logofs_flush; #endif cerr << "Error" << ": The NX forwarder cookie '" << value << "' doesn't match '" << authCookie << "'.\n"; return -1; } hasCookie = 1; } else { #ifdef WARNING *logofs << "Loop: WARNING! Ignoring unknown forwarder option '" << name << "' with value '" << value << "'.\n" << logofs_flush; #endif cerr << "Warning" << ": Ignoring unknown forwarder option '" << name << "' with value '" << value << "'.\n"; } name = strtok(NULL, "="); } // End of while (name) ... if (hasCookie == 0) { #ifdef PANIC *logofs << "Loop: PANIC! The NX forwarder didn't provide " << "the authentication cookie.\n" << logofs_flush; #endif cerr << "Error" << ": The NX forwarder didn't provide " << "the authentication cookie.\n"; return -1; } return 1; } int SetCore() { #ifdef COREDUMPS rlimit rlim; if (getrlimit(RLIMIT_CORE, &rlim)) { #ifdef TEST *logofs << "Cannot read RLIMIT_CORE. Error is '" << ESTR() << "'.\n" << logofs_flush; #endif return -1; } if (rlim.rlim_cur < rlim.rlim_max) { rlim.rlim_cur = rlim.rlim_max; if (setrlimit(RLIMIT_CORE, &rlim)) { #ifdef TEST *logofs << "Loop: Cannot read RLIMIT_CORE. Error is '" << ESTR() << "'.\n" << logofs_flush; #endif return -2; } } #ifdef TEST *logofs << "Loop: Set RLIMIT_CORE to "<< rlim.rlim_max << ".\n" << logofs_flush; #endif #endif // #ifdef COREDUMPS return 1; } char *GetLastCache(char *listBuffer, const char *searchPath) { if (listBuffer == NULL || searchPath == NULL || strncmp(listBuffer, "cachelist=", strlen("cachelist=")) != 0) { #ifdef TEST *logofs << "Loop: Invalid parameters '" << listBuffer << "' and '" << (searchPath != NULL ? searchPath : "") << "'. Can't select any cache.\n" << logofs_flush; #endif return NULL; } char *selectedName = new char[MD5_LENGTH * 2 + 3]; *selectedName = '\0'; char *localPrefix; char *remotePrefix; if (control -> ProxyMode == proxy_client) { localPrefix = "C-"; remotePrefix = "S-"; } else { localPrefix = "S-"; remotePrefix = "C-"; } // // Get rid of prefix. // listBuffer += strlen("cachelist="); char *fileName; fileName = strtok(listBuffer, ","); // // It is "/path/to/file" + "/" + "C-" + 32 + "\0". // char fullPath[strlen(searchPath) + MD5_LENGTH * 2 + 4]; time_t selectedTime = 0; struct stat fileStat; while (fileName) { if (strncmp(fileName, "none", strlen("none")) == 0) { #ifdef TEST *logofs << "Loop: No cache files seem to be available.\n" << logofs_flush; #endif delete [] selectedName; return NULL; } else if (strlen(fileName) != MD5_LENGTH * 2 + 2 || strncmp(fileName, remotePrefix, 2) != 0) { #ifdef PANIC *logofs << "Loop: PANIC! Bad cache file name '" << fileName << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Bad cache file name '" << fileName << "'.\n"; delete [] selectedName; HandleCleanup(); } #ifdef TEST *logofs << "Loop: Parsing remote cache name '" << fileName << "'.\n" << logofs_flush; #endif // // Prefix, received as "S-", becomes // "C-" and viceversa. // *fileName = *localPrefix; strcpy(fullPath, searchPath); strcat(fullPath, "/"); strcat(fullPath, fileName); if (stat(fullPath, &fileStat) == 0) { #ifdef TEST *logofs << "Loop: Found a matching cache '" << fullPath << "'.\n" << logofs_flush; #endif if (fileStat.st_mtime >= selectedTime) { strcpy(selectedName, fileName); selectedTime = fileStat.st_mtime; } } #ifdef TEST else { *logofs << "Loop: Can't get stats of file '" << fullPath << "'.\n" << logofs_flush; } #endif fileName = strtok(NULL, ","); } if (*selectedName != '\0') { return selectedName; } else { delete [] selectedName; return NULL; } } char *GetTempPath() { if (*tempDir == '\0') { // // Check the NX_TEMP environment, first, // then the TEMP variable. // const char *tempEnv = getenv("NX_TEMP"); if (tempEnv == NULL || *tempEnv == '\0') { #ifdef TEST *logofs << "Loop: WARNING! No environment for NX_TEMP.\n" << logofs_flush; #endif tempEnv = getenv("TEMP"); if (tempEnv == NULL || *tempEnv == '\0') { #ifdef TEST *logofs << "Loop: WARNING! No environment for TEMP.\n" << logofs_flush; #endif tempEnv = "/tmp"; } } if (strlen(tempEnv) > DEFAULT_STRING_LENGTH - 1) { #ifdef PANIC *logofs << "Loop: PANIC! Invalid value for the NX " << "temporary directory '" << tempEnv << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Invalid value for the NX " << "temporary directory '" << tempEnv << "'.\n"; HandleCleanup(); } strcpy(tempDir, tempEnv); #ifdef TEST *logofs << "Loop: Assuming temporary NX directory '" << tempDir << "'.\n" << logofs_flush; #endif } char *tempPath = new char[strlen(tempDir) + 1]; if (tempPath == NULL) { #ifdef PANIC *logofs << "Loop: PANIC! Can't allocate memory " << "for the temp path.\n" << logofs_flush; #endif cerr << "Error" << ": Can't allocate memory " << "for the temp path.\n"; HandleCleanup(); } strcpy(tempPath, tempDir); return tempPath; } char *GetClientPath() { if (*clientDir == '\0') { // // Check the NX_CLIENT environment. // const char *clientEnv = getenv("NX_CLIENT"); if (clientEnv == NULL || *clientEnv == '\0') { #ifdef TEST *logofs << "Loop: WARNING! No environment for NX_CLIENT.\n" << logofs_flush; #endif // // Try to guess the location of the client. // clientEnv = "/usr/NX/bin/nxclient"; #ifdef __APPLE__ clientEnv = "/Applications/NX Client for OSX.app/Contents/MacOS/nxclient"; #endif #ifdef __CYGWIN32__ clientEnv = "C:\\Program Files\\NX Client for Windows\\nxclient"; #endif } if (strlen(clientEnv) > DEFAULT_STRING_LENGTH - 1) { #ifdef PANIC *logofs << "Loop: PANIC! Invalid value for the NX " << "client directory '" << clientEnv << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Invalid value for the NX " << "client directory '" << clientEnv << "'.\n"; HandleCleanup(); } strcpy(clientDir, clientEnv); #ifdef TEST *logofs << "Loop: Assuming NX client location '" << clientDir << "'.\n" << logofs_flush; #endif } char *clientPath = new char[strlen(clientDir) + 1]; if (clientPath == NULL) { #ifdef PANIC *logofs << "Loop: PANIC! Can't allocate memory " << "for the client path.\n" << logofs_flush; #endif cerr << "Error" << ": Can't allocate memory " << "for the client path.\n"; HandleCleanup(); } strcpy(clientPath, clientDir); return clientPath; } char *GetSystemPath() { if (*systemDir == '\0') { // // Check the NX_SYSTEM environment. // const char *systemEnv = getenv("NX_SYSTEM"); if (systemEnv == NULL || *systemEnv == '\0') { #ifdef TEST *logofs << "Loop: WARNING! No environment for NX_SYSTEM.\n" << logofs_flush; #endif systemEnv = "/usr/NX"; } if (strlen(systemEnv) > DEFAULT_STRING_LENGTH - 1) { #ifdef PANIC *logofs << "Loop: PANIC! Invalid value for the NX " << "system directory '" << systemEnv << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Invalid value for the NX " << "system directory '" << systemEnv << "'.\n"; HandleCleanup(); } strcpy(systemDir, systemEnv); #ifdef TEST *logofs << "Loop: Assuming system NX directory '" << systemDir << "'.\n" << logofs_flush; #endif } char *systemPath = new char[strlen(systemDir) + 1]; if (systemPath == NULL) { #ifdef PANIC *logofs << "Loop: PANIC! Can't allocate memory " << "for the system path.\n" << logofs_flush; #endif cerr << "Error" << ": Can't allocate memory " << "for the system path.\n"; HandleCleanup(); } strcpy(systemPath, systemDir); return systemPath; } char *GetHomePath() { if (*homeDir == '\0') { // // Check the NX_HOME environment. // const char *homeEnv = getenv("NX_HOME"); if (homeEnv == NULL || *homeEnv == '\0') { #ifdef TEST *logofs << "Loop: WARNING! No environment for NX_HOME.\n" << logofs_flush; #endif homeEnv = getenv("HOME"); if (homeEnv == NULL || *homeEnv == '\0') { #ifdef PANIC *logofs << "Loop: PANIC! No environment for HOME.\n" << logofs_flush; #endif cerr << "Error" << ": No environment for HOME.\n"; HandleCleanup(); } } if (strlen(homeEnv) > DEFAULT_STRING_LENGTH - 1) { #ifdef PANIC *logofs << "Loop: PANIC! Invalid value for the NX " << "home directory '" << homeEnv << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Invalid value for the NX " << "home directory '" << homeEnv << "'.\n"; HandleCleanup(); } strcpy(homeDir, homeEnv); #ifdef TEST *logofs << "Loop: Assuming NX user's home directory '" << homeDir << "'.\n" << logofs_flush; #endif } char *homePath = new char[strlen(homeDir) + 1]; if (homePath == NULL) { #ifdef PANIC *logofs << "Loop: PANIC! Can't allocate memory " << "for the home path.\n" << logofs_flush; #endif cerr << "Error" << ": Can't allocate memory " << "for the home path.\n"; HandleCleanup(); } strcpy(homePath, homeDir); return homePath; } char *GetRootPath() { if (*rootDir == '\0') { // // Check the NX_ROOT environment. // const char *rootEnv = getenv("NX_ROOT"); if (rootEnv == NULL || *rootEnv == '\0') { #ifdef TEST *logofs << "Loop: WARNING! No environment for NX_ROOT.\n" << logofs_flush; #endif // // We will determine the root NX directory // based on the NX_HOME or HOME directory // settings. // const char *homeEnv = GetHomePath(); if (strlen(homeEnv) > DEFAULT_STRING_LENGTH - strlen("/.nx") - 1) { #ifdef PANIC *logofs << "Loop: PANIC! Invalid value for the NX " << "home directory '" << homeEnv << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Invalid value for the NX " << "home directory '" << homeEnv << "'.\n"; HandleCleanup(); } #ifdef TEST *logofs << "Loop: Assuming NX root directory in " << "the user's home '" << homeEnv << "'.\n" << logofs_flush; #endif strcpy(rootDir, homeEnv); strcat(rootDir, "/.nx"); delete [] homeEnv; // // Create the NX root directory. // struct stat dirStat; if ((stat(rootDir, &dirStat) == -1) && (EGET() == ENOENT)) { if (mkdir(rootDir, 0700) < 0 && (EGET() != EEXIST)) { #ifdef PANIC *logofs << "Loop: PANIC! Can't create directory '" << rootDir << ". Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Can't create directory '" << rootDir << ". Error is " << EGET() << " '" << ESTR() << "'.\n"; HandleCleanup(); } } } else { if (strlen(rootEnv) > DEFAULT_STRING_LENGTH - 1) { #ifdef PANIC *logofs << "Loop: PANIC! Invalid value for the NX " << "root directory '" << rootEnv << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Invalid value for the NX " << "root directory '" << rootEnv << "'.\n"; HandleCleanup(); } strcpy(rootDir, rootEnv); } #ifdef TEST *logofs << "Loop: Assuming NX root directory '" << rootDir << "'.\n" << logofs_flush; #endif } char *rootPath = new char[strlen(rootDir) + 1]; if (rootPath == NULL) { #ifdef PANIC *logofs << "Loop: PANIC! Can't allocate memory " << "for the root path.\n" << logofs_flush; #endif cerr << "Error" << ": Can't allocate memory " << "for the root path.\n"; HandleCleanup(); } strcpy(rootPath, rootDir); return rootPath; } char *GetCachePath() { char *rootPath = GetRootPath(); char *cachePath; if (*sessionType != '\0') { cachePath = new char[strlen(rootPath) + strlen("/cache-") + strlen(sessionType) + 1]; } else { cachePath = new char[strlen(rootPath) + strlen("/cache") + 1]; } strcpy(cachePath, rootPath); if (*sessionType != '\0') { strcat(cachePath, "/cache-"); strcat(cachePath, sessionType); } else { strcat(cachePath, "/cache"); } // // Create the cache directory if needed. // struct stat dirStat; if ((stat(cachePath, &dirStat) == -1) && (EGET() == ENOENT)) { if (mkdir(cachePath, 0700) < 0 && (EGET() != EEXIST)) { #ifdef PANIC *logofs << "Loop: PANIC! Can't create directory '" << cachePath << ". Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Can't create directory '" << cachePath << ". Error is " << EGET() << " '" << ESTR() << "'.\n"; delete [] rootPath; delete [] cachePath; return NULL; } } delete [] rootPath; return cachePath; } char *GetImagesPath() { char *rootPath = GetRootPath(); char *imagesPath = new char[strlen(rootPath) + strlen("/images") + 1]; strcpy(imagesPath, rootPath); strcat(imagesPath, "/images"); // // Create the cache directory if needed. // struct stat dirStat; if ((stat(imagesPath, &dirStat) == -1) && (EGET() == ENOENT)) { if (mkdir(imagesPath, 0700) < 0 && (EGET() != EEXIST)) { #ifdef PANIC *logofs << "Loop: PANIC! Can't create directory '" << imagesPath << ". Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Can't create directory '" << imagesPath << ". Error is " << EGET() << " '" << ESTR() << "'.\n"; delete [] rootPath; delete [] imagesPath; return NULL; } } // // Create 16 directories in the path to // hold the images whose name begins with // the corresponding hexadecimal digit. // char *digitPath = new char[strlen(imagesPath) + 5]; strcpy(digitPath, imagesPath); // // Image paths have format "[path][/I-c][\0]", // where c is the first digit of the checksum. // for (char digit = 0; digit < 16; digit++) { sprintf(digitPath + strlen(imagesPath), "/I-%01X", digit); if ((stat(digitPath, &dirStat) == -1) && (EGET() == ENOENT)) { if (mkdir(digitPath, 0700) < 0 && (EGET() != EEXIST)) { #ifdef PANIC *logofs << "Loop: PANIC! Can't create directory '" << digitPath << ". Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Can't create directory '" << digitPath << ". Error is " << EGET() << " '" << ESTR() << "'.\n"; delete [] rootPath; delete [] imagesPath; delete [] digitPath; return NULL; } } } delete [] rootPath; delete [] digitPath; return imagesPath; } char *GetSessionPath() { if (*sessionDir == '\0') { char *rootPath = GetRootPath(); strcpy(sessionDir, rootPath); if (control -> ProxyMode == proxy_client) { strcat(sessionDir, "/C-"); } else { strcat(sessionDir, "/S-"); } if (*sessionId == '\0') { char port[DEFAULT_STRING_LENGTH]; sprintf(port, "%d", proxyPort); strcpy(sessionId, port); } strcat(sessionDir, sessionId); struct stat dirStat; if ((stat(sessionDir, &dirStat) == -1) && (EGET() == ENOENT)) { if (mkdir(sessionDir, 0700) < 0 && (EGET() != EEXIST)) { #ifdef PANIC *logofs << "Loop: PANIC! Can't create directory '" << sessionDir << ". Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Can't create directory '" << sessionDir << ". Error is " << EGET() << " '" << ESTR() << "'.\n"; delete [] rootPath; return NULL; } } #ifdef TEST *logofs << "Loop: Root of NX session is '" << sessionDir << "'.\n" << logofs_flush; #endif delete [] rootPath; } char *sessionPath = new char[strlen(sessionDir) + 1]; strcpy(sessionPath, sessionDir); return sessionPath; } // // Identify requested link characteristics // and set control parameters accordingly. // int ParseLinkOption(const char *opt) { // // Normalize the user input. // if (strcasecmp(opt, "modem") == 0 || strcasecmp(opt, "33k") == 0 || strcasecmp(opt, "56k") == 0) { strcpy(linkSpeedName, "MODEM"); } else if (strcasecmp(opt, "isdn") == 0 || strcasecmp(opt, "64k") == 0 || strcasecmp(opt, "128k") == 0) { strcpy(linkSpeedName, "ISDN"); } else if (strcasecmp(opt, "adsl") == 0 || strcasecmp(opt, "256k") == 0 || strcasecmp(opt, "640k") == 0) { strcpy(linkSpeedName, "ADSL"); } else if (strcasecmp(opt, "wan") == 0 || strcasecmp(opt, "1m") == 0 || strcasecmp(opt, "2m") == 0 || strcasecmp(opt, "34m") == 0) { strcpy(linkSpeedName, "WAN"); } else if (strcasecmp(opt, "lan") == 0 || strcasecmp(opt, "10m") == 0 || strcasecmp(opt, "100m") == 0 || strcasecmp(opt, "local") == 0) { strcpy(linkSpeedName, "LAN"); } if (strcasecmp(linkSpeedName, "modem") != 0 && strcasecmp(linkSpeedName, "isdn") != 0 && strcasecmp(linkSpeedName, "adsl") != 0 && strcasecmp(linkSpeedName, "wan") != 0 && strcasecmp(linkSpeedName, "lan") != 0) { return -1; } return 1; } int ParsePackOption(const char *opt) { #ifdef DEBUG *logofs << "Loop: Pack method is " << packMethod << " quality is " << packQuality << ".\n" << logofs_flush; #endif #ifdef DEBUG *logofs << "Loop: Parsing pack method '" << opt << "'.\n" << logofs_flush; #endif if (strcasecmp(opt, "0") == 0 || strcasecmp(opt, "none") == 0 || strcasecmp(opt, "nopack") == 0 || strcasecmp(opt, "no-pack") == 0) { packMethod = PACK_NONE; } else if (strcasecmp(opt, "8") == 0) { packMethod = PACK_MASKED_8_COLORS; } else if (strcasecmp(opt, "64") == 0) { packMethod = PACK_MASKED_64_COLORS; } else if (strcasecmp(opt, "256") == 0) { packMethod = PACK_MASKED_256_COLORS; } else if (strcasecmp(opt, "512") == 0) { packMethod = PACK_MASKED_512_COLORS; } else if (strcasecmp(opt, "4k") == 0) { packMethod = PACK_MASKED_4K_COLORS; } else if (strcasecmp(opt, "32k") == 0) { packMethod = PACK_MASKED_32K_COLORS; } else if (strcasecmp(opt, "64k") == 0) { packMethod = PACK_MASKED_64K_COLORS; } else if (strcasecmp(opt, "256k") == 0) { packMethod = PACK_MASKED_256K_COLORS; } else if (strcasecmp(opt, "2m") == 0) { packMethod = PACK_MASKED_2M_COLORS; } else if (strcasecmp(opt, "16m") == 0) { packMethod = PACK_MASKED_16M_COLORS; } else if (strncasecmp(opt, "8-jpeg", strlen("8-jpeg")) == 0) { packMethod = PACK_JPEG_8_COLORS; } else if (strncasecmp(opt, "64-jpeg", strlen("64-jpeg")) == 0) { packMethod = PACK_JPEG_64_COLORS; } else if (strncasecmp(opt, "256-jpeg", strlen("256-jpeg")) == 0) { packMethod = PACK_JPEG_256_COLORS; } else if (strncasecmp(opt, "512-jpeg", strlen("512-jpeg")) == 0) { packMethod = PACK_JPEG_512_COLORS; } else if (strncasecmp(opt, "4k-jpeg", strlen("4k-jpeg")) == 0) { packMethod = PACK_JPEG_4K_COLORS; } else if (strncasecmp(opt, "32k-jpeg", strlen("32k-jpeg")) == 0) { packMethod = PACK_JPEG_32K_COLORS; } else if (strncasecmp(opt, "64k-jpeg", strlen("64k-jpeg")) == 0) { packMethod = PACK_JPEG_64K_COLORS; } else if (strncasecmp(opt, "256k-jpeg", strlen("256k-jpeg")) == 0) { packMethod = PACK_JPEG_256K_COLORS; } else if (strncasecmp(opt, "2m-jpeg", strlen("2m-jpeg")) == 0) { packMethod = PACK_JPEG_2M_COLORS; } else if (strncasecmp(opt, "16m-jpeg", strlen("16m-jpeg")) == 0) { packMethod = PACK_JPEG_16M_COLORS; } else if (strncasecmp(opt, "8-png", strlen("8-png")) == 0) { packMethod = PACK_PNG_8_COLORS; } else if (strncasecmp(opt, "64-png", strlen("64-png")) == 0) { packMethod = PACK_PNG_64_COLORS; } else if (strncasecmp(opt, "256-png", strlen("256-png")) == 0) { packMethod = PACK_PNG_256_COLORS; } else if (strncasecmp(opt, "512-png", strlen("512-png")) == 0) { packMethod = PACK_PNG_512_COLORS; } else if (strncasecmp(opt, "4k-png", strlen("4k-png")) == 0) { packMethod = PACK_PNG_4K_COLORS; } else if (strncasecmp(opt, "32k-png", strlen("32k-png")) == 0) { packMethod = PACK_PNG_32K_COLORS; } else if (strncasecmp(opt, "64k-png", strlen("64k-png")) == 0) { packMethod = PACK_PNG_64K_COLORS; } else if (strncasecmp(opt, "256k-png", strlen("256k-png")) == 0) { packMethod = PACK_PNG_256K_COLORS; } else if (strncasecmp(opt, "2m-png", strlen("2m-png")) == 0) { packMethod = PACK_PNG_2M_COLORS; } else if (strncasecmp(opt, "16m-png", strlen("16m-png")) == 0) { packMethod = PACK_PNG_16M_COLORS; } else if (strncasecmp(opt, "16m-rgb", strlen("16m-rgb")) == 0 || strncasecmp(opt, "rgb", strlen("rgb")) == 0) { packMethod = PACK_RGB_16M_COLORS; } else if (strncasecmp(opt, "16m-rle", strlen("16m-rle")) == 0 || strncasecmp(opt, "rle", strlen("rle")) == 0) { packMethod = PACK_RLE_16M_COLORS; } else if (strncasecmp(opt, "16m-bitmap", strlen("16m-bitmap")) == 0 || strncasecmp(opt, "bitmap", strlen("bitmap")) == 0) { packMethod = PACK_BITMAP_16M_COLORS; } else if (strncasecmp(opt, "lossy", strlen("lossy")) == 0) { packMethod = PACK_LOSSY; } else if (strncasecmp(opt, "lossless", strlen("lossless")) == 0) { packMethod = PACK_LOSSLESS; } else if (strncasecmp(opt, "adaptive", strlen("adaptive")) == 0) { packMethod = PACK_ADAPTIVE; } else { return -1; } if (packMethod == PACK_NONE) { strcpy(packMethodName, "none"); } else { strcpy(packMethodName, opt); } if (packMethod == PACK_RGB_16M_COLORS || packMethod == PACK_RLE_16M_COLORS || packMethod == PACK_BITMAP_16M_COLORS || (packMethod >= PACK_JPEG_8_COLORS && packMethod <= PACK_JPEG_16M_COLORS) || (packMethod >= PACK_PNG_8_COLORS && packMethod <= PACK_PNG_16M_COLORS) || packMethod == PACK_LOSSY || packMethod == PACK_LOSSLESS || packMethod == PACK_ADAPTIVE) { char *dash = rindex(opt, '-'); if (dash != NULL && strlen(dash) == 2 && *(dash + 1) >= '0' && *(dash + 1) <= '9') { packQuality = atoi(dash + 1); #ifdef DEBUG *logofs << "Loop: Using pack quality '" << packQuality << "'.\n" << logofs_flush; #endif } } else { packQuality = 0; } return 1; } int ParsePackMethod(const int method, const int quality) { switch (method) { case PACK_NONE: { strcpy(packMethodName, "none"); break; } case PACK_MASKED_8_COLORS: { strcpy(packMethodName, "8"); break; } case PACK_MASKED_64_COLORS: { strcpy(packMethodName, "64"); break; } case PACK_MASKED_256_COLORS: { strcpy(packMethodName, "256"); break; } case PACK_MASKED_512_COLORS: { strcpy(packMethodName, "512"); break; } case PACK_MASKED_4K_COLORS: { strcpy(packMethodName, "4k"); break; } case PACK_MASKED_32K_COLORS: { strcpy(packMethodName, "32k"); break; } case PACK_MASKED_64K_COLORS: { strcpy(packMethodName, "64k"); break; } case PACK_MASKED_256K_COLORS: { strcpy(packMethodName, "256k"); break; } case PACK_MASKED_2M_COLORS: { strcpy(packMethodName, "2m"); break; } case PACK_MASKED_16M_COLORS: { strcpy(packMethodName, "16m"); break; } case PACK_JPEG_8_COLORS: { strcpy(packMethodName, "8-jpeg"); break; } case PACK_JPEG_64_COLORS: { strcpy(packMethodName, "64-jpeg"); break; } case PACK_JPEG_256_COLORS: { strcpy(packMethodName, "256-jpeg"); break; } case PACK_JPEG_512_COLORS: { strcpy(packMethodName, "512-jpeg"); break; } case PACK_JPEG_4K_COLORS: { strcpy(packMethodName, "4k-jpeg"); break; } case PACK_JPEG_32K_COLORS: { strcpy(packMethodName, "32k-jpeg"); break; } case PACK_JPEG_64K_COLORS: { strcpy(packMethodName, "64k-jpeg"); break; } case PACK_JPEG_256K_COLORS: { strcpy(packMethodName, "256k-jpeg"); break; } case PACK_JPEG_2M_COLORS: { strcpy(packMethodName, "2m-jpeg"); break; } case PACK_JPEG_16M_COLORS: { strcpy(packMethodName, "16m-jpeg"); break; } case PACK_PNG_8_COLORS: { strcpy(packMethodName, "8-png"); break; } case PACK_PNG_64_COLORS: { strcpy(packMethodName, "64-png"); break; } case PACK_PNG_256_COLORS: { strcpy(packMethodName, "256-png"); break; } case PACK_PNG_512_COLORS: { strcpy(packMethodName, "512-png"); break; } case PACK_PNG_4K_COLORS: { strcpy(packMethodName, "4k-png"); break; } case PACK_PNG_32K_COLORS: { strcpy(packMethodName, "32k-png"); break; } case PACK_PNG_64K_COLORS: { strcpy(packMethodName, "64k-png"); break; } case PACK_PNG_256K_COLORS: { strcpy(packMethodName, "256k-png"); break; } case PACK_PNG_2M_COLORS: { strcpy(packMethodName, "2m-png"); break; } case PACK_PNG_16M_COLORS: { strcpy(packMethodName, "16m-png"); break; } case PACK_RGB_16M_COLORS: { strcpy(packMethodName, "16m-rgb"); break; } case PACK_RLE_16M_COLORS: { strcpy(packMethodName, "16m-rle"); break; } case PACK_BITMAP_16M_COLORS: { strcpy(packMethodName, "16m-bitmap"); break; } case PACK_LOSSY: { strcpy(packMethodName, "lossy"); break; } case PACK_LOSSLESS: { strcpy(packMethodName, "lossless"); break; } case PACK_ADAPTIVE: { strcpy(packMethodName, "adaptive"); break; } default: { return -1; } } if (quality < 0 || quality > 9) { return -1; } if (packMethod == PACK_RGB_16M_COLORS || packMethod == PACK_RLE_16M_COLORS || packMethod == PACK_BITMAP_16M_COLORS || (packMethod >= PACK_JPEG_8_COLORS && packMethod <= PACK_JPEG_16M_COLORS) || (packMethod >= PACK_PNG_8_COLORS && packMethod <= PACK_PNG_16M_COLORS) || packMethod == PACK_LOSSY || packMethod == PACK_LOSSLESS || packMethod == PACK_ADAPTIVE) { sprintf(packMethodName + strlen(packMethodName), "-%d", quality); } packMethod = method; packQuality = quality; control -> PackMethod = packMethod; control -> PackQuality = packQuality; return 1; } int SetDirectories() { // // Determine the location of the user's NX // directory and the other relevant paths. // The functions below will check the pa- // rameters passed to the program and will // query the environment, if needed. // control -> HomePath = GetHomePath(); control -> RootPath = GetRootPath(); control -> SystemPath = GetSystemPath(); control -> TempPath = GetTempPath(); control -> ClientPath = GetClientPath(); return 1; } int SetLogs() { // // So far we used stderr (or stdout under // WIN32). Now use the files selected by // the user. // if (*statsFileName == '\0') { strcpy(statsFileName, "stats"); #ifdef TEST *logofs << "Loop: Assuming default statistics file '" << statsFileName << "'.\n" << logofs_flush; #endif } #ifdef TEST else { *logofs << "Loop: Name selected for statistics is '" << statsFileName << "'.\n" << logofs_flush; } #endif if (OpenLogFile(statsFileName, statofs) < 0) { HandleCleanup(); } #ifndef MIXED if (*errorsFileName == '\0') { strcpy(errorsFileName, "errors"); #ifdef TEST *logofs << "Loop: Assuming default log file name '" << errorsFileName << "'.\n" << logofs_flush; #endif } #ifdef TEST else { *logofs << "Loop: Name selected for log file is '" << errorsFileName << "'.\n" << logofs_flush; } #endif // // Share the bebug output with the nxssh binder // process. The file must be made writable by // everybody because the nxssh process is run by // nxserver as the nx user. // #ifdef BINDER strcpy(errorsFileName, "/tmp/errors"); ostream *tmpfs = new ofstream(errorsFileName, ios::out); delete tmpfs; chmod(errorsFileName, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); #endif if (OpenLogFile(errorsFileName, logofs) < 0) { HandleCleanup(); } // // By default the session log is the standard error // of the process. It is anyway required to set the // option when running inside SSH, otherwise the // output will go to the same file as the SSH log, // depending where the NX client has redirected the // output. // if (*sessionFileName != '\0') { #ifdef TEST *logofs << "Loop: Name selected for session file is '" << sessionFileName << "'.\n" << logofs_flush; #endif if (errofs != NULL) { #ifdef WARNING *logofs << "Loop: WARNING! Unexpected value for stream errofs.\n" << logofs_flush; #endif cerr << "Warning" << ": Unexpected value for stream errofs.\n"; } if (errsbuf != NULL) { #ifdef WARNING *logofs << "Loop: WARNING! Unexpected value for buffer errsbuf.\n" << logofs_flush; #endif cerr << "Warning" << ": Unexpected value for buffer errsbuf.\n"; } errofs = NULL; errsbuf = NULL; if (OpenLogFile(sessionFileName, errofs) < 0) { HandleCleanup(); } // // Redirect the standard error to the file. // errsbuf = cerr.rdbuf(errofs -> rdbuf()); } #endif return 1; } int SetPorts() { // // Depending on the proxy side, we need to determine on which // port to listen for the given protocol or to which port we // will have to forward the connection. Three possibilities // are given for each supported protocol: // // Port <= 0: Disable port forwarding. // Port == 1: Use the default port. // Port > 1: Use the specified port. // // At the connectiong side the user should always explicitly // set the ports where the connections will be forwarded. This // is both for security reasons and because, when running both // proxies on the same host, there is a concrete possibility // that, by using the default ports, the connection will be // forwarded to the same port where the peer proxy is listen- // ing, causing a loop. // if (cupsPort <= 0) { #ifdef TEST *logofs << "Loop: Disabling cups connections.\n" << logofs_flush; #endif cupsPort = 0; useCupsSocket = 0; } else { if (control -> ProxyMode == proxy_client) { if (cupsPort == 1) { cupsPort = DEFAULT_NX_CUPS_PORT_OFFSET + proxyPort; } useCupsSocket = 1; } else { if (cupsPort == 1) { // // Use the well-known 631/tcp port of the // Internet Printing Protocol. // cupsPort = 631; } useCupsSocket = 0; } #ifdef TEST *logofs << "Loop: Using cups port '" << cupsPort << "'.\n" << logofs_flush; #endif } if (auxPort <= 0) { #ifdef TEST *logofs << "Loop: Disabling auxiliary X11 connections.\n" << logofs_flush; #endif auxPort = 0; useAuxSocket = 0; } else { if (control -> ProxyMode == proxy_client) { if (auxPort == 1) { auxPort = DEFAULT_NX_AUX_PORT_OFFSET + proxyPort; } useAuxSocket = 1; } else { // // Auxiliary X connections are always forwarded // to the display where the session is running. // The only value accepted is 1. // if (auxPort != 1) { #ifdef WARNING *logofs << "Loop: WARNING! Overriding auxiliary X11 " << "port with new value '" << 1 << "'.\n" << logofs_flush; #endif cerr << "Warning" << ": Overriding auxiliary X11 " << "port with new value '" << 1 << "'.\n"; auxPort = 1; } useAuxSocket = 0; } #ifdef TEST *logofs << "Loop: Using auxiliary X11 port '" << auxPort << "'.\n" << logofs_flush; #endif } if (smbPort <= 0) { #ifdef TEST *logofs << "Loop: Disabling SMB connections.\n" << logofs_flush; #endif smbPort = 0; useSmbSocket = 0; } else { if (control -> ProxyMode == proxy_client) { if (smbPort == 1) { smbPort = DEFAULT_NX_SMB_PORT_OFFSET + proxyPort; } useSmbSocket = 1; } else { if (smbPort == 1) { // // Assume the 139/tcp port used for SMB // over NetBIOS over TCP. // smbPort = 139; } useSmbSocket = 0; } #ifdef TEST *logofs << "Loop: Using SMB port '" << smbPort << "'.\n" << logofs_flush; #endif } if (mediaPort <= 0) { #ifdef TEST *logofs << "Loop: Disabling multimedia connections.\n" << logofs_flush; #endif mediaPort = 0; useMediaSocket = 0; } else { if (control -> ProxyMode == proxy_client) { if (mediaPort == 1) { mediaPort = DEFAULT_NX_MEDIA_PORT_OFFSET + proxyPort; } useMediaSocket = 1; } else { if (mediaPort == 1) { // // We don't have a well-known port to // be used for media connections. // #ifdef PANIC *logofs << "Loop: PANIC! No port specified for multimedia connections.\n" << logofs_flush; #endif cerr << "Error" << ": No port specified for multimedia connections.\n"; HandleCleanup(); } useMediaSocket = 0; } #ifdef TEST *logofs << "Loop: Using multimedia port '" << mediaPort << "'.\n" << logofs_flush; #endif } if (httpPort <= 0) { #ifdef TEST *logofs << "Loop: Disabling HTTP connections.\n" << logofs_flush; #endif httpPort = 0; useHttpSocket = 0; } else { if (control -> ProxyMode == proxy_client) { if (httpPort == 1) { httpPort = DEFAULT_NX_HTTP_PORT_OFFSET + proxyPort; } useHttpSocket = 1; } else { if (httpPort == 1) { // // Use the well-known 80/tcp port. // httpPort = 80; } useHttpSocket = 0; } #ifdef TEST *logofs << "Loop: Using HTTP port '" << httpPort << "'.\n" << logofs_flush; #endif } if (ParseFontPath(fontPort) <= 0) { #ifdef TEST *logofs << "Loop: Disabling font server connections.\n" << logofs_flush; #endif *fontPort = '\0'; useFontSocket = 0; } else { // // We don't know yet if the remote proxy supports // the font server connections. If needed, we will // disable the font server connections at later // time. // if (control -> ProxyMode == proxy_server) { useFontSocket = 1; } else { useFontSocket = 0; } #ifdef TEST *logofs << "Loop: Using font server port '" << fontPort << "'.\n" << logofs_flush; #endif } if (slavePort <= 0) { #ifdef TEST *logofs << "Loop: Disabling slave connections.\n" << logofs_flush; #endif slavePort = 0; useSlaveSocket = 0; } else { // // File transfer connections can // be originated by both sides. // if (slavePort == 1) { if (control -> ProxyMode == proxy_client) { slavePort = DEFAULT_NX_SLAVE_PORT_CLIENT_OFFSET + proxyPort; } else { slavePort = DEFAULT_NX_SLAVE_PORT_SERVER_OFFSET + proxyPort; } } useSlaveSocket = 1; #ifdef TEST *logofs << "Loop: Using slave port '" << slavePort << "'.\n" << logofs_flush; #endif } return 1; } int SetDescriptors() { unsigned int limit = 0; #ifdef RLIMIT_NOFILE rlimit limits; if (getrlimit(RLIMIT_NOFILE, &limits) == 0) { if (limits.rlim_max == RLIM_INFINITY) { limit = 0; } else { limit = (unsigned int) limits.rlim_max; } } #endif #ifdef _SC_OPEN_MAX if (limit == 0) { limit = sysconf(_SC_OPEN_MAX); } #endif #ifdef FD_SETSIZE if (limit > FD_SETSIZE) { limit = FD_SETSIZE; } #endif #ifdef RLIMIT_NOFILE if (limits.rlim_cur < limit) { limits.rlim_cur = limit; setrlimit(RLIMIT_NOFILE, &limits); } #endif if (limit == 0) { #ifdef PANIC *logofs << "Loop: PANIC! Cannot determine number of available " << "file descriptors.\n" << logofs_flush; #endif cerr << "Error" << ": Cannot determine number of available " << "file descriptors.\n"; return -1; } return 1; } // // Find the directory containing the caches // matching the session type. // int SetCaches() { if ((control -> PersistentCachePath = GetCachePath()) == NULL) { #ifdef PANIC *logofs << "Loop: PANIC! Error getting or creating the cache path.\n" << logofs_flush; #endif cerr << "Error" << ": Error getting or creating the cache path.\n"; HandleCleanup(); } #ifdef TEST *logofs << "Loop: Path of cache files is '" << control -> PersistentCachePath << "'.\n" << logofs_flush; #endif return 1; } // // Initialize all configuration parameters. // int SetParameters() { // // Find out the type of session. // SetSession(); // // Initialize the network and compression // parameters according to the settings // suggested by the user. // SetLink(); // // Set compression according to link speed. // SetCompression(); // // Be sure that we have a literal for current // cache size. Value will reflect control's // default unless we already parsed a 'cache' // option. Server side has no control on size // of cache but is informed at session nego- // tiation about how much memory is going to // be used. // SetStorage(); // // Set size of shared memory segments. // SetShmem(); // // Make adjustments to cache based // on the pack method. // SetPack(); // // Set disk-based image cache. // SetImages(); // // Set CPU and bandwidth limits. // SetLimits(); return 1; } // // According to session literal determine // the type of traffic that is going to be // transported. Literals should be better // standardized in future NX versions. // int SetSession() { if (strncmp(sessionType, "agent", strlen("agent")) == 0 || strncmp(sessionType, "desktop", strlen("desktop")) == 0 || strncmp(sessionType, "rootless", strlen("rootless")) == 0 || strncmp(sessionType, "console", strlen("console")) == 0 || strncmp(sessionType, "default", strlen("default")) == 0 || strncmp(sessionType, "gnome", strlen("gnome")) == 0 || strncmp(sessionType, "kde", strlen("kde")) == 0 || strncmp(sessionType, "cde", strlen("cde")) == 0 || strncmp(sessionType, "xdm", strlen("xdm")) == 0) { control -> SessionMode = session_agent; } else if (strncmp(sessionType, "win", strlen("win")) == 0 || strncmp(sessionType, "vnc", strlen("vnc")) == 0) { control -> SessionMode = session_agent; } else if (strncmp(sessionType, "shadow", strlen("shadow")) == 0) { control -> SessionMode = session_shadow; } else if (strncmp(sessionType, "proxy", strlen("proxy")) == 0 || strncmp(sessionType, "application", strlen("application")) == 0 || strncmp(sessionType, "raw", strlen("raw")) == 0) { control -> SessionMode = session_proxy; } else { // // If the session type is not passed or // it is not among the recognized strings, // we assume that the proxy is connected // to the agent. // if (*sessionType != '\0' && (control -> isProtoStep8() == 1 || strncmp(sessionType, "unix-", strlen("unix-")) != 0)) { #ifdef WARNING *logofs << "Loop: WARNING! Unrecognized session type '" << sessionType << "'. Assuming agent session.\n" << logofs_flush; #endif cerr << "Warning" << ": Unrecognized session type '" << sessionType << "'. Assuming agent session.\n"; } control -> SessionMode = session_agent; } #if defined(TEST) || defined(INFO) *logofs << "Loop: Assuming session type '" << DumpSession(control -> SessionMode) << "' with " << "string '" << sessionType << "'.\n" << logofs_flush; #endif // // By default the policy is immediate. Agents // will set a different policy, if they like. // Anyway we need to check if the user has // provided a custom flush policy. // if (usePolicy != -1) { if (usePolicy > 0) { control -> FlushPolicy = policy_deferred; } else { control -> FlushPolicy = policy_immediate; } #if defined(TEST) || defined(INFO) *logofs << "Loop: WARNING! Forcing flush policy to '" << DumpPolicy(control -> FlushPolicy) << ".\n" << logofs_flush; #endif } else { control -> FlushPolicy = policy_immediate; #if defined(TEST) || defined(INFO) *logofs << "Loop: Setting initial flush policy to '" << DumpPolicy(control -> FlushPolicy) << "'.\n" << logofs_flush; #endif } // // Check if the proxy library is run inside // another program providing encryption, as // it is the case of the SSH client. // if (useEncryption != -1) { if (useEncryption > 0) { control -> LinkEncrypted = 1; } else { control -> LinkEncrypted = 0; } } if (control -> LinkEncrypted == 1) { #if defined(TEST) || defined(INFO) *logofs << "Loop: Proxy running as part of an " << "encrypting client.\n" << logofs_flush; #endif } else { #if defined(TEST) || defined(INFO) *logofs << "Loop: Assuming proxy running as a " << "standalone program.\n" << logofs_flush; #endif } // // Check if the system administrator has // enabled the respawn of the client at // the end of session. // if (control -> ProxyMode == proxy_server) { struct stat fileStat; char fileName[DEFAULT_STRING_LENGTH]; snprintf(fileName, DEFAULT_STRING_LENGTH - 1, "%s/share/noexit", control -> SystemPath); *(fileName + DEFAULT_STRING_LENGTH - 1) = '\0'; if (stat(fileName, &fileStat) == 0) { #ifdef TEST *logofs << "Loop: Enabling respawn of client at session shutdown.\n" << logofs_flush; #endif control -> EnableRestartOnShutdown = 1; } } return 1; } int SetStorage() { // // If differential compression is disabled // we don't need a cache at all. // if (control -> LocalDeltaCompression == 0) { control -> ClientTotalStorageSize = 0; control -> ServerTotalStorageSize = 0; } // // Set a a cache size literal. // int size = control -> getUpperStorageSize(); if (size / 1024 > 0) { sprintf(cacheSizeName, "%dk", size / 1024); } else { sprintf(cacheSizeName, "%d", size); } if (control -> ProxyMode == proxy_client) { control -> LocalTotalStorageSize = control -> ClientTotalStorageSize; control -> RemoteTotalStorageSize = control -> ServerTotalStorageSize; } else { control -> LocalTotalStorageSize = control -> ServerTotalStorageSize; control -> RemoteTotalStorageSize = control -> ClientTotalStorageSize; } #ifdef DEBUG *logofs << "Loop: Storage size limit is " << control -> ClientTotalStorageSize << " at client and " << control -> ServerTotalStorageSize << " at server.\n" << logofs_flush; #endif #ifdef DEBUG *logofs << "Loop: Storage local limit set to " << control -> LocalTotalStorageSize << " remote limit set to " << control -> RemoteTotalStorageSize << ".\n" << logofs_flush; #endif // // Never reserve for split store more than // half the memory available for messages. // if (size > 0 && control -> SplitTotalStorageSize > size / 2) { #ifdef TEST *logofs << "Loop: Reducing size of split store to " << size / 2 << " bytes.\n" << logofs_flush; #endif control -> SplitTotalStorageSize = size / 2; } // // Don't load render from persistent // cache if extension is hidden or // not supported by agent. // if (control -> HideRender == 1) { #ifdef TEST *logofs << "Loop: Not loading render extension " << "from persistent cache.\n" << logofs_flush; #endif control -> PersistentCacheLoadRender = 0; } return 1; } int SetShmem() { // // If not set, adjust the size of the shared // memory segment according to size of the // message cache. // if (*shsegSizeName == '\0') { int size = control -> getUpperStorageSize(); const int mega = 1048576; if (size > 0) { if (size <= 1 * mega) { size = 0; } else if (size <= 2 * mega) { size = 524288; } else if (size < 4 * mega) { size = 1048576; } else { size = size / 4; } if (size > 4194304) { size = 4194304; } control -> ShmemClientSize = size; control -> ShmemServerSize = size; } else { // // The delta compression is disabled. // Use a default segment size of 2 MB. // control -> ShmemServerSize = 2 * mega; } } // // Client side shared memory support is // not useful and not implemented. // if (control -> ShmemServerSize >= 524288) { control -> ShmemServer = 1; #if defined(TEST) || defined(INFO) *logofs << "Loop: Set initial shared memory size " << "to " << control -> ShmemServerSize << " bytes.\n" << logofs_flush; #endif } else { #if defined(TEST) || defined(INFO) *logofs << "Loop: Disabled use of the shared memory " << "extension.\n" << logofs_flush; #endif control -> ShmemServer = 0; } return 1; } // // Adjust the pack method according to the // type of the session. // int SetPack() { #ifdef TEST *logofs << "Loop: Setting pack with initial method " << packMethod << " and quality " << packQuality << ".\n" << logofs_flush; #endif // // Check if this is a proxy session and, in // this case, set the pack method to none. // Packed images are not supported by plain // X applications. // if (control -> SessionMode == session_proxy) { #ifdef TEST *logofs << "Loop: WARNING! Disabling pack with proxy session.\n" << logofs_flush; #endif packMethod = PACK_NONE; } // // Adjust the internal settings according // to the newly selected pack method. // ParsePackMethod(packMethod, packQuality); // // Don't load messages from persistent // cache if packed images are disabled. // if (control -> PackMethod == PACK_NONE) { control -> PersistentCacheLoadPacked = 0; #ifdef TEST *logofs << "Loop: Not loading packed images " << "from persistent cache.\n" << logofs_flush; #endif } return 1; } // // Set the disk-based image cache parameters // according to the user's wishes. // int SetImages() { // // Be sure we disable the image cache if we // are connecting to plain X clients. // if (control -> SessionMode == session_proxy) { #ifdef TEST *logofs << "Loop: Disabling image cache with " << "session '" << DumpSession(control -> SessionMode) << "'.\n" << logofs_flush; #endif sprintf(imagesSizeName, "0"); control -> ImageCacheEnableLoad = 0; control -> ImageCacheEnableSave = 0; return 1; } int size = control -> ImageCacheDiskLimit; if (size / 1024 > 0) { sprintf(imagesSizeName, "%dk", size / 1024); } else { sprintf(imagesSizeName, "%d", size); } if (size > 0) { control -> ImageCacheEnableLoad = 1; control -> ImageCacheEnableSave = 1; if (control -> ProxyMode == proxy_server) { if ((control -> ImageCachePath = GetImagesPath()) == NULL) { #ifdef PANIC *logofs << "Loop: PANIC! Error getting or creating image cache path.\n" << logofs_flush; #endif cerr << "Error" << ": Error getting or creating image cache path.\n"; HandleCleanup(); } #ifdef TEST *logofs << "Loop: Path of image cache files is '" << control -> ImageCachePath << "'.\n" << logofs_flush; #endif } } else { #ifdef TEST *logofs << "Loop: Disabling the persistent image cache.\n" << logofs_flush; #endif control -> ImageCacheEnableLoad = 0; control -> ImageCacheEnableSave = 0; } return 1; } int SetVersion() { // // Normalize the different proxy versions. // int local = (control -> LocalVersionMajor << 24) | (control -> LocalVersionMinor << 16) | control -> LocalVersionPatch; int remote = (control -> RemoteVersionMajor << 24) | (control -> RemoteVersionMinor << 16) | control -> RemoteVersionPatch; int major = -1; int minor = -1; int patch = -1; if (control -> RemoteVersionMajor <= 1) { // // The remote proxy uses a different // logic to determine the version so // we default to the compatibility // version. // major = control -> CompatVersionMajor; minor = control -> CompatVersionMinor; patch = control -> CompatVersionPatch; #ifdef TEST *logofs << "Loop: Using compatibility version '" << major << "." << minor << "." << patch << "'.\n" << logofs_flush; #endif } else if (control -> LocalVersionMajor > control -> RemoteVersionMajor) { // // We use a more recent version. Let's // negotiate the version based on the // version supported by the remote. // major = control -> RemoteVersionMajor; minor = control -> RemoteVersionMinor; patch = control -> RemoteVersionPatch; #ifdef TEST *logofs << "Loop: Using remote version '" << major << "." << minor << "." << patch << "'.\n" << logofs_flush; #endif } else { // // We support a major version that is // equal or older than the remote. We // assume the smaller version between // the two, including the minor and // the patch numbers. // if (local > remote) { major = control -> RemoteVersionMajor; minor = control -> RemoteVersionMinor; patch = control -> RemoteVersionPatch; #ifdef TEST *logofs << "Loop: Using remote version '" << major << "." << minor << "." << patch << "'.\n" << logofs_flush; #endif } else { major = control -> LocalVersionMajor; minor = control -> LocalVersionMinor; patch = control -> LocalVersionPatch; #ifdef TEST *logofs << "Loop: Using local version '" << major << "." << minor << "." << patch << "'.\n" << logofs_flush; #endif } } // // Handle the 1.5.0 versions. The protocol // step 6 is the minimum supported version. // int step = 0; if (major == 1) { if (minor == 5) { step = 6; } } else if (major == 2) { step = 7; } else if (major >= 3) { if (minor > 0 || patch > 0) { step = 9; } else { step = 8; } } if (step == 0) { #ifdef PANIC *logofs << "Loop: PANIC! Incompatible remote version " << control -> RemoteVersionMajor << "." << control -> RemoteVersionMinor << "." << control -> RemoteVersionPatch << " with local version " << control -> LocalVersionMajor << "." << control -> LocalVersionMinor << "." << control -> LocalVersionPatch << ".\n" << logofs_flush; #endif cerr << "Error" << ": Incompatible remote version " << control -> RemoteVersionMajor << "." << control -> RemoteVersionMinor << "." << control -> RemoteVersionPatch << " with local version " << control -> LocalVersionMajor << "." << control -> LocalVersionMinor << "." << control -> LocalVersionPatch << ".\n"; return -1; } #ifdef TEST *logofs << "Loop: Using NX protocol step " << step << ".\n" << logofs_flush; #endif control -> setProtoStep(step); // // Ignore the differences in patch version // and print a warning if the local version // is different or obsolete compared to // the remote. // local &= 0xffff0000; remote &= 0xffff0000; if (local != remote) { #ifdef WARNING *logofs << "Loop: WARNING! Connected to remote version " << control -> RemoteVersionMajor << "." << control -> RemoteVersionMinor << "." << control -> RemoteVersionPatch << " with local version " << control -> LocalVersionMajor << "." << control -> LocalVersionMinor << "." << control -> LocalVersionPatch << ".\n" << logofs_flush; #endif cerr << "Warning" << ": Connected to remote version " << control -> RemoteVersionMajor << "." << control -> RemoteVersionMinor << "." << control -> RemoteVersionPatch << " with local version " << control -> LocalVersionMajor << "." << control -> LocalVersionMinor << "." << control -> LocalVersionPatch << ".\n" << logofs_flush; } if (local < remote) { cerr << "Warning" << ": Consider checking http://www.nomachine.com/ for updates.\n"; } // // Now that we are aware of the remote // version, let's adjust the options to // be compatible with the remote proxy. // if (control -> ProxyMode == proxy_client) { if (control -> isProtoStep8() == 0) { if (strncmp(sessionType, "shadow", strlen("shadow")) == 0 || strncmp(sessionType, "application", strlen("application")) == 0 || strncmp(sessionType, "console", strlen("console")) == 0 || strncmp(sessionType, "default", strlen("default")) == 0 || strncmp(sessionType, "gnome", strlen("gnome")) == 0 || strncmp(sessionType, "kde", strlen("kde")) == 0 || strncmp(sessionType, "cde", strlen("cde")) == 0 || strncmp(sessionType, "xdm", strlen("xdm")) == 0) { #if defined(TEST) || defined(INFO) *logofs << "Loop: WARNING! Prepending 'unix-' to the " << "name of the session.\n" << logofs_flush; #endif char buffer[DEFAULT_STRING_LENGTH]; snprintf(buffer, DEFAULT_STRING_LENGTH - 1, "unix-%s", sessionType); strcpy(sessionType, buffer); } } // // Check if the remote is able to handle // the selected pack method. // if (control -> isProtoStep8() == 0) { if (packMethod == PACK_ADAPTIVE || packMethod == PACK_LOSSY) { #ifdef TEST *logofs << "Loop: WARNING! Assuming a lossy encoding with " << "an old proxy version.\n" << logofs_flush; #endif packMethod = PACK_JPEG_16M_COLORS; } else if (packMethod == PACK_LOSSLESS) { #ifdef TEST *logofs << "Loop: WARNING! Assuming a lossless encoding with " << "an old proxy version.\n" << logofs_flush; #endif if (control -> isProtoStep7() == 1) { packMethod = PACK_RLE_16M_COLORS; } else { packMethod = PACK_PNG_16M_COLORS; } } } // // If the remote doesn't support the // selected method use something that // is compatible. // if ((packMethod == PACK_RGB_16M_COLORS || packMethod == PACK_RLE_16M_COLORS || packMethod == PACK_BITMAP_16M_COLORS) && control -> isProtoStep7() == 0) { #ifdef TEST *logofs << "Loop: WARNING! Setting the pack method to '" << PACK_PNG_16M_COLORS << "' with '" << packMethod << "' unsupported.\n" << logofs_flush; #endif packMethod = PACK_PNG_16M_COLORS; packQuality = 9; } else if (packMethod == PACK_BITMAP_16M_COLORS && control -> isProtoStep8() == 0) { #ifdef TEST *logofs << "Loop: WARNING! Setting the pack method to '" << PACK_RLE_16M_COLORS << "' with '" << packMethod << "' unsupported.\n" << logofs_flush; #endif packMethod = PACK_RLE_16M_COLORS; packQuality = 9; } // // Update the pack method name. // ParsePackMethod(packMethod, packQuality); } // // At the moment the image cache is not used by the // agent but we need to take care of the compatibi- // lity with old versions. Proxy versions older than // the 3.0.0 assume that it is enabled and will send // specific bits as part of the encoding. Conversely, // it is advisable to disable the cache right now. // By not enabling the image cache, the house-keep- // ing process will only take care of cleaning up // the "cache-" directories. // if (control -> isProtoStep8() == 1) { #ifdef TEST *logofs << "Loop: Disabling image cache with protocol " << "step '" << control -> getProtoStep() << "'.\n" << logofs_flush; #endif sprintf(imagesSizeName, "0"); control -> ImageCacheEnableLoad = 0; control -> ImageCacheEnableSave = 0; } return 1; } // // Identify the requested link settings // and update the control parameters // accordingly. // int SetLink() { #ifdef TEST *logofs << "Loop: Setting link with initial value " << linkSpeedName << ".\n" << logofs_flush; #endif if (*linkSpeedName == '\0') { strcpy(linkSpeedName, "lan"); } #ifdef TEST *logofs << "Loop: Link speed is " << linkSpeedName << ".\n" << logofs_flush; #endif if (strcasecmp(linkSpeedName, "modem") == 0) { SetLinkModem(); } else if (strcasecmp(linkSpeedName, "isdn") == 0) { SetLinkIsdn(); } else if (strcasecmp(linkSpeedName, "adsl") == 0) { SetLinkAdsl(); } else if (strcasecmp(linkSpeedName, "wan") == 0) { SetLinkWan(); } else if (strcasecmp(linkSpeedName, "lan") == 0) { SetLinkLan(); } else { return -1; } // // Set TCP_NODELAY according to the user's // wishes. // if (useNoDelay != -1) { control -> OptionProxyClientNoDelay = useNoDelay; control -> OptionProxyServerNoDelay = useNoDelay; } // // Select the image compression method. // if (packMethod == -1) { packMethod = control -> PackMethod; } if (packQuality == -1) { packQuality = control -> PackQuality; } if (ParsePackMethod(packMethod, packQuality) < 0) { #ifdef PANIC *logofs << "Loop: PANIC! Unrecognized pack method id " << packMethod << " with quality " << packQuality << ".\n" << logofs_flush; #endif cerr << "Error" << ": Unrecognized pack method id " << packMethod << " with quality " << packQuality << ".\n"; HandleCleanup(); } // // Check if the user disabled the ability // to generate simple replies at the client // side. // if (control -> SessionMode == session_proxy) { if (useTaint != -1) { control -> TaintReplies = (useTaint == 1); } else { #ifdef WARNING *logofs << "Loop: WARNING! Forcing taint of replies " << "with a proxy session.\n" << logofs_flush; #endif control -> TaintReplies = 1; } } else { // // There is no need to taint the // replies if we have an agent. // control -> TaintReplies = 0; } // // Be sure that the requests needing a reply // are flushed immediately. Normal X clients // use so many replies to make the queuing // completely useless. // if (control -> SessionMode == session_proxy) { #ifdef WARNING *logofs << "Loop: WARNING! Forcing flush on priority " << "with a proxy session.\n" << logofs_flush; #endif control -> FlushPriority = 1; } return 1; } // // Parameters for MODEM 28.8/33.6/56 Kbps. // int SetLinkModem() { #ifdef TEST *logofs << "Loop: Setting parameters for MODEM.\n" << logofs_flush; #endif control -> LinkMode = LINK_TYPE_MODEM; control -> TokenSize = 256; control -> TokenLimit = 24; control -> SplitMode = 1; control -> SplitTotalSize = 128; control -> SplitTotalStorageSize = 1048576; control -> SplitTimeout = 50; control -> MotionTimeout = 50; control -> IdleTimeout = 50; control -> PackMethod = PACK_ADAPTIVE; control -> PackQuality = 3; return 1; } // // Parameters for ISDN 64/128 Kbps. // int SetLinkIsdn() { #ifdef TEST *logofs << "Loop: Setting parameters for ISDN.\n" << logofs_flush; #endif control -> LinkMode = LINK_TYPE_ISDN; control -> TokenSize = 384; control -> TokenLimit = 24; control -> SplitMode = 1; control -> SplitTotalSize = 128; control -> SplitTotalStorageSize = 1048576; control -> SplitTimeout = 50; control -> MotionTimeout = 20; control -> IdleTimeout = 50; control -> PackMethod = PACK_ADAPTIVE; control -> PackQuality = 5; return 1; } // // Parameters for ADSL 256 Kbps. // int SetLinkAdsl() { #ifdef TEST *logofs << "Loop: Setting parameters for ADSL.\n" << logofs_flush; #endif control -> LinkMode = LINK_TYPE_ADSL; control -> TokenSize = 512; control -> TokenLimit = 24; control -> SplitMode = 1; control -> SplitTotalSize = 128; control -> SplitTotalStorageSize = 1048576; control -> SplitTimeout = 50; control -> MotionTimeout = 10; control -> IdleTimeout = 50; control -> PackMethod = PACK_ADAPTIVE; control -> PackQuality = 7; return 1; } // // Parameters for XDSL/FDDI/ATM 1/2/34 Mbps WAN. // int SetLinkWan() { #ifdef TEST *logofs << "Loop: Setting parameters for WAN.\n" << logofs_flush; #endif control -> LinkMode = LINK_TYPE_WAN; control -> TokenSize = 768; control -> TokenLimit = 24; control -> SplitMode = 1; control -> SplitTotalSize = 128; control -> SplitTotalStorageSize = 1048576; control -> SplitTimeout = 50; control -> MotionTimeout = 5; control -> IdleTimeout = 50; control -> PackMethod = PACK_ADAPTIVE; control -> PackQuality = 9; return 1; } // // Parameters for LAN 10/100 Mbps. // int SetLinkLan() { #ifdef TEST *logofs << "Loop: Setting parameters for LAN.\n" << logofs_flush; #endif control -> LinkMode = LINK_TYPE_LAN; control -> TokenSize = 1536; control -> TokenLimit = 24; control -> SplitMode = 1; control -> SplitTotalSize = 128; control -> SplitTotalStorageSize = 1048576; control -> SplitTimeout = 50; control -> MotionTimeout = 0; control -> IdleTimeout = 50; control -> PackMethod = PACK_ADAPTIVE; control -> PackQuality = 9; return 1; } // // Identify the requested link type and set // the control parameters accordingly. // int SetCompression() { if (strcasecmp(linkSpeedName, "modem") == 0) { SetCompressionModem(); } else if (strcasecmp(linkSpeedName, "isdn") == 0) { SetCompressionIsdn(); } else if (strcasecmp(linkSpeedName, "adsl") == 0) { SetCompressionAdsl(); } else if (strcasecmp(linkSpeedName, "wan") == 0) { SetCompressionWan(); } else if (strcasecmp(linkSpeedName, "lan") == 0) { SetCompressionLan(); } else { return -1; } if (control -> LocalDeltaCompression < 0) { control -> LocalDeltaCompression = 1; } // // If we didn't set remote delta compression // (as it should always be the case at client // side) assume value of local side. // if (control -> RemoteDeltaCompression < 0) { control -> RemoteDeltaCompression = control -> LocalDeltaCompression; } // // If we didn't set remote compression levels // assume values of local side. // if (control -> RemoteStreamCompression < 0) { control -> RemoteStreamCompressionLevel = control -> LocalStreamCompressionLevel; if (control -> RemoteStreamCompressionLevel > 0) { control -> RemoteStreamCompression = 1; } else { control -> RemoteStreamCompression = 0; } } if (control -> RemoteDataCompression < 0) { control -> RemoteDataCompressionLevel = control -> LocalDataCompressionLevel; if (control -> RemoteDataCompressionLevel > 0) { control -> RemoteDataCompression = 1; } else { control -> RemoteDataCompression = 0; } } return 1; } // // Compression for MODEM. // int SetCompressionModem() { if (control -> LocalDataCompression < 0) { control -> LocalDataCompression = 1; control -> LocalDataCompressionLevel = 1; } if (control -> LocalDataCompressionThreshold < 0) { control -> LocalDataCompressionThreshold = 32; } if (control -> LocalStreamCompression < 0) { control -> LocalStreamCompression = 1; control -> LocalStreamCompressionLevel = 9; } return 1; } // // Compression for ISDN. // int SetCompressionIsdn() { if (control -> LocalDataCompression < 0) { control -> LocalDataCompression = 1; control -> LocalDataCompressionLevel = 1; } if (control -> LocalDataCompressionThreshold < 0) { control -> LocalDataCompressionThreshold = 32; } if (control -> LocalStreamCompression < 0) { control -> LocalStreamCompression = 1; control -> LocalStreamCompressionLevel = 6; } return 1; } // // Compression for ADSL. // int SetCompressionAdsl() { if (control -> LocalDataCompression < 0) { control -> LocalDataCompression = 1; control -> LocalDataCompressionLevel = 1; } if (control -> LocalDataCompressionThreshold < 0) { control -> LocalDataCompressionThreshold = 32; } if (control -> LocalStreamCompression < 0) { control -> LocalStreamCompression = 1; control -> LocalStreamCompressionLevel = 4; } return 1; } // // Compression for WAN. // int SetCompressionWan() { if (control -> LocalDataCompression < 0) { control -> LocalDataCompression = 1; control -> LocalDataCompressionLevel = 1; } if (control -> LocalDataCompressionThreshold < 0) { control -> LocalDataCompressionThreshold = 32; } if (control -> LocalStreamCompression < 0) { control -> LocalStreamCompression = 1; control -> LocalStreamCompressionLevel = 1; } return 1; } // // Compression for LAN. // int SetCompressionLan() { // // Disable delta compression if not // explicitly enabled. // if (control -> LocalDeltaCompression < 0) { control -> LocalDeltaCompression = 0; } if (control -> LocalDataCompression < 0) { control -> LocalDataCompression = 0; control -> LocalDataCompressionLevel = 0; } if (control -> LocalDataCompressionThreshold < 0) { control -> LocalDataCompressionThreshold = 0; } if (control -> LocalStreamCompression < 0) { control -> LocalStreamCompression = 0; control -> LocalStreamCompressionLevel = 0; } return 1; } int SetLimits() { // // Check if the user requested strict // control flow parameters. // if (useStrict == 1) { #if defined(TEST) || defined(INFO) *logofs << "Loop: LIMIT! Decreasing the token limit " << "to " << control -> TokenLimit / 2 << " with option 'strict'.\n" << logofs_flush; #endif control -> TokenLimit /= 2; } #ifdef STRICT control -> TokenLimit = 1; #if defined(TEST) || defined(INFO) *logofs << "Loop: WARNING! LIMIT! Setting the token limit " << "to " << control -> TokenLimit << " to simulate the proxy congestion.\n" << logofs_flush; #endif #endif // // Reduce the size of the log file. // #ifdef QUOTA control -> FileSizeLimit = 8388608; #endif // // Check the bitrate limits. // if (control -> LocalBitrateLimit == -1) { if (control -> ProxyMode == proxy_client) { control -> LocalBitrateLimit = control -> ClientBitrateLimit; } else { control -> LocalBitrateLimit = control -> ServerBitrateLimit; } } #if defined(TEST) || defined(INFO) *logofs << "Loop: LIMIT! Setting client bitrate limit " << "to " << control -> ClientBitrateLimit << " server bitrate limit to " << control -> ServerBitrateLimit << " with local limit " << control -> LocalBitrateLimit << ".\n" << logofs_flush; #endif return 1; } // // These functions are used to parse literal // values provided by the user and set the // control parameters accordingly. // int ParseCacheOption(const char *opt) { int size = ParseArg("", "cache", opt); if (size < 0) { #ifdef PANIC *logofs << "Loop: PANIC! Invalid value '" << opt << "' for option 'cache'.\n" << logofs_flush; #endif cerr << "Error" << ": Invalid value '" << opt << "' for option 'cache'.\n"; return -1; } #ifdef TEST *logofs << "Loop: Setting size of cache to " << size << " bytes.\n" << logofs_flush; #endif control -> ClientTotalStorageSize = size; control -> ServerTotalStorageSize = size; strcpy(cacheSizeName, opt); if (size == 0) { #ifdef WARNING *logofs << "Loop: WARNING! Disabling NX delta compression.\n" << logofs_flush; #endif control -> LocalDeltaCompression = 0; #ifdef WARNING *logofs << "Loop: WARNING! Disabling use of NX persistent cache.\n" << logofs_flush; #endif control -> PersistentCacheEnableLoad = 0; control -> PersistentCacheEnableSave = 0; } return 1; } int ParseImagesOption(const char *opt) { int size = ParseArg("", "images", opt); if (size < 0) { #ifdef PANIC *logofs << "Loop: PANIC! Invalid value '" << opt << "' for option 'images'.\n" << logofs_flush; #endif cerr << "Error" << ": Invalid value '" << opt << "' for option 'images'.\n"; return -1; } #ifdef TEST *logofs << "Loop: Setting size of images cache to " << size << " bytes.\n" << logofs_flush; #endif control -> ImageCacheDiskLimit = size; strcpy(imagesSizeName, opt); return 1; } int ParseShmemOption(const char *opt) { int size = ParseArg("", "shseg", opt); if (size < 0) { #ifdef PANIC *logofs << "Loop: PANIC! Invalid value '" << opt << "' for option 'shseg'.\n" << logofs_flush; #endif cerr << "Error" << ": Invalid value '" << opt << "' for option 'shseg'.\n"; return -1; } control -> ShmemClientSize = size; control -> ShmemServerSize = size; #if defined(TEST) || defined(INFO) *logofs << "Loop: Set shared memory size to " << control -> ShmemServerSize << " bytes.\n" << logofs_flush; #endif strcpy(shsegSizeName, opt); return 1; } int ParseBitrateOption(const char *opt) { int bitrate = ParseArg("", "limit", opt); if (bitrate < 0) { #ifdef PANIC *logofs << "Loop: PANIC! Invalid value '" << opt << "' for option 'limit'.\n" << logofs_flush; #endif cerr << "Error" << ": Invalid value '" << opt << "' for option 'limit'.\n"; return -1; } strcpy(bitrateLimitName, opt); if (bitrate == 0) { #ifdef TEST *logofs << "Loop: Disabling bitrate limit on proxy link.\n" << logofs_flush; #endif control -> LocalBitrateLimit = 0; } else { #ifdef TEST *logofs << "Loop: Setting bitrate to " << bitrate << " bits per second.\n" << logofs_flush; #endif // // Internal representation is in bytes // per second. // control -> LocalBitrateLimit = bitrate >> 3; } return 1; } int ParseHostOption(const char *opt, char *host, int &port) { #ifdef TEST *logofs << "Loop: Trying to parse options string '" << opt << "' as a remote NX host.\n" << logofs_flush; #endif if (opt == NULL || *opt == '\0') { #ifdef PANIC *logofs << "Loop: PANIC! No host parameter provided.\n" << logofs_flush; #endif return 0; } else if (strlen(opt) >= DEFAULT_STRING_LENGTH) { #ifdef PANIC *logofs << "Loop: PANIC! Host parameter exceeds length of " << DEFAULT_STRING_LENGTH << " characters.\n" << logofs_flush; #endif return 0; } // // Look for a host name followed // by a colon followed by port. // int newPort = port; const char *separator = rindex(opt, ':'); if (separator != NULL) { const char *check = separator + 1; while (*check != '\0' && *check != ',' && *check != '=' && isdigit(*check) != 0) { check++; } newPort = atoi(separator + 1); if (newPort < 0 || *check != '\0') { #ifdef TEST *logofs << "Loop: Can't identify remote NX port in string '" << separator << "'.\n" << logofs_flush; #endif return 0; } } else if (newPort < 0) { // // Complain if port was not passed // by other means. // #ifdef TEST *logofs << "Loop: Can't identify remote NX port in string '" << opt << "'.\n" << logofs_flush; #endif return 0; } else { separator = opt + strlen(opt); } char newHost[DEFAULT_STRING_LENGTH] = { 0 }; strncpy(newHost, opt, strlen(opt) - strlen(separator)); *(newHost + strlen(opt) - strlen(separator)) = '\0'; const char *check = newHost; while (*check != '\0' && *check != ',' && *check != '=') { check++; } if (*check != '\0') { #ifdef TEST *logofs << "Loop: Can't identify remote NX host in string '" << newHost << "'.\n" << logofs_flush; #endif return 0; } else if (*acceptHost != '\0') { #ifdef PANIC *logofs << "Loop: PANIC! Can't manage to connect and accept connections " << "at the same time.\n" << logofs_flush; *logofs << "Loop: PANIC! Refusing remote NX host with string '" << opt << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Can't manage to connect and accept connections " << "at the same time.\n"; cerr << "Error" << ": Refusing remote NX host with string '" << opt << "'.\n"; return -1; } if (*host != '\0' && strcmp(host, newHost) != 0) { #ifdef WARNING *logofs << "Loop: WARNING! Overriding remote NX host '" << host << "' with new value '" << newHost << "'.\n" << logofs_flush; #endif } strcpy(host, newHost); if (port != -1 && port != newPort) { #ifdef WARNING *logofs << "Loop: WARNING! Overriding remote NX port '" << port << "' with new value '" << newPort << "'.\n" << logofs_flush; #endif } #ifdef TEST *logofs << "Loop: Parsed options string '" << opt << "' with host '" << newHost << "' and port '" << newPort << "'.\n" << logofs_flush; #endif port = newPort; return 1; } int ParseFontPath(char *path) { char oldPath[DEFAULT_STRING_LENGTH]; strcpy(oldPath, path); if (path == NULL || *path == '\0' || strcmp(path, "0") == 0) { return 0; } #ifdef TEST *logofs << "Loop: Parsing font server option '" << path << "'.\n" << logofs_flush; #endif // // Convert the value to our default port. // if (strcmp(fontPort, "1") == 0) { if (control -> ProxyMode == proxy_server) { snprintf(fontPort, DEFAULT_STRING_LENGTH - 1, "%d", DEFAULT_NX_FONT_PORT_OFFSET + proxyPort); } else { // // Let the client use the well-known // "unix/:7100" font path. // snprintf(fontPort, DEFAULT_STRING_LENGTH - 1, "unix/:7100"); } } // // Check if a simple numaric value was given. // if (atoi(path) > 0) { #ifdef TEST *logofs << "Loop: Assuming numeric TCP port '" << atoi(path) << "' for font server.\n" << logofs_flush; #endif return 1; } // // Let's assume that a port specification "unix/:7100" // corresponds to "$TEMP/.font-unix/fs7100" and a port // "unix/:-1" corresponds to "$TEMP/.font-unix/fs-1". // if (strncmp("unix/:", path, 6) == 0) { snprintf(path, DEFAULT_STRING_LENGTH - 1, "%s/.font-unix/fs%s", control -> TempPath, oldPath + 6); *(path + DEFAULT_STRING_LENGTH - 1) = '\0'; #ifdef TEST *logofs << "Loop: Assuming Unix socket '" << path << "' for font server.\n" << logofs_flush; #endif } else if (strncmp("tcp/:", path, 5) == 0) { snprintf(path, DEFAULT_STRING_LENGTH - 1, "%d", atoi(oldPath + 5)); *(path + DEFAULT_STRING_LENGTH - 1) = '\0'; if (atoi(path) <= 0) { goto ParseFontPathError; } #ifdef TEST *logofs << "Loop: Assuming TCP port '" << atoi(path) << "' for font server.\n" << logofs_flush; #endif } else { // // Accept an absolute file path as // a valid Unix socket. // if (*path != '/') { goto ParseFontPathError; } #ifdef TEST *logofs << "Loop: Assuming Unix socket '" << path << "' for font server.\n" << logofs_flush; #endif } return 1; ParseFontPathError: #ifdef TEST *logofs << "Loop: Unable to determine the font server " << "port in string '" << path << "'.\n" << logofs_flush; #endif return -1; } int ParseListenOption(int &address) { if (*listenHost == '\0') { // // On the X client side listen on any address. // On the X server side listen to the forwarder // on localhost. // if (control -> ProxyMode == proxy_server) { address = (int) inet_addr("127.0.0.1"); } else { address = htonl(INADDR_ANY); } } else { address = inet_addr(listenHost); } return 1; } int OpenLogFile(char *name, ostream *&stream) { if (name == NULL || *name == '\0') { #ifdef TEST *logofs << "Loop: WARNING! No name provided for output. Using standard error.\n" << logofs_flush; #endif if (stream == NULL) { stream = &cerr; } return 1; } if (stream == NULL || stream == &cerr) { if (*name != '/' && *name != '.') { char *filePath = GetSessionPath(); if (filePath == NULL) { #ifdef PANIC *logofs << "Loop: PANIC! Cannot determine directory of NX session file.\n" << logofs_flush; #endif cerr << "Error" << ": Cannot determine directory of NX session file.\n"; return -1; } if (strlen(filePath) + strlen("/") + strlen(name) + 1 > DEFAULT_STRING_LENGTH) { #ifdef PANIC *logofs << "Loop: PANIC! Full name of NX file '" << name << " would exceed length of " << DEFAULT_STRING_LENGTH << " characters.\n" << logofs_flush; #endif cerr << "Error" << ": Full name of NX file '" << name << " would exceed length of " << DEFAULT_STRING_LENGTH << " characters.\n"; return -1; } char *file = new char[strlen(filePath) + strlen("/") + strlen(name) + 1]; // // Transform name in a fully qualified name. // strcpy(file, filePath); strcat(file, "/"); strcat(file, name); strcpy(name, file); delete [] filePath; delete [] file; } mode_t fileMode = umask(0077); for (;;) { if ((stream = new ofstream(name, ios::app)) != NULL) { break; } usleep(200000); } umask(fileMode); } else { #ifdef PANIC *logofs << "Loop: PANIC! Bad stream provided for output.\n" << logofs_flush; #endif cerr << "Error" << ": Bad stream provided for output.\n"; return -1; } return 1; } int ReopenLogFile(char *name, ostream *&stream, int limit) { if (*name != '\0' && limit >= 0) { struct stat fileStat; if (limit > 0) { // // This is used for the log file, if the // size exceeds the limit. // if (stat(name, &fileStat) != 0) { #ifdef WARNING *logofs << "Loop: WARNING! Can't get stats of file '" << name << "'. Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif return 0; } else if (fileStat.st_size < (long) limit) { return 0; } } #ifdef TEST *logofs << "Loop: Deleting file '" << name << "' with size " << fileStat.st_size << ".\n" << logofs_flush; #endif // // Create a new stream over the previous // file. Trying to delete the file fails // to work on recent Cygwin installs. // *stream << flush; delete stream; mode_t fileMode = umask(0077); for (;;) { if ((stream = new ofstream(name, ios::out)) != NULL) { break; } usleep(200000); } umask(fileMode); #ifdef TEST *logofs << "Loop: Reopened file '" << name << "'.\n" << logofs_flush; #endif } return 1; } void PrintProcessInfo() { if (agent == NULL) { cerr << "\nNXPROXY - Version " << control -> LocalVersionMajor << "." << control -> LocalVersionMinor << "." << control -> LocalVersionPatch << "\n\n"; cerr << "Copyright (C) 2001, 2007 NoMachine.\n" << "See http://www.nomachine.com/ for more information.\n\n"; } // // People get confused by the fact that client // mode is running on NX server and viceversa. // Let's adopt an user-friendly naming conven- // tion here. // cerr << "Info: Proxy running in " << (control -> ProxyMode == proxy_client ? "server" : "client") << " mode with pid '" << getpid() << "'.\n"; if (agent == NULL) { cerr << "Session" << ": Starting session at '" << strTimestamp() << "'.\n"; } #ifdef TEST if (*errorsFileName != '\0') { cerr << "Info" << ": Using errors file '" << errorsFileName << "'.\n"; } if (*statsFileName != '\0') { cerr << "Info" << ": Using stats file '" << statsFileName << "'.\n"; } #endif } void PrintConnectionInfo() { cerr << "Info" << ": Using " << linkSpeedName << " link parameters " << control -> TokenSize << "/" << control -> TokenLimit << "/" << control -> FlushPolicy + 1 << "/" << control -> FlushPriority << ".\n"; if (control -> ProxyMode == proxy_client) { cerr << "Info" << ": Using agent parameters " << control -> PingTimeout << "/" << control -> MotionTimeout << "/" << control -> IdleTimeout << "/" << control -> TaintReplies << "/" << control -> HideRender << ".\n"; } if (control -> LocalDeltaCompression == 1) { cerr << "Info" << ": Using cache parameters " << control -> MinimumMessageSize << "/" << control -> MaximumMessageSize / 1024 << "KB" << "/" << control -> ClientTotalStorageSize / 1024 << "KB" << "/" << control -> ServerTotalStorageSize / 1024 << "KB" << ".\n"; } if (control -> ImageCacheEnableLoad == 1 || control -> ImageCacheEnableSave == 1) { cerr << "Info" << ": Using image streaming parameters " << control -> SplitTimeout << "/" << control -> SplitTotalSize << "/" << control -> SplitTotalStorageSize / 1024 << "KB" << "/" << control -> SplitDataThreshold << "/" << control -> SplitDataPacketLimit << ".\n"; cerr << "Info" << ": Using image cache parameters " << control -> ImageCacheEnableLoad << "/" << control -> ImageCacheEnableSave << "/" << control -> ImageCacheDiskLimit / 1024 << "KB" << ".\n"; } cerr << "Info" << ": Using pack method '" << packMethodName << "' with session '" << sessionType << "'.\n"; if (*productName != '\0') { cerr << "Info" << ": Using product '" << productName << "'.\n" << logofs_flush; } if (control -> LocalDeltaCompression == 0) { cerr << "Info" << ": Not using NX delta compression.\n"; } if (control -> LocalDataCompression == 1 || control -> RemoteDataCompression == 1) { cerr << "Info" << ": Using ZLIB data compression " << control -> LocalDataCompressionLevel << "/" << control -> RemoteDataCompressionLevel << "/" << control -> LocalDataCompressionThreshold << ".\n"; } else { cerr << "Info" << ": Not using ZLIB data compression.\n"; } if (control -> LocalStreamCompression == 1 || control -> RemoteStreamCompression == 1) { cerr << "Info" << ": Using ZLIB stream compression " << control -> LocalStreamCompressionLevel << "/" << control -> RemoteStreamCompressionLevel << ".\n"; } else { cerr << "Info" << ": Not using ZLIB stream compression.\n"; } if (control -> LocalBitrateLimit > 0) { cerr << "Info" << ": Using bandwidth limit of " << bitrateLimitName << " bits per second.\n"; } if (control -> PersistentCacheName != NULL) { cerr << "Info" << ": Using cache file '" << control -> PersistentCachePath << "/" << control -> PersistentCacheName << "'.\n"; } else { if (control -> PersistentCacheEnableLoad == 0 || control -> LocalDeltaCompression == 0) { cerr << "Info" << ": Not using a persistent cache.\n"; } else { cerr << "Info" << ": No suitable cache file found.\n"; } } if (control -> ProxyMode == proxy_client && useUnixSocket > 0 || useTcpSocket > 0 || useAgentSocket > 0) { cerr << "Info" << ": Listening to X11 connections " << "on display ':" << xPort << "'.\n"; } else if (control -> ProxyMode == proxy_server) { cerr << "Info" << ": Forwarding X11 connections " << "to display '" << displayHost << "'.\n"; } if (control -> ProxyMode == proxy_client && useCupsSocket > 0 && cupsPort > 0) { cerr << "Info" << ": Listening to CUPS connections " << "on port '" << cupsPort << "'.\n"; } else if (control -> ProxyMode == proxy_server && cupsPort > 0) { cerr << "Info" << ": Forwarding CUPS connections " << "to port '" << cupsPort << "'.\n"; } if (control -> ProxyMode == proxy_client && useAuxSocket > 0 && auxPort > 0) { cerr << "Info" << ": Listening to auxiliary X11 connections " << "on port '" << auxPort << "'.\n"; } else if (control -> ProxyMode == proxy_server && auxPort > 0) { cerr << "Info" << ": Forwarding auxiliary X11 connections " << "to display '" << displayHost << "'.\n"; } if (control -> ProxyMode == proxy_client && useSmbSocket > 0 && smbPort > 0) { cerr << "Info" << ": Listening to SMB connections " << "on port '" << smbPort << "'.\n"; } else if (control -> ProxyMode == proxy_server && smbPort > 0) { cerr << "Info" << ": Forwarding SMB connections " << "to port '" << smbPort << "'.\n"; } if (control -> ProxyMode == proxy_client && useMediaSocket > 0 && mediaPort > 0) { cerr << "Info" << ": Listening to multimedia connections " << "on port '" << mediaPort << "'.\n"; } else if (control -> ProxyMode == proxy_server && mediaPort > 0) { cerr << "Info" << ": Forwarding multimedia connections " << "to port '" << mediaPort << "'.\n"; } if (control -> ProxyMode == proxy_client && useHttpSocket > 0 && httpPort > 0) { cerr << "Info" << ": Listening to HTTP connections " << "on port '" << httpPort << "'.\n"; } else if (control -> ProxyMode == proxy_server && httpPort > 0) { cerr << "Info" << ": Forwarding HTTP connections " << "to port '" << httpPort << "'.\n"; } if (control -> ProxyMode == proxy_server && useFontSocket > 0 && *fontPort != '\0') { cerr << "Info" << ": Listening to font server connections " << "on port '" << fontPort << "'.\n"; } else if (control -> ProxyMode == proxy_client && *fontPort != '\0') { cerr << "Info" << ": Forwarding font server connections " << "to port '" << fontPort << "'.\n"; } if (useSlaveSocket > 0 && slavePort > 0) { cerr << "Info" << ": Listening to slave connections " << "on port '" << slavePort << "'.\n"; } } void PrintVersionInfo() { cerr << "NXPROXY - " << "Version " << control -> LocalVersionMajor << "." << control -> LocalVersionMinor << "." << control -> LocalVersionPatch; cerr << endl; } void PrintCopyrightInfo() { cerr << endl; PrintVersionInfo(); cerr << endl; cerr << GetCopyrightInfo(); // // Print third party's copyright info. // cerr << endl; cerr << GetOtherCopyrightInfo(); cerr << endl; } void PrintOptionIgnored(const char *type, const char *name, const char *value) { if (control -> ProxyMode == proxy_server) { #ifdef WARNING *logofs << "Loop: WARNING! Ignoring " << type << " option '" << name << "' with value '" << value << "' at " << "NX client side.\n" << logofs_flush; #endif cerr << "Warning" << ": Ignoring " << type << " option '" << name << "' with value '" << value << "' at " << "NX client side.\n"; } else { #ifdef WARNING *logofs << "Loop: WARNING! Ignoring " << type << " option '" << name << "' with value '" << value << "' at " << "NX server side.\n" << logofs_flush; #endif cerr << "Warning" << ": Ignoring " << type << " option '" << name << "' with value '" << value << "' at " << "NX server side.\n"; } } const char *GetOptions(const char *options) { if (options != NULL) { if (strncasecmp(options, "nx/nx,", 6) != 0 && strncasecmp(options, "nx,", 3) != 0 && strncasecmp(options, "nx:", 3) != 0) { #ifdef TEST *logofs << "Loop: PANIC! Display options string '" << options << "' must start with 'nx' or 'nx/nx' prefix.\n" << logofs_flush; #endif cerr << "Error" << ": Display options string '" << options << "' must start with 'nx' or 'nx/nx' prefix.\n"; HandleCleanup(); } } else { options = getenv("DISPLAY"); } return options; } const char *GetArg(int &argi, int argc, const char **argv) { // // Skip "-" and flag character. // const char *arg = argv[argi] + 2; if (*arg == 0) { if (argi + 1 == argc) { return NULL; } else { argi++; return (*argv[argi] == '-' ? NULL : argv[argi]); } } else { return (*arg == '-' ? NULL : arg); } } int CheckArg(const char *type, const char *name, const char *value) { #ifdef TEST *logofs << "Loop: Parsing " << type << " option '" << name << "' with value '" << (value ? value : "(null)") << "'.\n" << logofs_flush; #endif if (value == NULL || strstr(value, "=") != NULL) { #ifdef PANIC *logofs << "Loop: PANIC! Error in " << type << " option '" << name << "'. No value found.\n" << logofs_flush; #endif cerr << "Error" << ": Error in " << type << " option '" << name << "'. No value found.\n"; return -1; } else if (strstr(name, ",") != NULL) { #ifdef PANIC *logofs << "Loop: PANIC! Parse error at " << type << " option '" << name << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Parse error at " << type << " option '" << name << "'.\n"; return -1; } else if (strlen(value) >= DEFAULT_STRING_LENGTH) { #ifdef PANIC *logofs << "Loop: PANIC! Value '" << value << "' of " << type << " option '" << name << "' exceeds length of " << DEFAULT_STRING_LENGTH << " characters.\n" << logofs_flush; #endif cerr << "Error" << ": Value '" << value << "' of " << type << " option '" << name << "' exceeds length of " << DEFAULT_STRING_LENGTH << " characters.\n"; return -1; } return 1; } int ParseArg(const char *type, const char *name, const char *value) { if (strcasecmp(value, "0") == 0) { return 0; } // // Find the base factor. // double base; const char *id = value + strlen(value) - 1; if (strcasecmp(id, "g") == 0) { base = 1024 * 1024 * 1024; } else if (strcasecmp(id, "m") == 0) { base = 1024 * 1024; } else if (strcasecmp(id, "k") == 0) { base = 1024; } else if (strcasecmp(id, "b") == 0 || isdigit(*id) == 1) { base = 1; } else { return -1; } char *string = new char[strlen(value)]; strncpy(string, value, strlen(value) - 1); *(string + (strlen(value) - 1)) = '\0'; #ifdef TEST *logofs << "Loop: Parsing integer option '" << name << "' from string '" << string << "' with base set to "; switch (tolower(*id)) { case 'k': case 'm': case 'g': { *logofs << (char) toupper(*id); } break; } *logofs << ".\n" << logofs_flush; #endif double result = atof(string) * base; if (result < 0 || result > (((unsigned) -1) >> 1)) { delete [] string; return -1; } delete [] string; #ifdef TEST *logofs << "Loop: Integer option parsed to '" << (int) result << "'.\n" << logofs_flush; #endif return (int) result; } int ValidateArg(const char *type, const char *name, const char *value) { int number = atoi(value); if (number < 0) { #ifdef PANIC *logofs << "Loop: PANIC! Invalid " << type << " option '" << name << "' with value '" << value << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Invalid " << type << " option '" << name << "' with value '" << value << "'.\n"; HandleCleanup(); } return number; } int LowercaseArg(const char *type, const char *name, char *value) { char *next = value; while (*next != '\0') { *next = tolower(*next); next++; } return 1; } int CheckSignal(int signal) { // // Return 1 if the signal needs to be handled // by the proxy, 2 if the signal just needs to // be blocked to avoid interrupting a system // call. // switch (signal) { case SIGCHLD: case SIGUSR1: case SIGUSR2: case SIGHUP: case SIGINT: case SIGTERM: case SIGPIPE: case SIGALRM: { return 1; } case SIGVTALRM: case SIGWINCH: case SIGIO: case SIGTSTP: case SIGTTIN: case SIGTTOU: { return 2; } default: { #ifdef __CYGWIN32__ // // This signal can be raised by the Cygwin // library. // if (signal == 12) { return 1; } #endif return 0; } } } static void PrintUsageInfo(const char *option, int error) { if (error == 1) { cerr << "Error" << ": Invalid command line option '" << option << "'.\n"; } cerr << GetUsageInfo(); if (error == 1) { cerr << "Error" << ": NX transport initialization failed.\n"; } } static void handleCheckSessionInLoop() { // // Check if we completed the shutdown procedure // and the remote confirmed the shutdown. The // tear down should be always initiated by the // agent, but the X server side may unilateral- // ly shut down the link without our permission. // if (proxy -> getShutdown() > 0) { #ifdef TEST *logofs << "Loop: End of NX transport requested " << "by remote.\n" << logofs_flush; #endif handleTerminatingInLoop(); if (control -> ProxyMode == proxy_server) { #ifdef TEST *logofs << "Loop: Bytes received so far are " << (unsigned long long) statistics -> getBytesIn() << ".\n" << logofs_flush; #endif if (statistics -> getBytesIn() < 1024) { cerr << "Info" << ": Your session was closed before reaching " << "a usable state.\n"; cerr << "Info" << ": This can be due to the local X server " << "refusing access to the client.\n"; cerr << "Info" << ": Please check authorization provided " << "by the remote X application.\n"; } } #ifdef TEST *logofs << "Loop: Shutting down the NX transport.\n" << logofs_flush; #endif HandleCleanup(); } else if (proxy -> handlePing() < 0) { #ifdef TEST *logofs << "Loop: Failure handling the ping for " << "proxy FD#" << proxyFD << ".\n" << logofs_flush; #endif HandleShutdown(); } // // Check if the watchdog has exited and we didn't // get the SIGCHLD. This can happen if the parent // has overridden our signal handlers. // if (IsRunning(lastWatchdog) && CheckProcess(lastWatchdog, "watchdog") == 0) { #ifdef WARNING *logofs << "Loop: WARNING! Watchdog is gone unnoticed. " << "Setting the last signal to SIGTERM.\n" << logofs_flush; #endif lastSignal = SIGTERM; #ifdef WARNING *logofs << "Loop: WARNING! Resetting pid of last " << "watchdog process.\n" << logofs_flush; #endif SetNotRunning(lastWatchdog); } // // Let the client proxy find out if the agent's // channel is gone. This is the normal shutdown // procedure in the case of an internal connect- // ion to the agent. // int cleanup = 0; if (control -> ProxyMode == proxy_client && agent != NULL && proxy -> getType(agentFD[1]) == channel_none && lastKill == 0 && lastDestroy == 1) { #ifdef TEST *logofs << "Loop: End of NX transport requested " << "by agent.\n" << logofs_flush; #endif #ifdef TEST *logofs << "Loop: Bytes sent so far are " << (unsigned long long) statistics -> getBytesOut() << ".\n" << logofs_flush; #endif if (statistics -> getBytesOut() < 1024) { cerr << "Info" << ": Your session has died before reaching " << "an usable state.\n"; cerr << "Info" << ": This can be due to the remote X server " << "refusing access to the client.\n"; cerr << "Info" << ": Please check the authorization provided " << "by your X application.\n"; } cleanup = 1; } // // Check if the user requested the end of the // session by sending a signal to the proxy. // All signals are handled in the main loop // so we need to reset the value to get ready // for the next iteration. // int signal = 0; if (lastSignal != 0) { switch (lastSignal) { case SIGCHLD: case SIGUSR1: case SIGUSR2: { break; } default: { signal = lastSignal; cleanup = 1; break; } } lastSignal = 0; } if (cleanup == 1) { // // The first time termination signal is received // disable all further connections, close down any // X channel and wait for a second signal. // if (lastKill == 0) { // // Don't print a message if cleanup is // due to normal termination of agent. // if (signal != 0) { #ifdef TEST *logofs << "Loop: End of NX transport requested by signal '" << signal << "' '" << DumpSignal(signal) << "'.\n" << logofs_flush; #endif handleTerminatingInLoop(); } // // Disable any further connection. // CleanupListeners(); // // Close all the remaining X channels and // let proxies save their persistent cache // on disk. // CleanupConnections(); // // We'll need to wait for the X channels // to be shut down before waiting for the // cleanup signal. // lastKill = 1; } else if (lastKill == 2) { #ifdef TEST *logofs << "Loop: Shutting down the NX transport.\n" << logofs_flush; #endif proxy -> handleShutdown(); HandleCleanup(); } } if (lastKill == 1 && proxy -> getChannels(channel_x11) == 0) { // // Save the message stores to the // persistent cache. // proxy -> handleSave(); // // Run a watchdog process so we can finally // give up at the time the watchdog exits. // if (IsNotRunning(lastWatchdog)) { int timeout = control -> CleanupTimeout; if (timeout > 0) { if (proxy -> getChannels() == 0) { timeout = 500; } #ifdef TEST *logofs << "Loop: Starting watchdog process with timeout " << "of " << timeout << " Ms.\n" << logofs_flush; #endif } #ifdef TEST else { *logofs << "Loop: Starting watchdog process without " << "a timeout.\n" << logofs_flush; } #endif lastWatchdog = NXTransWatchdog(timeout); if (IsFailed(lastWatchdog)) { #ifdef PANIC *logofs << "Loop: PANIC! Can't start the NX watchdog " << "process in shutdown.\n" << logofs_flush; #endif cerr << "Error" << ": Can't start the NX watchdog " << "process in shutdown.\n"; HandleCleanup(); } #ifdef TEST else { *logofs << "Loop: Watchdog started with pid '" << lastWatchdog << "'.\n" << logofs_flush; } #endif } else { #ifdef PANIC *logofs << "Loop: PANIC! Previous watchdog detected " << "in shutdown with pid '" << lastWatchdog << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Previous watchdog detected " << "in shutdown with pid '" << lastWatchdog << "'.\n"; HandleCleanup(); } if (control -> CleanupTimeout > 0) { #ifdef TEST *logofs << "Loop: Waiting the cleanup timeout to complete.\n" << logofs_flush; #endif cerr << "Info" << ": Waiting the cleanup timeout to complete.\n"; } else { // // The NX server will kill the watchdog // process after having shut down the // service channels. // cerr << "Info" << ": Watchdog running with pid '" << lastWatchdog << "'.\n"; #ifdef TEST *logofs << "Loop: Waiting the watchdog process to complete.\n" << logofs_flush; #endif cerr << "Info" << ": Waiting the watchdog process to complete.\n"; } lastKill = 2; } } static void handleCheckBitrateInLoop() { static long int slept = 0; #ifdef TEST *logofs << "Loop: Bitrate is " << statistics -> getBitrateInShortFrame() << " B/s and " << statistics -> getBitrateInLongFrame() << " B/s in " << control -> ShortBitrateTimeFrame / 1000 << "/" << control -> LongBitrateTimeFrame / 1000 << " seconds timeframes.\n" << logofs_flush; #endif // // This can be improved. We may not jump out // of the select often enough to guarantee // the necessary accuracy. // if (control -> LocalBitrateLimit > 0) { #ifdef TEST *logofs << "Loop: Calculating bandwidth usage with limit " << control -> LocalBitrateLimit << ".\n" << logofs_flush; #endif int reference = (statistics -> getBitrateInLongFrame() + statistics -> getBitrateInShortFrame()) / 2; if (reference > control -> LocalBitrateLimit) { double ratio = ((double) reference) / ((double) control -> LocalBitrateLimit); if (ratio > 1.2) { ratio = 1.2; } slept += (unsigned int) (pow(50000, ratio) / 1000); if (slept > 2000) { #ifdef WARNING *logofs << "Loop: WARNING! Sleeping due to " << "reference bitrate of " << reference << " B/s.\n" << logofs_flush; #endif cerr << "Warning" << ": Sleeping due to " << "reference bitrate of " << reference << " B/s.\n"; slept %= 2000; } T_timestamp idleTs = getNewTimestamp(); usleep((unsigned int) pow(50000, ratio)); int diffTs = diffTimestamp(idleTs, getNewTimestamp()); statistics -> addIdleTime(diffTs); statistics -> subReadTime(diffTs); } } } #if defined(TEST) || defined(INFO) static void handleCheckStateInLoop(int &setFDs) { int fdLength; int fdPending; int fdSplits; for (int j = 0; j < setFDs; j++) { if (j != proxyFD) { fdPending = proxy -> getPending(j); if (fdPending > 0) { #ifdef PANIC *logofs << "Loop: PANIC! Buffer for descriptor FD#" << j << " has pending bytes to read.\n" << logofs_flush; #endif HandleCleanup(); } fdLength = proxy -> getLength(j); if (fdLength > 0) { #ifdef TEST *logofs << "Loop: WARNING! Buffer for descriptor FD#" << j << " has " << fdLength << " bytes to write.\n" << logofs_flush; #endif } } } fdPending = proxy -> getPending(proxyFD); if (fdPending > 0) { #ifdef PANIC *logofs << "Loop: PANIC! Buffer for proxy descriptor FD#" << proxyFD << " has pending bytes to read.\n" << logofs_flush; #endif HandleCleanup(); } fdLength = proxy -> getFlushable(proxyFD); if (fdLength > 0) { if (control -> FlushPolicy == policy_immediate && proxy -> getBlocked(proxyFD) == 0) { #ifdef PANIC *logofs << "Loop: PANIC! Buffer for proxy descriptor FD#" << proxyFD << " has " << fdLength << " bytes " << "to write with policy 'immediate'.\n" << logofs_flush; #endif HandleCleanup(); } else { #ifdef TEST *logofs << "Loop: WARNING! Buffer for proxy descriptor FD#" << proxyFD << " has " << fdLength << " bytes " << "to write.\n" << logofs_flush; #endif } } fdSplits = proxy -> getSplitSize(); if (fdSplits > 0) { #ifdef WARNING *logofs << "Loop: WARNING! Proxy descriptor FD#" << proxyFD << " has " << fdSplits << " splits to send.\n" << logofs_flush; #endif } } static void handleCheckSelectInLoop(int &setFDs, fd_set &readSet, fd_set &writeSet, T_timestamp selectTs) { #if defined(TEST) || defined(INFO) *logofs << "Loop: Maximum descriptors is [" << setFDs << "] at " << strMsTimestamp() << ".\n" << logofs_flush; #endif int i; if (setFDs > 0) { i = 0; #if defined(TEST) || defined(INFO) *logofs << "Loop: Selected for read are "; #endif for (int j = 0; j < setFDs; j++) { if (FD_ISSET(j, &readSet)) { #if defined(TEST) || defined(INFO) *logofs << "[" << j << "]" << logofs_flush; #endif i++; } } if (i > 0) { #if defined(TEST) || defined(INFO) *logofs << ".\n" << logofs_flush; #endif } else { #if defined(TEST) || defined(INFO) *logofs << "[none].\n" << logofs_flush; #endif } i = 0; #if defined(TEST) || defined(INFO) *logofs << "Loop: Selected for write are "; #endif for (int j = 0; j < setFDs; j++) { if (FD_ISSET(j, &writeSet)) { #if defined(TEST) || defined(INFO) *logofs << "[" << j << "]" << logofs_flush; #endif i++; } } if (i > 0) { #if defined(TEST) || defined(INFO) *logofs << ".\n" << logofs_flush; #endif } else { #if defined(TEST) || defined(INFO) *logofs << "[none].\n" << logofs_flush; #endif } } #if defined(TEST) || defined(INFO) *logofs << "Loop: Select timeout is " << selectTs.tv_sec << " S and " << (double) selectTs.tv_usec / 1000 << " Ms.\n" << logofs_flush; #endif } static void handleCheckResultInLoop(int &resultFDs, int &errorFDs, int &setFDs, fd_set &readSet, fd_set &writeSet, struct timeval &selectTs, struct timeval &startTs) { int diffTs = diffTimestamp(startTs, getNewTimestamp()); #if defined(TEST) || defined(INFO) if (diffTs >= (control -> PingTimeout - (control -> LatencyTimeout * 4))) { *logofs << "Loop: Select result is [" << resultFDs << "] at " << strMsTimestamp() << " with no " << "communication within " << diffTs << " Ms.\n" << logofs_flush; } else { *logofs << "Loop: Select result is [" << resultFDs << "] error is [" << errorFDs << "] at " << strMsTimestamp() << " after " << diffTs << " Ms.\n" << logofs_flush; } #endif int i; if (resultFDs > 0) { i = 0; #if defined(TEST) || defined(INFO) *logofs << "Loop: Selected for read are "; #endif for (int j = 0; j < setFDs; j++) { if (FD_ISSET(j, &readSet)) { #if defined(TEST) || defined(INFO) *logofs << "[" << j << "]" << logofs_flush; #endif i++; } } if (i > 0) { #if defined(TEST) || defined(INFO) *logofs << ".\n" << logofs_flush; #endif } else { #if defined(TEST) || defined(INFO) *logofs << "[none].\n" << logofs_flush; #endif } i = 0; #if defined(TEST) || defined(INFO) *logofs << "Loop: Selected for write are "; #endif for (int j = 0; j < setFDs; j++) { if (FD_ISSET(j, &writeSet)) { #if defined(TEST) || defined(INFO) *logofs << "[" << j << "]" << logofs_flush; #endif i++; } } if (i > 0) { #if defined(TEST) || defined(INFO) *logofs << ".\n" << logofs_flush; #endif } else { #if defined(TEST) || defined(INFO) *logofs << "[none].\n" << logofs_flush; #endif } } } #endif static void handleCheckSessionInConnect() { #ifdef TEST *logofs << "Loop: Going to check session in connect.\n" << logofs_flush; #endif if (control -> ProxyMode == proxy_client) { HandleAlert(FAILED_PROXY_CONNECTION_CLIENT_ALERT, 1); } else if (IsNotRunning(lastDialog)) { HandleAlert(FAILED_PROXY_CONNECTION_SERVER_ALERT, 1); } handleAlertInLoop(); } static void handleStatisticsInLoop() { if (lastSignal == 0) { return; } int mode = NO_STATS; if (control -> EnableStatistics == 1) { if (lastSignal == SIGUSR1) { // // Print overall statistics. // mode = TOTAL_STATS; } else if (lastSignal == SIGUSR2) { // // Print partial statistics. // mode = PARTIAL_STATS; } if (mode == TOTAL_STATS || mode == PARTIAL_STATS) { #ifdef TEST *logofs << "Loop: Going to request proxy statistics " << "with signal '" << DumpSignal(lastSignal) << "'.\n" << logofs_flush; #endif if (proxy != NULL) { if (ReopenLogFile(statsFileName, statofs, 0) < 0) { HandleCleanup(); } proxy -> handleStatistics(mode, statofs); } } } } static void handleNegotiationInLoop(int &setFDs, fd_set &readSet, fd_set &writeSet, T_timestamp &selectTs) { int yield = 0; while (yield == 0) { #ifdef TEST *logofs << "Loop: Going to run a new negotiation loop " << "with stage " << control -> ProxyStage << " at " << strMsTimestamp() << ".\n" << logofs_flush; #endif switch (control -> ProxyStage) { case stage_undefined: { #ifdef TEST *logofs << "Loop: Handling negotiation with '" << "stage_undefined" << "'.\n" << logofs_flush; #endif control -> ProxyStage = stage_initializing; break; } case stage_initializing: { #ifdef TEST *logofs << "Loop: Handling negotiation with '" << "stage_initializing" << "'.\n" << logofs_flush; #endif InitBeforeNegotiation(); control -> ProxyStage = stage_connecting; break; } case stage_connecting: { #ifdef TEST *logofs << "Loop: Handling negotiation with '" << "stage_connecting" << "'.\n" << logofs_flush; #endif SetupProxyConnection(); control -> ProxyStage = stage_connected; break; } case stage_connected: { #ifdef TEST *logofs << "Loop: Handling negotiation with '" << "stage_connected" << "'.\n" << logofs_flush; #endif // // Server side proxy must always be the one that // sends its version and options first, so, in // some way, client side can be the the one that // has the last word on the matter. // if (control -> ProxyMode == proxy_server) { // // Check if we have been listening for a // forwarder. In this case it will have to // authenticate itself. // if (WE_LISTEN_FORWARDER) { control -> ProxyStage = stage_waiting_forwarder_version; break; } control -> ProxyStage = stage_sending_proxy_options; } else { // // The X client side is the side that has to wait // for the authorization cookie and any remote // option. // control -> ProxyStage = stage_waiting_proxy_version; } break; } case stage_sending_proxy_options: { #ifdef TEST *logofs << "Loop: Handling negotiation with '" << "stage_sending_proxy_options" << "'.\n" << logofs_flush; #endif if (SendProxyOptions(proxyFD) < 0) { goto handleNegotiationInLoopError; } if (control -> ProxyMode == proxy_server) { control -> ProxyStage = stage_waiting_proxy_version; } else { control -> ProxyStage = stage_sending_proxy_caches; } break; } case stage_waiting_forwarder_version: { #ifdef TEST *logofs << "Loop: Handling negotiation with '" << "stage_waiting_forwarder_version" << "'.\n" << logofs_flush; #endif int result = ReadForwarderVersion(proxyFD); if (result == 0) { yield = 1; } else if (result == 1) { control -> ProxyStage = stage_waiting_forwarder_options; } else { goto handleNegotiationInLoopError; } break; } case stage_waiting_forwarder_options: { #ifdef TEST *logofs << "Loop: Handling negotiation with '" << "stage_waiting_forwarder_options" << "'.\n" << logofs_flush; #endif int result = ReadForwarderOptions(proxyFD); if (result == 0) { yield = 1; } else if (result == 1) { control -> ProxyStage = stage_sending_proxy_options; } else { goto handleNegotiationInLoopError; } break; } case stage_waiting_proxy_version: { #ifdef TEST *logofs << "Loop: Handling negotiation with '" << "stage_waiting_proxy_version" << "'.\n" << logofs_flush; #endif int result = ReadProxyVersion(proxyFD); if (result == 0) { yield = 1; } else if (result == 1) { control -> ProxyStage = stage_waiting_proxy_options; } else { goto handleNegotiationInLoopError; } break; } case stage_waiting_proxy_options: { #ifdef TEST *logofs << "Loop: Handling negotiation with '" << "stage_waiting_proxy_options" << "'.\n" << logofs_flush; #endif int result = ReadProxyOptions(proxyFD); if (result == 0) { yield = 1; } else if (result == 1) { if (control -> ProxyMode == proxy_server) { control -> ProxyStage = stage_waiting_proxy_caches; } else { control -> ProxyStage = stage_sending_proxy_options; } } else { goto handleNegotiationInLoopError; } break; } case stage_sending_proxy_caches: { #ifdef TEST *logofs << "Loop: Handling negotiation with '" << "stage_sending_proxy_caches" << "'.\n" << logofs_flush; #endif if (SendProxyCaches(proxyFD) < 0) { goto handleNegotiationInLoopError; } if (control -> ProxyMode == proxy_server) { control -> ProxyStage = stage_operational; } else { control -> ProxyStage = stage_waiting_proxy_caches; } break; } case stage_waiting_proxy_caches: { #ifdef TEST *logofs << "Loop: Handling negotiation with '" << "stage_waiting_proxy_caches" << "'.\n" << logofs_flush; #endif int result = ReadProxyCaches(proxyFD); if (result == 0) { yield = 1; } else if (result == 1) { if (control -> ProxyMode == proxy_server) { control -> ProxyStage = stage_sending_proxy_caches; } else { control -> ProxyStage = stage_operational; } } else { goto handleNegotiationInLoopError; } break; } case stage_operational: { #ifdef TEST *logofs << "Loop: Handling negotiation with '" << "stage_operational" << "'.\n" << logofs_flush; #endif InitAfterNegotiation(); yield = 1; break; } default: { #ifdef PANIC *logofs << "Loop: PANIC! Unmanaged case '" << control -> ProxyStage << "' while handling negotiation.\n" << logofs_flush; #endif cerr << "Error" << ": Unmanaged case '" << control -> ProxyStage << "' while handling negotiation.\n"; HandleCleanup(); } } } // // Check if the user requested the end of // the session. // if (CheckAbort() != 0) { HandleCleanup(); } // // Select the proxy descriptor so that we // can proceed negotiating the session. // FD_SET(proxyFD, &readSet); if (proxyFD >= setFDs) { setFDs = proxyFD + 1; } setMinTimestamp(selectTs, control -> PingTimeout); #ifdef TEST *logofs << "Loop: Selected proxy FD#" << proxyFD << " in negotiation " << "phase with timeout of " << selectTs.tv_sec << " S and " << selectTs.tv_usec << " Ms.\n" << logofs_flush; #endif return; handleNegotiationInLoopError: #ifdef PANIC *logofs << "Loop: PANIC! Failure negotiating the session in stage '" << control -> ProxyStage << "'.\n" << logofs_flush; #endif cerr << "Error" << ": Failure negotiating the session in stage '" << control -> ProxyStage << "'.\n"; if (control -> ProxyMode == proxy_server && control -> ProxyStage == stage_waiting_proxy_version) { #ifdef PANIC *logofs << "Loop: PANIC! Wrong version or invalid session " << "authentication cookie.\n" << logofs_flush; #endif cerr << "Error" << ": Wrong version or invalid session " << "authentication cookie.\n"; } handleTerminatingInLoop(); HandleCleanup(); } static void handleTerminatingInLoop() { if (getpid() == lastProxy) { if (control -> ProxyStage < stage_terminating) { if (agent == NULL) { cerr << "Session" << ": Terminating session at '" << strTimestamp() << "'.\n"; } control -> ProxyStage = stage_terminating; } } } static void handleTerminatedInLoop() { if (getpid() == lastProxy) { if (control -> ProxyStage < stage_terminated) { if (agent == NULL) { cerr << "Session" << ": Session terminated at '" << strTimestamp() << "'.\n"; } control -> ProxyStage = stage_terminated; } } } static void handleAlertInLoop() { if (lastAlert.code == 0) { return; } if (lastAlert.local == 0 && (lastAlert.code > LAST_PROTO_STEP_6_ALERT && control -> isProtoStep7() == 0)) { // // The remote proxy would be unable // to handle the alert. // #ifdef WARNING *logofs << "Loop: WARNING! Ignoring unsupported alert " << "with code '" << lastAlert.code << "'.\n" << logofs_flush; #endif } else if (lastAlert.local == 0) { if (proxy != NULL) { #if defined(TEST) || defined(INFO) *logofs << "Loop: Requesting a remote alert with code '" << lastAlert.code << "'.\n" << logofs_flush; #endif if (proxy -> handleAlert(lastAlert.code) < 0) { HandleShutdown(); } } } else { #if defined(TEST) || defined(INFO) *logofs << "Loop: Handling a local alert with code '" << lastAlert.code << "'.\n" << logofs_flush; #endif if (control -> ProxyMode == proxy_client) { // // If we are at X client side and server // proxy is not responding, we don't have // any possibility to interact with user. // if (lastAlert.code != CLOSE_DEAD_PROXY_CONNECTION_CLIENT_ALERT && lastAlert.code != RESTART_DEAD_PROXY_CONNECTION_CLIENT_ALERT && lastAlert.code != FAILED_PROXY_CONNECTION_CLIENT_ALERT) { // // Let the server proxy show the dialog. // if (proxy != NULL && proxy -> handleAlert(lastAlert.code) < 0) { HandleShutdown(); } } } else { char caption[DEFAULT_STRING_LENGTH]; strcpy(caption, ALERT_CAPTION_PREFIX); int length = strlen(sessionId); // // Get rid of the trailing MD5 from session id. // if (length > (MD5_LENGTH * 2 + 1) && *(sessionId + (length - (MD5_LENGTH * 2 + 1))) == '-') { strncat(caption, sessionId, length - (MD5_LENGTH * 2 + 1)); } else { strcat(caption, sessionId); } // // Use the display to which we are forwarding // the remote X connections. // char *display = displayHost; int replace = 1; int local = 1; char *message; char *type; switch (lastAlert.code) { case CLOSE_DEAD_X_CONNECTION_CLIENT_ALERT: { message = CLOSE_DEAD_X_CONNECTION_CLIENT_ALERT_STRING; type = CLOSE_DEAD_X_CONNECTION_CLIENT_ALERT_TYPE; break; } case CLOSE_DEAD_X_CONNECTION_SERVER_ALERT: { message = CLOSE_DEAD_X_CONNECTION_SERVER_ALERT_STRING; type = CLOSE_DEAD_X_CONNECTION_SERVER_ALERT_TYPE; break; } case CLOSE_DEAD_PROXY_CONNECTION_SERVER_ALERT: { message = CLOSE_DEAD_PROXY_CONNECTION_SERVER_ALERT_STRING; type = CLOSE_DEAD_PROXY_CONNECTION_SERVER_ALERT_TYPE; break; } case RESTART_DEAD_PROXY_CONNECTION_SERVER_ALERT: { message = RESTART_DEAD_PROXY_CONNECTION_SERVER_ALERT_STRING; type = RESTART_DEAD_PROXY_CONNECTION_SERVER_ALERT_TYPE; break; } case CLOSE_UNRESPONSIVE_X_SERVER_ALERT: { message = CLOSE_UNRESPONSIVE_X_SERVER_ALERT_STRING; type = CLOSE_UNRESPONSIVE_X_SERVER_ALERT_TYPE; break; } case WRONG_PROXY_VERSION_ALERT: { message = WRONG_PROXY_VERSION_ALERT_STRING; type = WRONG_PROXY_VERSION_ALERT_TYPE; break; } case FAILED_PROXY_CONNECTION_SERVER_ALERT: { message = FAILED_PROXY_CONNECTION_SERVER_ALERT_STRING; type = FAILED_PROXY_CONNECTION_SERVER_ALERT_TYPE; break; } case MISSING_PROXY_CACHE_ALERT: { message = MISSING_PROXY_CACHE_ALERT_STRING; type = MISSING_PROXY_CACHE_ALERT_TYPE; break; } case ABORT_PROXY_CONNECTION_ALERT: { message = ABORT_PROXY_CONNECTION_ALERT_STRING; type = ABORT_PROXY_CONNECTION_ALERT_TYPE; break; } case DISPLACE_MESSAGE_ALERT: { message = DISPLACE_MESSAGE_ALERT_STRING; type = DISPLACE_MESSAGE_ALERT_TYPE; break; } case GREETING_MESSAGE_ALERT: { message = GREETING_MESSAGE_ALERT_STRING; type = GREETING_MESSAGE_ALERT_TYPE; break; } case START_RESUME_SESSION_ALERT: { message = START_RESUME_SESSION_ALERT_STRING; type = START_RESUME_SESSION_ALERT_TYPE; break; } case FAILED_RESUME_DISPLAY_ALERT: { message = FAILED_RESUME_DISPLAY_ALERT_STRING; type = FAILED_RESUME_DISPLAY_ALERT_TYPE; break; } case FAILED_RESUME_DISPLAY_BROKEN_ALERT: { message = FAILED_RESUME_DISPLAY_BROKEN_STRING; type = FAILED_RESUME_DISPLAY_BROKEN_TYPE; break; } case FAILED_RESUME_VISUALS_ALERT: { message = FAILED_RESUME_VISUALS_ALERT_STRING; type = FAILED_RESUME_VISUALS_ALERT_TYPE; break; } case FAILED_RESUME_COLORMAPS_ALERT: { message = FAILED_RESUME_COLORMAPS_ALERT_STRING; type = FAILED_RESUME_COLORMAPS_ALERT_TYPE; break; } case FAILED_RESUME_PIXMAPS_ALERT: { message = FAILED_RESUME_PIXMAPS_ALERT_STRING; type = FAILED_RESUME_PIXMAPS_ALERT_TYPE; break; } case FAILED_RESUME_DEPTHS_ALERT: { message = FAILED_RESUME_DEPTHS_ALERT_STRING; type = FAILED_RESUME_DEPTHS_ALERT_TYPE; break; } case FAILED_RESUME_RENDER_ALERT: { message = FAILED_RESUME_RENDER_ALERT_STRING; type = FAILED_RESUME_RENDER_ALERT_TYPE; break; } case FAILED_RESUME_FONTS_ALERT: { message = FAILED_RESUME_FONTS_ALERT_STRING; type = FAILED_RESUME_FONTS_ALERT_TYPE; break; } case INTERNAL_ERROR_ALERT: { message = INTERNAL_ERROR_ALERT_STRING; type = INTERNAL_ERROR_ALERT_TYPE; break; } case ABORT_PROXY_NEGOTIATION_ALERT: { message = ABORT_PROXY_NEGOTIATION_ALERT_STRING; type = ABORT_PROXY_NEGOTIATION_ALERT_TYPE; break; } case ABORT_PROXY_SHUTDOWN_ALERT: { message = ABORT_PROXY_SHUTDOWN_ALERT_STRING; type = ABORT_PROXY_SHUTDOWN_ALERT_TYPE; break; } case FAILED_XDMCP_CONNECTION_ALERT: { message = FAILED_XDMCP_CONNECTION_ALERT_STRING; type = FAILED_XDMCP_CONNECTION_ALERT_TYPE; break; } default: { if (lastAlert.code > LAST_PROTO_STEP_7_ALERT) { #ifdef WARNING *logofs << "Loop: WARNING! An unrecognized alert type '" << lastAlert.code << "' was requested.\n" << logofs_flush; #endif cerr << "Warning" << ": An unrecognized alert type '" << lastAlert.code << "' was requested.\n"; } #ifdef WARNING else { *logofs << "Loop: WARNING! Ignoring obsolete alert type '" << lastAlert.code << "'.\n" << logofs_flush; } #endif message = NULL; type = NULL; replace = 0; break; } } if (replace == 1 && IsRunning(lastDialog)) { #if defined(TEST) || defined(INFO) *logofs << "Loop: Killing the previous dialog with pid '" << lastDialog << "'.\n" << logofs_flush; #endif // // The client ignores the TERM signal // on Windows. // #ifdef __CYGWIN32__ KillProcess(lastDialog, "dialog", SIGKILL, 0); #else KillProcess(lastDialog, "dialog", SIGTERM, 0); #endif SetNotRunning(lastDialog); if (proxy != NULL) { proxy -> handleResetAlert(); } } if (message != NULL && type != NULL) { lastDialog = NXTransDialog(caption, message, 0, type, local, display); if (IsFailed(lastDialog)) { #ifdef PANIC *logofs << "Loop: PANIC! Can't start the NX dialog process.\n" << logofs_flush; #endif SetNotRunning(lastDialog); } #if defined(TEST) || defined(INFO) else { *logofs << "Loop: Dialog started with pid '" << lastDialog << "'.\n" << logofs_flush; } #endif } #if defined(TEST) || defined(INFO) else { *logofs << "Loop: No new dialog required for code '" << lastAlert.code << "'.\n" << logofs_flush; } #endif } } // // Reset state. // lastAlert.code = 0; lastAlert.local = 0; } static inline void handleSetAgentInLoop(int &setFDs, fd_set &readSet, fd_set &writeSet, struct timeval &selectTs) { #ifdef TEST *logofs << "Loop: Preparing the masks for the agent descriptors.\n" << logofs_flush; #endif agent -> saveChannelState(); agent -> saveReadMask(&readSet); agent -> saveWriteMask(&writeSet); if (control -> ProxyStage >= stage_operational) { if (agent -> remoteCanRead(&readSet) || agent -> remoteCanWrite(&writeSet) || agent -> localCanRead() || agent -> proxyCanRead()) { #ifdef TEST *logofs << "Loop: Setting a null timeout with agent descriptors ready.\n" << logofs_flush; #endif // // Force a null timeout so we'll bail out // of the select immediately. We will ac- // comodate the result code later. // selectTs.tv_sec = 0; selectTs.tv_usec = 0; } } #ifdef TEST *logofs << "Loop: Clearing the read and write agent descriptors.\n" << logofs_flush; #endif agent -> clearReadMask(&readSet); agent -> clearWriteMask(&writeSet); } static inline void handleAgentInLoop(int &resultFDs, int &errorFDs, int &setFDs, fd_set &readSet, fd_set &writeSet, struct timeval &selectTs) { #if defined(TEST) || defined(INFO) *logofs << "Loop: Setting proxy and local agent descriptors.\n" << logofs_flush; #endif // // Check if I/O is possible on the local // agent or the proxy descriptor. // if (resultFDs >= 0) { // // Save if the proxy can read from the // the agent descriptor. // agent -> saveChannelState(); #if defined(TEST) || defined(INFO) *logofs << "Loop: Values were resultFDs " << resultFDs << " errorFDs " << errorFDs << " setFDs " << setFDs << ".\n" << logofs_flush; #endif if (agent -> localCanRead() == 1) { #if defined(TEST) || defined(INFO) *logofs << "Loop: Setting agent descriptor FD#" << agent -> getLocalFd() << " as ready to read.\n" << logofs_flush; #endif agent -> setLocalRead(&readSet, &resultFDs); } #if defined(TEST) || defined(INFO) if (agent -> proxyCanRead(&readSet) == 0 && agent -> proxyCanRead() == 1) { *logofs << "Loop: WARNING! Can read from proxy FD#" << proxyFD << " but the descriptor " << "is not selected.\n" << logofs_flush; } if (agent -> proxyCanRead(&readSet) == 1) { *logofs << "Loop: Setting proxy descriptor FD#" << agent -> getProxyFd() << " as ready to read.\n" << logofs_flush; } #endif #if defined(TEST) || defined(INFO) *logofs << "Loop: Values are now resultFDs " << resultFDs << " errorFDs " << errorFDs << " setFDs " << setFDs << ".\n" << logofs_flush; #endif } } static inline void handleAgentLateInLoop(int &resultFDs, int &errorFDs, int &setFDs, fd_set &readSet, fd_set &writeSet, struct timeval &selectTs) { #if defined(TEST) || defined(INFO) *logofs << "Loop: Setting remote agent descriptors.\n" << logofs_flush; #endif // // We reset the masks before calling our select. // We now set the descriptors that are ready but // only if they were set in the original mask. // We do this after having executed our loop as // we may have produced more data and the agent // descriptors may have become readable or writ- // able in the meanwhile. // if (resultFDs >= 0) { // // Save if the proxy can read from the // the agent descriptor. // agent -> saveChannelState(); #if defined(TEST) || defined(INFO) *logofs << "Loop: Values were resultFDs " << resultFDs << " errorFDs " << errorFDs << " setFDs " << setFDs << ".\n" << logofs_flush; #endif if (agent -> remoteCanRead(agent -> getSavedReadMask()) == 1) { #if defined(TEST) || defined(INFO) *logofs << "Loop: Setting agent descriptor FD#" << agent -> getRemoteFd() << " as ready to read.\n" << logofs_flush; #endif agent -> setRemoteRead(&readSet, &resultFDs); } if (agent -> remoteCanWrite(agent -> getSavedWriteMask()) == 1) { #if defined(TEST) || defined(INFO) *logofs << "Loop: Setting agent descriptor FD#" << agent -> getRemoteFd() << " as ready to write.\n" << logofs_flush; #endif agent -> setRemoteWrite(&writeSet, &resultFDs); } #if defined(TEST) || defined(INFO) *logofs << "Loop: Values are now resultFDs " << resultFDs << " errorFDs " << errorFDs << " setFDs " << setFDs << ".\n" << logofs_flush; #endif } } static inline void handleReadableInLoop(int &resultFDs, fd_set &readSet) { if (resultFDs > 0) { T_channel_type type = channel_none; char *label = NULL; int domain = -1; int fd = -1; if (tcpFD != -1 && FD_ISSET(tcpFD, &readSet)) { type = channel_x11; label = "X"; domain = AF_INET; fd = tcpFD; resultFDs--; } if (unixFD != -1 && FD_ISSET(unixFD, &readSet)) { type = channel_x11; label = "X"; domain = AF_UNIX; fd = unixFD; resultFDs--; } if (cupsFD != -1 && FD_ISSET(cupsFD, &readSet)) { type = channel_cups; label = "CUPS"; domain = AF_INET; fd = cupsFD; resultFDs--; } if (auxFD != -1 && FD_ISSET(auxFD, &readSet)) { // // Starting from version 1.5.0 we create real X // connections for the keyboard channel, so they // can use the fake authorization cookie. This // means that there is not such a thing like a // channel_aux anymore. // type = channel_x11; label = "auxiliary X11"; domain = AF_INET; fd = auxFD; resultFDs--; } if (smbFD != -1 && FD_ISSET(smbFD, &readSet)) { type = channel_smb; label = "SMB"; domain = AF_INET; fd = smbFD; resultFDs--; } if (mediaFD != -1 && FD_ISSET(mediaFD, &readSet)) { type = channel_media; label = "media"; domain = AF_INET; fd = mediaFD; resultFDs--; } if (httpFD != -1 && FD_ISSET(httpFD, &readSet)) { type = channel_http; label = "HTTP"; domain = AF_INET; fd = httpFD; resultFDs--; } if (fontFD != -1 && FD_ISSET(fontFD, &readSet)) { type = channel_font; label = "font server"; domain = AF_INET; fd = fontFD; resultFDs--; } if (slaveFD != -1 && FD_ISSET(slaveFD, &readSet)) { type = channel_slave; label = "slave"; domain = AF_INET; fd = slaveFD; resultFDs--; } if (type != channel_none) { int newFD = AcceptConnection(fd, domain, label); if (newFD != -1) { if (proxy -> handleNewConnection(type, newFD) < 0) { #ifdef PANIC *logofs << "Loop: PANIC! Error creating new " << label << " connection.\n" << logofs_flush; #endif cerr << "Error" << ": Error creating new " << label << " connection.\n"; close(newFD); // // Don't kill the proxy in the case of an error. // // HandleCleanup(); // } else if (proxy -> getReadable(newFD) > 0) { // // Add the descriptor, so we can try // to read immediately. // #ifdef TEST *logofs << "Loop: Trying to read immediately " << "from descriptor FD#" << newFD << ".\n" << logofs_flush; #endif FD_SET(newFD, &readSet); resultFDs++; } #ifdef TEST else { *logofs << "Loop: Nothing to read immediately " << "from descriptor FD#" << newFD << ".\n" << logofs_flush; } #endif } } } #ifdef DEBUG *logofs << "Loop: Going to check the readable descriptors.\n" << logofs_flush; #endif if (proxy -> handleRead(resultFDs, readSet) < 0) { #ifdef TEST *logofs << "Loop: Failure reading from descriptors " << "for proxy FD#" << proxyFD << ".\n" << logofs_flush; #endif HandleShutdown(); } } static inline void handleWritableInLoop(int &resultFDs, fd_set &writeSet) { #ifdef DEBUG *logofs << "Loop: Going to check the writable descriptors.\n" << logofs_flush; #endif if (resultFDs > 0 && proxy -> handleFlush(resultFDs, writeSet) < 0) { #ifdef TEST *logofs << "Loop: Failure writing to descriptors " << "for proxy FD#" << proxyFD << ".\n" << logofs_flush; #endif HandleShutdown(); } } static inline void handleFlushInLoop() { #ifdef DEBUG *logofs << "Loop: Going to flush any data to the proxy.\n" << logofs_flush; #endif if (agent == NULL || control -> FlushPolicy == policy_immediate) { #if defined(TEST) || defined(INFO) if (usePolicy == -1 && control -> ProxyMode == proxy_client) { *logofs << "Loop: WARNING! Flushing the proxy link " << "on behalf of the agent.\n" << logofs_flush; } #endif if (proxy -> handleFlush() < 0) { #ifdef TEST *logofs << "Loop: Failure flushing the proxy FD#" << proxyFD << ".\n" << logofs_flush; #endif HandleShutdown(); } } } static inline void handleRotateInLoop() { #ifdef DEBUG *logofs << "Loop: Going to rotate channels " << "for proxy FD#" << proxyFD << ".\n" << logofs_flush; #endif proxy -> handleRotate(); } static inline void handleEventsInLoop() { #ifdef DEBUG *logofs << "Loop: Going to check channel events " << "for proxy FD#" << proxyFD << ".\n" << logofs_flush; #endif if (proxy -> handleEvents() < 0) { #ifdef TEST *logofs << "Loop: Failure handling channel events " << "for proxy FD#" << proxyFD << ".\n" << logofs_flush; #endif HandleShutdown(); } } static void handleLogReopenInLoop(T_timestamp &logsTs, T_timestamp &nowTs) { // // If need to limit the size of the // log file, check the size at each // loop. // #ifndef QUOTA if (diffTimestamp(logsTs, nowTs) > control -> FileSizeCheckTimeout) #endif { #ifdef DEBUG *logofs << "Loop: Checking size of log file '" << errorsFileName << "'.\n" << logofs_flush; #endif #ifndef MIXED if (ReopenLogFile(errorsFileName, logofs, control -> FileSizeLimit) < 0) { HandleShutdown(); } #endif // // Reset to current timestamp. // logsTs = nowTs; } } static inline void handleSetReadInLoop(fd_set &readSet, int &setFDs, struct timeval &selectTs) { proxy -> setReadDescriptors(&readSet, setFDs, selectTs); } static inline void handleSetWriteInLoop(fd_set &writeSet, int &setFDs, struct timeval &selectTs) { proxy -> setWriteDescriptors(&writeSet, setFDs, selectTs); } static void handleSetListenersInLoop(fd_set &readSet, int &setFDs) { // // Set descriptors of listening sockets. // if (control -> ProxyMode == proxy_client) { if (useTcpSocket == 1) { FD_SET(tcpFD, &readSet); if (tcpFD >= setFDs) { setFDs = tcpFD + 1; } #ifdef DEBUG *logofs << "Loop: Selected listener tcpFD " << tcpFD << " with setFDs " << setFDs << ".\n" << logofs_flush; #endif } if (useUnixSocket == 1) { FD_SET(unixFD, &readSet); if (unixFD >= setFDs) { setFDs = unixFD + 1; } #ifdef DEBUG *logofs << "Loop: Selected listener unixFD " << unixFD << " with setFDs " << setFDs << ".\n" << logofs_flush; #endif } if (useCupsSocket == 1) { FD_SET(cupsFD, &readSet); if (cupsFD >= setFDs) { setFDs = cupsFD + 1; } #ifdef DEBUG *logofs << "Loop: Selected listener cupsFD " << cupsFD << " with setFDs " << setFDs << ".\n" << logofs_flush; #endif } if (useAuxSocket == 1) { FD_SET(auxFD, &readSet); if (auxFD >= setFDs) { setFDs = auxFD + 1; } #ifdef DEBUG *logofs << "Loop: Selected listener auxFD " << auxFD << " with setFDs " << setFDs << ".\n" << logofs_flush; #endif } if (useSmbSocket == 1) { FD_SET(smbFD, &readSet); if (smbFD >= setFDs) { setFDs = smbFD + 1; } #ifdef DEBUG *logofs << "Loop: Selected listener smbFD " << smbFD << " with setFDs " << setFDs << ".\n" << logofs_flush; #endif } if (useMediaSocket == 1) { FD_SET(mediaFD, &readSet); if (mediaFD >= setFDs) { setFDs = mediaFD + 1; } #ifdef DEBUG *logofs << "Loop: Selected listener mediaFD " << mediaFD << " with setFDs " << setFDs << ".\n" << logofs_flush; #endif } if (useHttpSocket == 1) { FD_SET(httpFD, &readSet); if (httpFD >= setFDs) { setFDs = httpFD + 1; } #ifdef DEBUG *logofs << "Loop: Selected listener httpFD " << httpFD << " with setFDs " << setFDs << ".\n" << logofs_flush; #endif } } else { if (useFontSocket == 1) { FD_SET(fontFD, &readSet); if (fontFD >= setFDs) { setFDs = fontFD + 1; } #ifdef DEBUG *logofs << "Loop: Selected listener fontFD " << fontFD << " with setFDs " << setFDs << ".\n" << logofs_flush; #endif } } if (useSlaveSocket == 1) { FD_SET(slaveFD, &readSet); if (slaveFD >= setFDs) { setFDs = slaveFD + 1; } #ifdef DEBUG *logofs << "Loop: Selected listener slaveFD " << slaveFD << " with setFDs " << setFDs << ".\n" << logofs_flush; #endif } }