CVS: rdesktop scard.c, NONE, 1.1 scard.h, NONE, 1.1 Makefile.in, 1.15, 1.16 README, 1.4, 1.5 configure.ac, 1.31, 1.32 proto.h, 1.99, 1.100 rdesktop.c, 1.153, 1.154 rdpdr.c, 1.43, 1.44 tcp.c, 1.32, 1.33 types.h, 1.28, 1.29

Michael Gernoth <[email protected]> Wed, 01 Nov 2006 12:52:04 -0800
Newsgroups gmane.network.rdesktop.cvs
Message-ID <[email protected]>
Update of /cvsroot/rdesktop/rdesktop
In directory sc8-pr-cvs9.sourceforge.net:/tmp/cvs-serv8160

Modified Files:
	Makefile.in README configure.ac proto.h rdesktop.c rdpdr.c 
	tcp.c types.h 
Added Files:
	scard.c scard.h 
Log Message:
Smartcard support by Alexi Volkov <[email protected]> and additional
patches for smartcard-support by Jennings Jared <[email protected]>


--- NEW FILE: scard.c ---
/************************************/
/* Smart Card support for RDesktop. */
/* Copyright (C) by Alexi Volkov    */
/* e-mail:  [email protected]     */
/* ICQ:     264679502               */
/************************************/

#include <stdio.h>
#include "rdesktop.h"
#include <unistd.h>
#include <fcntl.h>
#include <strings.h>
#include <sys/types.h>
#include <time.h>
#ifndef MAKE_PROTO
#include <pcsclite.h>
#include <winscard.h>
#include "scard.h"

[...2726 lines suppressed...]
scard_tcp_reset_state(void)
{
	int i;
	struct stream *p;

	for (i = 0, p = out; i < STREAM_COUNT; i++, p++)
	{
		if (p->data != NULL)
			xfree(p->data);
		p->p = NULL;
		p->end = NULL;
		p->data = NULL;
		p->size = 0;
		p->iso_hdr = NULL;
		p->mcs_hdr = NULL;
		p->sec_hdr = NULL;
		p->rdp_hdr = NULL;
		p->channel_hdr = NULL;
	}
}

--- NEW FILE: scard.h ---
/************************************/
/* Smart Card support for RDesktop. */
/* Copyright (C) by Alexi Volkov    */
/* e-mail:  [email protected]     */
/* ICQ:     264679502               */
/************************************/

#include <pthread.h>
#include <pcsclite.h>
#include "proto.h"

/*************************************************************************/
/* these are the additional types needed to split out 64-vs-32-bit APIs  */
/*                                                                       */

/* The point of all of this is to avoid patching the existing smartcard
 * infrastructure (PC/SC Lite, libmusclecard+libmusclepkcs11 or CoolKey, any
 * other apps linking against any of these) because the need for patches
 * spreads without limit. The alternative is to patch the heck out of rdesktop,
 * which is already being done anyway.
 *
 * - [email protected], 2 Aug 2006
 */

#include <stdint.h>

/* A DWORD when dealing with the smartcard stuff. Could be 32 bits or 64. */
typedef DWORD MYPCSC_DWORD;
/* A DWORD when talking to the server. Must be exactly 32 bits all the time.*/
typedef uint32_t SERVER_DWORD;

typedef SCARDCONTEXT MYPCSC_SCARDCONTEXT;
typedef SCARDHANDLE MYPCSC_SCARDHANDLE;
typedef uint32_t SERVER_SCARDCONTEXT;
typedef uint32_t SERVER_SCARDHANDLE;

typedef SCARD_READERSTATE_A MYPCSC_SCARD_READERSTATE_A;
typedef LPSCARD_READERSTATE_A MYPCSC_LPSCARD_READERSTATE_A;

typedef struct
{
	const char *szReader;
	void *pvUserData;
	SERVER_DWORD dwCurrentState;
	SERVER_DWORD dwEventState;
	SERVER_DWORD cbAtr;
	unsigned char rgbAtr[MAX_ATR_SIZE];
}
SERVER_SCARD_READERSTATE_A;

typedef SERVER_SCARD_READERSTATE_A *SERVER_LPSCARD_READERSTATE_A;

#define SERVER_SCARDSTATESIZE              (sizeof(SERVER_SCARD_READERSTATE_A) - sizeof(const char *) - sizeof(void *))
#define MYPCSC_SCARDSTATESIZE              (sizeof(MYPCSC_SCARD_READERSTATE_A) - sizeof(const char *) - sizeof(void *))

typedef struct _SERVER_SCARD_IO_REQUEST
{
	SERVER_DWORD dwProtocol;	/* Protocol identifier */
	SERVER_DWORD cbPciLength;	/* Protocol Control Inf Length */
}
SERVER_SCARD_IO_REQUEST, *SERVER_LPSCARD_IO_REQUEST;

typedef SCARD_IO_REQUEST MYPCSC_SCARD_IO_REQUEST;
typedef LPSCARD_IO_REQUEST MYPCSC_LPSCARD_IO_REQUEST;


/*                                                                       */
/*                                                                       */
/*************************************************************************/


#define	SC_TRUE                     1
#define SC_FALSE                    0

#define SC_ESTABLISH_CONTEXT        0x00090014	/* EstablishContext */
#define SC_RELEASE_CONTEXT          0x00090018	/* ReleaseContext */
#define SC_IS_VALID_CONTEXT         0x0009001C	/* IsValidContext */
#define SC_LIST_READER_GROUPS       0x00090020	/* ListReaderGroups */
#define SC_LIST_READERS             0x00090028	/* ListReadersA */
#define SC_INTRODUCE_READER_GROUP   0x00090050	/* IntroduceReaderGroup */
#define SC_FORGET_READER_GROUP      0x00090058	/* ForgetReader */
#define SC_INTRODUCE_READER         0x00090060	/* IntroduceReader */
#define SC_FORGET_READER            0x00090068	/* IntroduceReader */
#define SC_ADD_READER_TO_GROUP      0x00090070	/* AddReaderToGroup */
#define SC_REMOVE_READER_FROM_GROUP 0x00090078	/* RemoveReaderFromGroup */
#define SC_CONNECT                  0x000900AC	/* ConnectA */
#define SC_RECONNECT                0x000900B4	/* Reconnect */
#define SC_DISCONNECT               0x000900B8	/* Disconnect */
#define SC_GET_STATUS_CHANGE        0x000900A0	/* GetStatusChangeA */
#define SC_CANCEL                   0x000900A8	/* Cancel */
#define SC_BEGIN_TRANSACTION        0x000900BC	/* BeginTransaction */
#define SC_END_TRANSACTION          0x000900C0	/* EndTransaction */
#define SC_STATE		    0x000900C4	/* State */
#define SC_STATUS                   0x000900C8	/* StatusA */
#define SC_TRANSMIT                 0x000900D0	/* Transmit */
#define SC_CONTROL                  0x000900D4	/* Control */
#define SC_GETATTRIB                0x000900D8	/* GetAttrib */
#define SC_SETATTRIB                0x000900DC	/* SetAttrib */
#define SC_ACCESS_STARTED_EVENT	    0x000900E0	/* SCardAccessStartedEvent */
#define SC_LOCATE_CARDS_BY_ATR      0x000900E8	/* LocateCardsByATR */

/* #define INPUT_LINKED                0x00020000 */
#define INPUT_LINKED                0xFFFFFFFF

#define SC_THREAD_FUNCTION(f)       void *(*f)(void *)

extern RDPDR_DEVICE g_rdpdr_device[];

typedef struct _MEM_HANDLE
{
	struct _MEM_HANDLE *prevHandle;
	struct _MEM_HANDLE *nextHandle;
	int dataSize;
} MEM_HANDLE, *PMEM_HANDLE;

typedef struct _SCARD_ATRMASK_L
{
	unsigned int cbAtr;
	unsigned char rgbAtr[36];
	unsigned char rgbMask[36];
} SCARD_ATRMASK_L, *PSCARD_ATRMASK_L, *LPSCARD_ATRMASK_L;

typedef struct _TSCNameMapRec
{
	char alias[128];
	char name[128];
	char vendor[128];
} TSCNameMapRec, *PSCNameMapRec;

typedef struct _TSCHCardRec
{
	DWORD hCard;
	char *vendor;
	struct _TSCHCardRec *next;
	struct _TSCHCardRec *prev;
} TSCHCardRec, *PSCHCardRec;

typedef struct _TSCThreadData
{
	uint32 device;
	uint32 id;
	NTHANDLE handle;
	uint32 request;
	STREAM in;
	STREAM out;
	PMEM_HANDLE memHandle;
	struct _TSCThreadData *next;
} TSCThreadData, *PSCThreadData;

typedef struct _TThreadListElement
{
	pthread_t thread;
	pthread_mutex_t busy;
	pthread_mutex_t nodata;
	PSCThreadData data;
	struct _TThreadListElement *next;
} TThreadListElement, *PThreadListElement;

int scard_enum_devices(uint32 * id, char *optarg);

Index: Makefile.in
===================================================================
RCS file: /cvsroot/rdesktop/rdesktop/Makefile.in,v
retrieving revision 1.15
retrieving revision 1.16
diff -C2 -d -r1.15 -r1.16
*** Makefile.in	1 Oct 2006 10:56:39 -0000	1.15
--- Makefile.in	1 Nov 2006 20:52:00 -0000	1.16
***************
*** 25,28 ****
--- 25,29 ----
  VNCLINK     = @VNCLINK@
  SOUNDOBJ    = @SOUNDOBJ@
+ SCARDOBJ    = @SCARDOBJ@
  
  RDPOBJ   = tcp.o iso.o mcs.o secure.o licence.o rdp.o orders.o bitmap.o cache.o rdp5.o channels.o rdpdr.o serial.o printer.o disk.o parallel.o printercache.o mppc.o pstcache.o lspci.o seamless.o
***************
*** 33,41 ****
  all: $(TARGETS)
  
! rdesktop: $(X11OBJ) $(SOUNDOBJ) $(RDPOBJ)
! 	$(CC) $(CFLAGS) -o rdesktop $(X11OBJ) $(SOUNDOBJ) $(RDPOBJ) $(LDFLAGS) -lX11
  
! rdp2vnc: $(VNCOBJ) $(SOUNDOBJ) $(RDPOBJ) 
! 	$(VNCLINK) $(CFLAGS) -o rdp2vnc $(VNCOBJ) $(SOUNDOBJ) $(RDPOBJ) $(LDFLAGS) $(LDVNC)
  
  vnc/rdp2vnc.o: rdesktop.c
--- 34,42 ----
  all: $(TARGETS)
  
! rdesktop: $(X11OBJ) $(SOUNDOBJ) $(RDPOBJ) $(SCARDOBJ)
! 	$(CC) $(CFLAGS) -o rdesktop $(X11OBJ) $(SOUNDOBJ) $(RDPOBJ) $(SCARDOBJ) $(LDFLAGS) -lX11
  
! rdp2vnc: $(VNCOBJ) $(SOUNDOBJ) $(RDPOBJ) $(SCARDOBJ) 
! 	$(VNCLINK) $(CFLAGS) -o rdp2vnc $(VNCOBJ) $(SOUNDOBJ) $(RDPOBJ) $(SCARDOBJ) $(LDFLAGS) $(LDVNC)
  
  vnc/rdp2vnc.o: rdesktop.c
***************
*** 83,87 ****
  	iso.c licence.c mcs.c orders.c parallel.c printer.c printercache.c \
  	pstcache.c rdesktop.c rdp5.c rdp.c rdpdr.c rdpsnd.c \
! 	secure.c serial.c tcp.c xclip.c xkeymap.c xwin.c lspci.c seamless.c >> proto.h
  	cat proto.tail >> proto.h
  
--- 84,89 ----
  	iso.c licence.c mcs.c orders.c parallel.c printer.c printercache.c \
  	pstcache.c rdesktop.c rdp5.c rdp.c rdpdr.c rdpsnd.c \
! 	secure.c serial.c tcp.c xclip.c xkeymap.c xwin.c lspci.c seamless.c \
! 	scard.c >> proto.h
  	cat proto.tail >> proto.h
  

Index: README
===================================================================
RCS file: /cvsroot/rdesktop/rdesktop/README,v
retrieving revision 1.4
retrieving revision 1.5
diff -C2 -d -r1.4 -r1.5
*** README	8 May 2005 17:57:12 -0000	1.4
--- README	1 Nov 2006 20:52:00 -0000	1.5
***************
*** 31,32 ****
--- 31,42 ----
  The rdesktop manual page lists the possible options ("man rdesktop").
  
+ Smart-card support notes
+ ------------------------
+ 
+ Smart-card support module uses PCSC-lite. You should use PCSC-lite 1.2.9 or
+ later.
+ 
+ To enable smart-card support in the rdesktop just run "./configure" with the
+ "--enable-smartcard" option. Also you can enable smart-card debug with
+ additional "--with-debug-smartcard" option.
+ 

Index: configure.ac
===================================================================
RCS file: /cvsroot/rdesktop/rdesktop/configure.ac,v
retrieving revision 1.31
retrieving revision 1.32
diff -C2 -d -r1.31 -r1.32
*** configure.ac	30 Oct 2006 09:01:01 -0000	1.31
--- configure.ac	1 Nov 2006 20:52:00 -0000	1.32
***************
*** 80,83 ****
--- 80,99 ----
               ])
  
+ AC_ARG_ENABLE(smartcard, 
+              [  --enable-smartcard	  Enables smart-card support.
+ 	     ],
+              [ 
+ 		if test -n "$PKG_CONFIG"; then
+ 			PKG_CHECK_MODULES(PCSCLITE, libpcsclite, [WITH_SCARD=1], [WITH_SCARD=0])
+ 			if test x"$WITH_SCARD" = "x1"; then
+ 				SCARDOBJ="scard.o"
+ 				CFLAGS="$CFLAGS $PCSCLITE_CFLAGS"
+ 				LIBS="$LIBS $PCSCLITE_LIBS"
+ 				AC_DEFINE(WITH_SCARD)
+ 			fi
+ 		fi
+              ])
+ 
+ AC_SUBST(SCARDOBJ)
  
  #
***************
*** 786,789 ****
--- 802,816 ----
  
  
+ AC_ARG_WITH(debug-smartcard,
+     [  --with-debug-smartcard  enable debugging of smart-card code],
+     [
+         if test $withval != "no";
+         then
+ 		if test x"$WITH_SCARD" = "x1"; then
+ 			AC_DEFINE(WITH_SCARD_DEBUG,1)
+ 		fi
+ 	fi
+     ])
+ 
  #
  # target-specific stuff

Index: proto.h
===================================================================
RCS file: /cvsroot/rdesktop/rdesktop/proto.h,v
retrieving revision 1.99
retrieving revision 1.100
diff -C2 -d -r1.99 -r1.100
*** proto.h	27 Oct 2006 12:59:38 -0000	1.99
--- proto.h	1 Nov 2006 20:52:00 -0000	1.100
***************
*** 158,161 ****
--- 158,163 ----
  int get_device_index(NTHANDLE handle);
  void convert_to_unix_filename(char *filename);
+ void rdpdr_send_completion(uint32 device, uint32 id, uint32 status, uint32 result, uint8 * buffer,
+ 			   uint32 length);
  BOOL rdpdr_init(void);
  void rdpdr_add_fds(int *n, fd_set * rfds, fd_set * wfds, struct timeval *tv, BOOL * timeout);
***************
*** 303,306 ****
--- 305,316 ----
  unsigned int seamless_send_zchange(unsigned long id, unsigned long below, unsigned long flags);
  unsigned int seamless_send_focus(unsigned long id, unsigned long flags);
+ /* scard.c */
+ void scardSetInfo(uint32 device, uint32 id, uint32 bytes_out);
+ int scard_enum_devices(uint32 * id, char *optarg);
+ void scard_tcp_lock(void);
+ void scard_tcp_unlock(void);
+ STREAM scard_tcp_init(void);
+ void scard_tcp_connect(void);
+ void scard_tcp_reset_state(void);
  
  /* *INDENT-OFF* */

Index: rdesktop.c
===================================================================
RCS file: /cvsroot/rdesktop/rdesktop/rdesktop.c,v
retrieving revision 1.153
retrieving revision 1.154
diff -C2 -d -r1.153 -r1.154
*** rdesktop.c	27 Oct 2006 12:59:38 -0000	1.153
--- rdesktop.c	1 Nov 2006 20:52:00 -0000	1.154
***************
*** 194,197 ****
--- 194,212 ----
  	fprintf(stderr, "                      when sending data to server.\n");
  	fprintf(stderr, "                      'CLIPBOARD' looks at only CLIPBOARD.\n");
+ #ifdef WITH_SCARD
+ 	fprintf(stderr, "         '-r scard[:\"Scard Name\"=\"Alias Name[;Vendor Name]\"[,...]]\n");
+ 	fprintf(stderr, "          example: -r scard:\"eToken PRO 00 00\"=\"AKS ifdh 0\"\n");
+ 	fprintf(stderr,
+ 		"                   \"eToken PRO 00 00\" -> Device in Linux/Unix enviroment\n");
+ 	fprintf(stderr,
+ 		"                   \"AKS ifdh 0\"       -> Device shown in Windows enviroment \n");
+ 	fprintf(stderr, "          example: -r scard:\"eToken PRO 00 00\"=\"AKS ifdh 0;AKS\"\n");
+ 	fprintf(stderr,
+ 		"                   \"eToken PRO 00 00\" -> Device in Linux/Unix enviroment\n");
+ 	fprintf(stderr,
+ 		"                   \"AKS ifdh 0\"       -> Device shown in Windows enviroment \n");
+ 	fprintf(stderr,
+ 		"                   \"AKS\"              -> Device vendor name                 \n");
+ #endif
  	fprintf(stderr, "   -0: attach to console\n");
  	fprintf(stderr, "   -4: use RDP version 4\n");
***************
*** 741,747 ****
  						g_rdpclip = True;
  				}
  				else
  				{
! 					warning("Unknown -r argument\n\n\tPossible arguments are: comport, disk, lptport, printer, sound, clipboard\n");
  				}
  				break;
--- 756,770 ----
  						g_rdpclip = True;
  				}
+ 				else if (strncmp("scard", optarg, 5) == 0)
+ 				{
+ #ifdef WITH_SCARD
+ 					scard_enum_devices(&g_num_devices, optarg + 5);
+ #else
+ 					warning("Not compiled with smartcard support\n");
+ #endif
+ 				}
  				else
  				{
! 					warning("Unknown -r argument\n\n\tPossible arguments are: comport, disk, lptport, printer, sound, clipboard, scard\n");
  				}
  				break;

Index: rdpdr.c
===================================================================
RCS file: /cvsroot/rdesktop/rdesktop/rdpdr.c,v
retrieving revision 1.43
retrieving revision 1.44
diff -C2 -d -r1.43 -r1.44
*** rdpdr.c	15 Aug 2005 11:44:35 -0000	1.43
--- rdpdr.c	1 Nov 2006 20:52:00 -0000	1.44
***************
*** 61,64 ****
--- 61,67 ----
  extern DEVICE_FNS parallel_fns;
  extern DEVICE_FNS disk_fns;
+ #ifdef WITH_SCARD
+ extern DEVICE_FNS scard_fns;
+ #endif
  extern FILEINFO g_fileinfo[];
  extern BOOL g_notify_stamp;
***************
*** 310,314 ****
  }
  
! static void
  rdpdr_send_completion(uint32 device, uint32 id, uint32 status, uint32 result, uint8 * buffer,
  		      uint32 length)
--- 313,317 ----
  }
  
! void
  rdpdr_send_completion(uint32 device, uint32 id, uint32 status, uint32 result, uint8 * buffer,
  		      uint32 length)
***************
*** 396,399 ****
--- 399,407 ----
  
  		case DEVICE_TYPE_SCARD:
+ #ifdef WITH_SCARD
+ 			fns = &scard_fns;
+ 			rw_blocking = False;
+ 			break;
+ #endif
  		default:
  
***************
*** 672,678 ****
  			out.data = out.p = buffer;
  			out.size = sizeof(buffer);
  			status = fns->device_control(file, request, s, &out);
  			result = buffer_len = out.p - out.data;
! 
  			/* Serial SERIAL_WAIT_ON_MASK */
  			if (status == STATUS_PENDING)
--- 680,697 ----
  			out.data = out.p = buffer;
  			out.size = sizeof(buffer);
+ #ifdef  WITH_SCARD_DEBUG
+ 			printf("[SMART-CARD TRACE]\n");
+ 			printf("device 0x%.8x\n", device);
+ 			printf("file   0x%.8x\n", file);
+ 			printf("id     0x%.8x\n", id);
+ #endif
+ #ifdef WITH_SCARD
+ 			scardSetInfo(device, id, bytes_out + 0x14);
+ #endif
  			status = fns->device_control(file, request, s, &out);
  			result = buffer_len = out.p - out.data;
! #ifdef  WITH_SCARD_DEBUG
! 			printf("[SMART-CARD TRACE] OUT 0x%.8x\n", status);
! #endif
  			/* Serial SERIAL_WAIT_ON_MASK */
  			if (status == STATUS_PENDING)
***************
*** 685,688 ****
--- 704,711 ----
  				}
  			}
+ #ifdef WITH_SCARD
+ 			else if (status == (STATUS_PENDING | 0xC0000000))
+ 				status = STATUS_PENDING;
+ #endif
  			break;
  

Index: tcp.c
===================================================================
RCS file: /cvsroot/rdesktop/rdesktop/tcp.c,v
retrieving revision 1.32
retrieving revision 1.33
diff -C2 -d -r1.32 -r1.33
*** tcp.c	8 Aug 2005 19:15:57 -0000	1.32
--- tcp.c	1 Nov 2006 20:52:01 -0000	1.33
***************
*** 35,39 ****
--- 35,41 ----
  static int sock;
  static struct stream in;
+ #ifndef WITH_SCARD
  static struct stream out;
+ #endif
  int g_tcp_port_rdp = TCP_PORT_RDP;
  
***************
*** 42,54 ****
  tcp_init(uint32 maxlen)
  {
! 	if (maxlen > out.size)
  	{
! 		out.data = (uint8 *) xrealloc(out.data, maxlen);
! 		out.size = maxlen;
  	}
  
! 	out.p = out.data;
! 	out.end = out.data + out.size;
! 	return &out;
  }
  
--- 44,68 ----
  tcp_init(uint32 maxlen)
  {
! 	STREAM result = NULL;
! 
! #ifdef WITH_SCARD
! 	scard_tcp_lock();
! 	result = scard_tcp_init();
! #else
! 	result = &out;
! #endif
! 
! 	if (maxlen > result->size)
  	{
! 		result->data = (uint8 *) xrealloc(result->data, maxlen);
! 		result->size = maxlen;
  	}
  
! 	result->p = result->data;
! 	result->end = result->data + result->size;
! #ifdef WITH_SCARD
! 	scard_tcp_unlock();
! #endif
! 	return result;
  }
  
***************
*** 60,63 ****
--- 74,80 ----
  	int sent, total = 0;
  
+ #ifdef WITH_SCARD
+ 	scard_tcp_lock();
+ #endif
  	while (total < length)
  	{
***************
*** 71,74 ****
--- 88,94 ----
  		total += sent;
  	}
+ #ifdef WITH_SCARD
+ 	scard_tcp_unlock();
+ #endif
  }
  
***************
*** 215,220 ****
--- 235,244 ----
  	in.data = (uint8 *) xmalloc(in.size);
  
+ #ifdef WITH_SCARD
+ 	scard_tcp_connect();
+ #else
  	out.size = 4096;
  	out.data = (uint8 *) xmalloc(out.size);
+ #endif
  
  	return True;
***************
*** 264,268 ****
  	in.channel_hdr = NULL;
  
! 	/* Clear the outgoing stream */
  	if (out.data != NULL)
  		xfree(out.data);
--- 288,295 ----
  	in.channel_hdr = NULL;
  
! 	/* Clear the outgoing stream(s) */
! #ifdef WITH_SCARD
! 	scard_tcp_reset_state();
! #else
  	if (out.data != NULL)
  		xfree(out.data);
***************
*** 276,278 ****
--- 303,306 ----
  	out.rdp_hdr = NULL;
  	out.channel_hdr = NULL;
+ #endif
  }

Index: types.h
===================================================================
RCS file: /cvsroot/rdesktop/rdesktop/types.h,v
retrieving revision 1.28
retrieving revision 1.29
diff -C2 -d -r1.28 -r1.29
*** types.h	2 Mar 2006 15:22:25 -0000	1.28
--- types.h	1 Nov 2006 20:52:01 -0000	1.29
***************
*** 19,23 ****
--- 19,27 ----
  */
  
+ #ifdef WITH_SCARD
+ #include <wintypes.h>
+ #else
  typedef int BOOL;
+ #endif
  
  #ifndef True


-------------------------------------------------------------------------
Using Tomcat but need to do more? Need to support web services, security?
Get stuff done quickly with pre-integrated technology to make your job easier
Download IBM WebSphere Application Server v.1.0.1 based on Apache Geronimo
http://sel.as-us.falkag.net/sel?cmd=lnk&kid=120709&bid=263057&dat=121642