Sybase and SecurID patch for GNU RADIUS 0.96.2

Steve Bleazard <[email protected]> Mon, 10 Jun 2002 20:47:51 +0100
Newsgroups gmane.comp.gnu.radius.bugs
Message-ID <[email protected]>
Hi

Attached are the context diffs to add SecurID and Sybase support to GNU
RADIUS 0.96.2.  The patch.README file contains the details of the
changes and the patch.sybase is a context diff file

Please let me know if there are any problems and I will try try to
resolve them.

Steve
patch.README (application/x-unknown-content-type-readme_auto_file, 2.2 KB) - not displayed
patch.sybace (text/plain, 62.4 KB)
diff -N -r -u gnuradius-0.96.2.orig/README gnuradius-0.96.2/README
--- gnuradius-0.96.2.orig/README	Tue Mar 19 22:31:59 2002
+++ gnuradius-0.96.2/README	Thu Jun  6 21:51:44 2002
@@ -48,10 +48,88 @@
     --enable-pam
     	Enable PAM support.
 	
+    --enable-securid
+	Enable SecurID support.  This option automatically detects libaceclnt.a
+	first and then falls back to sdiclient.a.  However, on OS's that
+	require a lib prefix to recognise a library it may be necessary to
+	ln -s sdiclient.a libsdiclient.a before running configure.  In
+	addition it may be necessary to set --with-lib-path and
+	--with-include-path to include the directory containing the Ace
+	client libraries.
+	
+    	SecurID Inc / RSA have a number of versions of the client library:
+	Version prior to 3.x where called sdiclient.a and supported basic
+	synchronous authentication.  Version 3.x and 4.x were shipped with
+	both sdiclient.a and an additional libaceclnt.a library that supported
+	asynchronous (multithreaded) operation.
+	
+	From 5.x onwards the two libaries were combined into a single
+	libaceclnt.a and (on Solaris and maybe others) a shared version called
+	libaceclnt.so.  These versions also supports all version of the
+	Ace/Server from 3.3.1.  The 5.x library can be downloaded for free
+	from RSA's web site. However, access to this may require SecurCare
+	Online to have been purchased.  For 5.02 of the library the file is
+	called Agentsdk_502.zip
+
+	With the release of the asynchronous library it was possible to pass
+	the client IP address so that authentication proxies (such as RADIUS)
+	could have the Ace/Server perform client authorisation for the
+	original client and not the proxy.
+	
+	Unfortunately the asychronous libraries are tricky to work with,
+	requiring threads programming that can be hard to make portable.
+	However, with the introduction of the 5.x client library a new set
+	of sychronous client functions has been added and it is now easy
+	to pass the client ip address to the Ace/Server.
+
+	As a consquence support for passing the client address to the
+	Ace/Server is only available with version 5.x and later of the
+	libraries.
+
+	The code has been tested with 3.3.1 and 5.02 libraries against a
+	4.1 ACE/Server.  However, experience shows that all libraries after
+	2.3 (and possibly earlier) work with ACE/Server 2.3 onwards.  The
+	reverse (where the library is newer than the server) may not be the
+	case except with 5.02 and (presumably) later version.
+
+    --enable-securid-resend-hack
+	Enable code to prevent duplicate request from disabling a users token.
+	SecurID and the RADIUS retry timeouts can interact very badly and cause
+	the users token to be disabled.  This code attempts to fix the problem.
+	
+	The problem is caused by the SecurID server inserting, by design, a
+	configurable 1 second delay before replying.  Unfortunately, this is
+	very similar to a common NAS retry timeout of around 1 second.  As a
+	consequence the request is often retried.
+
+	Unfortunately with some NASes the resent packet is different enough to
+	make it difficult to spot that it is infact a resend and the request
+	is passed to the backend authentication code.
+
+	At this point the SecurID server is sent the same passcode again and
+	detects a simultaneous login.  After a couple of login attempts the
+	users account is disabled.
+
+	The code added by this option detects these duplicate requests using
+	the username, passcode and NAS ip.  Any request that received within
+	auth request-cleanup-delay seconds (see the config file auth statement)
+	are treated as resends and replied to in the same way as previously.
+
+	Note that the ACE/Server delay should be set to 1 second (the default).
+	Any longer and the code is unlikely to work with resend timouts of a
+	second.
+
     --enable-dbm[={dbm|ndbm}]
 	Enable dbm support. If no argument is specified, usual DBM is
 	assumed. The `ndbm' argument instructs to enable support of NDBM.
 		
+    --enable-nas-ip-hack
+	Enable adding of missing NAS-IP-Address attribute.  Some devices do
+	not include the NAS-IP-Address attribute in the request.  This can
+	make NAS based access control difficult if not impossibe. Enabling
+	this option includes code to add the NAS-IP-Address attribute with the
+	source address of the request if it's missing
+
     --with-sql
 	Enable MySQL support. Usually this requires adding appropriate
 	--with-lib-path and --with-include-path options.
@@ -60,6 +138,14 @@
 	Enable PostgreSQL support. Usually this requires adding appropriate
 	--with-lib-path and --with-include-path options.
 
+    --with-sybase
+	Enable Sybase support. Usually this requires adding appropriate
+	--with-lib-path and --with-include-path options.  Note that with
+	Sybase the SYBASE environment variable must be set appropriately
+	before running the server.  In addition, the name specified in the sql
+	server parameter must have an entry in the interfaces file and the
+	port option is ignored.
+    
     --with-odbc[={odbc|iodbc}]
         Configure to work with ODBC. This is an experimental feature, it
         has not been tested thoroughly.
diff -N -r -u gnuradius-0.96.2.orig/acconfig.h gnuradius-0.96.2/acconfig.h
--- gnuradius-0.96.2.orig/acconfig.h	Tue Mar 19 22:31:59 2002
+++ gnuradius-0.96.2/acconfig.h	Thu Jun  6 14:03:17 2002
@@ -67,12 +67,16 @@
 
 #undef HAVE_LIBPAM
 
+#undef HAVE_LIBSECURID
+
 #undef HAVE_LIBMYSQL
 
 #undef HAVE_LIBPQ
 
 #undef HAVE_LIBODBC
 
+#undef HAVE_LIBSYBASE
+
 /* Define this to disable shadow support */
 #undef PWD_SHADOW
 
@@ -101,6 +105,9 @@
 /* Define this to enable ODBC subsystem of SQL support */
 #undef USE_SQL_ODBC
 
+/* Define this to enable Sybase subsystem of SQL support */
+#undef USE_SQL_SYBASE
+
 /* Define this to enable Livingston-compatible menus */
 #undef USE_LIVINGSTON_MENUS
 
@@ -110,6 +117,18 @@
 
 /* Define this to enable PAM support */
 #undef USE_PAM
+
+/* Define this to enable SecurID support */
+#undef USE_SECURID
+
+/* Define this to enable SecurID legacy function support */
+#undef USE_SECURID_LEGACY
+
+/* Define this to enable SecurID resend detection */
+#undef USE_SECURID_RESEND_HACK
+
+/* Define this to enable NAS IP address adding code */
+#undef USE_NAS_IP_HACK
 
 /* Define this if you wish the DBM support */
 #undef USE_DBM
diff -N -r -u gnuradius-0.96.2.orig/aclocal.m4 gnuradius-0.96.2/aclocal.m4
--- gnuradius-0.96.2.orig/aclocal.m4	Wed Mar 20 18:34:16 2002
+++ gnuradius-0.96.2/aclocal.m4	Mon May 27 17:47:39 2002
@@ -4035,7 +4035,7 @@
    do
       LIBS="$save_LIBS -L$path"
       AC_CHECK_LIB($1, $2,
-                   [rad_cv_lib_$1="$3 -L$path -l$1"
+                   [rad_cv_lib_$1="-L$path -l$1 $3"
                     break],
                    [rad_cv_lib_$1=no],$3)
    done
diff -N -r -u gnuradius-0.96.2.orig/config.h.in gnuradius-0.96.2/config.h.in
--- gnuradius-0.96.2.orig/config.h.in	Wed Mar 20 18:20:35 2002
+++ gnuradius-0.96.2/config.h.in	Thu Jun  6 14:03:17 2002
@@ -273,12 +273,16 @@
 
 #undef HAVE_LIBPAM
 
+#undef HAVE_LIBSECURID
+
 #undef HAVE_LIBMYSQL
 
 #undef HAVE_LIBPQ
 
 #undef HAVE_LIBODBC
 
+#undef HAVE_LIBSYBASE
+
 /* Define this to disable shadow support */
 #undef PWD_SHADOW
 
@@ -307,6 +311,9 @@
 /* Define this to enable ODBC subsystem of SQL support */
 #undef USE_SQL_ODBC
 
+/* Define this to enable Sybase subsystem of SQL support */
+#undef USE_SQL_SYBASE
+
 /* Define this to enable Livingston-compatible menus */
 #undef USE_LIVINGSTON_MENUS
 
@@ -316,6 +323,18 @@
 
 /* Define this to enable PAM support */
 #undef USE_PAM
+
+/* Define this to enable SecurID support */
+#undef USE_SECURID
+
+/* Define this to enable legacy SecurID support */
+#undef USE_SECURID_LEGACY
+
+/* Define this to enable SecurID resend detection */
+#undef USE_SECURID_RESEND_HACK
+
+/* Define this to enable NAS IP address adding code */
+#undef USE_NAS_IP_HACK
 
 /* Define this if you wish the DBM support */
 #undef USE_DBM
diff -N -r -u gnuradius-0.96.2.orig/configure.in gnuradius-0.96.2/configure.in
--- gnuradius-0.96.2.orig/configure.in	Tue Mar 19 23:32:38 2002
+++ gnuradius-0.96.2/configure.in	Thu Jun  6 14:03:17 2002
@@ -316,6 +316,76 @@
 		  exit 1])
 fi
 
+## **************
+## SecurID support
+## **************
+	
+AC_ARG_ENABLE(securid,
+	[  --enable-securid		         enable securid support],
+	[case $enableval in
+		yes)		USE_SECURID=yes; AC_DEFINE(USE_SECURID);;
+		no)		;;
+		*)		AC_MSG_ERROR(--enable-securid can't be used with an argument)
+				exit 1;;
+	esac])
+
+AC_ARG_ENABLE(securid-resend-hack,
+	[  --enable-securid-resend-hack	         enable securid resend detection],
+	[case $enableval in
+		yes)		USE_SECURID_RESEND_HACK=yes;
+				AC_DEFINE(USE_SECURID_RESEND_HACK);;
+		no)		;;
+		*)		AC_MSG_ERROR(--enable-securid-resend-hack can't be used with an argument)
+				exit 1;;
+	esac])
+
+if test x$USE_SECURID = xyes; then
+	HAVE_ACECLNT=no
+	HAVE_SDICLIENT=no
+	AC_CHECK_LIB(aceclnt, SD_Init,
+			[ AC_DEFINE(HAVE_LIBSECURID)
+			HAVE_ACECLNT=yes
+			HAVE_ACE=yes
+               	  	RADIUSD_LDADD="$RADIUSD_LDADD -laceclnt" ])
+
+	if test x$HAVE_ACECLNT = xno; then
+		save_LIBS=$LIBS
+		LIBS="-lsdiclient $LIBS"
+		AC_TRY_RUN([char configure; tst(){creadcfg();}main(){exit(0);}],
+			   [ HAVE_SDICLIENT=yes
+			   HAVE_ACE=yes
+			   AC_DEFINE(USE_SECURID_LEGACY)
+               	  	   RADIUSD_LDADD="$RADIUSD_LDADD -lsdiclient"],
+			   [ AC_MSG_ERROR(can't find aceclnt or sdiclient libraries - have you linked sdiclient.a to libsdiclient.a)],
+			   [ HAVE_SDICLIENT=yes
+			   AC_DEFINE(USE_SECURID_LEGACY)
+               	  	   RADIUSD_LDADD="$RADIUSD_LDADD -lsdiclient"])
+		LIBS=$save_LIBS
+	fi
+	if test x$HAVE_ACECLNT = xyes; then
+		AC_TRY_CPP(acexport.h,,
+				[ AC_MSG_ERROR(can't find header acexport.h)
+				exit 0] )
+	fi
+	if test x$HAVE_SDICLIENT = xyes; then
+		AC_TRY_CPP(sdi_athd.h,,
+				[ AC_MSG_ERROR(can't find header sdi_athd.h)
+				exit 0] )
+	fi
+	if test x$HAVE_ACE = xyes; then
+		if test x$USE_SECURID_RESEND_HACK = xyes; then
+			AC_CHECK_HEADER(ndbm.h,,
+					[ AC_MSG_ERROR(can't find header ndbm.h - ndbm is required by SecurID)
+					exit 0] )
+			AC_CHECK_FUNC(dbm_open,,
+				AC_CHECK_LIB(ndbm, dbm_open,
+					[ AC_DEFINE(HAVE_LIBNDBM) 
+			  		RADIUSD_LDADD="$RADIUSD_LDADD -lndbm" ],
+				[ AC_MSG_ERROR(-lndbm not found - ndbm is required by SecurID) ]))
+		fi
+	fi
+fi
+	
 
 ## **************
 ## DBM 
@@ -395,6 +465,20 @@
 	 no)    ;;
         esac])
 
+AC_ARG_WITH(sybase,
+        [  --with-sybase       Configure to work with Sybase],
+	[case $withval in
+	 yes)	rad_CHECK_LIB(ct, ct_init, [-lcs -ltcl -lcomn],
+			    [ USE_SQL=1
+                              AC_DEFINE(USE_SQL_SYBASE)
+                              AC_DEFINE(HAVE_LIBSYBASE) 
+                              RADIUSD_LDADD="$RADIUSD_LDADD $rad_cv_lib_ct" ],
+		            [ AC_MSG_ERROR(-lct not found) ],
+                            [/usr/sybase/lib])
+		;;
+	 no)    ;;
+        esac])
+
 rad_lib_odbc() {
 rad_CHECK_LIB(odbc, SQLAllocHandle, [],
 			    [ USE_SQL=1
@@ -483,6 +567,21 @@
 				;;
 		no)		;;
 		*)		AC_MSG_ERROR(--enable-snmp can't be used with an argument)				
+				exit 1;;
+	esac])
+	
+## **************
+## NAS_IP_HACK
+## **************
+
+AC_ARG_ENABLE(nas-ip-hack,
+	[  --enable-nas-ip-hack          enable NAS IP Hack support],
+	[case $enableval in
+		yes)		USE_NAS_IP_HACK=yes
+				AC_DEFINE(USE_NAS_IP_HACK)
+				;;
+		no)		;;
+		*)		AC_MSG_ERROR(--enable-nas-ip-hack can't be used with an argument)				
 				exit 1;;
 	esac])
 	
diff -N -r -u gnuradius-0.96.2.orig/doc/texinfo/configure.texi gnuradius-0.96.2/doc/texinfo/configure.texi
--- gnuradius-0.96.2.orig/doc/texinfo/configure.texi	Tue Mar 19 22:31:59 2002
+++ gnuradius-0.96.2/doc/texinfo/configure.texi	Thu Jun  6 21:56:28 2002
@@ -86,6 +86,7 @@
 * acct::        Configure accounting service.
 * proxy::       Configure proxy service.
 * usedbm::      Enable the DBM feature.
+* aceclientip:: Enable the DBM feature.
 * snmp::        Configure SNMP service.
 * guile::       Configure Guile interface.
 * message::     Configure server reply messages.
@@ -566,6 +567,33 @@
 @end table
 
 @comment **L3***************************************************************
+@node aceclientip
+@subsection @code{aceclientip} statement
+@cindex Ace: enabling
+@cindex Enabling Ace Client IP
+@kwindex aceclientip 
+
+@subheading Syntax:
+@example
+aceclientip ( yes | no ) ;
+@end example
+
+@subheading Usage
+The @code{aceclientip} statement determines whether the the NAS' IP address
+is passed to the Ace/Server or the IP address of the RADIUS server itself.
+
+@table @code
+@item no
+The IP address of the RADIUS server will be passed to the Ace/Server. As a
+consequence the Ace/Server will see the RADIUS server as the client.
+
+@item yes
+The IP address of the original NAS server will be passed to the Ace/Server.
+As a consequence the Ace/Server will see each NAS as a seperate client.
+
+@end table
+
+@comment **L3***************************************************************
 @node snmp
 @subsection @code{snmp} statement 
 @cindex SNMP service parameters
@@ -1555,11 +1583,12 @@
 @table @code
 @item interface @var{iface-type}
 Specifies the @sc{sql} interface to use. Currently supported values
-for @var{iface-type} are @code{mysql} and @code{postgres}. Depending
-on this, the default communication port number is set: it is 3306 for
+for @var{iface-type} are @code{mysql}, @code{sybase} and @code{postgres}.
+Depending on this, the default communication port number is set: it is 3306 for
 @code{interface mysql} and 5432 for @code{interface postgres}. Use of
 this statement is only meaningful when the package was configured with
-both @option{--with-mysql} and @option{--with-postgres} option.
+a combination of @option{--with-mysql}, @option{--with-sybase} and
+@option{--with-postgres} option.
 @item server @var{string}
 Specifies the hostname or @IP{} of the @sc{sql} server.
 @item port @var{number}
diff -N -r -u gnuradius-0.96.2.orig/include/debugmod.h gnuradius-0.96.2/include/debugmod.h
--- gnuradius-0.96.2.orig/include/debugmod.h	Tue Mar 19 22:32:00 2002
+++ gnuradius-0.96.2/include/debugmod.h	Fri May 24 13:17:29 2002
@@ -61,3 +61,6 @@
 #ifdef RADIUS_MODULE_GRAM
 # define RADIUS_MODULE 5
 #endif
+#ifdef RADIUS_MODULE_SECURID_C
+# define RADIUS_MODULE 20
+#endif
diff -N -r -u gnuradius-0.96.2.orig/include/radiusd.h gnuradius-0.96.2/include/radiusd.h
--- gnuradius-0.96.2.orig/include/radiusd.h	Tue Mar 19 22:32:00 2002
+++ gnuradius-0.96.2/include/radiusd.h	Tue Jun  4 17:25:20 2002
@@ -238,6 +238,7 @@
 extern UINT4 warning_seconds;
 extern int radius_pid;
 extern int use_dbm;
+extern int ace_client_ip;
 extern UINT4 myip;
 extern UINT4 warning_seconds;
 extern int auth_port;
@@ -319,6 +320,12 @@
 #ifdef USE_PAM
 int pam_pass(char *name, char *passwd, const char *pamauth, char **reply_msg);
 # define PAM_DEFAULT_TYPE    "radius"
+#endif
+
+/* securid.c */
+#ifdef USE_SECURID
+int securid_pass(char *name, char *passcode, char **reply_msg,
+		 RADIUS_REQ *radreq);
 #endif
 
 /* proxy.c */
diff -N -r -u gnuradius-0.96.2.orig/include/radsql.h gnuradius-0.96.2/include/radsql.h
--- gnuradius-0.96.2.orig/include/radsql.h	Tue Mar 19 22:32:00 2002
+++ gnuradius-0.96.2/include/radsql.h	Mon May 27 17:18:25 2002
@@ -19,7 +19,8 @@
 #define SQLT_MYSQL    1
 #define SQLT_POSTGRES 2
 #define SQLT_ODBC     3
-#define SQLT_MAX      4
+#define SQLT_SYBASE   4
+#define SQLT_MAX      5
 
 #ifdef USE_SQL
 
@@ -115,6 +116,11 @@
 extern SQL_DISPATCH_TAB odbc_dispatch_tab[];
 #else
 #define odbc_dispatch_tab NULL
+#endif
+#ifdef USE_SQL_SYBASE
+extern SQL_DISPATCH_TAB sybase_dispatch_tab[];
+#else
+#define sybase_dispatch_tab NULL
 #endif
 
 #else
diff -N -r -u gnuradius-0.96.2.orig/m4/lib.m4 gnuradius-0.96.2/m4/lib.m4
--- gnuradius-0.96.2.orig/m4/lib.m4	Tue Mar 19 22:32:00 2002
+++ gnuradius-0.96.2/m4/lib.m4	Fri Jun  7 17:52:30 2002
@@ -14,7 +14,7 @@
    do
       LIBS="$save_LIBS -L$path"
       AC_CHECK_LIB($1, $2,
-                   [rad_cv_lib_$1="$3 -L$path -l$1"
+                   [rad_cv_lib_$1="-L$path -l$1 $3"
                     break],
                    [rad_cv_lib_$1=no],$3)
    done
diff -N -r -u gnuradius-0.96.2.orig/radiusd/Makefile.am gnuradius-0.96.2/radiusd/Makefile.am
--- gnuradius-0.96.2.orig/radiusd/Makefile.am	Tue Mar 19 22:32:00 2002
+++ gnuradius-0.96.2/radiusd/Makefile.am	Thu May 23 15:55:06 2002
@@ -25,6 +25,7 @@
  log.c\
  menu.c\
  pam.c\
+ securid.c\
  proxy.c\
  radck.c\
  radius.c\
@@ -79,7 +80,7 @@
 	$(LINT.c) -u $(radiusd_LNS) -L../radlib -lrad
 
 SRCS=radiusd.c acct.c auth.c exec.c files.c menu.c sql.c\
-         config.y pam.c proxy.c radius.c\
+         config.y pam.c securid.c proxy.c radius.c\
          timestr.c version.c log.c stat.c\
          snmpserv.c shmem.c radutil.c rewrite.y\
          checkrad.c radck.c builddbm.c
diff -N -r -u gnuradius-0.96.2.orig/radiusd/Makefile.in gnuradius-0.96.2/radiusd/Makefile.in
--- gnuradius-0.96.2.orig/radiusd/Makefile.in	Wed Mar 20 19:52:03 2002
+++ gnuradius-0.96.2/radiusd/Makefile.in	Thu May 23 15:57:34 2002
@@ -129,7 +129,7 @@
 lispdir = @lispdir@
 
 sbin_PROGRAMS = radiusd
-radiusd_SOURCES =   acct.c  auth.c  builddbm.c  checkrad.c  config.c  config_kw.c  config_tok.h  debugmod.c  exec.c  files.c  log.c  menu.c  pam.c  proxy.c  radck.c  radius.c  radiusd.c  radutil.c  rewrite.c  scheme.c  shmem.c  snmpserv.c  sql.c  stat.c  timestr.c  version.c
+radiusd_SOURCES =   acct.c  auth.c  builddbm.c  checkrad.c  config.c  config_kw.c  config_tok.h  debugmod.c  exec.c  files.c  log.c  menu.c  pam.c securid.c  proxy.c  radck.c  radius.c  radiusd.c  radutil.c  rewrite.c  scheme.c  shmem.c  snmpserv.c  sql.c  stat.c  timestr.c  version.c
 
 
 radiusd_LDADD = @LEXLIB@ @SQLLIB@ ../snmplib/libradsnmp.a @RADIUSD_LDADD@ ../radlib/librad.la 
@@ -148,7 +148,7 @@
 @MAINTAINER_MODE_TRUE@LINTFLAGS = -gezra -DHAVE_CONFIG_H=1 -DMAINTAINER_MODE=1 $(INCLUDES)
 @MAINTAINER_MODE_TRUE@radiusd_LNS = $(radiusd_SOURCES:.c=.ln)
 
-@MAINTAINER_MODE_TRUE@SRCS = radiusd.c acct.c auth.c exec.c files.c menu.c sql.c         config.y pam.c proxy.c radius.c         timestr.c version.c log.c stat.c         snmpserv.c shmem.c radutil.c rewrite.y         checkrad.c radck.c builddbm.c
+@MAINTAINER_MODE_TRUE@SRCS = radiusd.c acct.c auth.c exec.c files.c menu.c sql.c         config.y pam.c securid.c proxy.c radius.c         timestr.c version.c log.c stat.c         snmpserv.c shmem.c radutil.c rewrite.y         checkrad.c radck.c builddbm.c
 mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs
 CONFIG_HEADER = ../config.h
 CONFIG_CLEAN_FILES = 
@@ -163,7 +163,7 @@
 radiusd_OBJECTS =  acct.$(OBJEXT) auth.$(OBJEXT) builddbm.$(OBJEXT) \
 checkrad.$(OBJEXT) config.$(OBJEXT) config_kw.$(OBJEXT) \
 debugmod.$(OBJEXT) exec.$(OBJEXT) files.$(OBJEXT) log.$(OBJEXT) \
-menu.$(OBJEXT) pam.$(OBJEXT) proxy.$(OBJEXT) radck.$(OBJEXT) \
+menu.$(OBJEXT) pam.$(OBJEXT) securid.$(OBJEXT) proxy.$(OBJEXT) radck.$(OBJEXT) \
 radius.$(OBJEXT) radiusd.$(OBJEXT) radutil.$(OBJEXT) rewrite.$(OBJEXT) \
 scheme.$(OBJEXT) shmem.$(OBJEXT) snmpserv.$(OBJEXT) sql.$(OBJEXT) \
 stat.$(OBJEXT) timestr.$(OBJEXT) version.$(OBJEXT)
@@ -381,6 +381,10 @@
 	../include/log.h ../include/mem.h ../include/radpaths.h \
 	../include/radsnmp.h
 pam.o: pam.c ../config.h ../include/debugmod.h ../include/radiusd.h \
+	../include/sysdep.h ../include/radius.h ../include/raddict.h \
+	../include/log.h ../include/mem.h ../include/radpaths.h \
+	../include/radsnmp.h
+securid.o: securid.c ../config.h ../include/debugmod.h ../include/radiusd.h \
 	../include/sysdep.h ../include/radius.h ../include/raddict.h \
 	../include/log.h ../include/mem.h ../include/radpaths.h \
 	../include/radsnmp.h
diff -N -r -u gnuradius-0.96.2.orig/radiusd/auth.c gnuradius-0.96.2/radiusd/auth.c
--- gnuradius-0.96.2.orig/radiusd/auth.c	Tue Mar 19 23:10:18 2002
+++ gnuradius-0.96.2/radiusd/auth.c	Tue Jun  4 17:24:16 2002
@@ -335,6 +335,19 @@
 #endif
 		break;
 
+	case DV_AUTH_TYPE_SECURID:
+#ifdef USE_SECURID
+		debug(1, ("  auth: SecurID"));
+		if (securid_pass(name, userpass, user_msg, radreq) != 0)
+			result = AUTH_FAIL;
+#else
+		radlog(L_ERR,
+		       _("%s: SecurID authentication not available"),
+		       name);
+		result = AUTH_NOUSER;
+#endif
+		break;
+
 	case DV_AUTH_TYPE_CRYPT_LOCAL:
 		debug(1, ("  auth: Crypt"));
 		if (real_password == NULL) {
diff -N -r -u gnuradius-0.96.2.orig/radiusd/config.kw gnuradius-0.96.2/radiusd/config.kw
--- gnuradius-0.96.2.orig/radiusd/config.kw	Tue Mar 19 22:32:00 2002
+++ gnuradius-0.96.2/radiusd/config.kw	Tue Jun  4 01:07:36 2002
@@ -33,6 +33,7 @@
 %%
 acct, T_ACCT
 acct-dir, T_ACCT_DIR
+aceclientip, T_ACECLIENTIP
 acl, T_ACL
 alert, T_SEVERITY, _Y(L_ALERT)
 allow, T_ALLOW,
diff -N -r -u gnuradius-0.96.2.orig/radiusd/config.y gnuradius-0.96.2/radiusd/config.y
--- gnuradius-0.96.2.orig/radiusd/config.y	Tue Mar 19 23:10:18 2002
+++ gnuradius-0.96.2/radiusd/config.y	Tue Jun  4 22:50:07 2002
@@ -154,7 +154,7 @@
 %token T_USEDBM T_CHECKRAD_ASSUME_LOGGED T_DELAY T_DETAIL T_HOST           
 %token T_EXEC_PROGRAM_GROUP T_EXEC_PROGRAM_USER T_LOAD T_LOAD_PATH T_LOG_DIR
 %token T_MAX_REQUESTS T_MESSAGE T_PORT T_REQUEST_CLEANUP_DELAY T_RETRY T_SPAWN 
-%token T_STRIP_NAMES T_TTL T_USERNAME_CHARS T_USR2DELAY              
+%token T_STRIP_NAMES T_TTL T_USERNAME_CHARS T_USR2DELAY T_ACECLIENTIP
 
 %token T_SOURCE_IP T_ACCT_DIR T_ACCT T_CNTL T_PROXY T_CHANNEL
 %token T_SYSLOG T_NOTIFY T_SNMP T_COMMUNITY T_ACL
@@ -200,6 +200,7 @@
                 | options_stmt
                 | notify_stmt
                 | usedbm_stmt
+                | aceclientip_stmt
                 | auth_stmt
                 | acct_stmt
                 | proxy_stmt 
@@ -732,6 +733,30 @@
 #endif
 		  }
                 ;
+
+	/* ACE Client IP support */
+
+aceclientip_stmt     : T_ACECLIENTIP T_BOOL
+                  {
+#ifdef USE_SECURID
+#ifdef USE_SECURID_LEGACY
+			  radlog(L_WARN,
+				 _("%s:%d: aceclientip statement ignored: library does not support SD_ClientCheck"),
+				 filename, line_num);
+#else
+			  ace_client_ip = $2;
+			  if (debug_config)
+				  radlog(L_DEBUG, _("ace client ip: %d"),
+					 ace_client_ip);
+#endif
+#else
+			  radlog(L_WARN,
+				 _("%s:%d: aceclientip statement ignored: radiusd compiled without SecurID support"),
+				 filename, line_num);
+#endif
+		  }
+                ;
+
 
 	/* SNMP server parameters */
 
diff -N -r -u gnuradius-0.96.2.orig/radiusd/radck.c gnuradius-0.96.2/radiusd/radck.c
--- gnuradius-0.96.2.orig/radiusd/radck.c	Tue Mar 19 22:32:00 2002
+++ gnuradius-0.96.2/radiusd/radck.c	Thu May 23 20:07:24 2002
@@ -354,10 +354,23 @@
 		break;
 
 	case DV_AUTH_TYPE_SECURID:
+#ifdef USE_SECURID
+		if (password || crypt_password) {
+			radlog(L_WARN,
+		  _("%s:%d: Password attribute ignored for this Auth-Type"),
+			       filename, line);
+		}
+		if (pass_loc) {
+			radlog(L_WARN,
+		  _("%s:%d: Password-Location attribute ignored for this Auth-Type"),
+			       filename, line);
+		}
+#else
 		radlog(L_ERR,
 		       _("%s:%d: Authentication type not supported"),
 		       filename, line);
 		errcnt++;
+#endif
 		break;
 		
 	case DV_AUTH_TYPE_SQL:
diff -N -r -u gnuradius-0.96.2.orig/radiusd/radius.c gnuradius-0.96.2/radiusd/radius.c
--- gnuradius-0.96.2.orig/radiusd/radius.c	Tue Mar 19 22:32:00 2002
+++ gnuradius-0.96.2/radiusd/radius.c	Mon May 27 17:21:42 2002
@@ -345,6 +345,9 @@
 	VALUE_PAIR	*prev;
 	VALUE_PAIR	*pair;
 	RADIUS_REQ	*radreq;
+#ifdef USE_NAS_IP_HACK
+	int		has_nas_addr;
+#endif
 
 	/*
 	 *	Pre-allocate the new request data structure
@@ -384,6 +387,9 @@
 	length -= AUTH_HDR_LEN;
 	first_pair = NULL;
 	prev = NULL;
+#ifdef USE_NAS_IP_HACK
+	has_nas_addr = 0;
+#endif
 
 	while (length > 0) {
 
@@ -412,6 +418,10 @@
 				length -= 6;
 			}
 		}
+#ifdef USE_NAS_IP_HACK
+		else if (attribute == DA_NAS_IP_ADDRESS)
+			has_nas_addr = 1;
+#endif
 
 		if ((attr = attr_number_to_dict(attribute)) == NULL) {
 			debug(1, ("Received unknown attribute %d", attribute));
@@ -474,6 +484,39 @@
 		ptr += attrlen;
 		length -= attrlen;
 	}
+#ifdef USE_NAS_IP_HACK
+	if (!has_nas_addr)
+	{
+		/* 	Some vendors fail to include the NAS-IP-Address
+		 *	field in the request.  This makes enforcing NAS
+		 *	specific controls difficult in many cases.  The
+		 *	following adds the source host as the NAS-IP-Address
+		 *	if it's missing.
+		 */
+		if ((attr = attr_number_to_dict(DA_NAS_IP_ADDRESS)) == NULL) {
+			debug(1, ("Failed to find standard attribute %d",
+				  DA_NAS_IP_ADDRESS));
+		} else {
+			pair = avp_alloc();
+			
+			pair->name = attr->name;
+			pair->attribute = attr->value;
+			pair->type = attr->type;
+			pair->prop = attr->prop;
+			pair->next = NULL;
+			pair->lvalue = host;
+
+			debug(10, ("recv: added: %s", format_pair(pair)));
+
+			if (first_pair == NULL) 
+				first_pair = pair;
+			else 
+				prev->next = pair;
+
+			prev = pair;
+		}
+	}
+#endif
 	radreq->request = first_pair;
 	return(radreq);
 }
diff -N -r -u gnuradius-0.96.2.orig/radiusd/radiusd.c gnuradius-0.96.2/radiusd/radiusd.c
--- gnuradius-0.96.2.orig/radiusd/radiusd.c	Tue Mar 19 23:30:53 2002
+++ gnuradius-0.96.2/radiusd/radiusd.c	Fri Jun  7 17:19:25 2002
@@ -149,6 +149,7 @@
 static int foreground; /* Stay in the foreground */
 static int spawn_flag; 
 int use_dbm = 0;
+int ace_client_ip = 0;
 int open_acct = 1;
 int auth_detail = 0;
 int acct_detail = 1;      
diff -N -r -u gnuradius-0.96.2.orig/radiusd/securid.c gnuradius-0.96.2/radiusd/securid.c
--- gnuradius-0.96.2.orig/radiusd/securid.c	Thu Jan  1 07:30:00 1970
+++ gnuradius-0.96.2/radiusd/securid.c	Thu Jun  6 20:38:58 2002
@@ -0,0 +1,500 @@
+/* This file is part of GNU RADIUS.
+   Copyright (C) 2001 Sergey Poznyakoff
+  
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+  
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+  
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
+
+#define RADIUS_MODULE_SECURID_C
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#ifdef USE_SECURID
+
+/** SecurID interface.
+ */
+
+#ifndef lint
+static char rcsid[] = "@(#) $Id: pam.c,v 1.9 2001/11/27 12:13:36 gray Exp $";
+#endif
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <time.h>
+#include <ctype.h>
+
+#include <radiusd.h>
+
+#ifdef USE_SECURID_LEGACY
+#include "sdi_athd.h"
+#include "sdi_size.h"
+#include "sdi_type.h"
+#include "sdi_defs.h"
+#include "sdconf.h"
+#include "sdacmvls.h"
+
+unsigned long strtoul ();
+
+union config_record configure;
+#else
+#include "acexport.h"
+#endif
+
+#ifndef SDACE_FILE
+#define SDACE_FILE "/etc/sdace.txt"
+#endif
+
+#ifdef USE_SECURID_LEGACY
+
+/** Fake the new (5.x onward) synchronous functions
+ */
+typedef struct SD_CLIENT *SDI_HANDLE;
+
+int AceInitialize(void)
+{
+	return creadcfg() < 0 ? 0 : 1;
+}
+
+int SD_Init(hdlp)
+	SDI_HANDLE *hdlp;
+{
+	SDI_HANDLE hdl;
+
+	*hdlp = hdl = calloc(1, sizeof(struct SD_CLIENT));
+	if (sd_init(hdl) == 0)
+		return ACM_OK;
+	free(hdl);
+
+	return 1;
+}
+
+int SD_Check(hdl, passcode, name)
+	SDI_HANDLE hdl;
+	char *passcode;
+	char *name;
+{
+	char *spasscode;
+	int ret;
+
+	/* sd_check overwrites the argument, but the 5.x SD_Check does not */
+
+	spasscode = estrdup(passcode);
+	ret = sd_check(spasscode, name, hdl);
+	memset(spasscode, '\0', strlen(spasscode));
+	efree(spasscode);
+
+	return ret;
+}
+
+int SD_ClientCheck(hdl, passcode, name, client)
+	SDI_HANDLE hdl;
+	char *passcode;
+	char *name;
+	unsigned long client;
+{
+	/* not implemented - treat as a normal non-client specific check */
+
+	return SD_Check(hdl, passcode, name);
+}
+
+int SD_Close(hdl)
+	SDI_HANDLE hdl;
+{
+
+	sd_close();
+	free(hdl);
+}
+#endif /* USE_SECURID_LEGACY */
+
+#ifdef USE_SECURID_RESEND_HACK
+
+/** See README for an explanation for this code.
+ */
+
+/** hash_passcode - generate a hash of the passcode using md5crypt so that
+ *  PINs (which are the first part of the passcode - except for the soft
+ *  token) are not exposed.
+ */
+static char *hash_passcode(passcode, usesalt)
+	char *passcode;
+	char *usesalt;
+{
+	int i;
+	char salt[9];
+	static char *m =
+	  "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+	static int srand_called = 0;
+
+	if (usesalt == NULL) {
+		strcpy(salt, "$1$");
+		if (!srand_called) {
+			srand(time(NULL));
+			srand_called = 1;
+		}
+        	for (i = 0; i < 5; i++)
+                	salt[i+3] = m[rand()%64];
+        	salt[8] = '\0';
+		usesalt = salt;
+	}
+	return md5crypt(passcode, usesalt);
+}
+
+struct databuf {
+	int len;
+	void *buf;
+};
+
+#define USE_SECURID_NDBM 1
+
+#ifdef USE_SECURID_NDBM
+
+#include <ndbm.h>
+#include <fcntl.h>
+#include <errno.h>
+
+typedef DBM *DBMH;
+
+/** Attempt to avoid multiple db access as this may corrupt the database.
+ *  Gives up after 1 sec.
+ */
+static void
+getlock()
+{
+	char *fpath, *flink, ffile[32];
+	int fd, pid, ret, i;
+	struct timeval tm;
+
+	sprintf(ffile, "securid.%d", getpid());
+	fpath = mkfilename(radius_dir, ffile);
+	debug(20, ("file: [%s]\n", fpath));
+
+	if ((fd = open(fpath, O_WRONLY | O_CREAT | O_TRUNC, 0600)) < 0)
+	{
+		debug(20, ("lock file <%s> open failed: %d", fpath, errno));
+		efree(fpath);
+		return;
+	}
+	close(fd);
+
+	flink = mkfilename(radius_dir, "securid.lock");
+	debug(20, ("link: [%s]\n", flink));
+
+	for (i = 0; i<20 && (ret=link(fpath, flink))<0 && errno==EEXIST; i++) {
+		tm.tv_sec = 0;  tm.tv_usec = 50000;
+		select(1, NULL, NULL, NULL, &tm);
+	}
+
+	if (ret < 0)
+		debug(20, ("failed to link: %d", errno));
+
+	unlink(fpath);
+	efree(fpath);
+	efree(flink);
+}
+
+static void
+releaselock()
+{
+	char *flink = mkfilename(radius_dir, "securid.lock");
+
+	debug(20, ("unlink: [%s]\n", flink));
+	efree(flink);
+	unlink(flink);
+}
+
+static DBMH
+open_securid_db(void)
+{
+	char *path;
+	DBMH dbh;
+
+	path = mkfilename(radius_dir, "securid");
+	getlock();
+	dbh = dbm_open(path, O_RDWR|O_CREAT, 0600);
+	if (dbh == NULL)
+	{
+		releaselock();
+		debug(1, ("Failed to create securid database: %s", path));
+	}
+	efree(path);
+
+	return dbh;
+}
+
+close_securid_db(DBMH dbh)
+{
+	dbm_close(dbh);
+	releaselock();
+}
+
+static void
+putdb(key, data)
+	char *key;
+	struct databuf *data;
+{
+	datum k, d;
+	DBMH dbmfile;
+
+	k.dptr = key;
+	k.dsize = strlen(key);
+	d.dptr = data->buf;
+	d.dsize = data->len;
+
+	if ((dbmfile = open_securid_db()) == NULL)
+		return;
+	dbm_store(dbmfile, k, d, DBM_REPLACE);
+	close_securid_db(dbmfile);
+}
+
+static struct databuf
+getdb(key)
+	char *key;
+{
+	datum k, d;
+	struct databuf b;
+	DBMH dbmfile;
+
+	k.dptr = key;
+	k.dsize = strlen(key);
+	b.len = 0;
+	b.buf = NULL;
+
+	if ((dbmfile = open_securid_db()) == NULL)
+		return b;
+	d = dbm_fetch(dbmfile, k);
+	b.len = d.dsize;
+	if (d.dsize > 0) {
+		b.buf = emalloc(d.dsize);
+		memcpy(b.buf, d.dptr, d.dsize);
+	}
+	else
+		b.buf = NULL;
+	close_securid_db(dbmfile);
+
+	return b;
+}
+
+#endif /* USE_SECURID_NDBM */
+
+/** check_resend - compare the current request name, passcode and ip address
+ *  with the last one for the user and assume a resend if all match and the
+ *  request is younger that auth.request-cleanup-delay seconds.
+ */
+char check_resend(name, passcode, tm, ipaddr)
+	char *name;
+	char *passcode;
+	time_t tm;
+	UINT4 ipaddr;
+{
+	char *path, *s, *t, *p, *f, *hash, *j;
+	int ret = 0;
+	struct databuf b;
+	time_t lt;
+	char flag;
+	UINT4 sip;
+
+	debug(20, ("check resend: name=%s tm=%ld", name, tm));
+
+	b = getdb(name);
+	if (b.buf == NULL || b.len == 0)
+	{
+		debug(20, ("no data for: %s", name));
+		return '\0';
+	}
+	
+	s = (char *)b.buf;
+	t = s + strlen(s) + 1;
+	p = t + strlen(t) + 1;
+	f = p + strlen(p) + 1;
+	if (f - s > b.len)
+	{
+		debug(20, ("malformed data: name=%s len=%d(%d)", name,
+			   b.len, f-s));
+		efree(b.buf);
+		return '\0';
+	}
+	flag = *f;
+	debug(20, ("saved: name=%s hash=%s time=%s ip=%s flag=%s", name, s,
+		   t, p, f));
+
+	lt = atol(t);
+	sip = strtoul(p, &j, 10);
+
+	if (tm - lt > request_class[R_AUTH].cleanup_delay)
+	{
+		debug(20, ("%d second timeout - not resend: %ld - %ld = %ld",
+			   request_class[R_AUTH].cleanup_delay, tm, lt, tm-lt));
+		efree(b.buf);
+		return '\0';
+	}
+
+	if (ipaddr != sip)
+	{
+		debug(20, ("ip addresses differ: %08x != %08x", ipaddr, sip));
+		efree(b.buf);
+		return '\0';
+	}
+
+	hash = hash_passcode(passcode, s);
+	debug(20, ("passcode hash: hash=%s salt=%s", hash, s));
+
+	if (strcmp(hash, s) == 0)
+	{
+		debug(20, ("same passcode, returning flag: %c", flag));
+		efree(b.buf);
+		return flag;
+	}
+	debug(20, ("passcode differ, not resend"));
+
+	efree(b.buf);
+	return '\0';
+
+	return ret;
+}
+
+void save_resend_info(name, passcode, tm, ip, flag)
+	char *name;
+	char *passcode;
+	time_t tm;
+	UINT4 ip;
+	char flag;
+{
+	char stm[20], sip[20];
+	char *data, *s, *hash;
+	int len;
+	struct databuf dat;
+
+	hash = hash_passcode(passcode, NULL);
+	sprintf(stm, "%ld", tm);
+	sprintf(sip, "%lu", ip);
+	debug(20, ("saving: name=%s hash=%s time=%s ip=%s flag=%c",
+		  name, hash, stm, sip, flag));
+	len = strlen(hash) + strlen(stm) + strlen(sip) + 1 + 4;
+
+	s = data = emalloc(len);
+	strcpy(s, hash);
+	s += strlen(s);
+	*s++ = '\0';
+
+	strcpy(s, stm);
+	s += strlen(s);
+	*s++ = '\0';
+
+	strcpy(s, sip);
+	s += strlen(s);
+	*s++ = '\0';
+
+	*s++ = flag;
+	*s++ = '\0';
+
+	dat.len = len;
+	dat.buf = data;
+
+	putdb(name, dat);
+	efree(data);
+}
+
+#endif /* USE_SECURID_RESEND_HACK */
+
+int
+securid_pass(name, passcode, reply_msg, radreq)
+	char *name;
+	char *passcode;
+	char **reply_msg;
+	RADIUS_REQ *radreq;
+{
+	char buf[1024];
+	SDI_HANDLE acehdl;
+	int status;
+	time_t t;
+
+	debug(1, ("securid_pass: %s", name));
+	
+	time(&t);
+#ifdef USE_SECURID_RESEND_HACK
+	switch (check_resend(name, passcode, t, radreq->ipaddr)) {
+	case '\0':
+		break;
+	case 'n':
+		return 1;
+	case 'y':
+		return 0;
+	}
+#endif /* USE_SECURID_RESEND_HACK */
+
+	*reply_msg = NULL;
+	if (!AceInitialize()) {
+		debug(1, ("AceInitialize failed"));
+	  	*reply_msg = make_string("internal error: AceInitialize");
+		return 1;
+	}
+
+	if ((status = SD_Init(&acehdl)) != ACM_OK) {
+		debug(1, ("SD_init failed: %d", status));
+	  	*reply_msg = make_string("internal error: SD_Init");
+		return 1;
+	}
+
+	if (ace_client_ip)
+		status = SD_ClientCheck(acehdl, passcode, name, radreq->ipaddr);
+	else
+		status = SD_Check(acehdl, passcode, name);
+	SD_Close(acehdl);
+
+	switch (status)
+	{
+	  case ACM_OK:
+		debug(1, ("%s/%s, access allowed",name,passcode));
+#ifdef USE_SECURID_RESEND_HACK
+		save_resend_info(name, passcode, t, radreq->ipaddr, 'y');
+#endif 
+		return 0;
+
+#ifndef USE_SECURID_LEGACY
+	  case ACE_UNDEFINED_PASSCODE:
+	  case ACE_UNDEFINED_USERNAME:
+	  case ACE_ERR_INVALID_HANDLE:
+		debug(1, ("%s/%s, internal error - status: %d",
+			  name, passcode, status));
+	  	*reply_msg = make_string("internal error");
+		break;
+#endif
+	  case ACM_ACCESS_DENIED:
+		debug(1, ("%s/%s, access denied",name,passcode));
+	  	*reply_msg = make_string("access denied");
+		break;
+	  case ACM_NEXT_CODE_REQUIRED:
+		debug(1,("%s/%s, next token mode",name,passcode));
+		radlog(L_INFO, _("SecurID token in next token mode: %s"), name);
+	  	*reply_msg = make_string("next token mode");
+		break;
+	  case ACM_NEW_PIN_REQUIRED:
+		debug(1,("%s/%s, new pin mode", name, passcode));
+		radlog(L_INFO, _("SecurID token in new pin mode: %s"), name);
+	  	*reply_msg = make_string("new pin mode");
+		break;
+	}
+#ifdef USE_SECURID_RESEND_HACK
+	save_resend_info(name, passcode, t, radreq->ipaddr, 'n');
+#endif
+	return 1;
+}
+
+#endif
diff -N -r -u gnuradius-0.96.2.orig/radiusd/version.c gnuradius-0.96.2/radiusd/version.c
--- gnuradius-0.96.2.orig/radiusd/version.c	Tue Mar 19 22:32:00 2002
+++ gnuradius-0.96.2/radiusd/version.c	Thu Jun  6 14:03:17 2002
@@ -85,6 +85,18 @@
 #if defined(USE_PAM)
 	"USE_PAM",
 #endif
+#if defined(USE_SECURID)
+	"USE_SECURID",
+#endif
+#if defined(USE_SECURID_LEGACY)
+	"USE_SECURID_LEGACY",
+#endif
+#if defined(USE_SECURID_RESEND_HACK)
+	"USE_SECURID_RESEND_HACK",
+#endif
+#if defined(USE_NAS_IP_HACK)
+	"USE_NAS_IP_HACK",
+#endif
 #if defined(USE_DBM)
 # if USE_DBM == DBM_DBM	
 	"USE_DBM=DBM",
@@ -100,6 +112,9 @@
 #endif	
 #ifdef USE_SQL_ODBC
 	"USE_SQL_ODBC",
+#endif	
+#ifdef USE_SQL_SYBASE
+	"USE_SQL_SYBASE",
 #endif	
 #if defined(USE_SNMP)
 	"USE_SNMP",
diff -N -r -u gnuradius-0.96.2.orig/sql/Makefile.am gnuradius-0.96.2/sql/Makefile.am
--- gnuradius-0.96.2.orig/sql/Makefile.am	Tue Mar 19 22:32:05 2002
+++ gnuradius-0.96.2/sql/Makefile.am	Mon May 27 17:25:33 2002
@@ -14,7 +14,7 @@
 EXTRA_LTLIBRARIES=libsql.la
 
 INCLUDES= -I$(top_srcdir)/include @INCLUDEPATH@
-libsql_la_SOURCES=disp.c mysql.c postgres.c odbc.c
+libsql_la_SOURCES=disp.c mysql.c postgres.c odbc.c sybase.c
 
 SUFFIXES=.S .c .ln .o .s
 
diff -N -r -u gnuradius-0.96.2.orig/sql/Makefile.in gnuradius-0.96.2/sql/Makefile.in
--- gnuradius-0.96.2.orig/sql/Makefile.in	Wed Mar 20 19:52:00 2002
+++ gnuradius-0.96.2/sql/Makefile.in	Mon May 27 17:26:06 2002
@@ -133,7 +133,7 @@
 EXTRA_LTLIBRARIES = libsql.la
 
 INCLUDES = -I$(top_srcdir)/include @INCLUDEPATH@
-libsql_la_SOURCES = disp.c mysql.c postgres.c odbc.c
+libsql_la_SOURCES = disp.c mysql.c postgres.c odbc.c sybase.c
 
 SUFFIXES = .S .c .ln .o .s
 
@@ -151,7 +151,7 @@
 LIBS = @LIBS@
 libsql_la_LDFLAGS = 
 libsql_la_LIBADD = 
-libsql_la_OBJECTS =  disp.lo mysql.lo postgres.lo odbc.lo
+libsql_la_OBJECTS =  disp.lo mysql.lo postgres.lo odbc.lo sybase.lo
 CFLAGS = @CFLAGS@
 COMPILE = $(CC) $(DEFS) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
 LTCOMPILE = $(LIBTOOL) --mode=compile $(CC) $(DEFS) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
@@ -310,6 +310,9 @@
 	../include/radius.h ../include/sysdep.h ../include/raddict.h \
 	../include/log.h ../include/mem.h ../include/radsql.h
 postgres.lo postgres.o : postgres.c ../config.h ../include/debugmod.h \
+	../include/radius.h ../include/sysdep.h ../include/raddict.h \
+	../include/log.h ../include/mem.h ../include/radsql.h
+sybase.lo sybase.o : sybase.c ../config.h ../include/debugmod.h \
 	../include/radius.h ../include/sysdep.h ../include/raddict.h \
 	../include/log.h ../include/mem.h ../include/radsql.h
 
diff -N -r -u gnuradius-0.96.2.orig/sql/disp.c gnuradius-0.96.2/sql/disp.c
--- gnuradius-0.96.2.orig/sql/disp.c	Tue Mar 19 22:32:05 2002
+++ gnuradius-0.96.2/sql/disp.c	Thu Jun  6 22:07:01 2002
@@ -23,10 +23,27 @@
 #include <radsql.h>
 
 static SQL_DISPATCH_TAB *sql_dispatch_tab[] = {
+#ifdef USE_SQL_MYSQL
+	mysql_dispatch_tab,
+#else
+# ifdef USE_SQL_POSTGRES 
+	postgres_dispatch_tab,
+# else
+#   ifdef USE_SQL_ODBC
+	odbc_dispatch_tab,
+#   else
+#     ifdef USE_SQL_SYBASE
+	sybase_dispatch_tab,
+#     else
 	NULL,
+#     endif
+#   endif
+# endif
+#endif
 	mysql_dispatch_tab,
 	postgres_dispatch_tab,
 	odbc_dispatch_tab,
+	sybase_dispatch_tab,
 };
 
 #define NDISP sizeof(sql_dispatch_tab)/sizeof(sql_dispatch_tab[0])
diff -N -r -u gnuradius-0.96.2.orig/sql/sybase.c gnuradius-0.96.2/sql/sybase.c
--- gnuradius-0.96.2.orig/sql/sybase.c	Thu Jan  1 07:30:00 1970
+++ gnuradius-0.96.2/sql/sybase.c	Fri Jun  7 16:34:19 2002
@@ -0,0 +1,968 @@
+/* This file is part of GNU RADIUS.
+   Copyright (C) 2000,2001 Sergey Poznyakoff
+   Copyright (C) 2002 Steve Bleazard
+  
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+  
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+  
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.*/
+
+#define RADIUS_MODULE_SYBASE_C
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#ifndef lint
+static char rcsid[] =
+ "@(#) $Id: sybase.c,v 1.3 2001/08/22 13:18:04 gray Exp $" ;
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <radiusd.h>
+#include <radsql.h>
+
+#ifdef USE_SQL_SYBASE
+
+#include <ctpublic.h>
+
+struct sybase_connection {
+	CS_CONTEXT *context;
+	CS_CONNECTION *connection;
+	CS_COMMAND *cmd;
+};
+
+static int rad_sybase_reconnect(int type, struct sql_connection *conn);
+static void rad_sybase_disconnect(struct sql_connection *conn);
+static int rad_sybase_query(struct sql_connection *conn, char *query, int *return_count);
+static char *rad_sybase_getpwd(struct sql_connection *conn, char *query);
+static void *rad_sybase_exec(struct sql_connection *conn, char *query);
+static char *rad_sybase_column(void *data, int ncol);
+static int rad_sybase_next_tuple(struct sql_connection *conn, void *data);
+static void rad_sybase_free(struct sql_connection *conn, void *data);
+
+/* a suitable exit macro to handle the closedown and any messages */
+
+#define CLEANUP_ON_FAIL(conn, ret, func, dbg) \
+	if (ret != CS_SUCCEED) { \
+		debug(1, dbg); \
+		radlog(L_ERR, _(func " failed: %d"), ret); \
+		if (conn != NULL) { \
+			rad_sql_need_reconnect(conn->type); \
+		} \
+	}
+
+#define RETURN_ON_FAIL(conn, ret, func, dbg, retval) \
+	if (ret != CS_SUCCEED) { \
+		CLEANUP_ON_FAIL(conn, ret, func, dbg) \
+		return retval; \
+	}
+
+/** Error handler helper functions 
+ */
+static void
+force_close_sybase_connection(sconn)
+	struct sybase_connection *sconn;
+{
+	CS_RETCODE ret;
+
+	if (sconn->cmd) {
+		ret = ct_cmd_drop(sconn->cmd);
+		if (ret != CS_SUCCEED)
+		{
+			debug(1, ("ct_cmd_drop failed: %d", ret));
+			radlog(L_ERR, _("ct_cmd_drop failed: %d"), ret);
+		}
+		sconn->cmd = NULL;
+	}
+	if (sconn->connection) {
+		ret = ct_close(sconn->connection, CS_FORCE_CLOSE);
+		if (ret != CS_SUCCEED)
+		{
+			debug(1, ("ct_close failed: %d", ret));
+			radlog(L_ERR, _("ct_close failed: %d"), ret);
+		}
+		ret = ct_con_drop(sconn->connection);
+		if (ret != CS_SUCCEED)
+		{
+			debug(1, ("ct_con_drop failed: %d", ret));
+			radlog(L_ERR, _("ct_con_drop failed: %d"), ret);
+		}
+		sconn->connection = NULL;
+	}
+}
+
+
+/** Error callback functions - just log errors */
+
+CS_RETCODE
+clientmsg_callback(context, conn, emsgp)
+	CS_CONTEXT *context;
+	CS_CONNECTION *conn;
+	CS_CLIENTMSG *emsgp;
+{
+	debug(1, ("sybase client library error: severity(%ld) layer(%ld) "
+		  "origin(%ld) number(%ld), %s",
+		  (long) CS_SEVERITY(emsgp->severity),
+		  (long) CS_LAYER(emsgp->msgnumber),
+		  (long) CS_ORIGIN(emsgp->msgnumber),
+		  (long) CS_NUMBER(emsgp->msgnumber),
+		  emsgp->msgstring));
+
+	radlog(L_ERR,
+		  _("sybase error: severity(%ld) layer(%ld) origin(%ld) "
+		    "number(%ld), %s"),
+		  (long) CS_SEVERITY(emsgp->severity),
+		  (long) CS_LAYER(emsgp->msgnumber),
+		  (long) CS_ORIGIN(emsgp->msgnumber),
+		  (long) CS_NUMBER(emsgp->msgnumber),
+		  emsgp->msgstring);
+
+	if (emsgp->osstringlen > 0) {
+		debug(1, ("sybase client library OS error: %ld (%s)",
+			  emsgp->osnumber, emsgp->osstring));
+		radlog(L_ERR, _("sybase os error: %ld (%s)"), 
+			      (long) emsgp->osnumber, emsgp->osstring);
+	}
+
+	return CS_SUCCEED;
+}
+
+CS_RETCODE
+csmsg_callback(context, conn, emsgp)
+	CS_CONTEXT *context;
+	CS_CLIENTMSG *emsgp;
+{
+	debug(1, ("sybase CS library error: severity(%ld) layer(%ld) "
+		  "origin(%ld) number(%ld), %s",
+		  (long) CS_SEVERITY(emsgp->severity),
+		  (long) CS_LAYER(emsgp->msgnumber),
+		  (long) CS_ORIGIN(emsgp->msgnumber),
+		  (long) CS_NUMBER(emsgp->msgnumber),
+		  emsgp->msgstring));
+
+	radlog(L_ERR,
+		  _("sybase error: severity(%ld) layer(%ld) origin(%ld) "
+		    "number(%ld), %s"),
+		  (long) CS_SEVERITY(emsgp->severity),
+		  (long) CS_LAYER(emsgp->msgnumber),
+		  (long) CS_ORIGIN(emsgp->msgnumber),
+		  (long) CS_NUMBER(emsgp->msgnumber),
+		  emsgp->msgstring);
+
+	if (emsgp->osstringlen > 0) {
+		debug(1, ("sybase CS library OS error: %ld (%s)",
+			  (long)emsgp->osnumber, emsgp->osstring));
+		radlog(L_ERR, _("sybase os error: %ld (%s)"), 
+			      (long) emsgp->osnumber, emsgp->osstring);
+	}
+
+	return CS_SUCCEED;
+}
+
+CS_RETCODE
+servermsg_callback(context, conn, smsgp)
+	CS_CONTEXT *context;
+	CS_CONNECTION *conn;
+	CS_SERVERMSG *smsgp;
+{
+	char *s, *p, *sf, *pf;
+
+	s = p = sf = pf = "";
+	if (smsgp->svrnlen > 0) {
+		s = smsgp->svrname;
+		sf = ", server: ";
+	}
+	if (smsgp->proclen > 0) {
+		s = smsgp->proc;
+		sf = ", proc: ";
+	}
+
+	debug(1, ("sybase server message: number(%ld) severity(%ld) "
+		  "state(%ld) line(%ld)%s%s%s%s, %s",
+		  smsgp->msgnumber, smsgp->severity, smsgp->state, 
+		  smsgp->line, sf, s, pf, p, smsgp->text));
+
+	radlog(L_ERR, _("sybase server message: number(%ld) severity(%ld) "
+		        "state(%ld) line(%ld)%s%s%s%s, %s"),
+			smsgp->msgnumber, smsgp->severity, smsgp->state, 
+			smsgp->line, sf, s, pf, p, smsgp->text);
+
+	return CS_SUCCEED;
+
+
+}
+
+/** Helper functions */
+/* TODO */
+
+static CS_COMMAND *
+sybase_alloc_cmd(sybcon, conn) 
+	struct sybase_connection *sybcon;
+	struct sql_connection *conn;
+{
+	CS_RETCODE ret;
+	CS_COMMAND *cmd;
+
+	ret = ct_cmd_alloc(sybcon->connection, &cmd);
+	RETURN_ON_FAIL(conn, ret, "ct_cmd_alloc",
+			("ct_cmnd_alloc failed: %d", ret), NULL);
+
+	return cmd;
+}
+
+static int
+sybase_db_use(sybcon, database, conn)
+	struct sybase_connection *sybcon;
+	char *database;
+	struct sql_connection *conn;
+{
+	CS_COMMAND *cmd = sybase_alloc_cmd(sybcon, conn);
+	CS_RETCODE ret;
+	CS_INT restype;
+	char statement[255];
+
+	if(!cmd)
+		return -1;
+
+	sprintf(statement, "use %s", database);
+	ret = ct_command(cmd, CS_LANG_CMD, statement, CS_NULLTERM, CS_UNUSED);
+	RETURN_ON_FAIL(conn, ret, "ct_command",
+			("ct_command failed: %d", ret), -1);
+
+	ret = ct_send(cmd);
+	RETURN_ON_FAIL(conn, ret, "ct_send",
+			("ct_send failed: %d", ret), -1);
+
+	while ((ret = ct_results(cmd, &restype)) == CS_SUCCEED) {
+		if (restype == CS_CMD_FAIL)
+			radlog(L_ERR, _("'%s' failed: %d"), statement, ret);
+	}
+	ret = ct_cmd_drop(cmd);
+	RETURN_ON_FAIL(conn, ret, "ct_cmd_drop",
+			("ct_cmd_drop failed: %d", ret), -1);
+
+	return 0;
+}
+
+
+/* ************************************************************************* */
+/* Interface routines */
+int
+rad_sybase_reconnect(type, conn)
+	int    type;
+	struct sql_connection *conn;
+{
+	struct sybase_connection *sybcon;
+	char *dbname;
+	CS_RETCODE ret;
+#define XRETURN_ON_FAIL(ret, func, dbg) \
+	if (ret != CS_SUCCEED) { \
+		debug(1, dbg); \
+		radlog(L_ERR, _(func " failed: %d"), ret); \
+		force_close_sybase_connection(sybcon); \
+		if (sybcon->context != NULL) { \
+			ct_exit(sybcon->context, CS_FORCE_EXIT); \
+			cs_ctx_drop(sybcon->context); \
+			sybcon->context = NULL; \
+		} \
+		conn->data = NULL; \
+		conn->connected = 0; \
+		efree(sybcon); \
+		return -1; \
+	}
+
+	switch (type) {
+	case SQL_AUTH:
+		dbname = sql_cfg.auth_db;
+		break;
+	case SQL_ACCT:
+		dbname = sql_cfg.acct_db;
+		break;
+	}
+
+	/* Create a context and initialise */
+
+	conn->data = sybcon = emalloc(sizeof *sybcon);
+	conn->connected = 0;
+	sybcon->context = NULL;
+
+	debug(10, ("Initialising sybase cs/ct library"));
+	ret = cs_ctx_alloc(CS_VERSION_100, &sybcon->context);
+	XRETURN_ON_FAIL(ret,"cs_ctx_alloc",("cs_ctx_alloc failed: %d",ret));
+
+	ret = ct_init(sybcon->context, CS_VERSION_100);
+	XRETURN_ON_FAIL(ret, "ct_init", ("ct_init failed: %d", ret));
+
+	/* install callbacks */
+
+	debug(10, ("Installing callbacks"));
+	ret = cs_config(sybcon->context, CS_SET, CS_MESSAGE_CB,
+			(CS_VOID *)csmsg_callback, CS_UNUSED, NULL);
+	XRETURN_ON_FAIL(ret, "cs_config",
+		("cs_config failed installing cs callback: %d", ret));
+
+	ret = ct_callback(sybcon->context, NULL, CS_SET, CS_CLIENTMSG_CB,
+			  (CS_VOID *)clientmsg_callback);
+	XRETURN_ON_FAIL(ret, "ct_callback",
+		("ct_callback failed installing client callback: %d", ret));
+
+	ret = ct_callback(sybcon->context, NULL, CS_SET, CS_SERVERMSG_CB,
+			  (CS_VOID *)servermsg_callback);
+	XRETURN_ON_FAIL(ret, "ct_callback",
+		("ct_callback failed installing server callback: %d", ret));
+
+	/* Connect to the server, first alloc a connection structure and
+	 * set the username and password.
+	 */
+
+	debug(10, ("connecting to server '%s' with username/pw: %s/%s",
+		   sql_cfg.server, sql_cfg.login, sql_cfg.password));
+	ret = ct_con_alloc(sybcon->context, &sybcon->connection);
+	XRETURN_ON_FAIL(ret,"ct_con_alloc",("ct_con_alloc failed: %d", ret));
+	
+	ret = ct_con_props(sybcon->connection, CS_SET, CS_USERNAME,
+			   sql_cfg.login, CS_NULLTERM, NULL);
+	XRETURN_ON_FAIL(ret, "ct_con_props",
+			("ct_con_props(username) failed: %d", ret));
+
+	ret = ct_con_props(sybcon->connection, CS_SET, CS_PASSWORD,
+			   sql_cfg.password, CS_NULLTERM, NULL);
+	XRETURN_ON_FAIL(ret, "ct_con_props",
+			("ct_con_props(password) failed: %d", ret));
+
+	/** Now connect to the server.  Note that the server connection
+	 *  details (ipaddress, port etc) are contained in the Sybase
+	 *  "interfaces" file and the server name is simply a reference into
+	 *  this file.
+	 */
+
+	ret = ct_connect(sybcon->connection, sql_cfg.server, CS_NULLTERM);
+	XRETURN_ON_FAIL(ret, "ct_connect", ("ct_connect failed: %d", ret));
+
+	/* connection complete - mark the connection connected */
+
+	debug(10, ("connected to database"));
+	conn->connected = 1;
+	
+	/* force the correct database */
+
+	if (sybase_db_use(sybcon, dbname, conn) < 0)
+	{
+		conn->data = NULL;
+		conn->connected = 0;
+		efree(sybcon);
+		return -1;
+	}
+
+	return 0;
+
+#undef XRETURN_ON_FAIL
+}
+
+void 
+rad_sybase_disconnect(conn)
+	struct sql_connection *conn;
+{
+	struct sybase_connection *sybcon;
+	CS_RETCODE ret;
+
+	conn->connected = 0;
+	if (!conn->data)
+		return;
+	sybcon = conn->data;
+	conn->data = NULL;
+
+	if (sybcon->context != NULL) {
+		force_close_sybase_connection(sybcon);
+
+		ret = ct_exit(sybcon->context, CS_FORCE_EXIT);
+		if (ret != CS_SUCCEED)
+		{
+			debug(1, ("ct_exit failed: %d", ret));
+			radlog(L_ERR, _("ct_exit failed: %d"), ret);
+		}
+
+		cs_ctx_drop(sybcon->context);
+		if (ret != CS_SUCCEED)
+		{
+			debug(1, ("cs_ctx_drop failed: %d", ret));
+			radlog(L_ERR, _("cs_ctx_drop failed: %d"), ret);
+		}
+		sybcon->context = NULL;
+	}
+
+	efree(sybcon);
+}
+
+static int
+sybase_do_query(conn, query)
+	struct sql_connection *conn;
+	char *query;
+{
+	struct sybase_connection *sybcon;
+	CS_RETCODE ret;
+
+	sybcon = conn->data;
+	if (!conn->connected || sybcon == NULL || sybcon->context == NULL)
+	{
+		if (rad_sybase_reconnect(conn->type, conn) < 0)
+			return -1;
+		sybcon = conn->data;
+	}
+
+	if ((sybcon->cmd = sybase_alloc_cmd(sybcon, conn)) == NULL)
+		return -1;
+
+	ret = ct_command(sybcon->cmd,CS_LANG_CMD,query,CS_NULLTERM,CS_UNUSED);
+	RETURN_ON_FAIL(conn, ret, "ct_command",
+			("ct_command failed: %d", ret), -1);
+
+	ret = ct_send(sybcon->cmd);
+	RETURN_ON_FAIL(conn, ret, "ct_send", ("ct_send failed: %d", ret), -1);
+
+	return 0;
+}
+
+/** rad_sybase_query - action a query without returning data.  This is usually
+ *  used for insert queries.  Return the number of rows effected.
+ */
+int
+rad_sybase_query(conn, query, return_count)
+	struct sql_connection *conn;
+	char *query;
+	int *return_count;
+{
+	struct sybase_connection *sybcon;
+	CS_RETCODE ret, res_ret;
+	CS_INT result_type;
+	CS_INT count, ocnt, fcnt;
+
+	if (!conn)
+		return -1;
+	debug(2, ("query: %s", query));
+
+	if (sybase_do_query(conn, query) < 0)
+		return -1;
+
+	sybcon = conn->data;
+	count = -1;
+
+	/* run through the rows until complete */
+
+	while ((res_ret = ct_results(sybcon->cmd, &result_type))==CS_SUCCEED) {
+		switch (result_type) {
+		case CS_ROW_RESULT:
+		case CS_PARAM_RESULT:
+		case CS_STATUS_RESULT:
+		case CS_CURSOR_RESULT:
+		case CS_COMPUTE_RESULT:
+			debug(1, ("results return in rad_sybase_query"));
+			while ((ret = ct_fetch(sybcon->cmd, CS_UNUSED,
+						CS_UNUSED, CS_UNUSED,
+						&fcnt)) == CS_SUCCEED ||
+				ret == CS_ROW_FAIL) {
+
+				/* report any row errors */
+				if (ret == CS_ROW_FAIL) {
+					debug(1, ("ct_fetch row error", ret));
+					radlog(L_ERR, _("ct_fetch row error"));
+				}
+			}
+			if (ret != CS_END_DATA)
+			{
+				RETURN_ON_FAIL(conn, ret, "ct_fetch",
+					("ct_fetch failed: %d", ret), -1);
+			}
+			break;
+		case CS_CMD_SUCCEED:
+		case CS_CMD_DONE:
+			debug(1, ("ct_results cmd success"));
+			ret = ct_res_info(sybcon->cmd, CS_ROW_COUNT, &count,
+						CS_UNUSED, &ocnt);
+			RETURN_ON_FAIL(conn, ret, "ct_res_info",
+					("ct_res_info failed: %d", ret), -1);
+			break;
+		case CS_CMD_FAIL:
+			debug(1, ("ct_results cmd fail"));
+			break;
+		default:
+			RETURN_ON_FAIL(conn, CS_FAIL, "ct_results",
+					("ct_results failed: %d", res_ret),-1);
+
+		}
+	}
+
+	if (res_ret == CS_END_RESULTS) {
+		if (count == -1 &&
+		    (result_type==CS_CMD_SUCCEED || result_type==CS_CMD_DONE)) {
+			ret = ct_res_info(sybcon->cmd, CS_ROW_COUNT, &count,
+						CS_UNUSED, &ocnt);
+			RETURN_ON_FAIL(conn, ret, "ct_res_info",
+					("ct_res_info failed: %d", ret), -1);
+		}
+	} else {
+		RETURN_ON_FAIL(conn, res_ret, "ct_results",
+				("ct_results failed: %d", res_ret), -1);
+	}
+
+	if (count == -1)
+		count = 0;
+
+	ret = ct_cmd_drop(sybcon->cmd);
+	sybcon->cmd = NULL;
+	RETURN_ON_FAIL(conn, ret, "ct_cmd_drop",
+			("ct_cmd_drop failed: %d", ret), -1);
+
+	debug(2, ("query effected %d row(s)", count));
+	if (return_count)
+		*return_count = count;
+	
+	return 0;
+}
+
+char *
+rad_sybase_getpwd(conn, query)
+	struct sql_connection *conn;
+	char *query;
+{
+	struct sybase_connection *sybcon;
+	CS_RETCODE ret, res_ret;
+	CS_INT result_type;
+	CS_INT fcnt;
+	CS_DATAFMT passwd_data;
+	CS_SMALLINT indicator;
+	char passwd[512+1];
+	char *return_passwd = NULL;
+
+	if (!conn)
+		return NULL;
+
+	debug(1, ("query: %s", query));
+
+	if (sybase_do_query(conn, query) < 0)
+		return NULL;
+
+	sybcon = conn->data;
+
+	/* run through the rows getting the first row data */
+
+	while ((res_ret = ct_results(sybcon->cmd, &result_type))==CS_SUCCEED) {
+		switch (result_type) {
+		case CS_ROW_RESULT:
+		case CS_PARAM_RESULT:
+		case CS_STATUS_RESULT:
+		case CS_CURSOR_RESULT:
+		case CS_COMPUTE_RESULT:
+			/* Bind the first column to the password buffer */
+
+			passwd_data.datatype = CS_CHAR_TYPE;
+			passwd_data.format = CS_FMT_NULLTERM;
+			passwd_data.maxlength = sizeof passwd - 1;
+			passwd_data.count = 1;
+			passwd_data.locale = NULL;
+			ret = ct_bind(sybcon->cmd, 1, &passwd_data, passwd,
+				      NULL, &indicator);
+			RETURN_ON_FAIL(conn, ret, "cs_bind",
+					("ct_bind failed: %d", ret), NULL);
+
+			while ((ret = ct_fetch(sybcon->cmd, CS_UNUSED,
+						CS_UNUSED, CS_UNUSED,
+						&fcnt)) == CS_SUCCEED ||
+				ret == CS_ROW_FAIL) {
+
+				/* report any row errors */
+				if (ret == CS_ROW_FAIL) {
+					debug(1, ("ct_fetch row error", ret));
+					radlog(L_ERR, _("ct_fetch row error"));
+				}
+				if (return_passwd == NULL && indicator >= 0)
+					return_passwd = estrdup(passwd);
+			}
+			if (ret != CS_END_DATA) {
+				RETURN_ON_FAIL(conn, ret, "ct_fetch",
+					("ct_fetch failed: %d", ret), NULL);
+			}
+			break;
+		case CS_CMD_SUCCEED:
+		case CS_CMD_DONE:
+		case CS_CMD_FAIL:
+			break;
+		default:
+			RETURN_ON_FAIL(conn, CS_FAIL, "ct_results",
+					("ct_results failed: %d",res_ret),NULL);
+
+		}
+	}
+
+	ret = ct_cmd_drop(sybcon->cmd);
+	sybcon->cmd = NULL;
+	RETURN_ON_FAIL(conn, ret, "ct_cmd_drop",
+			("ct_cmd_drop failed: %d", ret), NULL);
+
+	debug(2, ("password %sfound", return_passwd ? "" : "not "));
+	debug(3, ("returning password:[%s]", return_passwd?return_passwd:""));
+
+	return return_passwd;
+}
+
+#define SYBASE_EXEC_STATE_START		0
+#define SYBASE_EXEC_STATE_RESULTS	1
+#define SYBASE_EXEC_STATE_FETCH		2
+#define SYBASE_EXEC_STATE_END		3
+#define SYBASE_EXEC_STATE_ERROR		4
+
+struct colinfo {
+	CS_SMALLINT indicator;
+	char *data;
+	struct colinfo *next;
+};
+
+struct sybase_exec_data {
+	int state;
+	CS_RETCODE results_ret;
+	CS_INT result_type;
+	CS_RETCODE fetch_ret;
+	struct colinfo *coldata;
+	CS_COMMAND *cmd;
+};
+
+void *
+rad_sybase_exec(conn, query)
+	struct sql_connection *conn;
+	char *query;
+{
+	struct sybase_connection *sybcon;
+	struct sybase_exec_data *sinfo;
+
+	if (!conn)
+		return NULL;
+	debug(2, ("query: %s", query));
+
+	if (sybase_do_query(conn, query) < 0)
+		return NULL;
+	
+	sybcon = conn->data;
+	sinfo = emalloc(sizeof *sinfo);
+	sinfo->state = SYBASE_EXEC_STATE_START;
+	sinfo->cmd = sybcon->cmd;
+	sinfo->results_ret = CS_SUCCEED;
+	sinfo->result_type = CS_CMD_SUCCEED;
+	sinfo->fetch_ret = CS_SUCCEED;
+	sinfo->coldata = NULL;
+	sybcon->cmd = NULL;
+
+	return (void*)sinfo;
+}
+
+char *
+rad_sybase_column(data, ncol)
+	void *data;
+	int ncol;
+{
+	struct sybase_exec_data *sinfo;
+	struct colinfo *cp;
+	int i;
+
+	if (!data)
+		return NULL;
+
+	sinfo = (struct sybase_exec_data *)data;
+	for (i=0, cp=sinfo->coldata; i<ncol && cp!=NULL; cp=cp->next, i++)
+		;
+	
+	if (cp == NULL) {
+		radlog(L_ERR,
+		       _("too few columns returned (%d req'd)"), ncol);
+		return NULL;
+	}
+
+	debug(10, ("col %d:[%s]", ncol, cp->data ? cp->data : ""));
+	return cp->data;
+}
+
+#define MIN(a, b) (a < b ? a : b)
+#define MAX(a, b) (a > b ? a : b)
+#define MAX_CHAR_BUF 1024
+
+static CS_INT
+data_len(column)
+CS_DATAFMT *column;
+{
+	CS_INT len;
+
+	switch ((int) column->datatype) {
+	case CS_CHAR_TYPE:
+	case CS_VARCHAR_TYPE:
+	case CS_TEXT_TYPE:
+	case CS_IMAGE_TYPE:
+		len = MIN(column->maxlength, MAX_CHAR_BUF);
+		break;
+	case CS_BINARY_TYPE:
+	case CS_VARBINARY_TYPE:
+		len = MIN((2 * column->maxlength) + 2, MAX_CHAR_BUF);
+		break;
+	case CS_BIT_TYPE:
+	case CS_TINYINT_TYPE:
+		len = 3;
+		break;
+	case CS_SMALLINT_TYPE:
+		len = 6;
+		break;
+	case CS_INT_TYPE:
+		len = 11;
+		break;
+	case CS_REAL_TYPE:
+	case CS_FLOAT_TYPE:
+		len = 20;
+		break;
+	case CS_MONEY_TYPE:
+	case CS_MONEY4_TYPE:
+		len = 24;
+		break;
+	case CS_DATETIME_TYPE:
+	case CS_DATETIME4_TYPE:
+		len = 30;
+		break;
+	case CS_NUMERIC_TYPE:
+	case CS_DECIMAL_TYPE:
+		len = (CS_MAX_PREC + 2);
+		break;
+	default:
+		len = column->maxlength;
+		break;
+	}
+    
+	return MAX(strlen(column->name) + 1, len);
+}
+
+static struct colinfo *
+describe(cmd)
+	CS_COMMAND *cmd;
+{
+	
+	CS_RETCODE ret;
+	CS_INT num_cols, i;
+	CS_DATAFMT datafmt;
+	struct colinfo start, *curr;
+
+	ret = ct_res_info(cmd, CS_NUMDATA, &num_cols, CS_UNUSED, NULL);
+	if (ret != CS_SUCCEED)
+	{
+		debug(1, ("ct_res_info failed: %d", ret));
+		radlog(L_ERR, _("ct_res_info failed: %d"), ret);
+		return NULL;
+	}
+	if (num_cols <= 0)
+	{
+		debug(1, ("num_cols <= 0: %d", num_cols));
+		radlog(L_ERR, _("num_cols <= 0: %d"), num_cols);
+		return NULL;
+	}
+
+	debug(10, ("num_cols: %d", num_cols));
+	for (curr = &start, i = 0; i < num_cols; i++)
+	{
+		if((ret = ct_describe(cmd, (i+1), &datafmt)) != CS_SUCCEED)
+		{
+			debug(1, ("ct_describe failed: %d", ret));
+			radlog(L_ERR, _("ct_describe failed: %d"), ret);
+			break;
+		}
+		datafmt.maxlength = data_len(&datafmt) + 1;
+		datafmt.datatype = CS_CHAR_TYPE;
+		datafmt.format   = CS_FMT_NULLTERM;
+		debug(10, ("col %d len: %d", i, datafmt.maxlength));
+
+		curr->next = emalloc(sizeof(struct colinfo));
+		curr = curr->next;
+		curr->data = emalloc(datafmt.maxlength);
+
+		if ((ret = ct_bind(cmd, (i+1), &datafmt, curr->data, NULL,
+					    &curr->indicator)) != CS_SUCCEED)
+		{
+			debug(1, ("ct_bind failed: %d", ret));
+			radlog(L_ERR, _("ct_bind failed: %d"), ret);
+			break;
+		}
+	}
+
+	curr->next = NULL;
+	return start.next;
+}
+
+static void
+free_coldata(data)
+	struct colinfo *data;
+{
+	struct colinfo *curr, *junk;
+
+	for (curr = data; curr != NULL; ) {
+		if (curr->data != NULL)
+			efree(curr->data);
+		junk = curr;
+		curr = curr->next;
+		efree(junk);
+	}
+}
+
+/*ARGSUSED*/
+int
+rad_sybase_next_tuple(conn, data)
+	struct sql_connection *conn;
+	void *data;
+{
+	struct sybase_connection *sybcon;
+	struct sybase_exec_data *sinfo;
+	CS_INT fcnt;
+	int loop = 1;
+
+	if (!conn || !data)
+		return -1;
+
+	sybcon = conn->data;
+	sinfo = (struct sybase_exec_data *)data;
+
+	while (loop) {
+		debug(10, ("state = %d", sinfo->state));
+		switch (sinfo->state) {
+		case SYBASE_EXEC_STATE_START:
+		case SYBASE_EXEC_STATE_RESULTS:
+			sinfo->state = SYBASE_EXEC_STATE_RESULTS;
+			sinfo->results_ret =
+				ct_results(sinfo->cmd, &sinfo->result_type);
+			switch (sinfo->results_ret) {
+			case CS_SUCCEED:
+				switch (sinfo->result_type)
+				{
+				case CS_ROW_RESULT:
+				case CS_PARAM_RESULT:
+				case CS_STATUS_RESULT:
+				case CS_CURSOR_RESULT:
+				case CS_COMPUTE_RESULT:
+					/* Setup the column bindings */
+					if (sinfo->coldata != NULL)
+						free_coldata(sinfo->coldata);
+					sinfo->coldata = describe(sinfo->cmd);
+					sinfo->state = SYBASE_EXEC_STATE_FETCH;
+					break;
+				case CS_CMD_SUCCEED:
+				case CS_CMD_DONE:
+					break;
+				case CS_CMD_FAIL:
+					/* error reported via callback */
+					break;
+				default:
+					debug(2, ("ct_results err: %d",
+						  sinfo->result_type));
+					break;
+				}
+				break;
+			case CS_END_RESULTS:
+				sinfo->state = SYBASE_EXEC_STATE_END;
+				break;
+			default:
+				sinfo->state = SYBASE_EXEC_STATE_ERROR;
+				RETURN_ON_FAIL(conn, sinfo->results_ret,
+					"ct_results", ("ct_results failed: %d",
+					 	sinfo->results_ret), -1);
+				loop = 0;
+				return -1;
+			}
+			break;
+		case SYBASE_EXEC_STATE_FETCH:
+			loop = 0;
+			sinfo->fetch_ret = ct_fetch(sinfo->cmd, CS_UNUSED,
+						    CS_UNUSED, CS_UNUSED,&fcnt);
+			switch (sinfo->fetch_ret) {
+			case CS_SUCCEED:
+				break;
+			case CS_ROW_FAIL:
+				debug(2, ("ct_fetch row fail: %d",
+					  sinfo->fetch_ret));
+				break;
+			case CS_END_DATA:
+				sinfo->state = SYBASE_EXEC_STATE_RESULTS;
+				loop = 1;
+				break;
+			default:
+				debug(2,("ct_fetch fail: %d",sinfo->fetch_ret));
+				sinfo->state = SYBASE_EXEC_STATE_ERROR;
+				loop = 1;
+				break;
+			}
+			break;
+		case SYBASE_EXEC_STATE_END:
+			loop = 0;
+			return 1;
+			break;
+		case SYBASE_EXEC_STATE_ERROR:
+			loop = 0;
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+/*ARGSUSED*/
+void
+rad_sybase_free(conn, data)
+	struct sql_connection *conn;
+	void *data;
+{
+	struct sybase_connection *sybcon;
+	struct sybase_exec_data *sinfo;
+	CS_RETCODE ret;
+
+	if (!data)
+		return;
+
+	debug(20, ("freeing command data"));
+	sinfo = (struct sybase_exec_data *)data;
+
+	if (conn && conn->data)
+	{
+		if (sinfo->state != SYBASE_EXEC_STATE_END) {
+			debug(20, ("cancelling pending command"));
+			ret = ct_cancel(NULL, sinfo->cmd, CS_CANCEL_ALL);
+			debug(20, ("cancelled pending command"));
+		}
+		ret = ct_cmd_drop(sinfo->cmd);
+	}
+
+	if (sinfo->coldata)
+		free_coldata(sinfo->coldata);
+
+	debug(20, ("data freed"));
+
+	efree(sinfo);
+}
+
+SQL_DISPATCH_TAB sybase_dispatch_tab[] = {
+	"sybase",     
+	3000,
+	rad_sybase_reconnect,
+	rad_sybase_disconnect,
+	rad_sybase_query,
+	rad_sybase_getpwd,
+	rad_sybase_exec,
+	rad_sybase_column,
+	rad_sybase_next_tuple,
+	rad_sybase_free
+};
+
+#endif
+