Import of tac_plus.v8.tar.gz: 173206 bytes, md5: rel_F4_0_3_alpha_8
authorshort <>
Thu, 5 Apr 2001 10:18:40 +0000 (10:18 +0000)
committershort <>
Thu, 5 Apr 2001 10:18:40 +0000 (10:18 +0000)
b0a433f65638106853064919692351a7

73 files changed:
CHANGES [new file with mode: 0644]
INSTALL [new file with mode: 0644]
Makefile.in [new file with mode: 0644]
README.PAM [new file with mode: 0644]
acct.c [new file with mode: 0644]
authen.c [new file with mode: 0644]
author.c [new file with mode: 0644]
choose_authen.c [new file with mode: 0644]
config.c [new file with mode: 0644]
config.guess [new file with mode: 0644]
config.h.in [new file with mode: 0644]
config.sub [new file with mode: 0644]
configure [new file with mode: 0755]
configure.in [new file with mode: 0644]
convert.pl [new file with mode: 0755]
db.c [new file with mode: 0644]
db_mysql.c [new file with mode: 0644]
db_null.c [new file with mode: 0644]
db_pgsql.c [new file with mode: 0644]
default_fn.c [new file with mode: 0644]
default_v0_fn.c [new file with mode: 0644]
do_acct.c [new file with mode: 0644]
do_author.c [new file with mode: 0644]
dump.c [new file with mode: 0644]
enable.c [new file with mode: 0644]
encrypt.c [new file with mode: 0644]
expire.c [new file with mode: 0644]
expire.h [new file with mode: 0644]
generate_passwd.c [new file with mode: 0644]
hash.c [new file with mode: 0644]
install-sh [new file with mode: 0644]
ldap.c [new file with mode: 0644]
ldap.h [new file with mode: 0644]
ldap_author.c [new file with mode: 0644]
ldap_author.h [new file with mode: 0644]
maxsess.c [new file with mode: 0644]
md4.c [new file with mode: 0644]
md4.h [new file with mode: 0644]
md5.c [new file with mode: 0644]
md5.h [new file with mode: 0644]
mschap.h [new file with mode: 0644]
packet.c [new file with mode: 0644]
parse.c [new file with mode: 0644]
parse.h [new file with mode: 0644]
programs.c [new file with mode: 0644]
pw.c [new file with mode: 0644]
pwlib.c [new file with mode: 0644]
regexp.3 [new file with mode: 0644]
regexp.c [new file with mode: 0644]
regexp.h [new file with mode: 0644]
regmagic.h [new file with mode: 0644]
report.c [new file with mode: 0644]
sendauth.c [new file with mode: 0644]
sendpass.c [new file with mode: 0644]
skey_fn.c [new file with mode: 0644]
stamp-h [new file with mode: 0644]
tac_pam.c [new file with mode: 0644]
tac_plus.1 [new file with mode: 0644]
tac_plus.c [new file with mode: 0644]
tac_plus.cfg [new file with mode: 0644]
tac_plus.h [new file with mode: 0644]
tac_plus.init [new file with mode: 0644]
tac_plus.rotate [new file with mode: 0644]
tac_plus.sql [new file with mode: 0644]
tac_regexp.3 [new file with mode: 0644]
tac_regexp.c [new file with mode: 0644]
tac_regexp.h [new file with mode: 0644]
tac_regmagic.h [new file with mode: 0644]
tcpwrap.c [new file with mode: 0644]
time_limit.c [new file with mode: 0644]
time_limit.h [new file with mode: 0644]
users_guide [new file with mode: 0644]
utils.c [new file with mode: 0644]

diff --git a/CHANGES b/CHANGES
new file mode 100644 (file)
index 0000000..fdab6d2
--- /dev/null
+++ b/CHANGES
@@ -0,0 +1,255 @@
+>Changes from Release 0.0 to release 0.1
+---------------------------------------
+
+You must now say "default attribute = permit" instead of 
+default authorization = permit" when configuring service
+defaults
+
+You must now say "default svc = permit" instead of "default
+authorization = permit" when configuring service defaults for a user.
+
+When authorizing enable requests, the daemon used to prompt for a
+username which it then ignored. It no longer prompts for a username.
+
+Fix recursion issues with service and command lookups. They are now
+fully recursive c.f. password lookups.
+
+Add debugging output to password verification to provide information
+about expiry processing.
+
+Keep track of longest hash chain we create, for fine tuning. Hash all
+keywords into a keyword table instead of doing linear lookup.
+
+Update users_guide to reflect the new configuration syntax.
+
+The convert.pl script now generates the new configuration file syntax.
+
+Accounting code now honours the "more" flag.
+
+
+Changes from Release 0.1 to release 0.2
+---------------------------------------
+
+You can now send a SIGHUP to the daemon to cause it to reinitialize
+itself and re-read its CONFIG file. There is a new debugging flag
+devoted to this section of the code.
+
+Node types are now pretty-printed in debug output.
+
+The conversion script "convert.pl" now will not print out an expires
+field if it doesn't think the syntax of the field is correct. It also
+now ignores blank lines in its input files.
+
+When doing authorization, the NAS supplied attribute "cmd=" is now
+correctly ignored. This would previously have caused exec
+authorization to be denied.
+
+Changes from Release 0.2 to release 0.3
+---------------------------------------
+Warn when not invoked as uid 0.
+Improved Usage message
+Add make install target
+
+Changes from Release 0.3 to release 0.4
+---------------------------------------
+Add TAC_PLUS_PIDFILE to makefile per Andy Linton's suggestion.
+Fix bug in authorization code (protocol field needs to be
+uppercase) which prevented authorization from working.
+
+Changes from Release 0.4 to release 0.5
+---------------------------------------
+Add pre and post authorization calls to shell commands.
+Minor bugfixes and code cleanup
+The "More" bit in accounting records is now honoured.
+Fix a bug in convert.pl
+Redo accounting output routines. You can now name the accounting file
+in the configuration file.
+Change "svc" to "service" and "proto" to "protocol".
+You can use any string to name a ppp protocol, even one which doesn't yet exist.
+Add PPP/LCP special case processing
+Revised authorization algorithm (see user's guide)
+Add hex debug flag to allow skipping hex in packet dumps.
+Update user's guide to reflect changes
+
+Changes from Release 0.4 to release 1.0
+---------------------------------------
+Changed format of syslog messages to make writing scripts easier
+Added ability to use cleartext passwords instead of DES passwords
+Updated man page to reflect the fact that we use SIGUSR1 to re-read
+the config file. SIGHUP is now ignored.
+Updated the users guide.
+
+Changes from Release 1.0 to release 1.1
+---------------------------------------
+Release 1.1 corresponds to RCS version 1.64 of tac_plus 
+(see tac_plus -v)
+
+A typo in the Solaris section of the Makefile has been fixed.
+
+The keyword 'des' has been introduced which must be used before all
+des encrypted passwords.
+
+The keyword 'password' has been changed to 'login', so
+    password = f23sac783n
+has become
+    login = des f23sac783n
+
+The convert.pl script knows about these changes.
+
+arap and chap now require the keyword 'cleartext' in front of their
+passwords.
+
+A cleartext, per-user, global password can now be configured, which
+works for login, arap and chap.
+
+The users_guide has been updated to include a list of all A/V pairs
+recognised by IOS 10.3(3) code.
+
+Some solaris binaries have been provided as a courtesy.
+
+Changes from Release 1.1 to release 2.0
+---------------------------------------
+generate_password.pl has been removed in favour of a C program
+generate_passwd.c
+
+The version number reported by tac_plus has been changed to agree with the
+release number. This is why the version has jumped to 2.0
+
+skey was broken by changes made in 1.1. These are now fixed.
+
+Documentation has been added for the authorization AV pairs supported
+by IOS releases 10.3(3) and 11.0.
+
+Changes from Release 2.0 to release 2.1
+---------------------------------------
+There are now Makefile definitions for most of the major platforms.
+
+Minor changes to remove some spurious debugging output.
+
+A prematurely closed NAS connection will now call the authentication
+function with the abort flag set, so that it can do any clean up it
+requires.
+
+syslog messages will contain the string "unknown" for usernames and
+ports which are NULL, so that the messages always contain a fixed
+number of fields.
+
+The authentication code has been rearranged to better reflect the
+structure of the API.
+
+The "default user = permit" directive is still accepted but is now
+deprecated in favour of "default authorization = permit".
+
+A bug in the handling of substring AV pairs which caused the attribute
+"addr" to erroneously match "addr-pool" has been fixed.
+
+Added new files: enable.c generate_passwd.c skey_fn.c 
+
+New #defines have been added to make it easier to port tacacs+ to new
+systems.
+
+Many more iterations are allowed before an error is declared.
+
+Changes from Release 2.1 to release 2.2
+---------------------------------------
+The expiry field in the shadow file on Solaris machines is now
+honored, if it exists.
+
+Added TAC_PLUS_AUTHEN_SVC_NASI
+
+Changes from Release 2.2 to release F3.0.13
+-------------------------------------------
+NEW REVISION OF THE PROTOCOL corresponding to tacacs+.spec.v1.63.ps
+(which see) to increase security in the case of compromised keys.
+
+Inbound pap logins and outbound pap password are now configurable as
+separate entries for each principal.  Inbound pap logins are now
+declared by using a "pap = " configuration directive. Outbound PAP is
+now configured using "opap =".
+
+Substantial code rearrangement of authentication routines.
+
+Cleartext passwords can be up to 255 characters in length (previously
+only the first 8 characters were used).
+
+default service = permit is now fully recursive and now allows you to
+say default service = deny in case you belong to a group where the
+default is to permit.
+
+Include backward compatibility with old revision of the protocol
+(prior to v1.63).
+
+post_authorization scripts are now invoked for command authorization.
+
+Better sanity checking of authorization and accounting packets.
+
+The API has changed slightly. All character string fields in the
+identity structure are now allocated from the heap and can be up to
+255 bytes long (instead of being character arrays of 32 and 64 bytes,
+as specified in the API document revision 1.30 or earlier).
+
+Double quotes can now appear inside strings if they are escaped with a
+backslash.
+
+Added code which limits the number of simultaneous sessions a user can
+have (see MAXSESS in the user's guide).
+
+The accounting "more" bit is gone (It was deprecated from the spec).
+
+Hooks are now in so that if you have DES code, you can do ARAP more
+securely, per the new protocol. 
+
+The packet read/write routines now handle exceptions more gracefully.
+
+Lots of stuff added to the user's guide.
+
+If you use a port number other than the default, the pidfile has the
+port number appended to it, in case you are running multiple daemons.
+We also now remove the pidfile when the daemon terminates via SIGTERM.
+
+user = DEFAULT has been added, deprecating "default authorization =
+permit". See the user's guide.
+
+Arbitrary service types can now be configured in the config file.
+
+REARMSIGNAL has been added for those systems which install one-shot
+signal handlers which need to be rearmed after use (LINUX, HPUX).
+
+A \n can now be embedded within strings.
+
+Concede defeat. Allow SIGHUP as synonym for SIGUSR1.
+
+Avoid symbol buffer overflow by checking the maximum length of a
+string or token.
+
+Make peer DNS lookup on incoming connections optional.
+
+Do not close socket when servicing a SIGHUP
+
+Fix a bad bug where service/cmd declarations which were not contiguous
+were parsed but ignored (reported by Gabor Kiss).
+
+Patch maxsessions to not count the current port on a different
+NAS. Add various other fixes to maxsession code.
+
+Add timeout to finger read routine.
+
+Changes from release F3.0.13 to F4.0.1
+-------------------------------------------
+Added MSCHAP routines
+
+CSCdi37706 exposed a bug in command authorization on the daemon.
+Change assemble_args so it returns "" if there are no command
+arguments.
+
+Changes from release F4.0.1 to F4.0.2
+-------------------------------------------
+Fix fseek problem in maxsess code
+
+Changes from release F4.0.2 to F4.0.3
+-------------------------------------------
+Add option for wtmp file logging in accounting
+Added -DGLIBC for Linux.
+Support PAP with des encrypted passwords
+Support a return code of 3 for external authorization scripts
+
diff --git a/INSTALL b/INSTALL
new file mode 100644 (file)
index 0000000..3884e10
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,20 @@
+This is modified verision of tacacs+
+The installation is very basic.
+just type :
+./configure
+(if you like to learn the configure parameters try --help option with configure )
+make install 
+
+Thats all.
+
+This packet consist some helper file  to using tacacs+ easly.
+tac_plus.cfg: Example tacacs+ config file
+README.PAM: For PAM supported systems 
+tac_plus.init: For starting and stoping tacacs+
+tac_plus.sql: table description (Mysql) 
+
+Sorry for this short description.:(
+
+Please feel free to send comments and suggestion to my mail address
+
+devrim seral <devrim@tef.gazi.edu.tr>
diff --git a/Makefile.in b/Makefile.in
new file mode 100644 (file)
index 0000000..b5af4b6
--- /dev/null
@@ -0,0 +1,177 @@
+# Please NOTE:  None of the TACACS code available here comes with any
+# warranty or support.
+# Copyright (c) 1995-1998 by Cisco systems, Inc.
+# 
+# Permission to use, copy, modify, and distribute this software for any
+# purpose and without fee is hereby granted, provided that this
+# copyright and permission notice appear on all copies of the software and
+# supporting documentation, the name of Cisco Systems, Inc. not be used
+# in advertising or publicity pertaining to distribution of the
+# program without specific prior permission, and notice be given
+# in supporting documentation that modification, copying and distribution is by
+# permission of Cisco Systems, Inc.   
+# 
+# Cisco Systems, Inc. makes no representations about the suitability of this
+# software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS IS''
+# AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT
+# LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE. 
+
+SHELL = /bin/sh
+VPATH = @srcdir@
+
+top_srcdir = @top_srcdir@
+srcdir = @srcdir@
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+bindir = $(exec_prefix)/bin
+sbindir = $(exec_prefix)/sbin
+infodir = $(prefix)/info
+libdir = $(prefix)/lib/gnudl
+mandir = $(prefix)/man/man1
+
+CC = @CC@
+CPPFLAGS = @CPPFLAGS@ 
+FLAGS = $(CPPFLAGS) @CFLAGS@
+LDFLAGS = @LDFLAGS@
+OSLIBS = @LIBS@
+DEFINES = @DEFINES@
+OS = @OS@
+PIDFILE = @PIDFILE@
+DB = @DB@
+
+# For AIX
+# See /usr/lpp/bos/bsdport on your system for details of how to define bsdcc
+# CC=bsdcc
+# OS=-DAIX
+
+# For HP/UX uncomment the following line
+# OS=-DHPUX
+
+# For MIPS, uncomment the following line
+# OS=-DMIPS
+
+# For Solaris (SUNOS 5.3, 5.4, 5.5, 5.6) uncomment the following two lines
+#OS=-DSOLARIS
+#OSLIBS=-lsocket -lnsl
+
+# For FreeBSD
+# OS=-DFREEBSD
+# You may also need to add:
+# OSLIBS=-lcrypt
+# NOTE: If you want your password encryption to be compatible with
+# e.g. SunOS, you may need to instead use:
+# OSLIBS=-ldescrypt
+
+# For LINUX
+# OS=-DLINUX
+#
+# On REDHAT 5.0 systems, or systems that use the new glibc,
+# you might instead need the following:
+#OS=-DLINUX -DGLIBC
+#OSLIBS=-lcrypt
+
+# Athough invoked as root, most of the time you don't want tac_plus to
+# be running as root. If USERID and GROUPID are set, tac_plus will
+# attempt change to run as that user & group after reading the
+# configuration file and obtaining a privileged socket. If you always
+# want tac_plus to run as root, then just comment out the FLAGS line.
+
+# USERID  = 1500
+# GROUPID = 25
+# FLAGS   = -DTAC_PLUS_USERID=$(USERID) -DTAC_PLUS_GROUPID=$(GROUPID)
+
+# Definitions for SKEY functionality
+# DEFINES = -DSKEY
+# LIBS = ../crimelab/skey/src/libskey.a
+# INCLUDES = -I../crimelab/skey/src
+
+# Debugging flags
+DEBUG = 
+
+# Opt flags
+OPT_FLAGS = 
+
+# Enforce a limit on maximum sessions per user. See the user's guide
+# for more information.
+#MAXSESS = -DMAXSESS
+
+# Microsoft CHAP extension support. See the user's guide for more
+# information.
+# MSCHAP = -DMSCHAP
+# MSCHAP_DES = -DMSCHAP_DES
+# MSCHAP_MD4_SRC = md4.c
+
+# On startup, tac_plus creates the file /etc/tac_plus.pid (if
+# possible), containing its process id. Uncomment and modify the
+# following line to change this filename
+
+#PIDFILE = -DTAC_PLUS_PIDFILE=\"/var/run/tac_plus.pid\" 
+
+#
+# End of customisable section of Makefile
+#
+
+CFLAGS = $(DEBUG) $(OPT_FLAGS) $(DEFINES) $(INCLUDES) $(FLAGS) $(OS) $(PIDFILE) $(LDFLAGS) $(DB) 
+
+HFILES = expire.h parse.h regmagic.h md5.h regexp.h tac_plus.h 
+
+SRCS = acct.c authen.c author.c choose_authen.c config.c do_acct.c \
+       do_author.c dump.c encrypt.c expire.c $(MSCHAP_MD4_SRC) md5.c \
+       packet.c report.c sendauth.c tac_plus.c utils.c pw.c hash.c \
+       parse.c regexp.c programs.c enable.c pwlib.c default_fn.c \
+       skey_fn.c default_v0_fn.c sendpass.c maxsess.c tac_pam.c \
+       db.c db_null.c db_mysql.c db_pgsql.c tcpwrap.c ldap.c time_limit.c
+
+OBJS = $(SRCS:.c=.o)
+
+all:
+       @echo "Please edit the Makefile and then make tac_plus"
+
+tac_plus: $(OBJS) $(LIBS) generate_passwd
+       $(CC) -o tac_plus $(CFLAGS) $(OBJS) $(LIBS) $(OSLIBS) 
+       strip tac_plus
+
+purecov: $(OBJS) $(LIBS)
+       purecov -follow-child-processes -handle-signals=SIGTERM \
+           -append-logfile -log-file=purecov.log \
+           -cache-dir=`pwd` \
+           $(CC) -o tac_plus $(CFLAGS) $(OBJS) $(LIBS) $(OSLIBS)
+
+purify: $(OBJS) $(LIBS)
+       purify -follow-child-processes=yes -log-file=./tac_plus_purify.log \
+           -handle-signals=SIGTERM -cache-dir=. \
+           $(CC) -o tac_plus $(CFLAGS) $(OBJS) $(LIBS) $(OSLIBS)
+
+generate_passwd:
+       $(CC) $(CFLAGS) -o generate_passwd generate_passwd.c $(OSLIBS)
+
+clean:
+       -rm -f *.o *~ *.BAK core tac_plus generate_passwd
+
+distclean: clean
+       -rm -f Makefile config.h config.status config.cache config.log
+
+clean_obj:
+       -rm -f *.o *~ *.BAK
+
+make_dir:
+       mkdir -p  $(bindir)
+       mkdir -p  $(sbindir)
+       mkdir -p  $(mandir)
+install: make_dir
+       cp tac_plus $(sbindir)/
+       cp generate_passwd $(bindir)/
+       cp tac_plus.1 $(mandir)/
+
+rpm_install: make_dir
+       install -c -m 0755 tac_plus $(sbindir)/
+       install -c -m 0755 generate_passwd $(sbindir)/
+       install -c -m 0644 tac_plus.1 $(mandir)/
+
+depend:
+       makedepend $(CFLAGS) $(SRCS)
+
+# DO NOT DELETE THIS LINE -- make depend depends on it.
+
diff --git a/README.PAM b/README.PAM
new file mode 100644 (file)
index 0000000..0c1d468
--- /dev/null
@@ -0,0 +1,40 @@
+Tacacs+ with PAM Authentication and Authorization support.
+
+patch version 0.4 (7 February 2000)
+Now Tacacs+ PAM Authorization Working.. 
+I am add some function to do it and also rename tac_pam_acct.c file to  tam_pam.c 
+But we must do more test ..:p 
+devrim(devrim@tef.gazi.edu.tr)
+
+patch version 0.3  (17 November 1999)
+tac_pam_auth is MT-safe now.
+Tacacs reports pam error strings.
+More examples into the tac_plus.conf. Corrected the version number at start-up.
+
+patch version 0.2   (31 August 1999)
+Pammified default authentication. If -DUSE_PAM used, I hope that PAM is the default
+system authentication method. The PAM service used is the name of the passwd_file (???).
+Yes, it's terrible, but I have not time to spend for changing
+default authentication = file /etc/passwd
+         in
+default authentication = pam name_of_service..........any volunteer ???
+As usual, look at the tac_plus.conf file.
+
+patch version 0.1    (August 1999)
+I introduced an optional new authetication method via PAM. This makes authentication
+modular and external to the tacacs+ code.
+I have not implemented pam-authorization because the existence of pre and post autorization
+calls.
+In order to pass tacacs+ attributes to the pam modules, I use the PAM_RUSER item
+for storing the rem_addr. Have a look to tac_pam_auth.c for the details.
+As you can see, pam-authentication only works for pap and login authetication-types:
+I would like to extend it to the default authetication case, but the code seems so
+cryptic :-(.......
+You can choose the pam service by the configuration file,for a simple example look
+into tac_plus.conf.
+
+
+
+Max Liccardo <ravel@tiscalinet.it>
+
+
diff --git a/acct.c b/acct.c
new file mode 100644 (file)
index 0000000..315064f
--- /dev/null
+++ b/acct.c
@@ -0,0 +1,182 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+#include "tac_plus.h"
+
+/*
+ *  Come here when we receive an Start Accounting packet
+ */
+
+void account();
+
+/* For  DB accounting */
+#ifdef DB
+int db_acct();
+#endif /* DB */
+
+void
+accounting(pak)
+u_char *pak;
+{
+    struct acct *acct_pak;
+    u_char *p;
+    HDR *hdr;
+    u_char *read_packet();
+    int i, len;
+
+    if (debug & DEBUG_ACCT_FLAG)
+       report(LOG_DEBUG, "Start accounting request");
+
+    hdr = (HDR *) pak;
+    acct_pak = (struct acct *) (pak + TAC_PLUS_HDR_SIZE);
+
+    /* Do some sanity checking on the packet */
+
+    /* arg counts start here */
+    p = pak + TAC_PLUS_HDR_SIZE + TAC_ACCT_REQ_FIXED_FIELDS_SIZE;
+
+    /* Length checks */
+    len = TAC_ACCT_REQ_FIXED_FIELDS_SIZE;
+    len += acct_pak->user_len + acct_pak->port_len + 
+       acct_pak->rem_addr_len + acct_pak->arg_cnt;
+    for (i = 0; i < (int)acct_pak->arg_cnt; i++) {
+       len += p[i];
+    }
+
+    if (len != ntohl(hdr->datalength)) {
+       send_error_reply(TAC_PLUS_ACCT, NULL);
+       return;
+    }
+
+    account(pak);
+
+    free(pak);
+}
+
+void
+account(pak)
+u_char *pak;
+{
+    struct acct *acct_pak;
+    u_char *p, *argsizep;
+    struct acct_rec rec;
+    struct identity identity;
+    char **cmd_argp;
+    int i, errors, status;
+
+    acct_pak = (struct acct *) (pak + TAC_PLUS_HDR_SIZE);
+
+    /* Fill out accounting record structure */
+    bzero(&rec, sizeof(struct acct_rec));
+
+    if (acct_pak->flags & TAC_PLUS_ACCT_FLAG_WATCHDOG)
+       rec.acct_type = ACCT_TYPE_UPDATE;
+    if (acct_pak->flags & TAC_PLUS_ACCT_FLAG_START)
+       rec.acct_type = ACCT_TYPE_START;
+    if (acct_pak->flags & TAC_PLUS_ACCT_FLAG_STOP)
+       rec.acct_type = ACCT_TYPE_STOP;
+
+    rec.authen_method  = acct_pak->authen_method;
+    rec.authen_type    = acct_pak->authen_type;
+    rec.authen_service = acct_pak->authen_service;
+
+    /* start of variable length data is here */
+    p = pak + TAC_PLUS_HDR_SIZE + TAC_ACCT_REQ_FIXED_FIELDS_SIZE;
+
+    /* skip arg cnts */
+    p += acct_pak->arg_cnt;
+
+    /* zero out identity struct */
+    bzero(&identity, sizeof(struct identity));
+
+    identity.username = tac_make_string(p, (int)acct_pak->user_len);
+    p += acct_pak->user_len;
+
+    identity.NAS_name = tac_strdup(session.peer);
+
+    identity.NAS_port = tac_make_string(p, (int)acct_pak->port_len);
+    p += acct_pak->port_len;
+    if (acct_pak->port_len <= 0) {
+       strcpy(session.port, "unknown-port");
+    } else {
+       strcpy(session.port, identity.NAS_port);
+    }
+
+    identity.NAC_address = tac_make_string(p, (int)acct_pak->rem_addr_len);
+    p += acct_pak->rem_addr_len;
+
+    identity.priv_lvl = acct_pak->priv_lvl;
+
+    rec.identity = &identity;
+
+    /* Now process cmd args */
+    argsizep = pak + TAC_PLUS_HDR_SIZE + TAC_ACCT_REQ_FIXED_FIELDS_SIZE;
+
+    cmd_argp = (char **) tac_malloc(acct_pak->arg_cnt * sizeof(char *));
+
+    for (i = 0; i < (int)acct_pak->arg_cnt; i++) {
+       cmd_argp[i] = tac_make_string(p, *argsizep);
+       p += *argsizep++;
+    }
+
+    rec.args = cmd_argp;
+    rec.num_args = acct_pak->arg_cnt;
+
+#ifdef MAXSESS
+    /*
+     * Tally for MAXSESS counting
+     */
+    loguser(&rec);
+#endif
+
+    /*
+     * Do accounting.
+     */
+    if (wtmpfile) {
+       errors = do_wtmp(&rec);
+    } else {
+       errors = do_acct(&rec);
+#ifdef DB 
+    if (session.db_acct && rec.acct_type==ACCT_TYPE_STOP )
+       db_acct(&rec);
+#endif
+       }
+
+    if (errors) {
+       status = TAC_PLUS_ACCT_STATUS_ERROR;
+    } else {
+       status = TAC_PLUS_ACCT_STATUS_SUCCESS;
+    }
+    send_acct_reply(status, rec.msg, rec.admin_msg);
+
+    free(identity.username);
+    free(identity.NAS_name);
+    free(identity.NAS_port);
+    free(identity.NAC_address);
+
+    for (i = 0; i < (int)acct_pak->arg_cnt; i++) {
+       free(cmd_argp[i]);
+    }    
+    free(cmd_argp);
+    
+    if (rec.msg)
+       free(rec.msg);
+    if (rec.admin_msg)
+       free(rec.admin_msg);
+}
diff --git a/authen.c b/authen.c
new file mode 100644 (file)
index 0000000..a12745e
--- /dev/null
+++ b/authen.c
@@ -0,0 +1,472 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+#include "tac_plus.h"
+
+static int choose();
+static void authenticate();
+static void do_start();
+
+/*
+ *  Come here when we receive an authentication START packet
+ */
+
+void
+authen(pak)
+u_char *pak;
+{
+    char msg[55];
+    struct authen_start *start;
+    HDR *hdr;
+
+    hdr = (HDR *) pak;
+    start = (struct authen_start *) (pak + TAC_PLUS_HDR_SIZE);
+
+    if ((hdr->seq_no != 1) ||
+       (ntohl(hdr->datalength) != TAC_AUTHEN_START_FIXED_FIELDS_SIZE + 
+        start->user_len + start->port_len + start->rem_addr_len +
+        start->data_len)) {
+       send_authen_error("Invalid AUTHEN/START packet (check keys)");
+       return;
+    }
+
+    switch (start->action) {
+    case TAC_PLUS_AUTHEN_LOGIN:
+    case TAC_PLUS_AUTHEN_SENDAUTH:
+    case TAC_PLUS_AUTHEN_SENDPASS:
+       do_start(pak);
+       return;
+
+    default:
+       sprintf(msg, "Invalid AUTHEN/START action=%d", start->action);
+       send_authen_error(msg);
+       return;
+    }
+}
+
+/*
+ * We have a valid AUTHEN/START packet. Fill out data structures and
+ * attempt to authenticate.
+ */
+
+static void
+do_start(pak)
+u_char *pak;
+{
+    struct identity identity;
+    struct authen_data authen_data;
+    struct authen_type authen_type;
+    struct authen_start *start;
+    u_char *p;
+    int ret;
+
+    if (debug & DEBUG_PACKET_FLAG)
+       report(LOG_DEBUG, "Authen Start request");
+
+    /* fixed fields of this packet */
+    start = (struct authen_start *) (pak + TAC_PLUS_HDR_SIZE);
+
+    /* variable length data starts here */
+    p = pak + TAC_PLUS_HDR_SIZE + TAC_AUTHEN_START_FIXED_FIELDS_SIZE;
+
+    /* The identity structure */
+
+    /* zero out identity struct so that all strings can be NULL terminated */
+    bzero(&identity, sizeof(struct identity));
+
+    identity.username = tac_make_string(p, (int)start->user_len);
+    p += start->user_len;
+
+    identity.NAS_name = tac_strdup(session.peer);
+
+    identity.NAS_port = tac_make_string(p, (int)start->port_len);
+    p += start->port_len;
+
+    if (start->port_len <= 0) {
+       strcpy(session.port, "unknown-port");
+    } else {
+       strcpy(session.port, identity.NAS_port);
+    }
+
+    identity.NAC_address = tac_make_string(p, (int)start->rem_addr_len);
+    p += start->rem_addr_len;
+
+    identity.priv_lvl = start->priv_lvl;
+
+    /* The authen_data structure */
+
+    bzero(&authen_data, sizeof(struct authen_data));
+
+    authen_data.NAS_id      = &identity;
+    authen_data.action      = start->action;
+    authen_data.service     = start->service;
+    authen_data.type        = start->authen_type;
+    authen_data.client_dlen = start->data_len;
+
+    authen_data.client_data = tac_malloc(start->data_len);
+    bcopy(p, authen_data.client_data, start->data_len);
+
+    /* The authen_type structure */
+
+    bzero(&authen_type, sizeof(struct authen_type));
+
+    authen_type.authen_type = start->authen_type;
+
+    /* All data structures are now initialised. Now see if we can
+     * authenticate this puppy. Begin by choosing a suitable
+     * authentication function to call to actually do the work. */
+
+#ifdef TCPWRAPPER
+if (check_from_wrap(&identity)) {   
+#endif
+    ret = choose(&authen_data, &authen_type);
+
+    switch (ret) {
+    case 1:
+       /* A successful choice. Authenticate */
+       authenticate(&authen_data, &authen_type);
+       break;
+
+    case 0:
+       /* We lost our connection, aborted, or something dreadful happened */
+       break;
+    }
+#ifdef TCPWRAPPER
+} else {
+send_authen_error("You are not allowed to access here");
+}
+#endif
+    /* free data structures */
+    if (authen_data.server_msg) {
+       free(authen_data.server_msg);
+       authen_data.server_msg = NULL;
+    }
+    if (authen_data.server_data) {
+       free(authen_data.server_data);
+       authen_data.server_data = NULL;
+    }
+    if (authen_data.client_msg) {
+       free(authen_data.client_msg);
+       authen_data.client_msg = NULL;
+    }
+    if (authen_data.client_data) {
+       free(authen_data.client_data);
+       authen_data.client_data = NULL;
+    }
+    if (authen_data.method_data) {
+       report(LOG_ERR, 
+              "%s: Method data not set to NULL after authentication",
+              session.peer);
+    }
+    free(identity.username);
+    free(identity.NAS_name);
+    free(identity.NAS_port);
+    free(identity.NAC_address);
+    return;
+}
+
+/* Choose an authentication function. Return 1 if we successfully
+   chose a function.  0 if we couldn't make a choice for some reason */
+
+static int 
+choose(datap, typep)
+struct authen_data *datap;
+struct authen_type *typep;
+{
+    int iterations = 0;
+    int status;
+    char *prompt;
+    struct authen_cont *cont;
+    u_char *reply;
+    u_char *p;
+    struct identity *identp;
+
+    while (1) {
+
+       /* check interation counter here */
+
+       if (++iterations >= TAC_PLUS_MAX_ITERATIONS) {
+           report(LOG_ERR, "%s: %s Too many iterations for choose_authen",
+                  session.peer, 
+                  session.port);
+           return (0);
+       }
+       status = choose_authen(datap, typep);
+
+       if (status && (debug & DEBUG_PACKET_FLAG))
+           report(LOG_DEBUG, "choose_authen returns %d", status);
+
+       switch (status) {
+
+       case CHOOSE_BADTYPE: /* FIXME */
+       default:
+           send_authen_error("choose_authen: unexpected failure return");
+           return (0);
+
+       case CHOOSE_OK:
+           if (debug & DEBUG_PACKET_FLAG)
+               report(LOG_DEBUG, "choose_authen chose %s", typep->authen_name);
+           return (1);
+
+       case CHOOSE_FAILED:
+           send_authen_error("choose_authen: unacceptable authen method");
+           return (0);
+
+       case CHOOSE_GETUSER:
+           /* respond with GETUSER containing an optional message from
+            * authen_data.server_msg. */
+
+           datap->status = TAC_PLUS_AUTHEN_STATUS_GETUSER;
+           if (datap->service == TAC_PLUS_AUTHEN_SVC_LOGIN) {
+               prompt = "\nUser Access Verification\n\nUsername: ";
+           } else {
+               prompt = "Username: ";
+           }
+           send_authen_reply(TAC_PLUS_AUTHEN_STATUS_GETUSER, /* status */
+                             prompt, /* msg */
+                             strlen(prompt), /* msg_len */
+                             datap->server_data,
+                             datap->server_dlen,
+                             0 /* flags */);
+
+           if (datap->server_data) {
+               free(datap->server_data);
+               datap->server_dlen = 0;
+           }
+           /* expect a CONT from the NAS */
+           reply = get_authen_continue();
+           if (reply == NULL) {
+               /* Typically premature close of connection */
+               report(LOG_ERR, "%s %s: Null reply packet, expecting CONTINUE",
+                      session.peer, session.port);
+               return (0);
+           }
+
+           cont = (struct authen_cont *) (reply + TAC_PLUS_HDR_SIZE);
+
+           if (cont->flags & TAC_PLUS_CONTINUE_FLAG_ABORT) {
+               char buf[65537];
+               buf[0] = '\0';
+               session.aborted = 1;
+
+               if (cont->user_data_len) {
+                   /* An abort message exists. Log it */
+                   p = reply + TAC_PLUS_HDR_SIZE + 
+                       TAC_AUTHEN_CONT_FIXED_FIELDS_SIZE + cont->user_msg_len;
+
+                   bcopy(p, buf, cont->user_data_len);
+                   buf[cont->user_data_len] = '\0';
+               }
+               report(LOG_INFO, "%s %s: Login aborted by request -- msg: %s", 
+                      session.peer, session.port, buf);
+               free(reply);
+               return(0);
+           }
+
+           p = reply + TAC_PLUS_HDR_SIZE + TAC_AUTHEN_CONT_FIXED_FIELDS_SIZE;
+
+           identp = datap->NAS_id;
+
+           if (identp->username) {
+               free(identp->username);
+           }
+           identp->username = tac_make_string(p, cont->user_msg_len);
+           free(reply);
+       }
+    }
+    /* NOTREACHED */
+}
+
+/* Perform authentication assuming we have successfully chosen an
+   authentication method */
+static void
+authenticate(datap, typep)
+struct authen_data *datap;
+struct authen_type *typep;
+{
+    int iterations = 0;
+    u_char *reply, *p;
+    struct authen_cont *cont;
+    int (*func) ();
+
+    if (debug & DEBUG_PACKET_FLAG)
+       report(LOG_DEBUG, "Calling authentication function");
+
+    func = typep->authen_func;
+
+    if (!func) {
+       send_authen_error("authenticate: cannot find function pointer");
+       return;
+    }
+
+    while (1) {
+       if (session.aborted)
+           return;
+
+       if (++iterations >= TAC_PLUS_MAX_ITERATIONS) {
+           send_authen_error("Too many iterations while authenticating");
+           return;
+       }
+
+       if ((*func) (datap)) {
+           send_authen_error("Unexpected authentication function failure");
+           return;
+       }
+
+       switch (datap->status) {
+
+       default:
+           send_authen_error("Illegal status value from authentication function");
+           return;
+
+       case TAC_PLUS_AUTHEN_STATUS_PASS:
+           /* A successful authentication */
+           send_authen_reply(TAC_PLUS_AUTHEN_STATUS_PASS,
+                             datap->server_msg,
+                             datap->server_msg ? strlen(datap->server_msg) : 0,
+                             datap->server_data,
+                             datap->server_dlen,
+                             0);
+           return;
+
+       case TAC_PLUS_AUTHEN_STATUS_ERROR:
+           /* never supposed to happen. reply with a server_msg if any, and
+            * bail out */
+           send_authen_error(datap->server_msg ? datap->server_msg :
+                           "authentication function: unspecified failure");
+           return;
+
+       case TAC_PLUS_AUTHEN_STATUS_FAIL:
+
+           /* An invalid user/password combination */
+           send_authen_reply(TAC_PLUS_AUTHEN_STATUS_FAIL,
+                             datap->server_msg,
+                             datap->server_msg ? strlen(datap->server_msg) : 0,
+                             NULL,
+                             0,
+                             0);
+           return;
+
+       case TAC_PLUS_AUTHEN_STATUS_GETUSER:
+       case TAC_PLUS_AUTHEN_STATUS_GETPASS:
+       case TAC_PLUS_AUTHEN_STATUS_GETDATA:
+
+           /* ship GETPASS/GETDATA containing
+            * datap->server_msg to NAS. */
+
+           send_authen_reply(datap->status,
+                             datap->server_msg,
+                             datap->server_msg ? strlen(datap->server_msg) : 0,
+                             datap->server_data,
+                             datap->server_dlen,
+                             datap->flags);
+
+           datap->flags = 0;
+
+           if (datap->server_msg) {
+               free(datap->server_msg);
+               datap->server_msg = NULL;
+           }
+           if (datap->server_data) {
+               free(datap->server_data);
+               datap->server_data = NULL;
+           }
+           if (datap->client_msg) {
+               free(datap->client_msg);
+               datap->client_msg = NULL;
+           }
+           reply = get_authen_continue();
+           if (!reply) {
+
+               /* Typically due to a premature connection close */
+               report(LOG_ERR, "%s %s: Null reply packet, expecting CONTINUE",
+                      session.peer, session.port);
+
+               /* Tell the authentication function it should clean up
+                  any private data */
+
+               datap->flags |= TAC_PLUS_CONTINUE_FLAG_ABORT;
+
+               if (datap->method_data)
+                   ((*func) (datap));
+
+               datap->flags = 0;
+               return;
+           }
+
+           cont = (struct authen_cont *) (reply + TAC_PLUS_HDR_SIZE);
+
+           if (cont->flags & TAC_PLUS_CONTINUE_FLAG_ABORT) {
+
+               session.aborted = 1;
+
+               /* Tell the authentication function to clean up
+                  its private data, if there is any */
+
+               datap->flags |= TAC_PLUS_CONTINUE_FLAG_ABORT;
+               if (datap->method_data)
+                   ((*func) (datap));
+               datap->flags = 0;
+
+               if (cont->user_data_len) {
+
+                   /* An abort message exists. Create a
+                      null-terminated string for authen_data */
+
+                   datap->client_data = (char *) 
+                       tac_malloc(cont->user_data_len + 1);
+
+                   p = reply + TAC_PLUS_HDR_SIZE + TAC_AUTHEN_CONT_FIXED_FIELDS_SIZE +
+                       cont->user_msg_len;
+
+                   bcopy(p, datap->client_data, cont->user_data_len);
+                   datap->client_data[cont->user_data_len] = '\0';
+               }
+
+               free(reply);
+               return;
+           }
+
+           p = reply + TAC_PLUS_HDR_SIZE + TAC_AUTHEN_CONT_FIXED_FIELDS_SIZE;
+
+           switch (datap->status) {
+
+           case TAC_PLUS_AUTHEN_STATUS_GETDATA:
+           case TAC_PLUS_AUTHEN_STATUS_GETPASS:
+               /* A response to our GETDATA/GETPASS request. Create a
+                * null-terminated string for authen_data */
+               datap->client_msg = (char *) tac_malloc(cont->user_msg_len + 1);
+               bcopy(p, datap->client_msg, cont->user_msg_len);
+               datap->client_msg[cont->user_msg_len] = '\0';
+               free(reply);
+               continue;
+
+           case TAC_PLUS_AUTHEN_STATUS_GETUSER:
+           default:
+               report(LOG_ERR, "%s: authenticate: cannot happen",
+                      session.peer);
+               send_authen_error("authenticate: cannot happen");
+               free(reply);
+               return;
+           }
+       }
+       /* NOTREACHED */
+    }
+}
+
diff --git a/author.c b/author.c
new file mode 100644 (file)
index 0000000..a9f2277
--- /dev/null
+++ b/author.c
@@ -0,0 +1,175 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+#include "tac_plus.h"
+
+/*
+ *  Come here when we receive an authorization START packet
+ */
+
+void
+author(pak)
+u_char *pak;
+{
+    HDR *hdr;
+    struct author *apak;
+    struct identity identity;
+    struct author_data author_data;
+    u_char *p;
+    u_char *argsizep;
+    char **cmd_argp;
+    int i, len;
+
+    if (debug & DEBUG_AUTHOR_FLAG)
+       report(LOG_DEBUG, "Start authorization request");
+
+    hdr = (HDR *)pak;
+    apak = (struct author *) (pak + TAC_PLUS_HDR_SIZE);
+
+    /* Do some sanity checks */
+    if (hdr->seq_no != 1) {
+       send_error_reply(TAC_PLUS_AUTHOR, NULL);
+       return;
+    }
+
+    /* arg counts start here */
+    p = pak + TAC_PLUS_HDR_SIZE + TAC_AUTHOR_REQ_FIXED_FIELDS_SIZE;
+
+    /* Length checks */
+    len = TAC_AUTHOR_REQ_FIXED_FIELDS_SIZE;
+    len += apak->user_len + apak->port_len + apak->rem_addr_len + apak->arg_cnt;
+    for (i = 0; i < (int)apak->arg_cnt; i++) {
+       len += p[i];
+    }
+
+    if (len != ntohl(hdr->datalength)) {
+       send_error_reply(TAC_PLUS_AUTHOR, NULL);
+       return;
+    }
+
+    /* start of variable length data is here */
+    p = pak + TAC_PLUS_HDR_SIZE + TAC_AUTHOR_REQ_FIXED_FIELDS_SIZE;
+
+    /* arg length data starts here */
+    argsizep = p;
+
+    p += apak->arg_cnt;
+
+    bzero(&author_data, sizeof(struct author_data));
+
+    /* The identity structure */
+
+    /* zero out identity struct */
+    bzero(&identity, sizeof(struct identity));
+    identity.username = tac_make_string(p, (int) apak->user_len);
+    p += apak->user_len;
+
+    identity.NAS_name = tac_strdup(session.peer);
+
+    identity.NAS_port = tac_make_string(p, (int)apak->port_len);
+    p += apak->port_len;
+    if (apak->port_len <= 0) {
+       strcpy(session.port, "unknown-port");
+    } else {
+       strcpy(session.port, identity.NAS_port);
+    }
+
+    identity.NAC_address = tac_make_string(p, (int)apak->rem_addr_len);
+    p += apak->rem_addr_len;
+
+    identity.priv_lvl = apak->priv_lvl;
+
+    /* The author_data structure */
+
+    author_data.id = &identity;        /* user id */
+
+    /* FIXME: validate these fields */
+    author_data.authen_method = apak->authen_method;
+    author_data.authen_type = apak->authen_type;
+    author_data.service = apak->service;
+    author_data.num_in_args = apak->arg_cnt;
+
+    /* Space for args + NULL */
+    cmd_argp = (char **) tac_malloc(apak->arg_cnt * sizeof(char *));
+
+    /* p  points to the start of args. Step thru them making strings */
+    for (i = 0; i < (int)apak->arg_cnt; i++) {
+       cmd_argp[i] = tac_make_string(p, *argsizep);
+       p += *argsizep++;
+    }
+
+    author_data.input_args = cmd_argp; /* input command arguments */
+
+    if (do_author(&author_data)) {
+       report(LOG_ERR, "%s: do_author returned an error", session.peer);
+       send_author_reply(AUTHOR_STATUS_ERROR,
+                         author_data.msg,
+                         author_data.admin_msg,
+                         author_data.num_out_args,
+                         author_data.output_args);
+       return;
+    }
+
+    /* Send a reply packet */
+    send_author_reply(author_data.status,
+                     author_data.msg,
+                     author_data.admin_msg,
+                     author_data.num_out_args,
+                     author_data.output_args);
+
+    if (debug)
+       report(LOG_INFO, "authorization query for '%s' %s from %s %s",
+              author_data.id->username  && author_data.id->username[0] ?
+              author_data.id->username : "unknown",
+              author_data.id->NAS_port && author_data.id->NAS_port[0] ?
+              author_data.id->NAS_port : "unknown",
+              session.peer,
+              (author_data.status == AUTHOR_STATUS_PASS_ADD ||
+               author_data.status == AUTHOR_STATUS_PASS_REPL) ?
+              "accepted" : "rejected");
+
+    /* free the input args */
+    if (author_data.input_args) {
+       for (i = 0; i < author_data.num_in_args; i++)
+           free(author_data.input_args[i]);
+       
+       free(author_data.input_args);
+       author_data.input_args = NULL;
+    }
+
+    /* free the output args */
+    if (author_data.output_args) {
+       for (i=0; i < author_data.num_out_args; i++)
+           free(author_data.output_args[i]);
+
+       free(author_data.output_args);
+       author_data.output_args = NULL;
+    }
+
+    if (author_data.msg)
+       free(author_data.msg);
+
+    if (author_data.admin_msg)
+       free(author_data.admin_msg);
+
+    free(identity.username);
+    free(identity.NAS_name);
+    free(identity.NAS_port);
+    free(identity.NAC_address);
+}
diff --git a/choose_authen.c b/choose_authen.c
new file mode 100644 (file)
index 0000000..9329b73
--- /dev/null
@@ -0,0 +1,294 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+#include "tac_plus.h"
+#include "expire.h"
+
+static int choose_login();
+static int choose_sendpass();
+static int choose_sendauth();
+
+int 
+get_minor_version()
+{
+    return(session.version & ~TAC_PLUS_MAJOR_VER_MASK);
+}
+
+/* 
+ * Choose an authentication function. Return CHOOSE_OK if chosen,
+ * CHOOSE_GETUSER if we need a username, CHOOSE_FAILED on failure 
+ */
+
+int
+choose_authen(data, type)
+struct authen_data *data;
+struct authen_type *type;
+{
+    char *name = data->NAS_id->username;
+
+    switch (data->action) {
+    case TAC_PLUS_AUTHEN_SENDPASS:
+       return(choose_sendpass(data, type));
+
+    case TAC_PLUS_AUTHEN_SENDAUTH:
+       return(choose_sendauth(data, type));
+
+    case TAC_PLUS_AUTHEN_LOGIN:
+       /* For enabling, enable_fn handles everything. Must be minor
+        * version zero 
+        */
+       if (data->service == TAC_PLUS_AUTHEN_SVC_ENABLE) {
+           if (session.version != TAC_PLUS_VER_0) {
+               /* must be version 0 */
+               break;
+           }
+           type->authen_func = enable_fn;
+           strcpy(type->authen_name, "enable_fn");
+           return (CHOOSE_OK);
+       }
+       return(choose_login(data, type));
+
+    case TAC_PLUS_AUTHEN_CHPASS:
+       /* we don't support chpass */
+       return(CHOOSE_FAILED);
+
+    default:
+       break;
+    }
+
+    /* never heard of this lot */
+    report(LOG_ERR, "%s: %s %s Illegal packet ver=%d action=%d type=%d",
+          session.peer, 
+          session.port,
+          name ? name : "<unknown>",
+          session.version,
+          data->action,
+          type->authen_type);
+
+    return(CHOOSE_FAILED);
+}
+
+/* Choose an authentication function for action == LOGIN, service != enable */
+static int
+choose_login(data, type)
+struct authen_data *data;
+struct authen_type *type;
+{
+    char *name = data->NAS_id->username;
+    char *cfg_passwd;
+
+    switch(type->authen_type) {
+    case TAC_PLUS_AUTHEN_TYPE_ASCII:
+       if (session.version != TAC_PLUS_VER_0) {
+           break;
+       }
+
+       if (!name[0]) {
+           /* request a user name if not already supplied */
+           return (CHOOSE_GETUSER);
+       }
+
+       /* Does this user require s/key? */
+       cfg_passwd = cfg_get_login_secret(name, TAC_PLUS_RECURSE);
+       if (cfg_passwd && STREQ(cfg_passwd, "skey")) {
+           if (debug & DEBUG_PASSWD_FLAG)
+               report(LOG_DEBUG, "%s %s: user %s requires skey", 
+                      session.peer, session.port, name);
+#ifdef SKEY
+           type->authen_func = skey_fn;
+           strcpy(type->authen_name, "skey_fn");
+           return (CHOOSE_OK);
+#else /* SKEY */
+           report(LOG_ERR, 
+                  "%s %s: user %s s/key support has not been compiled in",
+                  name ? name : "<unknown>",
+                  session.peer, session.port);
+           return(CHOOSE_FAILED);
+#endif /* SKEY */
+       }
+
+       /* Not an skey user. Must be none, des, cleartext or file password */
+       type->authen_func = default_fn;
+       strcpy(type->authen_name, "default_fn");
+       return (CHOOSE_OK);
+
+    case TAC_PLUS_AUTHEN_TYPE_ARAP:
+#ifndef ARAP_DES
+       /* 
+        * If we have no des code we can't do ARAP via SENDAUTH. We'll
+        * have to do it via SENDPASS. Return a down-rev reply
+        * packet and hope the NAS is smart enough to deal with it.
+        */
+       session.version = TAC_PLUS_VER_0;
+       report(LOG_ERR, "%s %s: user %s DES is unavailable",
+              name ? name : "<unknown>", session.peer, session.port);
+       return (CHOOSE_FAILED);
+#endif /* ARAP_DES */
+       /* FALLTHROUGH */
+
+#ifdef MSCHAP
+    case TAC_PLUS_AUTHEN_TYPE_MSCHAP:
+#ifndef MSCHAP_DES
+       /* 
+        * If we have no des code we can't do MSCHAP via LOGIN. We'll
+        * have to do it via SENDPASS. Return a down-rev reply
+        * packet and hope the NAS is smart enough to deal with it.
+        */
+       session.version = TAC_PLUS_VER_0;
+       report(LOG_ERR, "%s %s: user %s DES is unavailable",
+              name ? name : "<unknown>", session.peer, session.port);
+       return (CHOOSE_FAILED);
+#endif /* MSCHAP_DES */
+       /* FALLTHROUGH */
+#endif /* MSCHAP */
+
+    case TAC_PLUS_AUTHEN_TYPE_PAP:
+    case TAC_PLUS_AUTHEN_TYPE_CHAP:
+       if (session.version == TAC_PLUS_VER_0) {
+           type->authen_func = default_v0_fn;
+           strcpy(type->authen_name, "default_v0_fn");
+           return (CHOOSE_OK);
+       }
+
+       /* Version 1 login/[pap|chap|arap].
+        * The username must in the initial START packet 
+        */
+       if (!name[0]) {
+           report(LOG_ERR, "%s %s: No user in START packet for PAP/CHAP/ARAP",
+                  session.peer, session.port);
+           return (CHOOSE_FAILED);
+       }
+       type->authen_func = default_fn;
+       strcpy(type->authen_name, "default_fn");
+       return (CHOOSE_OK);
+
+    default:
+       break;
+    }
+
+    /* Illegal value combination */
+    report(LOG_ERR, "%s: %s %s Illegal packet ver=%d action=%d type=%d",
+          session.peer, 
+          session.port,
+          name ? name : "<unknown>",
+          session.version,
+          data->action,
+          type->authen_type);
+    return(CHOOSE_FAILED);
+}
+
+static int
+choose_sendauth(data, type)
+struct authen_data *data;
+struct authen_type *type;
+{
+    char *name = data->NAS_id->username;
+
+    switch (type->authen_type) {
+#ifdef MSCHAP
+    case TAC_PLUS_AUTHEN_TYPE_MSCHAP:
+#ifndef MSCHAP_DES
+       /* 
+        * If we have no des code we can't do MSCHAP via SENDAUTH. We'll
+        * have to do it via SENDPASS. Return a down-rev reply
+        * packet and hope the NAS is smart enough to deal with it.
+        */
+       session.version = TAC_PLUS_VER_0;
+       report(LOG_ERR, "%s %s: user %s DES is unavailable",
+              name ? name : "<unknown>", session.peer, session.port);
+       return (CHOOSE_FAILED);
+#endif /* MSCHAP_DES */
+       /* FALLTHROUGH */
+#endif /* MSCHAP */
+
+    case TAC_PLUS_AUTHEN_TYPE_CHAP:
+    case TAC_PLUS_AUTHEN_TYPE_PAP:
+       /* Must be minor version 1 */
+       if (session.version != TAC_PLUS_VER_1) {
+           break;
+       }
+
+       /* The start packet must contain the username */
+       if (!name[0]) {
+           return (CHOOSE_FAILED);
+       }
+       type->authen_func = sendauth_fn;
+       strcpy(type->authen_name, "sendauth_fn");
+       return (CHOOSE_OK);
+
+    default:
+       break;
+    }
+    /* Illegal value combination */
+    report(LOG_ERR, "%s: %s %s Illegal packet ver=%d action=%d type=%d",
+          session.peer, 
+          session.port,
+          name ? name : "<unknown>",
+          session.version,
+          data->action,
+          type->authen_type);
+
+    return(CHOOSE_FAILED);
+}
+
+/* Compatibility routine for (obsolete) minor version == 0 */
+static int
+choose_sendpass(data, type)
+struct authen_data *data;
+struct authen_type *type;
+{
+    char *name = data->NAS_id->username;
+
+    switch (type->authen_type) {
+    case TAC_PLUS_AUTHEN_TYPE_CHAP:
+#ifdef MSCHAP
+    case TAC_PLUS_AUTHEN_TYPE_MSCHAP:
+#endif /* MSCHAP */
+    case TAC_PLUS_AUTHEN_TYPE_PAP:
+    case TAC_PLUS_AUTHEN_TYPE_ARAP:
+       /* must be minor version 0 */
+       if (TAC_PLUS_VER_0 != session.version) {
+           break;
+       }
+
+       /* We need a username */
+       if (!name[0]) {
+           return (CHOOSE_GETUSER);
+       }
+
+       type->authen_func = sendpass_fn;
+       strcpy(type->authen_name, "sendpass_fn");
+       return (CHOOSE_OK);
+
+    default:
+       break;
+    }
+
+    /* Illegal value combination */
+    report(LOG_ERR, "%s: %s %s Illegal packet ver=%d action=%d type=%d",
+          session.peer, 
+          session.port,
+          name ? name : "<unknown>",
+          session.version, 
+          data->action,
+          type->authen_type);
+
+    return(CHOOSE_FAILED);
+}
+
diff --git a/config.c b/config.c
new file mode 100644 (file)
index 0000000..6eaef53
--- /dev/null
+++ b/config.c
@@ -0,0 +1,2206 @@
+/*
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+#include "tac_plus.h"
+#include <stdio.h>
+#include <errno.h>
+#include "regexp.h"
+
+/*
+   <config>         := <decl>*
+
+   <decl>           := <top_level_decl> | <user_decl>
+
+   <top_level_decl> := <authen_default> |
+                       accounting file = <string>
+                       default authorization = permit |
+                       key = <string>
+
+   <authen_default> := default authentication = file <filename> 
+#if defined(DB)
+                   | db <string> )
+#endif
+   
+<permission>     := permit | deny
+
+   <filename>       := <string>
+
+   <password>       := <string>
+
+   <user_decl>      := user = <string> {
+                        [ default service = [ permit | deny ] ]
+                        <user_attr>*
+                        <svc>*
+                   }
+
+   <password_spec>  := file <filename> | 
+                      skey | 
+                      cleartext <password> |
+                      des <password> |
+
+#ifdef USE_PAM         
+                      pam <pam_service> |
+#endif                 
+#if defined(DB)                
+                       db <string>
+#endif
+                      nopassword
+
+   <user_attr>      :=   name     = <string> |
+                         login    = <password_spec> |
+                        member   = <string> |
+                        expires  = <string> |
+                        arap     = cleartext <string> |
+                        chap     = cleartext <string> |
+#ifdef MSCHAP
+                        ms-chap  = cleartext <string> |
+#endif
+                        pap      = cleartext <string> |
+                        pap      = des <string> |
+#ifdef USE_PAM 
+                        pap      = pam <pam_service> |
+#endif                 
+                        opap     = cleartext <string> |
+                        global   = cleartext <string> |
+                        msg      = <string>
+                        before authorization = <string> |
+                        after authorization = <string>
+
+   <svc>            := <svc_auth> | <cmd_auth>
+
+   <cmd_auth>       := cmd = <string> {
+                        <cmd-match>*
+                    }
+
+   <cmd-match>      := <permission> <string>
+
+   <svc_auth>       := service = ( exec | arap | slip | ppp protocol = <string> {
+                        [ default attribute = permit ]
+                        <attr_value_pair>*
+                    }
+
+   <attr_value_pair> := [ optional ] <string> = <string>
+
+*/
+
+static char sym_buf[MAX_INPUT_LINE_LEN];       /* parse buffer */
+static int sym_pos=0;           /* current place in sym_buf */
+static int sym_ch;             /* current parse character */
+static int sym_code;           /* parser output */
+static int sym_line = 1;       /* current line number for parsing */
+static FILE *cf = NULL;                /* config file pointer */
+static int sym_error = 0;      /* a parsing error has occurred */
+static int no_user_dflt = 0;   /* default if user doesn't exist */
+static char *authen_default = NULL;    /* top level authentication default */
+static int authen_default_method = 0; /*For method check */
+static char *nopasswd_str = "nopassword";
+
+/* A host definition structure. Currently unused, but when we start
+   configuring host-specific information e.g. per-host keys, this is
+   where it should be kept.
+
+   The first 2 fields (name and hash) are used by the hash table
+   routines to hash this structure into a table.  Do not (re)move them */
+
+struct host {
+    char *name;                        /* host name */
+    void *hash;                        /* hash table next pointer */
+    int line;                  /* line number defined on */
+    char *key;                 /* host spesific key */
+    char *type;                        /* host type         */
+};
+
+/* A user or group definition
+
+   The first 2 fields (name and hash) are used by the hash table
+   routines to hash this structure into a table.  Move them at your
+   peril */
+
+struct user {
+    char *name;                        /* username */
+    void *hash;                        /* hash table next pointer */
+    int line;                  /* line number defined on */
+    long flags;                        /* flags field */
+
+#define FLAG_ISUSER  1         /* this structure represents a user */
+#define FLAG_ISGROUP 2         /* this structure represents a group */
+#define FLAG_SEEN    4         /* for circular definition detection */
+
+    char *full_name;           /* users full name */
+    char *login;               /* Login password */
+    int nopasswd;               /* user requires no password */
+    char *global;              /* password to use if none set */
+    char *member;              /* group we are a member of */
+    char *expires;             /* expiration date */
+    char *arap;                        /* our arap secret */
+    char *pap;                 /* our pap secret */
+    char *opap;                        /* our outbound pap secret */
+    char *chap;                        /* our chap secret */
+#ifdef MSCHAP
+    char *mschap;              /* our mschap secret */
+#endif /* MSCHAP */
+    char *msg;                 /* a message for this user */
+    char *before_author;       /* command to run before authorization */
+    char *after_author;                /* command to run after authorization */
+    int svc_dflt;              /* default authorization behaviour for svc or
+                                * cmd */
+    NODE *svcs;                        /* pointer to svc nodes */
+#ifdef MAXSESS
+    int maxsess;               /* Max sessions/user */
+#endif /* MAXSESS */
+    char *time;                /* Timestamp  */
+};
+
+typedef struct host HOST;
+typedef struct user USER;
+
+/* Only the first 2 fields (name and hash) are used by the hash table
+   routines to hashh structures into a table.
+*/
+
+union hash {
+    struct user u;
+    struct host h;
+};
+
+typedef union hash HASH;
+
+void *grouptable[HASH_TAB_SIZE];/* Table of group declarations */
+void *usertable[HASH_TAB_SIZE];        /* Table of user declarations */
+void *hosttable[HASH_TAB_SIZE];        /* Table of host declarations */
+
+
+static void
+ sym_get();
+
+
+#ifdef __STDC__
+#include <stdarg.h>            /* ANSI C, variable length args */
+static void
+parse_error(char *fmt,...)
+#else
+#include <varargs.h>           /* has 'vararg' definitions */
+/* VARARGS2 */
+static void
+parse_error(fmt, va_alist)
+char *fmt;
+
+va_dcl                         /* no terminating semi-colon */
+#endif
+{
+    char msg[256];             /* temporary string */
+    va_list ap;
+
+#ifdef __STDC__
+    va_start(ap, fmt);
+#else
+    va_start(ap);
+#endif
+    vsprintf(msg, fmt, ap);
+    va_end(ap);
+
+    report(LOG_ERR, "%s", msg);
+    fprintf(stderr, "Error: %s\n", msg);
+    tac_exit(1);
+}
+
+char *
+cfg_nodestring(type)
+    int type;
+{
+    switch (type) {
+    default:
+       return ("unknown node type");
+    case N_arg:
+       return ("N_arg");
+    case N_optarg:
+       return ("N_optarg");
+    case N_svc:
+       return ("N_svc");
+    case N_svc_exec:
+       return ("N_svc_exec");
+    case N_svc_slip:
+       return ("N_svc_slip");
+    case N_svc_ppp:
+       return ("N_svc_ppp");
+    case N_svc_arap:
+       return ("N_svc_arap");
+    case N_svc_cmd:
+       return ("N_svc_cmd");
+    case N_permit:
+       return ("N_permit");
+    case N_deny:
+       return ("N_deny");
+    }
+}
+
+static void
+free_attrs(node)
+NODE *node;
+{
+    NODE *next;
+
+    while (node) {
+       switch (node->type) {
+       case N_optarg:
+       case N_arg:
+           if (debug & DEBUG_CLEAN_FLAG)
+               report(LOG_DEBUG, "free_cmd_match %s %s",
+                      cfg_nodestring(node->type),
+                      node->value);
+           break;
+       default:
+           report(LOG_ERR, "Illegal node type %s for free_attrs", 
+                  cfg_nodestring(node->type));
+           return;
+       }
+
+       free(node->value);
+       next = node->next;
+       free(node);
+       node = next;
+    }
+}
+
+static void
+free_cmd_matches(node)
+NODE *node;
+{
+    NODE *next;
+
+    while (node) {
+       if (debug & DEBUG_CLEAN_FLAG)
+           report(LOG_DEBUG, "free_cmd_match %s %s",
+                  cfg_nodestring(node->type),
+                  node->value);
+
+       free(node->value);      /* text */
+       free(node->value1);     /* regexp compiled text */
+       next = node->next;
+       free(node);
+       node = next;
+    }
+}
+
+static void
+free_svcs(node)
+NODE *node;
+{
+    NODE *next;
+
+    while (node) {
+
+       switch (node->type) {
+       case N_svc_cmd:
+           if (debug & DEBUG_CLEAN_FLAG)
+               report(LOG_DEBUG, "free %s %s",
+                      cfg_nodestring(node->type), node->value);
+           free(node->value);  /* cmd name */
+           free_cmd_matches(node->value1);
+           next = node->next;
+           free(node);
+           node = next;
+           continue;
+
+       case N_svc:
+       case N_svc_ppp:
+           free(node->value1);
+           /* FALL-THROUGH */
+       case N_svc_exec:
+       case N_svc_arap:
+       case N_svc_slip:
+           if (debug & DEBUG_CLEAN_FLAG)
+               report(LOG_DEBUG, "free %s", cfg_nodestring(node->type));
+           free_attrs(node->value);
+           next = node->next;
+           free(node);
+           node = next;
+           continue;
+
+       default:
+           report(LOG_ERR, "Illegal node type %d for free_svcs", node->type);
+           return;
+       }
+    }
+}
+
+static void
+free_userstruct(user)
+USER *user;
+{
+    if (debug & DEBUG_CLEAN_FLAG)
+       report(LOG_DEBUG, "free %s %s",
+              (user->flags & FLAG_ISUSER) ? "user" : "group",
+              user->name);
+
+    if (user->name)
+       free(user->name);
+    if (user->full_name)
+       free(user->full_name);
+    if (user->login)
+       free(user->login);
+    if (user->member)
+       free(user->member);
+    if (user->expires)
+       free(user->expires);
+    if (user->time)
+       free(user->time);
+    if (user->arap)
+       free(user->arap);
+    if (user->chap)
+       free(user->chap);
+#ifdef MSCHAP
+    if (user->mschap)
+       free(user->mschap);
+#endif /* MSCHAP */
+    if (user->pap)
+       free(user->pap);
+    if (user->opap)
+       free(user->opap);
+    if (user->global)
+       free(user->global);
+    if (user->msg)
+       free(user->msg);
+    if (user->before_author)
+       free(user->before_author);
+    if (user->after_author)
+       free(user->after_author);
+    free_svcs(user->svcs);
+}
+
+static void
+free_hoststruct(host)
+HOST *host;
+{
+    if (debug & DEBUG_CLEAN_FLAG)
+       report(LOG_DEBUG, "free %s",
+               host->name);
+
+    if (host->name)
+       free(host->name);
+    
+    if (host->key)
+       free(host->key);
+    
+    if (host->type)
+       free(host->type);
+}
+
+/*
+ * Exported routines
+ */
+
+/* Free all allocated structures preparatory to re-reading the config file */
+void
+cfg_clean_config()
+{
+    int i;
+    USER *entry, *next;
+    HOST *host_entry,*hn;
+
+    if (authen_default) {
+       free(authen_default);
+       authen_default = NULL;
+    }
+   
+   if (authen_default_method) {
+       authen_default_method = 0;
+    }
+
+    if (session.key) {
+       free(session.key);
+       session.key = NULL;
+    }
+
+    if (session.acctfile) {
+       free(session.acctfile);
+       session.acctfile = NULL;
+    }
+    
+    if (session.db_acct) {
+       free(session.db_acct);
+       session.db_acct = NULL;
+    }
+
+    /* clean the hosttable */
+    for (i = 0; i < HASH_TAB_SIZE; i++) {
+       host_entry = (HOST *) hosttable[i];
+       while (host_entry) {
+           hn = host_entry->hash;
+           free_hoststruct(host_entry);
+           free(host_entry);
+           host_entry = hn;
+       }
+       hosttable[i] = NULL;
+    }
+
+    /* the grouptable */
+    for (i = 0; i < HASH_TAB_SIZE; i++) {
+       entry = (USER *) grouptable[i];
+       while (entry) {
+           next = entry->hash;
+           free_userstruct(entry);
+           free(entry);
+           entry = next;
+       }
+       grouptable[i] = NULL;
+    }
+
+    /* the usertable */
+    for (i = 0; i < HASH_TAB_SIZE; i++) {
+       entry = (USER *) usertable[i];
+       while (entry) {
+           next = entry->hash;
+           free_userstruct(entry);
+           free(entry);
+           entry = next;
+       }
+       usertable[i] = NULL;
+    }
+}
+
+static int
+parse_permission()
+{
+    int symbol = sym_code;
+
+    if (sym_code != S_permit && sym_code != S_deny) {
+       parse_error("expecting permit or deny but found '%s' on line %d",
+                   sym_buf, sym_line);
+       return (0);
+    }
+    sym_get();
+
+    return (symbol);
+}
+
+static int
+parse(symbol)
+int symbol;
+
+{
+    if (sym_code != symbol) {
+       parse_error("expecting '%s' but found '%s' on line %d",
+                   (symbol == S_string ? "string" : codestring(symbol)),
+                   sym_buf, sym_line);
+       return (1);
+    }
+    sym_get();
+    return (0);
+}
+
+static int
+parse_opt_svc_default()
+{
+    if (sym_code != S_default) {
+       return (0);
+    }
+
+    parse(S_default);
+    parse(S_svc);
+    parse(S_separator);
+    if (sym_code == S_permit) {
+       parse(S_permit);
+       return (S_permit);
+    }
+    parse(S_deny);
+    return (S_deny);
+}
+
+static int
+parse_opt_attr_default()
+{
+    if (sym_code != S_default)
+       return (S_deny);
+
+    parse(S_default);
+    parse(S_attr);
+    parse(S_separator);
+    parse(S_permit);
+    return (S_permit);
+}
+
+static int parse_user();
+static int parse_host();
+
+static void
+ rch();
+
+/*
+   Parse lines in the config file, creating data structures
+   Return 1 on error, otherwise 0 */
+
+static int
+parse_decls()
+{
+    no_user_dflt = 0; /* default if user doesn't exist */
+
+    sym_code = 0;
+    rch();
+
+    bzero(grouptable, sizeof(grouptable));
+    bzero(usertable, sizeof(usertable));
+    bzero(hosttable, sizeof(hosttable)); 
+
+    sym_get();
+
+    /* Top level of parser */
+    while (1) {
+
+       switch (sym_code) {
+       case S_eof:
+           return (0);
+
+       case S_accounting:
+           sym_get();
+           parse(S_file);
+           parse(S_separator);
+           if (session.acctfile) 
+               free(session.acctfile);
+           session.acctfile = tac_strdup(sym_buf);
+           sym_get();
+           continue;
+
+#ifdef DB      
+       case S_db_accounting:
+           sym_get();
+           parse(S_separator);
+           if (session.db_acct) 
+               free(session.db_acct);
+           session.db_acct = tac_strdup(sym_buf);
+           sym_get();
+           continue;
+#endif
+
+       case S_default:
+           sym_get();
+           switch (sym_code) {
+           default:
+               parse_error(
+               "Expecting default authorization/authentication on lines %d",
+                           sym_line);
+               return (1);
+
+           case S_authentication:
+               if (authen_default) {
+                   parse_error(
+                   "Multiply defined authentication default on line %d",
+                               sym_line);
+                   return (1);
+               }
+               parse(S_authentication);
+               parse(S_separator);
+
+               switch(sym_code) {
+                
+               case S_file:
+#ifdef DB
+               case S_db:
+#endif
+#ifdef USE_LDAP
+               case S_ldap;
+#endif
+#ifdef USE_PAM
+               case S_pam:
+#endif
+                authen_default_method = sym_code;
+               break;
+
+               default:
+                parse_error("expecting default_method keyword after 'default authentication = ' on line %d",sym_line);
+               return (1);
+                }
+                sym_get();
+
+               authen_default = tac_strdup(sym_buf);
+               sym_get();
+               continue;
+
+           case S_authorization:
+               parse(S_authorization);
+               parse(S_separator);
+               parse(S_permit);
+               no_user_dflt = S_permit;
+               report(LOG_INFO, 
+                      "default authorization = permit is now deprecated. Please use user = DEFAULT instead");
+               continue;
+           }
+
+       case S_key:
+           /* Process a key declaration. */
+           sym_get();
+           parse(S_separator);
+           if (session.key) {
+               parse_error("multiply defined key on lines %d and %d",
+                           session.keyline, sym_line);
+               return (1);
+           }
+           session.key = tac_strdup(sym_buf);
+           session.keyline = sym_line;
+           sym_get();
+           continue;
+       
+       case S_host:
+           parse_host();
+           continue;
+       
+       case S_user:
+       case S_group:
+           parse_user();
+           continue;
+
+           /* case S_host: parse_host(); continue; */
+
+       default:
+           parse_error("Unrecognised token %s on line %d", sym_buf, sym_line);
+           return (1);
+       }
+    }
+}
+
+static NODE *parse_svcs();
+
+/* Assign a value to a field. Issue an error message and return 1 if
+   it's already been assigned. This is a macro because I was sick of
+   repeating the same code fragment over and over */
+
+#define ASSIGN(field) \
+sym_get(); parse(S_separator); if (field) { \
+       parse_error("Duplicate value for %s %s and %s on line %d", \
+                   codestring(sym_code), field, sym_buf, sym_line); \
+        tac_exit(1); \
+    } \
+    field = tac_strdup(sym_buf);
+
+static int
+parse_host()
+{
+    HOST *h;
+    HOST *host = (HOST *) tac_malloc(sizeof(HOST));
+    int save_sym;
+    char buf[MAX_INPUT_LINE_LEN];
+
+    bzero(host, sizeof(HOST));
+
+    sym_get();
+    parse(S_separator);
+    host->name = tac_strdup(sym_buf);
+    host->line = sym_line;
+    
+    h = hash_add_entry(hosttable, (void *) host);
+    
+    if (h) {
+        parse_error("multiply defined %s on lines %d and %d",
+                    host->name, h->line, sym_line);
+        return (1);
+    }
+
+    sym_get();
+    parse(S_openbra);
+    
+    while (1) {
+       switch (sym_code) {
+        case S_eof:
+            return (0);
+       case S_key:
+           ASSIGN(host->key);
+            sym_get();
+            continue;
+       case S_type:
+           ASSIGN(host->type);
+            sym_get();
+            continue;
+       
+       case S_closebra:
+            parse(S_closebra);
+            return (0);
+
+       default:
+           parse_error("Unrecognised keyword %s for host %s on line %d",
+                        sym_buf, host->name,sym_line);
+
+            return (0);
+        }
+    } /* while */
+} /* finish parse_host */
+
+
+static int
+parse_user()
+{
+    USER *n;
+    int isuser;
+    USER *user = (USER *) tac_malloc(sizeof(USER));
+    int save_sym;
+    char **fieldp;
+    char buf[MAX_INPUT_LINE_LEN];
+
+    bzero(user, sizeof(USER));
+
+    isuser = (sym_code == S_user);
+
+    sym_get();
+    parse(S_separator);
+    user->name = tac_strdup(sym_buf);
+    user->line = sym_line;
+
+    if (isuser) {
+       user->flags |= FLAG_ISUSER;
+       n = hash_add_entry(usertable, (void *) user);
+    } else {
+       user->flags |= FLAG_ISGROUP;
+       n = hash_add_entry(grouptable, (void *) user);
+    }
+
+    if (n) {
+       parse_error("multiply defined %s %s on lines %d and %d",
+                   isuser ? "user" : "group",
+                   user->name, n->line, sym_line);
+       return (1);
+    }
+    sym_get();
+    parse(S_openbra);
+
+    /* Is the default deny for svcs or cmds to be overridden? */
+    user->svc_dflt = parse_opt_svc_default();
+
+    while (1) {
+       switch (sym_code) {
+       case S_eof:
+           return (0);
+       
+       case S_time:
+          ASSIGN(user->time);
+          sym_get(); 
+          continue;
+
+       case S_before:
+           sym_get();
+           parse(S_authorization);
+           if (user->before_author)
+               free(user->before_author);
+           user->before_author = tac_strdup(sym_buf);
+           sym_get();
+           continue;
+
+       case S_after:
+           sym_get();
+           parse(S_authorization);
+           if (user->after_author)
+               free(user->after_author);
+           user->after_author = tac_strdup(sym_buf);
+           sym_get();
+           continue;
+
+       case S_svc:
+       case S_cmd:
+           
+           if (user->svcs) {   
+               /* 
+                * Already parsed some services/commands. Thanks to Gabor Kiss
+                * who found this bug.
+                */
+               NODE *p;
+               for (p=user->svcs; p->next; p=p->next) 
+                   /* NULL STMT */;
+               p->next = parse_svcs();
+           } else {
+               user->svcs = parse_svcs();
+           }
+           continue;
+
+       case S_login:
+           if (user->login) {
+               parse_error("Duplicate value for %s %s and %s on line %d",
+                           codestring(sym_code), user->login,
+                           sym_buf, sym_line);
+               tac_exit(1);
+           }
+           sym_get();
+           parse(S_separator);
+           switch(sym_code) {
+
+           case S_skey:
+               user->login = tac_strdup(sym_buf);
+               break;
+
+           case S_nopasswd:
+               /* set to dummy string, so that we detect a duplicate
+                * password definition attempt
+                */
+               user->login = tac_strdup(nopasswd_str);
+               user->nopasswd = 1;
+               break;
+               
+           case S_file:
+           case S_cleartext:
+           case S_des:
+#ifdef USE_PAM 
+           case S_pam: 
+#endif /* USE_PAM */           
+#ifdef DB
+           case S_db:
+#endif /* USE DB */
+               sprintf(buf, "%s ", sym_buf);
+               sym_get();
+               strcat(buf, sym_buf);
+               user->login = tac_strdup(buf);
+               break;
+       
+           default:
+#ifdef USE_PAM
+               parse_error(
+ "expecting 'file', 'cleartext', 'pam'.'nopassword', 'skey', or 'des' keyword after 'login =' on line %d",
+                           sym_line);
+#else  
+               parse_error(
+ "expecting 'file', 'cleartext', 'nopassword', 'skey', or 'des' keyword after 'login =' on line %d", 
+                           sym_line);
+#endif /* USE_PAM */                   
+           }
+           sym_get();
+           continue;
+
+       case S_pap:
+           if (user->pap) {
+               parse_error("Duplicate value for %s %s and %s on line %d",
+                           codestring(sym_code), user->pap,
+                           sym_buf, sym_line);
+               tac_exit(1);
+           }
+           sym_get();
+           parse(S_separator);
+           switch(sym_code) {
+
+           case S_cleartext:
+           case S_des:
+#ifdef USE_PAM 
+           case S_pam:
+#endif /*USE_PAM */                    
+               sprintf(buf, "%s ", sym_buf);
+               sym_get();
+               strcat(buf, sym_buf);
+               user->pap = tac_strdup(buf);
+               break;  
+
+               sprintf(buf, "%s ", sym_buf);
+               user->pap = tac_strdup(buf);
+               break;
+
+           default:
+#ifdef USE_PAM
+               parse_error(
+ "expecting 'cleartext', 'pam', or 'des' keyword after 'pap =' on line %d",
+ sym_line);
+#else
+               parse_error(
+ "expecting 'cleartext', or 'des' keyword after 'pap =' on line %d", 
+ sym_line);
+#endif /*USE_PAM */
+           }
+           sym_get();
+           continue;
+
+       case S_name:
+           ASSIGN(user->full_name);
+           sym_get();
+           continue;
+
+       case S_member:
+           ASSIGN(user->member);
+           sym_get();
+           continue;
+       
+
+       case S_expires:
+           ASSIGN(user->expires);
+           sym_get();
+           continue;
+       
+       case S_message:
+           ASSIGN(user->msg);
+           sym_get();
+           continue;
+
+       case S_arap:
+       case S_chap:
+#ifdef MSCHAP
+       case S_mschap:
+#endif /* MSCHAP */
+       case S_opap:
+       case S_global:
+           save_sym = sym_code;
+           sym_get(); 
+           parse(S_separator); 
+           sprintf(buf, "%s ", sym_buf);
+           parse(S_cleartext);
+           strcat(buf, sym_buf);
+
+           if (save_sym == S_arap)
+               fieldp = &user->arap;
+           if (save_sym == S_chap)
+               fieldp = &user->chap;
+#ifdef MSCHAP
+           if (save_sym == S_mschap)
+               fieldp = &user->mschap;
+#endif /* MSCHAP */
+           if (save_sym == S_pap)
+               fieldp = &user->pap;
+           if (save_sym == S_opap)
+               fieldp = &user->opap;
+           if (save_sym == S_global)
+               fieldp = &user->global;
+
+           if (*fieldp) {
+               parse_error("Duplicate value for %s %s and %s on line %d",
+                           codestring(save_sym), *fieldp, sym_buf, sym_line);
+               tac_exit(1);
+           }
+           *fieldp = tac_strdup(buf);
+           sym_get();
+           continue;
+
+       case S_closebra:
+           parse(S_closebra);
+           return (0);
+
+#ifdef MAXSESS
+       case S_maxsess:
+           sym_get(); 
+           parse(S_separator);
+           if (sscanf(sym_buf, "%d", &user->maxsess) != 1) {
+               parse_error("expecting integer, found '%s' on line %d",
+                   sym_buf, sym_line);
+           }
+           sym_get();
+           continue;
+#endif /* MAXSESS */
+       default:
+           if (STREQ(sym_buf, "password")) {
+               fprintf(stderr,
+                       "\npassword = <string> is obsolete. Use login = des <string>\n");
+           }
+           parse_error("Unrecognised keyword %s for user on line %d",
+                       sym_buf, sym_line);
+
+           return (0);
+       }
+    }
+}
+
+static NODE *parse_attrs();
+static NODE *parse_cmd_matches();
+
+static NODE *
+parse_svcs()
+{
+    NODE *result;
+
+    switch (sym_code) {
+    default:
+       return (NULL);
+    case S_svc:
+    case S_cmd:
+       break;
+    }
+
+    result = (NODE *) tac_malloc(sizeof(NODE));
+
+    bzero(result, sizeof(NODE));
+    result->line = sym_line;
+
+    /* cmd declaration */
+    if (sym_code == S_cmd) {
+       parse(S_cmd);
+       parse(S_separator);
+       result->value = tac_strdup(sym_buf);
+
+       sym_get();
+       parse(S_openbra);
+
+       result->value1 = parse_cmd_matches();
+       result->type = N_svc_cmd;
+
+       parse(S_closebra);
+       result->next = parse_svcs();
+       return (result);
+    }
+
+    /* svc declaration */
+    parse(S_svc);
+    parse(S_separator);
+    switch (sym_code) {
+    default:
+       parse_error("expecting service type but found %s on line %d",
+                   sym_buf, sym_line);
+       return (NULL);
+
+    case S_string:
+       result->type = N_svc;
+       /* should perhaps check that this is an allowable service name */
+       result->value1 = tac_strdup(sym_buf);
+       break;
+    case S_exec:
+       result->type = N_svc_exec;
+       break;
+    case S_arap:
+       result->type = N_svc_arap;
+       break;
+    case S_slip:
+       result->type = N_svc_slip;
+       break;
+    case S_ppp:
+       result->type = N_svc_ppp;
+       parse(S_ppp);
+       parse(S_protocol);
+       parse(S_separator);
+       /* Should perhaps check that this is a known PPP protocol name */
+       result->value1 = tac_strdup(sym_buf);
+       break;
+    }
+    sym_get();
+    parse(S_openbra);
+    result->dflt = parse_opt_attr_default();
+    result->value = parse_attrs();
+    parse(S_closebra);
+    result->next = parse_svcs();
+    return (result);
+}
+
+/*  <cmd-match>         := <permission> <string> */
+
+static NODE *
+parse_cmd_matches()
+{
+    NODE *result;
+
+    if (sym_code != S_permit && sym_code != S_deny) {
+       return (NULL);
+    }
+    result = (NODE *) tac_malloc(sizeof(NODE));
+
+    bzero(result, sizeof(NODE));
+    result->line = sym_line;
+
+    result->type = (parse_permission() == S_permit) ? N_permit : N_deny;
+    result->value = tac_strdup(sym_buf);
+
+    result->value1 = (void *) regcomp(result->value);
+    if (!result->value1) {
+       report(LOG_ERR, "in regular expression %s on line %d",
+              sym_buf, sym_line);
+       tac_exit(1);
+    }
+    sym_get();
+
+    result->next = parse_cmd_matches();
+
+    return (result);
+}
+
+static NODE *
+parse_attrs()
+{
+    NODE *result;
+    char buf[MAX_INPUT_LINE_LEN];
+    int optional = 0;
+
+    if (sym_code == S_closebra) {
+       return (NULL);
+    }
+    result = (NODE *) tac_malloc(sizeof(NODE));
+
+    bzero(result, sizeof(NODE));
+    result->line = sym_line;
+
+    if (sym_code == S_optional) {
+       optional++;
+       sym_get();
+    }
+    result->type = optional ? N_optarg : N_arg;
+
+    strcpy(buf, sym_buf);
+    parse(S_string);
+    strcat(buf, sym_buf);
+    parse(S_separator);
+    strcat(buf, sym_buf);
+    parse(S_string);
+
+    result->value = tac_strdup(buf);
+    result->next = parse_attrs();
+    return (result);
+}
+
+
+static void
+ getsym();
+
+static void
+sym_get()
+{
+    getsym();
+
+    if (debug & DEBUG_PARSE_FLAG) {
+       report(LOG_DEBUG, "line=%d sym=%s code=%d buf='%s'",
+              sym_line, codestring(sym_code), sym_code, sym_buf);
+    }
+}
+
+static char *
+sym_buf_add(c)
+char c;
+{
+    if (sym_pos >= MAX_INPUT_LINE_LEN) {
+       sym_buf[MAX_INPUT_LINE_LEN-1] = '\0';
+       if (debug & DEBUG_PARSE_FLAG) {
+           report(LOG_DEBUG, "line too long: line=%d sym=%s code=%d buf='%s'",
+                  sym_line, codestring(sym_code), sym_code, sym_buf);
+       }
+       return(NULL);
+    }
+
+    sym_buf[sym_pos++] = c;
+    return(sym_buf);
+}
+    
+static void
+getsym()
+{
+
+next:
+    switch (sym_ch) {
+
+    case EOF:
+       sym_code = S_eof;
+       return;
+
+    case '\n':
+       sym_line++;
+       rch();
+       goto next;
+
+    case '\t':
+    case ' ':
+       while (sym_ch == ' ' || sym_ch == '\t')
+           rch();
+       goto next;
+
+    case '=':
+       strcpy(sym_buf, "=");
+       sym_code = S_separator;
+       rch();
+       return;
+
+    case '{':
+       strcpy(sym_buf, "{");
+       sym_code = S_openbra;
+       rch();
+       return;
+
+    case '}':
+       strcpy(sym_buf, "}");
+       sym_code = S_closebra;
+       rch();
+       return;
+
+    case '#':
+       while ((sym_ch != '\n') && (sym_ch != EOF))
+           rch();
+       goto next;
+
+    case '"':
+       rch();
+       sym_pos = 0;
+       while (1) {
+
+           if (sym_ch == '"') {
+               break;
+           }
+
+           /* backslash-double-quote is supported inside strings */
+           /* also allow \n */
+           if (sym_ch == '\\') {
+               rch();
+               switch (sym_ch) {
+               case 'n':
+                   /* preserve the slash for \n */
+                   if (!sym_buf_add('\\')) {
+                       sym_code = S_unknown;
+                       rch();
+                       return;
+                   }
+                   
+                   /* fall through */
+               case '"':
+                   if (!sym_buf_add(sym_ch)) {
+                       sym_code = S_unknown;
+                       rch();
+                       return;
+                   }
+                   rch();
+                   continue;
+               default:
+                   sym_code = S_unknown;
+                   rch();
+                   return;
+               }
+           }
+           if (!sym_buf_add(sym_ch)) {
+               sym_code = S_unknown;
+               rch();
+               return;
+           }
+           rch();
+       }
+       rch();
+
+       if (!sym_buf_add('\0')) {
+           sym_code = S_unknown;
+           rch();
+           return;
+       }
+       sym_code = S_string;
+       return;
+
+    default:
+       sym_pos = 0;
+       while (sym_ch != '\t' && sym_ch != ' ' && sym_ch != '='
+              && sym_ch != '\n') {
+
+           if (!sym_buf_add(sym_ch)) {
+               sym_code = S_unknown;
+               rch();
+               return;
+           }
+           rch();
+       }
+
+       if (!sym_buf_add('\0')) {
+           sym_code = S_unknown;
+           rch();
+           return;
+       }
+       sym_code = keycode(sym_buf);
+       if (sym_code == S_unknown)
+           sym_code = S_string;
+       return;
+    }
+}
+
+static void
+rch()
+{
+    if (sym_error) {
+       sym_ch = EOF;
+       return;
+    }
+    sym_ch = getc(cf);
+
+    if (parse_only && sym_ch != EOF)
+       fprintf(stderr, "%c", sym_ch);
+}
+
+
+/* For a user or group, find the value of a field. Does not recurse. */
+VALUE
+get_value(user, field)
+USER *user;
+int field;
+{
+    VALUE v;
+
+    v.intval = 0;
+
+    if (!user) {
+       parse_error("get_value: illegal user");
+       return (v);
+    }
+    switch (field) {
+
+    case S_name:
+       v.pval = user->name;
+       break;
+
+    case S_login:
+       v.pval = user->login;
+       break;
+
+    case S_global:
+       v.pval = user->global;
+       break;
+
+    case S_member:
+       v.pval = user->member;
+       break;
+
+    case S_expires:
+       v.pval = user->expires;
+       break;
+
+    case S_arap:
+       v.pval = user->arap;
+       break;
+
+    case S_chap:
+       v.pval = user->chap;
+       break;
+
+#ifdef MSCHAP
+    case S_mschap:
+       v.pval = user->mschap;
+       break;
+#endif /* MSCHAP */
+
+    case S_pap:
+       v.pval = user->pap;
+       break;
+
+    case S_opap:
+       v.pval = user->opap;
+       break;
+
+    case S_message:
+       v.pval = user->msg;
+       break;
+
+    case S_svc:
+       v.pval = user->svcs;
+       break;
+
+    case S_before:
+       v.pval = user->before_author;
+       break;
+
+    case S_after:
+       v.pval = user->after_author;
+       break;
+
+    case S_svc_dflt:
+       v.intval = user->svc_dflt;
+       break;
+
+#ifdef MAXSESS
+    case S_maxsess:
+       v.intval = user->maxsess;
+       break;
+#endif 
+
+    case S_nopasswd:
+       v.intval = user->nopasswd;
+       break;
+       
+    case S_time:
+       v.pval = user->time;
+       break;
+
+    default:
+       report(LOG_ERR, "get_value: unknown field %d", field);
+       break;
+    }
+    return (v);
+}
+
+/* For host , find value of field. Doesn't recursive */
+VALUE
+get_hvalue(host, field)
+HOST *host;
+int field;
+{
+    VALUE v;
+    v.intval = 0;
+    if(!host) {
+       parse_error("get_hvalue: illegal host");
+        return (v);
+    }
+    switch (field) {
+       case S_name:
+        v.pval = host->name;
+        break;
+       
+       case S_key:
+       v.pval = host->key;
+       break;
+       
+       default:
+        report(LOG_ERR, "get_value: unknown field %d", field);
+        break;
+    }
+    return (v);
+}
+
+
+/* For each user, check she doesn't circularly reference a
+   group. Return 1 if it does */
+static int
+circularity_check()
+{
+    USER *user, *entry, *group;
+    USER **users = (USER **) hash_get_entries(usertable);
+    USER **groups = (USER **) hash_get_entries(grouptable);
+    USER **p, **q;
+
+    /* users */
+    for (p = users; *p; p++) {
+       user = *p;
+
+       if (debug & DEBUG_PARSE_FLAG)
+           report(LOG_DEBUG, "circularity_check: user=%s", user->name);
+
+       /* Initialise all groups "seen" flags to zero */
+       for (q = groups; *q; q++) {
+           group = *q;
+           group->flags &= ~FLAG_SEEN;
+       }
+
+       entry = user;
+
+       while (entry) {
+           /* check groups we are a member of */
+           char *groupname = entry->member;
+
+           if (debug & DEBUG_PARSE_FLAG)
+               report(LOG_DEBUG, "\tmember of group %s",
+                      groupname ? groupname : "<none>");
+
+
+           /* if not a member of any groups, go on to next user */
+           if (!groupname)
+               break;
+
+           group = (USER *) hash_lookup(grouptable, groupname);
+           if (!group) {
+               report(LOG_ERR, "%s=%s, group %s does not exist",
+                      (entry->flags & FLAG_ISUSER) ? "user" : "group",
+                      entry->name, groupname);
+               free(users);
+               free(groups);
+               return (1);
+           }
+           if (group->flags & FLAG_SEEN) {
+               report(LOG_ERR, "recursively defined groups");
+
+               /* print all seen "groups" */
+               for (q = groups; *q; q++) {
+                   group = *q;
+                   if (group->flags & FLAG_SEEN)
+                       report(LOG_ERR, "%s", group->name);
+               }
+               free(users);
+               free(groups);
+               return (1);
+           }
+           group->flags |= FLAG_SEEN;  /* mark group as seen */
+           entry = group;
+       }
+    }
+    free(users);
+    free(groups);
+    return (0);
+}
+
+
+/* Return a value for a group or user (isuser says if
+   this name is a group or a user name).
+
+   If no value exists, and recurse is true, also check groups we are a
+   member of, recursively.
+
+   Returns void * because it can return a string or a node pointer
+   (should really return a union pointer).
+*/
+static VALUE
+cfg_get_value(name, isuser, attr, recurse)
+char *name;
+int isuser, attr, recurse;
+{
+    USER *user, *group;
+    VALUE value;
+
+    value.pval = NULL;
+
+    if (debug & DEBUG_CONFIG_FLAG)
+       report(LOG_DEBUG, "cfg_get_value: name=%s isuser=%d attr=%s rec=%d",
+              name, isuser, codestring(attr), recurse);
+
+    /* find the user/group entry */
+
+    user = (USER *) hash_lookup(isuser ? usertable : grouptable, name);
+
+    if (!user) {
+       if (debug & DEBUG_CONFIG_FLAG)
+           report(LOG_DEBUG, "cfg_get_value: no user/group named %s", name);
+       return (value);
+    }
+
+    /* found the entry. Lookup value from attr=value */
+    value = get_value(user, attr);
+
+    if (value.pval || !recurse) {
+       return (value);
+    }
+    /* no value. Check containing group */
+    if (user->member)
+       group = (USER *) hash_lookup(grouptable, user->member);
+    else
+       group = NULL;
+
+    while (group) {
+       if (debug & DEBUG_CONFIG_FLAG)
+           report(LOG_DEBUG, "cfg_get_value: recurse group = %s",
+                  group->name);
+
+       value = get_value(group, attr);
+
+       if (value.pval) {
+           return (value);
+       }
+       /* still nothing. Check containing group and so on */
+
+       if (group->member)
+           group = (USER *) hash_lookup(grouptable, group->member);
+       else
+           group = NULL;
+    }
+
+    /* no value for this user or her containing groups */
+    value.pval = NULL;
+    return (value);
+}
+
+
+/* Wrappers for cfg_get_value */
+int
+cfg_get_intvalue(name, isuser, attr, recurse)
+char *name;
+int isuser, attr, recurse;
+{
+    int val = cfg_get_value(name, isuser, attr, recurse).intval;
+
+    if (debug & DEBUG_CONFIG_FLAG)
+       report(LOG_DEBUG, "cfg_get_intvalue: returns %d", val);
+    return(val);
+}
+
+char *
+cfg_get_pvalue(name, isuser, attr, recurse)
+char *name;
+int isuser, attr, recurse;
+{
+    char *p = cfg_get_value(name, isuser, attr, recurse).pval;
+
+    if (debug & DEBUG_CONFIG_FLAG)
+       report(LOG_DEBUG, "cfg_get_pvalue: returns %s", 
+              p ? p : "NULL");
+    return(p);
+}
+
+/* For getting host values */
+static VALUE
+cfg_get_hvalue(name, attr)
+char *name;
+int attr;
+{
+    HOST *host;
+    VALUE value;
+
+    value.pval = NULL;
+    if (debug & DEBUG_CONFIG_FLAG)
+        report(LOG_DEBUG, "cfg_get_hvalue: name=%s attr=%s ",
+               name, codestring(attr));
+    
+    /* find the host entry in hash table */
+
+    host = (HOST *) hash_lookup( hosttable, name);
+
+    if (!host) {
+        if (debug & DEBUG_CONFIG_FLAG)
+            report(LOG_DEBUG, "cfg_get_hvalue: no host named %s", name);
+        return (value);
+    }
+
+    /* found the entry. Lookup value from attr=value */
+    value = get_hvalue(host, attr);
+
+    if (value.pval) {
+        return (value);
+    }
+    /* No any value for this host */    
+    value.pval = NULL;
+    return (value);
+}
+
+/* Wrappers for cfg_get_hvalue */
+char *
+cfg_get_phvalue(name, attr)
+char *name;
+int attr;
+{
+    char *p = cfg_get_hvalue(name, attr).pval;
+
+    if (debug & DEBUG_CONFIG_FLAG)
+       report(LOG_DEBUG, "cfg_get_phvalue: returns %s", 
+              p ? p : "NULL");
+    return(p);
+}
+
+/*
+   Read the config file and do some basic sanity checking on
+   it. Return 1 if we find any errors. */
+
+cfg_read_config(cfile)
+char *cfile;
+{
+    sym_line = 1;
+
+    if ((cf = fopen(cfile, "r")) == NULL) {
+       report(LOG_ERR, "read_config: fopen() error for file %s %s, exiting",
+              cfile, sys_errlist[errno]);
+       return (1);
+    }
+    if (parse_decls() || sym_error) {
+       fclose(cf);
+       return (1);
+    }
+
+    if (circularity_check()) {
+       fclose(cf);
+       return (1);
+    }
+
+    fclose(cf);
+    return (0);
+}
+
+/* return 1 if user exists, 0 otherwise */
+int
+cfg_user_exists(username)
+char *username;
+{
+    USER *user = (USER *) hash_lookup(usertable, username);
+
+    return (user != NULL);
+}
+
+/* return expiry string of user. If none, try groups she is a member
+   on, and so on, recursively if recurse is non-zero */
+char *
+cfg_get_expires(username, recurse)
+char *username;
+
+{
+    return (cfg_get_pvalue(username, TAC_IS_USER, S_expires, recurse));
+}
+
+/* return time string of user. If none, try groups she is a member
+   on, and so on, recursively if recurse is non-zero */
+char *
+cfg_get_timestamp(username, recurse)
+char *username;
+{
+    return (cfg_get_pvalue(username, TAC_IS_USER, S_time, recurse));
+}
+
+
+/* return password string of user. If none, try groups she is a member
+   on, and so on, recursively if recurse is non-zero */
+char *
+cfg_get_login_secret(user, recurse)
+char *user;
+
+{
+    return (cfg_get_pvalue(user, TAC_IS_USER, S_login, recurse));
+}
+
+/* return value of the nopasswd field. If none, try groups she is a member
+   on, and so on, recursively if recurse is non-zero */
+int
+cfg_get_user_nopasswd(user, recurse)
+    char *user;
+{
+    return (cfg_get_intvalue(user, TAC_IS_USER, S_nopasswd, recurse));
+}
+
+/* return user's secret. If none, try groups she is a member
+   on, and so on, recursively if recurse is non-zero */
+char *
+cfg_get_arap_secret(user, recurse)
+char *user;
+
+{
+    return (cfg_get_pvalue(user, TAC_IS_USER, S_arap, recurse));
+}
+
+char *
+cfg_get_chap_secret(user, recurse)
+char *user;
+
+{
+    return (cfg_get_pvalue(user, TAC_IS_USER, S_chap, recurse));
+}
+
+#ifdef MSCHAP
+char *
+cfg_get_mschap_secret(user, recurse)
+char *user;
+
+{
+    return (cfg_get_pvalue(user, TAC_IS_USER, S_mschap, recurse));
+}
+#endif /* MSCHAP */
+
+char *
+cfg_get_pap_secret(user, recurse)
+char *user;
+{
+    return (cfg_get_pvalue(user, TAC_IS_USER, S_pap, recurse));
+}
+
+char *
+cfg_get_opap_secret(user, recurse)
+char *user;
+{
+    return (cfg_get_pvalue(user, TAC_IS_USER, S_opap, recurse));
+}
+
+/* return the global password for the user (or the group, etc.) */
+
+char *
+cfg_get_global_secret(user, recurse)
+char *user;
+
+{
+    return (cfg_get_pvalue(user, TAC_IS_USER, S_global, recurse));
+}
+
+#ifdef USE_PAM
+/* Return a pointer to a node representing a PAM Service name */
+char *
+cfg_get_pam_service(user,recurse)
+char *user;
+
+{
+ char *cfg_passwd;
+ char *p;   
+
+cfg_passwd = cfg_get_pap_secret(user, recurse);
+if (!cfg_passwd) {
+               cfg_passwd = cfg_get_global_secret(user, recurse);
+}
+if (!cfg_passwd && !cfg_user_exists(user)) {
+        cfg_passwd = cfg_get_authen_default();
+        switch (cfg_get_authen_default_method()) {
+               case (S_pam): 
+                       if (debug & DEBUG_AUTHOR_FLAG)
+                        report(LOG_DEBUG, "Get Default PAM Service :%s",cfg_passwd);
+                       return(cfg_passwd);
+                       break;
+               default:
+                       if (debug & DEBUG_AUTHOR_FLAG)
+                        report(LOG_DEBUG, "I havent find any PAM Service!!");
+                       return(NULL);/* Haven't any PAM Service!! */
+       }
+}
+
+p=tac_find_substring("pam ", cfg_passwd);
+
+if(p) {  /* We find PAM services */
+       if (debug & DEBUG_AUTHOR_FLAG)
+               report(LOG_DEBUG, "I get PAM sevice:%s",p);
+return (p);
+}
+
+if (debug & DEBUG_AUTHOR_FLAG)
+       report(LOG_DEBUG, "No any PAM Sevice");
+
+return(NULL);
+}
+
+#endif /* For PAM */
+       
+
+
+/* Return a pointer to a node representing a given service
+   authorization, taking care of recursion issues correctly. Protocol
+   is only read if the type is N_svc_ppp. svcname is only read if type
+   is N_svc.
+*/
+
+NODE *
+cfg_get_svc_node(username, type, protocol, svcname, recurse)
+char *username;
+int type;
+char *protocol, *svcname;
+int recurse;
+{
+    USER *user, *group;
+    NODE *svc;
+
+    if (debug & DEBUG_CONFIG_FLAG)
+       report(LOG_DEBUG, 
+              "cfg_get_svc_node: username=%s %s proto=%s svcname=%s rec=%d",
+              username, 
+              cfg_nodestring(type), 
+              protocol ? protocol : "", 
+              svcname ? svcname : "", 
+              recurse);
+
+    /* find the user/group entry */
+    user = (USER *) hash_lookup(usertable, username);
+
+    if (!user) {
+       if (debug & DEBUG_CONFIG_FLAG)
+           report(LOG_DEBUG, "cfg_get_svc_node: no user named %s", username);
+       return (NULL);
+    }
+
+    /* found the user entry. Find svc node */
+    for(svc = (NODE *) get_value(user, S_svc).pval; svc; svc = svc->next) {
+
+       if (svc->type != type) 
+           continue;
+
+       if (type == N_svc_ppp && !STREQ(svc->value1, protocol)) {
+           continue;
+       }
+
+       if (type == N_svc && !STREQ(svc->value1, svcname)) {
+           continue;
+       }
+
+       if (debug & DEBUG_CONFIG_FLAG)
+           report(LOG_DEBUG, 
+                  "cfg_get_svc_node: found %s proto=%s svcname=%s",
+                  cfg_nodestring(type), 
+                  protocol ? protocol : "", 
+                  svcname ? svcname : "");
+
+       return(svc);
+    }
+
+    if (!recurse) {
+       if (debug & DEBUG_CONFIG_FLAG)
+           report(LOG_DEBUG, "cfg_get_svc_node: returns NULL");
+       return (NULL);
+    }
+
+    /* no matching node. Check containing group */
+    if (user->member)
+       group = (USER *) hash_lookup(grouptable, user->member);
+    else
+       group = NULL;
+
+    while (group) {
+       if (debug & DEBUG_CONFIG_FLAG)
+           report(LOG_DEBUG, "cfg_get_svc_node: recurse group = %s",
+                  group->name);
+
+       for(svc = (NODE *) get_value(group, S_svc).pval; svc; svc = svc->next) {
+
+           if (svc->type != type) 
+               continue;
+
+           if (type == N_svc_ppp && !STREQ(svc->value1, protocol)) {
+               continue;
+           }
+
+           if (type == N_svc && !STREQ(svc->value1, svcname)) {
+               continue;
+           }
+
+           if (debug & DEBUG_CONFIG_FLAG)
+               report(LOG_DEBUG, 
+                      "cfg_get_svc_node: found %s proto=%s svcname=%s",
+                      cfg_nodestring(type), 
+                      protocol ? protocol : "", 
+                      svcname ? svcname : "");
+
+           return(svc);
+       }
+
+       /* still nothing. Check containing group and so on */
+
+       if (group->member)
+           group = (USER *) hash_lookup(grouptable, group->member);
+       else
+           group = NULL;
+    }
+
+    if (debug & DEBUG_CONFIG_FLAG)
+       report(LOG_DEBUG, "cfg_get_svc_node: returns NULL");
+
+    /* no matching svc node for this user or her containing groups */
+    return (NULL);
+}
+
+/* Return a pointer to the node representing a set of command regexp
+   matches for a user and command, handling recursion issues correctly */
+NODE *
+cfg_get_cmd_node(name, cmdname, recurse)
+char *name, *cmdname;
+int recurse;
+
+{
+    USER *user, *group;
+    NODE *svc;
+
+    if (debug & DEBUG_CONFIG_FLAG)
+       report(LOG_DEBUG, "cfg_get_cmd_node: name=%s cmdname=%s rec=%d",
+              name, cmdname, recurse);
+
+    /* find the user/group entry */
+    user = (USER *) hash_lookup(usertable, name);
+
+    if (!user) {
+       if (debug & DEBUG_CONFIG_FLAG)
+           report(LOG_DEBUG, "cfg_get_cmd_node: no user named %s", name);
+       return (NULL);
+    }
+    /* found the user entry. Find svc node */
+    svc = (NODE *) get_value(user, S_svc).pval;
+
+    while (svc) {
+       if (svc->type == N_svc_cmd && STREQ(svc->value, cmdname)) {
+           if (debug & DEBUG_CONFIG_FLAG)
+               report(LOG_DEBUG, "cfg_get_cmd_node: found cmd %s %s node",
+                      cmdname, cfg_nodestring(svc->type));
+           return (svc);
+       }
+       svc = svc->next;
+    }
+
+    if (!recurse) {
+       if (debug & DEBUG_CONFIG_FLAG)
+           report(LOG_DEBUG, "cfg_get_cmd_node: returns NULL");
+       return (NULL);
+    }
+    /* no matching node. Check containing group */
+    if (user->member)
+       group = (USER *) hash_lookup(grouptable, user->member);
+    else
+       group = NULL;
+
+    while (group) {
+       if (debug & DEBUG_CONFIG_FLAG)
+           report(LOG_DEBUG, "cfg_get_cmd_node: recurse group = %s",
+                  group->name);
+
+       svc = get_value(group, S_svc).pval;
+
+       while (svc) {
+           if (svc->type == N_svc_cmd && STREQ(svc->value, cmdname)) {
+               if (debug & DEBUG_CONFIG_FLAG)
+                   report(LOG_DEBUG, "cfg_get_cmd_node: found cmd %s node %s",
+                          cmdname, cfg_nodestring(svc->type));
+               return (svc);
+           }
+           svc = svc->next;
+       }
+
+       /* still nothing. Check containing group and so on */
+
+       if (group->member)
+           group = (USER *) hash_lookup(grouptable, group->member);
+       else
+           group = NULL;
+    }
+
+    if (debug & DEBUG_CONFIG_FLAG)
+       report(LOG_DEBUG, "cfg_get_cmd_node: returns NULL");
+
+    /* no matching cmd node for this user or her containing groups */
+    return (NULL);
+}
+
+/* Return an array of character strings representing configured AV
+ * pairs, given a username and a service node. 
+ *
+ * In the AV strings returned, manipulate the separator character to
+ * indicate which args are optional and which are mandatory.
+ *
+ * Lastly, indicate what default permission was configured by setting
+ * denyp */
+
+char **
+cfg_get_svc_attrs(svcnode, denyp)
+NODE *svcnode;
+int *denyp;
+{
+    int i;
+    NODE *node;
+    char **args;
+
+    *denyp = 1;
+
+    if (!svcnode)
+       return (NULL);
+
+    *denyp = (svcnode->dflt == S_deny);
+
+    i = 0;
+    for (node = svcnode->value; node; node = node->next)
+       i++;
+
+    args = (char **) tac_malloc(sizeof(char *) * (i + 1));
+
+    i = 0;
+    for (node = svcnode->value; node; node = node->next) {
+       char *arg = tac_strdup(node->value);
+       char *p = index(arg, '=');
+
+       if (p && node->type == N_optarg)
+           *p = '*';
+       args[i++] = arg;
+    }
+    args[i] = NULL;
+    return (args);
+}
+
+
+int
+cfg_user_svc_default_is_permit(user)
+char *user;
+
+{
+    int permit = cfg_get_intvalue(user, TAC_IS_USER, S_svc_dflt,
+                              TAC_PLUS_RECURSE);
+
+    switch (permit) {
+    default:                   /* default is deny */
+    case S_deny:
+       return (0);
+    case S_permit:
+       return (1);
+    }
+}
+
+int
+cfg_no_user_permitted()
+{
+    if (no_user_dflt == S_permit)
+       return (1);
+    return (0);
+}
+
+
+char *
+cfg_get_authen_default()
+{
+    return (authen_default);
+}
+
+/* For describe authentication method(pam,file,db..etc) */
+int 
+cfg_get_authen_default_method()
+{
+   return (authen_default_method);
+}
+
+
+/* Return 1 if this user has any ppp services configured. Used for
+   authorizing ppp/lcp requests */
+int
+cfg_ppp_is_configured(username, recurse)
+    char *username;
+    int recurse;
+{
+    USER *user, *group;
+    NODE *svc;
+
+    if (debug & DEBUG_CONFIG_FLAG)
+       report(LOG_DEBUG, "cfg_ppp_is_configured: username=%s rec=%d",
+              username, recurse);
+
+    /* find the user/group entry */
+    user = (USER *) hash_lookup(usertable, username);
+
+    if (!user) {
+       if (debug & DEBUG_CONFIG_FLAG)
+           report(LOG_DEBUG, "cfg_ppp_is_configured: no user named %s", 
+                  username);
+       return (0);
+    }
+
+    /* found the user entry. Find svc node */
+    for(svc = (NODE *) get_value(user, S_svc).pval; svc; svc = svc->next) {
+
+       if (svc->type != N_svc_ppp) 
+           continue;
+
+       if (debug & DEBUG_CONFIG_FLAG)
+           report(LOG_DEBUG, "cfg_ppp_is_configured: found svc ppp %s node",
+                  svc->value1);
+       
+       return(1);
+    }
+
+    if (!recurse) {
+       if (debug & DEBUG_CONFIG_FLAG)
+           report(LOG_DEBUG, "cfg_ppp_is_configured: returns 0");
+       return (0);
+    }
+
+    /* no matching node. Check containing group */
+    if (user->member)
+       group = (USER *) hash_lookup(grouptable, user->member);
+    else
+       group = NULL;
+
+    while (group) {
+       if (debug & DEBUG_CONFIG_FLAG)
+           report(LOG_DEBUG, "cfg_ppp_is_configured: recurse group = %s",
+                  group->name);
+
+       for(svc = (NODE *) get_value(group, S_svc).pval; svc; svc = svc->next) {
+
+           if (svc->type != N_svc_ppp)
+               continue;
+
+           if (debug & DEBUG_CONFIG_FLAG)
+               report(LOG_DEBUG, "cfg_ppp_is_configured: found svc ppp %s node",
+                      svc->value1);
+       
+           return(1);
+       }
+
+       /* still nothing. Check containing group and so on */
+
+       if (group->member)
+           group = (USER *) hash_lookup(grouptable, group->member);
+       else
+           group = NULL;
+    }
+
+    if (debug & DEBUG_CONFIG_FLAG)
+       report(LOG_DEBUG, "cfg_ppp_is_configured: returns 0");
+
+    /* no PPP svc nodes for this user or her containing groups */
+    return (0);
+}
+
+/* For getting host key */
+char *
+cfg_get_host_key(host)
+char *host;
+{
+    return (cfg_get_phvalue(host, S_key));
+}
+
diff --git a/config.guess b/config.guess
new file mode 100644 (file)
index 0000000..e1b5871
--- /dev/null
@@ -0,0 +1,1121 @@
+#! /bin/sh
+# Attempt to guess a canonical system name.
+#   Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999
+#   Free Software Foundation, Inc.
+#
+# This file 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.
+#
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# Written by Per Bothner <bothner@cygnus.com>.
+# The master version of this file is at the FSF in /home/gd/gnu/lib.
+# Please send patches to <autoconf-patches@gnu.org>.
+#
+# This script attempts to guess a canonical system name similar to
+# config.sub.  If it succeeds, it prints the system name on stdout, and
+# exits with 0.  Otherwise, it exits with 1.
+#
+# The plan is that this can be called by configure scripts if you
+# don't specify an explicit system type (host/target name).
+#
+# Only a few systems have been added to this list; please add others
+# (but try to keep the structure clean).
+#
+
+# Use $HOST_CC if defined. $CC may point to a cross-compiler
+if test x"$CC_FOR_BUILD" = x; then
+  if test x"$HOST_CC" != x; then
+    CC_FOR_BUILD="$HOST_CC"
+  else
+    if test x"$CC" != x; then
+      CC_FOR_BUILD="$CC"
+    else
+      CC_FOR_BUILD=cc
+    fi
+  fi
+fi
+
+
+# This is needed to find uname on a Pyramid OSx when run in the BSD universe.
+# (ghazi@noc.rutgers.edu 8/24/94.)
+if (test -f /.attbin/uname) >/dev/null 2>&1 ; then
+       PATH=$PATH:/.attbin ; export PATH
+fi
+
+UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown
+UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown
+UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown
+UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown
+
+dummy=dummy-$$
+trap 'rm -f $dummy.c $dummy.o $dummy; exit 1' 1 2 15
+
+# Note: order is significant - the case branches are not exclusive.
+
+case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in
+    alpha:OSF1:*:*)
+       if test $UNAME_RELEASE = "V4.0"; then
+               UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'`
+       fi
+       # A Vn.n version is a released version.
+       # A Tn.n version is a released field test version.
+       # A Xn.n version is an unreleased experimental baselevel.
+       # 1.2 uses "1.2" for uname -r.
+       cat <<EOF >$dummy.s
+       .globl main
+       .ent main
+main:
+       .frame \$30,0,\$26,0
+       .prologue 0
+       .long 0x47e03d80 # implver $0
+       lda \$2,259
+       .long 0x47e20c21 # amask $2,$1
+       srl \$1,8,\$2
+       sll \$2,2,\$2
+       sll \$0,3,\$0
+       addl \$1,\$0,\$0
+       addl \$2,\$0,\$0
+       ret \$31,(\$26),1
+       .end main
+EOF
+       $CC_FOR_BUILD $dummy.s -o $dummy 2>/dev/null
+       if test "$?" = 0 ; then
+               ./$dummy
+               case "$?" in
+                       7)
+                               UNAME_MACHINE="alpha"
+                               ;;
+                       15)
+                               UNAME_MACHINE="alphaev5"
+                               ;;
+                       14)
+                               UNAME_MACHINE="alphaev56"
+                               ;;
+                       10)
+                               UNAME_MACHINE="alphapca56"
+                               ;;
+                       16)
+                               UNAME_MACHINE="alphaev6"
+                               ;;
+               esac
+       fi
+       rm -f $dummy.s $dummy
+       echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[VTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'`
+       exit 0 ;;
+    Alpha\ *:Windows_NT*:*)
+       # How do we know it's Interix rather than the generic POSIX subsystem?
+       # Should we change UNAME_MACHINE based on the output of uname instead
+       # of the specific Alpha model?
+       echo alpha-pc-interix
+       exit 0 ;;
+    21064:Windows_NT:50:3)
+       echo alpha-dec-winnt3.5
+       exit 0 ;;
+    Amiga*:UNIX_System_V:4.0:*)
+       echo m68k-cbm-sysv4
+       exit 0;;
+    amiga:NetBSD:*:*)
+      echo m68k-cbm-netbsd${UNAME_RELEASE}
+      exit 0 ;;
+    amiga:OpenBSD:*:*)
+       echo m68k-unknown-openbsd${UNAME_RELEASE}
+       exit 0 ;;
+    *:[Aa]miga[Oo][Ss]:*:*)
+       echo ${UNAME_MACHINE}-unknown-amigaos
+       exit 0 ;;
+    arc64:OpenBSD:*:*)
+       echo mips64el-unknown-openbsd${UNAME_RELEASE}
+       exit 0 ;;
+    arc:OpenBSD:*:*)
+       echo mipsel-unknown-openbsd${UNAME_RELEASE}
+       exit 0 ;;
+    hkmips:OpenBSD:*:*)
+       echo mips-unknown-openbsd${UNAME_RELEASE}
+       exit 0 ;;
+    pmax:OpenBSD:*:*)
+       echo mipsel-unknown-openbsd${UNAME_RELEASE}
+       exit 0 ;;
+    sgi:OpenBSD:*:*)
+       echo mips-unknown-openbsd${UNAME_RELEASE}
+       exit 0 ;;
+    wgrisc:OpenBSD:*:*)
+       echo mipsel-unknown-openbsd${UNAME_RELEASE}
+       exit 0 ;;
+    *:OS/390:*:*)
+       echo i370-ibm-openedition
+       exit 0 ;;
+    arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*)
+       echo arm-acorn-riscix${UNAME_RELEASE}
+       exit 0;;
+    arm32:NetBSD:*:*)
+       echo arm-unknown-netbsd`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'`
+       exit 0 ;;
+    SR2?01:HI-UX/MPP:*:*)
+       echo hppa1.1-hitachi-hiuxmpp
+       exit 0;;
+    Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*)
+       # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE.
+       if test "`(/bin/universe) 2>/dev/null`" = att ; then
+               echo pyramid-pyramid-sysv3
+       else
+               echo pyramid-pyramid-bsd
+       fi
+       exit 0 ;;
+    NILE*:*:*:dcosx)
+       echo pyramid-pyramid-svr4
+       exit 0 ;;
+    sun4H:SunOS:5.*:*)
+       echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+       exit 0 ;;
+    sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*)
+       echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+       exit 0 ;;
+    i86pc:SunOS:5.*:*)
+       echo i386-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+       exit 0 ;;
+    sun4*:SunOS:6*:*)
+       # According to config.sub, this is the proper way to canonicalize
+       # SunOS6.  Hard to guess exactly what SunOS6 will be like, but
+       # it's likely to be more like Solaris than SunOS4.
+       echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+       exit 0 ;;
+    sun4*:SunOS:*:*)
+       case "`/usr/bin/arch -k`" in
+           Series*|S4*)
+               UNAME_RELEASE=`uname -v`
+               ;;
+       esac
+       # Japanese Language versions have a version number like `4.1.3-JL'.
+       echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'`
+       exit 0 ;;
+    sun3*:SunOS:*:*)
+       echo m68k-sun-sunos${UNAME_RELEASE}
+       exit 0 ;;
+    sun*:*:4.2BSD:*)
+       UNAME_RELEASE=`(head -1 /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null`
+       test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3
+       case "`/bin/arch`" in
+           sun3)
+               echo m68k-sun-sunos${UNAME_RELEASE}
+               ;;
+           sun4)
+               echo sparc-sun-sunos${UNAME_RELEASE}
+               ;;
+       esac
+       exit 0 ;;
+    aushp:SunOS:*:*)
+       echo sparc-auspex-sunos${UNAME_RELEASE}
+       exit 0 ;;
+    atari*:NetBSD:*:*)
+       echo m68k-atari-netbsd${UNAME_RELEASE}
+       exit 0 ;;
+    atari*:OpenBSD:*:*)
+       echo m68k-unknown-openbsd${UNAME_RELEASE}
+       exit 0 ;;
+    # The situation for MiNT is a little confusing.  The machine name
+    # can be virtually everything (everything which is not
+    # "atarist" or "atariste" at least should have a processor 
+    # > m68000).  The system name ranges from "MiNT" over "FreeMiNT"
+    # to the lowercase version "mint" (or "freemint").  Finally
+    # the system name "TOS" denotes a system which is actually not
+    # MiNT.  But MiNT is downward compatible to TOS, so this should
+    # be no problem.
+    atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*)
+        echo m68k-atari-mint${UNAME_RELEASE}
+       exit 0 ;;
+    atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*)
+       echo m68k-atari-mint${UNAME_RELEASE}
+        exit 0 ;;
+    *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*)
+        echo m68k-atari-mint${UNAME_RELEASE}
+       exit 0 ;;
+    milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*)
+        echo m68k-milan-mint${UNAME_RELEASE}
+        exit 0 ;;
+    hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*)
+        echo m68k-hades-mint${UNAME_RELEASE}
+        exit 0 ;;
+    *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*)
+        echo m68k-unknown-mint${UNAME_RELEASE}
+        exit 0 ;;
+    sun3*:NetBSD:*:*)
+       echo m68k-sun-netbsd${UNAME_RELEASE}
+       exit 0 ;;
+    sun3*:OpenBSD:*:*)
+       echo m68k-unknown-openbsd${UNAME_RELEASE}
+       exit 0 ;;
+    mac68k:NetBSD:*:*)
+       echo m68k-apple-netbsd${UNAME_RELEASE}
+       exit 0 ;;
+    mac68k:OpenBSD:*:*)
+       echo m68k-unknown-openbsd${UNAME_RELEASE}
+       exit 0 ;;
+    mvme68k:OpenBSD:*:*)
+       echo m68k-unknown-openbsd${UNAME_RELEASE}
+       exit 0 ;;
+    mvme88k:OpenBSD:*:*)
+       echo m88k-unknown-openbsd${UNAME_RELEASE}
+       exit 0 ;;
+    powerpc:machten:*:*)
+       echo powerpc-apple-machten${UNAME_RELEASE}
+       exit 0 ;;
+    macppc:NetBSD:*:*)
+        echo powerpc-apple-netbsd${UNAME_RELEASE}
+        exit 0 ;;
+    RISC*:Mach:*:*)
+       echo mips-dec-mach_bsd4.3
+       exit 0 ;;
+    RISC*:ULTRIX:*:*)
+       echo mips-dec-ultrix${UNAME_RELEASE}
+       exit 0 ;;
+    VAX*:ULTRIX*:*:*)
+       echo vax-dec-ultrix${UNAME_RELEASE}
+       exit 0 ;;
+    2020:CLIX:*:* | 2430:CLIX:*:*)
+       echo clipper-intergraph-clix${UNAME_RELEASE}
+       exit 0 ;;
+    mips:*:*:UMIPS | mips:*:*:RISCos)
+       sed 's/^        //' << EOF >$dummy.c
+#ifdef __cplusplus
+       int main (int argc, char *argv[]) {
+#else
+       int main (argc, argv) int argc; char *argv[]; {
+#endif
+       #if defined (host_mips) && defined (MIPSEB)
+       #if defined (SYSTYPE_SYSV)
+         printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0);
+       #endif
+       #if defined (SYSTYPE_SVR4)
+         printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0);
+       #endif
+       #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD)
+         printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0);
+       #endif
+       #endif
+         exit (-1);
+       }
+EOF
+       $CC_FOR_BUILD $dummy.c -o $dummy \
+         && ./$dummy `echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` \
+         && rm $dummy.c $dummy && exit 0
+       rm -f $dummy.c $dummy
+       echo mips-mips-riscos${UNAME_RELEASE}
+       exit 0 ;;
+    Night_Hawk:Power_UNIX:*:*)
+       echo powerpc-harris-powerunix
+       exit 0 ;;
+    m88k:CX/UX:7*:*)
+       echo m88k-harris-cxux7
+       exit 0 ;;
+    m88k:*:4*:R4*)
+       echo m88k-motorola-sysv4
+       exit 0 ;;
+    m88k:*:3*:R3*)
+       echo m88k-motorola-sysv3
+       exit 0 ;;
+    AViiON:dgux:*:*)
+        # DG/UX returns AViiON for all architectures
+        UNAME_PROCESSOR=`/usr/bin/uname -p`
+       if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110]
+       then
+           if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \
+              [ ${TARGET_BINARY_INTERFACE}x = x ]
+           then
+               echo m88k-dg-dgux${UNAME_RELEASE}
+           else
+               echo m88k-dg-dguxbcs${UNAME_RELEASE}
+           fi
+       else
+           echo i586-dg-dgux${UNAME_RELEASE}
+       fi
+       exit 0 ;;
+    M88*:DolphinOS:*:*)        # DolphinOS (SVR3)
+       echo m88k-dolphin-sysv3
+       exit 0 ;;
+    M88*:*:R3*:*)
+       # Delta 88k system running SVR3
+       echo m88k-motorola-sysv3
+       exit 0 ;;
+    XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3)
+       echo m88k-tektronix-sysv3
+       exit 0 ;;
+    Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD)
+       echo m68k-tektronix-bsd
+       exit 0 ;;
+    *:IRIX*:*:*)
+       echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'`
+       exit 0 ;;
+    ????????:AIX?:[12].1:2)   # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX.
+       echo romp-ibm-aix      # uname -m gives an 8 hex-code CPU id
+       exit 0 ;;              # Note that: echo "'`uname -s`'" gives 'AIX '
+    i?86:AIX:*:*)
+       echo i386-ibm-aix
+       exit 0 ;;
+    *:AIX:2:3)
+       if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then
+               sed 's/^                //' << EOF >$dummy.c
+               #include <sys/systemcfg.h>
+
+               main()
+                       {
+                       if (!__power_pc())
+                               exit(1);
+                       puts("powerpc-ibm-aix3.2.5");
+                       exit(0);
+                       }
+EOF
+               $CC_FOR_BUILD $dummy.c -o $dummy && ./$dummy && rm $dummy.c $dummy && exit 0
+               rm -f $dummy.c $dummy
+               echo rs6000-ibm-aix3.2.5
+       elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then
+               echo rs6000-ibm-aix3.2.4
+       else
+               echo rs6000-ibm-aix3.2
+       fi
+       exit 0 ;;
+    *:AIX:*:4)
+       IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | head -1 | awk '{ print $1 }'`
+       if /usr/sbin/lsattr -EHl ${IBM_CPU_ID} | grep POWER >/dev/null 2>&1; then
+               IBM_ARCH=rs6000
+       else
+               IBM_ARCH=powerpc
+       fi
+       if [ -x /usr/bin/oslevel ] ; then
+               IBM_REV=`/usr/bin/oslevel`
+       else
+               IBM_REV=4.${UNAME_RELEASE}
+       fi
+       echo ${IBM_ARCH}-ibm-aix${IBM_REV}
+       exit 0 ;;
+    *:AIX:*:*)
+       echo rs6000-ibm-aix
+       exit 0 ;;
+    ibmrt:4.4BSD:*|romp-ibm:BSD:*)
+       echo romp-ibm-bsd4.4
+       exit 0 ;;
+    ibmrt:*BSD:*|romp-ibm:BSD:*)            # covers RT/PC NetBSD and
+       echo romp-ibm-bsd${UNAME_RELEASE}   # 4.3 with uname added to
+       exit 0 ;;                           # report: romp-ibm BSD 4.3
+    *:BOSX:*:*)
+       echo rs6000-bull-bosx
+       exit 0 ;;
+    DPX/2?00:B.O.S.:*:*)
+       echo m68k-bull-sysv3
+       exit 0 ;;
+    9000/[34]??:4.3bsd:1.*:*)
+       echo m68k-hp-bsd
+       exit 0 ;;
+    hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*)
+       echo m68k-hp-bsd4.4
+       exit 0 ;;
+    9000/[34678]??:HP-UX:*:*)
+       case "${UNAME_MACHINE}" in
+           9000/31? )            HP_ARCH=m68000 ;;
+           9000/[34]?? )         HP_ARCH=m68k ;;
+           9000/[678][0-9][0-9])
+              sed 's/^              //' << EOF >$dummy.c
+              #include <stdlib.h>
+              #include <unistd.h>
+
+              int main ()
+              {
+              #if defined(_SC_KERNEL_BITS)
+                  long bits = sysconf(_SC_KERNEL_BITS);
+              #endif
+                  long cpu  = sysconf (_SC_CPU_VERSION);
+
+                  switch (cpu)
+               {
+               case CPU_PA_RISC1_0: puts ("hppa1.0"); break;
+               case CPU_PA_RISC1_1: puts ("hppa1.1"); break;
+               case CPU_PA_RISC2_0:
+              #if defined(_SC_KERNEL_BITS)
+                   switch (bits)
+                       {
+                       case 64: puts ("hppa2.0w"); break;
+                       case 32: puts ("hppa2.0n"); break;
+                       default: puts ("hppa2.0"); break;
+                       } break;
+              #else  /* !defined(_SC_KERNEL_BITS) */
+                   puts ("hppa2.0"); break;
+              #endif
+               default: puts ("hppa1.0"); break;
+               }
+                  exit (0);
+              }
+EOF
+       (CCOPTS= $CC_FOR_BUILD $dummy.c -o $dummy 2>/dev/null ) && HP_ARCH=`./$dummy`
+       rm -f $dummy.c $dummy
+       esac
+       HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'`
+       echo ${HP_ARCH}-hp-hpux${HPUX_REV}
+       exit 0 ;;
+    3050*:HI-UX:*:*)
+       sed 's/^        //' << EOF >$dummy.c
+       #include <unistd.h>
+       int
+       main ()
+       {
+         long cpu = sysconf (_SC_CPU_VERSION);
+         /* The order matters, because CPU_IS_HP_MC68K erroneously returns
+            true for CPU_PA_RISC1_0.  CPU_IS_PA_RISC returns correct
+            results, however.  */
+         if (CPU_IS_PA_RISC (cpu))
+           {
+             switch (cpu)
+               {
+                 case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break;
+                 case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break;
+                 case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break;
+                 default: puts ("hppa-hitachi-hiuxwe2"); break;
+               }
+           }
+         else if (CPU_IS_HP_MC68K (cpu))
+           puts ("m68k-hitachi-hiuxwe2");
+         else puts ("unknown-hitachi-hiuxwe2");
+         exit (0);
+       }
+EOF
+       $CC_FOR_BUILD $dummy.c -o $dummy && ./$dummy && rm $dummy.c $dummy && exit 0
+       rm -f $dummy.c $dummy
+       echo unknown-hitachi-hiuxwe2
+       exit 0 ;;
+    9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* )
+       echo hppa1.1-hp-bsd
+       exit 0 ;;
+    9000/8??:4.3bsd:*:*)
+       echo hppa1.0-hp-bsd
+       exit 0 ;;
+    *9??*:MPE/iX:*:*)
+       echo hppa1.0-hp-mpeix
+       exit 0 ;;
+    hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* )
+       echo hppa1.1-hp-osf
+       exit 0 ;;
+    hp8??:OSF1:*:*)
+       echo hppa1.0-hp-osf
+       exit 0 ;;
+    i?86:OSF1:*:*)
+       if [ -x /usr/sbin/sysversion ] ; then
+           echo ${UNAME_MACHINE}-unknown-osf1mk
+       else
+           echo ${UNAME_MACHINE}-unknown-osf1
+       fi
+       exit 0 ;;
+    parisc*:Lites*:*:*)
+       echo hppa1.1-hp-lites
+       exit 0 ;;
+    hppa*:OpenBSD:*:*)
+       echo hppa-unknown-openbsd
+       exit 0 ;;
+    C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*)
+       echo c1-convex-bsd
+        exit 0 ;;
+    C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*)
+       if getsysinfo -f scalar_acc
+       then echo c32-convex-bsd
+       else echo c2-convex-bsd
+       fi
+        exit 0 ;;
+    C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*)
+       echo c34-convex-bsd
+        exit 0 ;;
+    C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*)
+       echo c38-convex-bsd
+        exit 0 ;;
+    C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*)
+       echo c4-convex-bsd
+        exit 0 ;;
+    CRAY*X-MP:*:*:*)
+       echo xmp-cray-unicos
+        exit 0 ;;
+    CRAY*Y-MP:*:*:*)
+       echo ymp-cray-unicos${UNAME_RELEASE}
+       exit 0 ;;
+    CRAY*[A-Z]90:*:*:*)
+       echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \
+       | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \
+             -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/
+       exit 0 ;;
+    CRAY*TS:*:*:*)
+       echo t90-cray-unicos${UNAME_RELEASE}
+       exit 0 ;;
+    CRAY*T3E:*:*:*)
+       echo alpha-cray-unicosmk${UNAME_RELEASE}
+       exit 0 ;;
+    CRAY-2:*:*:*)
+       echo cray2-cray-unicos
+        exit 0 ;;
+    F300:UNIX_System_V:*:*)
+        FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'`
+        FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'`
+        echo "f300-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}"
+        exit 0 ;;
+    F301:UNIX_System_V:*:*)
+       echo f301-fujitsu-uxpv`echo $UNAME_RELEASE | sed 's/ .*//'`
+       exit 0 ;;
+    hp3[0-9][05]:NetBSD:*:*)
+       echo m68k-hp-netbsd${UNAME_RELEASE}
+       exit 0 ;;
+    hp300:OpenBSD:*:*)
+       echo m68k-unknown-openbsd${UNAME_RELEASE}
+       exit 0 ;;
+    i?86:BSD/386:*:* | i?86:BSD/OS:*:*)
+       echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE}
+       exit 0 ;;
+    sparc*:BSD/OS:*:*)
+       echo sparc-unknown-bsdi${UNAME_RELEASE}
+       exit 0 ;;
+    *:BSD/OS:*:*)
+       echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE}
+       exit 0 ;;
+    *:FreeBSD:*:*)
+       if test -x /usr/bin/objformat; then
+           if test "elf" = "`/usr/bin/objformat`"; then
+               echo ${UNAME_MACHINE}-unknown-freebsdelf`echo ${UNAME_RELEASE}|sed -e 's/[-_].*//'`
+               exit 0
+           fi
+       fi
+       echo ${UNAME_MACHINE}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`
+       exit 0 ;;
+    *:NetBSD:*:*)
+       echo ${UNAME_MACHINE}-unknown-netbsd`echo ${UNAME_RELEASE}|sed -e 's/[-_].*//'`
+       exit 0 ;;
+    *:OpenBSD:*:*)
+       echo ${UNAME_MACHINE}-unknown-openbsd`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'`
+       exit 0 ;;
+    i*:CYGWIN*:*)
+       echo ${UNAME_MACHINE}-pc-cygwin
+       exit 0 ;;
+    i*:MINGW*:*)
+       echo ${UNAME_MACHINE}-pc-mingw32
+       exit 0 ;;
+    i*:Windows_NT*:* | Pentium*:Windows_NT*:*)
+       # How do we know it's Interix rather than the generic POSIX subsystem?
+       # It also conflicts with pre-2.0 versions of AT&T UWIN. Should we
+       # UNAME_MACHINE based on the output of uname instead of i386?
+       echo i386-pc-interix
+       exit 0 ;;
+    i*:UWIN*:*)
+       echo ${UNAME_MACHINE}-pc-uwin
+       exit 0 ;;
+    p*:CYGWIN*:*)
+       echo powerpcle-unknown-cygwin
+       exit 0 ;;
+    prep*:SunOS:5.*:*)
+       echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+       exit 0 ;;
+    *:GNU:*:*)
+       echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-gnu`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'`
+       exit 0 ;;
+    *:Linux:*:*)
+
+       # The BFD linker knows what the default object file format is, so
+       # first see if it will tell us. cd to the root directory to prevent
+       # problems with other programs or directories called `ld' in the path.
+       ld_help_string=`cd /; ld --help 2>&1`
+       ld_supported_emulations=`echo $ld_help_string \
+                        | sed -ne '/supported emulations:/!d
+                                   s/[         ][      ]*/ /g
+                                   s/.*supported emulations: *//
+                                   s/ .*//
+                                   p'`
+        case "$ld_supported_emulations" in
+         *ia64)
+               echo "${UNAME_MACHINE}-unknown-linux"
+               exit 0
+               ;;
+         i?86linux)
+               echo "${UNAME_MACHINE}-pc-linux-gnuaout"
+               exit 0
+               ;;
+         i?86coff)
+               echo "${UNAME_MACHINE}-pc-linux-gnucoff"
+               exit 0
+               ;;
+         sparclinux)
+               echo "${UNAME_MACHINE}-unknown-linux-gnuaout"
+               exit 0
+               ;;
+         armlinux)
+               echo "${UNAME_MACHINE}-unknown-linux-gnuaout"
+               exit 0
+               ;;
+         elf32arm*)
+               echo "${UNAME_MACHINE}-unknown-linux-gnu"
+               exit 0
+               ;;
+         armelf_linux*)
+               echo "${UNAME_MACHINE}-unknown-linux-gnu"
+               exit 0
+               ;;
+         m68klinux)
+               echo "${UNAME_MACHINE}-unknown-linux-gnuaout"
+               exit 0
+               ;;
+         elf32ppc)
+               # Determine Lib Version
+               cat >$dummy.c <<EOF
+#include <features.h>
+#if defined(__GLIBC__)
+extern char __libc_version[];
+extern char __libc_release[];
+#endif
+main(argc, argv)
+     int argc;
+     char *argv[];
+{
+#if defined(__GLIBC__)
+  printf("%s %s\n", __libc_version, __libc_release);
+#else
+  printf("unkown\n");
+#endif
+  return 0;
+}
+EOF
+               LIBC=""
+               $CC_FOR_BUILD $dummy.c -o $dummy 2>/dev/null
+               if test "$?" = 0 ; then
+                       ./$dummy | grep 1\.99 > /dev/null
+                       if test "$?" = 0 ; then
+                               LIBC="libc1"
+                       fi
+               fi      
+               rm -f $dummy.c $dummy
+               echo powerpc-unknown-linux-gnu${LIBC}
+               exit 0
+               ;;
+       esac
+
+       if test "${UNAME_MACHINE}" = "alpha" ; then
+               sed 's/^        //'  <<EOF >$dummy.s
+               .globl main
+               .ent main
+       main:
+               .frame \$30,0,\$26,0
+               .prologue 0
+               .long 0x47e03d80 # implver $0
+               lda \$2,259
+               .long 0x47e20c21 # amask $2,$1
+               srl \$1,8,\$2
+               sll \$2,2,\$2
+               sll \$0,3,\$0
+               addl \$1,\$0,\$0
+               addl \$2,\$0,\$0
+               ret \$31,(\$26),1
+               .end main
+EOF
+               LIBC=""
+               $CC_FOR_BUILD $dummy.s -o $dummy 2>/dev/null
+               if test "$?" = 0 ; then
+                       ./$dummy
+                       case "$?" in
+                       7)
+                               UNAME_MACHINE="alpha"
+                               ;;
+                       15)
+                               UNAME_MACHINE="alphaev5"
+                               ;;
+                       14)
+                               UNAME_MACHINE="alphaev56"
+                               ;;
+                       10)
+                               UNAME_MACHINE="alphapca56"
+                               ;;
+                       16)
+                               UNAME_MACHINE="alphaev6"
+                               ;;
+                       esac
+
+                       objdump --private-headers $dummy | \
+                         grep ld.so.1 > /dev/null
+                       if test "$?" = 0 ; then
+                               LIBC="libc1"
+                       fi
+               fi
+               rm -f $dummy.s $dummy
+               echo ${UNAME_MACHINE}-unknown-linux-gnu${LIBC} ; exit 0
+       elif test "${UNAME_MACHINE}" = "mips" ; then
+         cat >$dummy.c <<EOF
+#ifdef __cplusplus
+       int main (int argc, char *argv[]) {
+#else
+       int main (argc, argv) int argc; char *argv[]; {
+#endif
+#ifdef __MIPSEB__
+  printf ("%s-unknown-linux-gnu\n", argv[1]);
+#endif
+#ifdef __MIPSEL__
+  printf ("%sel-unknown-linux-gnu\n", argv[1]);
+#endif
+  return 0;
+}
+EOF
+         $CC_FOR_BUILD $dummy.c -o $dummy 2>/dev/null && ./$dummy "${UNAME_MACHINE}" && rm $dummy.c $dummy && exit 0
+         rm -f $dummy.c $dummy
+       else
+         # Either a pre-BFD a.out linker (linux-gnuoldld)
+         # or one that does not give us useful --help.
+         # GCC wants to distinguish between linux-gnuoldld and linux-gnuaout.
+         # If ld does not provide *any* "supported emulations:"
+         # that means it is gnuoldld.
+         echo "$ld_help_string" | grep >/dev/null 2>&1 "supported emulations:"
+         test $? != 0 && echo "${UNAME_MACHINE}-pc-linux-gnuoldld" && exit 0
+
+         case "${UNAME_MACHINE}" in
+         i?86)
+           VENDOR=pc;
+           ;;
+         *)
+           VENDOR=unknown;
+           ;;
+         esac
+         # Determine whether the default compiler is a.out or elf
+         cat >$dummy.c <<EOF
+#include <features.h>
+#ifdef __cplusplus
+       int main (int argc, char *argv[]) {
+#else
+       int main (argc, argv) int argc; char *argv[]; {
+#endif
+#ifdef __ELF__
+# ifdef __GLIBC__
+#  if __GLIBC__ >= 2
+    printf ("%s-${VENDOR}-linux-gnu\n", argv[1]);
+#  else
+    printf ("%s-${VENDOR}-linux-gnulibc1\n", argv[1]);
+#  endif
+# else
+   printf ("%s-${VENDOR}-linux-gnulibc1\n", argv[1]);
+# endif
+#else
+  printf ("%s-${VENDOR}-linux-gnuaout\n", argv[1]);
+#endif
+  return 0;
+}
+EOF
+         $CC_FOR_BUILD $dummy.c -o $dummy 2>/dev/null && ./$dummy "${UNAME_MACHINE}" && rm $dummy.c $dummy && exit 0
+         rm -f $dummy.c $dummy
+       fi ;;
+# ptx 4.0 does uname -s correctly, with DYNIX/ptx in there.  earlier versions
+# are messed up and put the nodename in both sysname and nodename.
+    i?86:DYNIX/ptx:4*:*)
+       echo i386-sequent-sysv4
+       exit 0 ;;
+    i?86:UNIX_SV:4.2MP:2.*)
+        # Unixware is an offshoot of SVR4, but it has its own version
+        # number series starting with 2...
+        # I am not positive that other SVR4 systems won't match this,
+       # I just have to hope.  -- rms.
+        # Use sysv4.2uw... so that sysv4* matches it.
+       echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION}
+       exit 0 ;;
+    i?86:*:4.*:* | i?86:SYSTEM_V:4.*:*)
+       UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'`
+       if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then
+               echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL}
+       else
+               echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL}
+       fi
+       exit 0 ;;
+    i?86:*:5:7*)
+        # Fixed at (any) Pentium or better
+        UNAME_MACHINE=i586
+        if [ ${UNAME_SYSTEM} = "UnixWare" ] ; then
+           echo ${UNAME_MACHINE}-sco-sysv${UNAME_RELEASE}uw${UNAME_VERSION}
+       else
+           echo ${UNAME_MACHINE}-pc-sysv${UNAME_RELEASE}
+       fi
+       exit 0 ;;
+    i?86:*:3.2:*)
+       if test -f /usr/options/cb.name; then
+               UNAME_REL=`sed -n 's/.*Version //p' </usr/options/cb.name`
+               echo ${UNAME_MACHINE}-pc-isc$UNAME_REL
+       elif /bin/uname -X 2>/dev/null >/dev/null ; then
+               UNAME_REL=`(/bin/uname -X|egrep Release|sed -e 's/.*= //')`
+               (/bin/uname -X|egrep i80486 >/dev/null) && UNAME_MACHINE=i486
+               (/bin/uname -X|egrep '^Machine.*Pentium' >/dev/null) \
+                       && UNAME_MACHINE=i586
+               (/bin/uname -X|egrep '^Machine.*Pent ?II' >/dev/null) \
+                       && UNAME_MACHINE=i686
+               (/bin/uname -X|egrep '^Machine.*Pentium Pro' >/dev/null) \
+                       && UNAME_MACHINE=i686
+               echo ${UNAME_MACHINE}-pc-sco$UNAME_REL
+       else
+               echo ${UNAME_MACHINE}-pc-sysv32
+       fi
+       exit 0 ;;
+    pc:*:*:*)
+        # uname -m prints for DJGPP always 'pc', but it prints nothing about
+        # the processor, so we play safe by assuming i386.
+       echo i386-pc-msdosdjgpp
+        exit 0 ;;
+    Intel:Mach:3*:*)
+       echo i386-pc-mach3
+       exit 0 ;;
+    paragon:*:*:*)
+       echo i860-intel-osf1
+       exit 0 ;;
+    i860:*:4.*:*) # i860-SVR4
+       if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then
+         echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4
+       else # Add other i860-SVR4 vendors below as they are discovered.
+         echo i860-unknown-sysv${UNAME_RELEASE}  # Unknown i860-SVR4
+       fi
+       exit 0 ;;
+    mini*:CTIX:SYS*5:*)
+       # "miniframe"
+       echo m68010-convergent-sysv
+       exit 0 ;;
+    M68*:*:R3V[567]*:*)
+       test -r /sysV68 && echo 'm68k-motorola-sysv' && exit 0 ;;
+    3[34]??:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 4850:*:4.0:3.0)
+       OS_REL=''
+       test -r /etc/.relid \
+       && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid`
+       /bin/uname -p 2>/dev/null | grep 86 >/dev/null \
+         && echo i486-ncr-sysv4.3${OS_REL} && exit 0
+       /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \
+         && echo i586-ncr-sysv4.3${OS_REL} && exit 0 ;;
+    3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*)
+        /bin/uname -p 2>/dev/null | grep 86 >/dev/null \
+          && echo i486-ncr-sysv4 && exit 0 ;;
+    m68*:LynxOS:2.*:*)
+       echo m68k-unknown-lynxos${UNAME_RELEASE}
+       exit 0 ;;
+    mc68030:UNIX_System_V:4.*:*)
+       echo m68k-atari-sysv4
+       exit 0 ;;
+    i?86:LynxOS:2.*:* | i?86:LynxOS:3.[01]*:*)
+       echo i386-unknown-lynxos${UNAME_RELEASE}
+       exit 0 ;;
+    TSUNAMI:LynxOS:2.*:*)
+       echo sparc-unknown-lynxos${UNAME_RELEASE}
+       exit 0 ;;
+    rs6000:LynxOS:2.*:* | PowerPC:LynxOS:2.*:*)
+       echo rs6000-unknown-lynxos${UNAME_RELEASE}
+       exit 0 ;;
+    SM[BE]S:UNIX_SV:*:*)
+       echo mips-dde-sysv${UNAME_RELEASE}
+       exit 0 ;;
+    RM*:ReliantUNIX-*:*:*)
+       echo mips-sni-sysv4
+       exit 0 ;;
+    RM*:SINIX-*:*:*)
+       echo mips-sni-sysv4
+       exit 0 ;;
+    *:SINIX-*:*:*)
+       if uname -p 2>/dev/null >/dev/null ; then
+               UNAME_MACHINE=`(uname -p) 2>/dev/null`
+               echo ${UNAME_MACHINE}-sni-sysv4
+       else
+               echo ns32k-sni-sysv
+       fi
+       exit 0 ;;
+    PENTIUM:CPunix:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort
+                           # says <Richard.M.Bartel@ccMail.Census.GOV>
+        echo i586-unisys-sysv4
+        exit 0 ;;
+    *:UNIX_System_V:4*:FTX*)
+       # From Gerald Hewes <hewes@openmarket.com>.
+       # How about differentiating between stratus architectures? -djm
+       echo hppa1.1-stratus-sysv4
+       exit 0 ;;
+    *:*:*:FTX*)
+       # From seanf@swdc.stratus.com.
+       echo i860-stratus-sysv4
+       exit 0 ;;
+    mc68*:A/UX:*:*)
+       echo m68k-apple-aux${UNAME_RELEASE}
+       exit 0 ;;
+    news*:NEWS-OS:*:6*)
+       echo mips-sony-newsos6
+       exit 0 ;;
+    R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*)
+       if [ -d /usr/nec ]; then
+               echo mips-nec-sysv${UNAME_RELEASE}
+       else
+               echo mips-unknown-sysv${UNAME_RELEASE}
+       fi
+        exit 0 ;;
+    BeBox:BeOS:*:*)    # BeOS running on hardware made by Be, PPC only.
+       echo powerpc-be-beos
+       exit 0 ;;
+    BeMac:BeOS:*:*)    # BeOS running on Mac or Mac clone, PPC only.
+       echo powerpc-apple-beos
+       exit 0 ;;
+    BePC:BeOS:*:*)     # BeOS running on Intel PC compatible.
+       echo i586-pc-beos
+       exit 0 ;;
+    SX-4:SUPER-UX:*:*)
+       echo sx4-nec-superux${UNAME_RELEASE}
+       exit 0 ;;
+    SX-5:SUPER-UX:*:*)
+       echo sx5-nec-superux${UNAME_RELEASE}
+       exit 0 ;;
+    Power*:Rhapsody:*:*)
+       echo powerpc-apple-rhapsody${UNAME_RELEASE}
+       exit 0 ;;
+    *:Rhapsody:*:*)
+       echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE}
+       exit 0 ;;
+    *:QNX:*:4*)
+       echo i386-qnx-qnx${UNAME_VERSION}
+       exit 0 ;;
+esac
+
+#echo '(No uname command or uname output not recognized.)' 1>&2
+#echo "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" 1>&2
+
+cat >$dummy.c <<EOF
+#ifdef _SEQUENT_
+# include <sys/types.h>
+# include <sys/utsname.h>
+#endif
+main ()
+{
+#if defined (sony)
+#if defined (MIPSEB)
+  /* BFD wants "bsd" instead of "newsos".  Perhaps BFD should be changed,
+     I don't know....  */
+  printf ("mips-sony-bsd\n"); exit (0);
+#else
+#include <sys/param.h>
+  printf ("m68k-sony-newsos%s\n",
+#ifdef NEWSOS4
+          "4"
+#else
+         ""
+#endif
+         ); exit (0);
+#endif
+#endif
+
+#if defined (__arm) && defined (__acorn) && defined (__unix)
+  printf ("arm-acorn-riscix"); exit (0);
+#endif
+
+#if defined (hp300) && !defined (hpux)
+  printf ("m68k-hp-bsd\n"); exit (0);
+#endif
+
+#if defined (NeXT)
+#if !defined (__ARCHITECTURE__)
+#define __ARCHITECTURE__ "m68k"
+#endif
+  int version;
+  version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`;
+  if (version < 4)
+    printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version);
+  else
+    printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version);
+  exit (0);
+#endif
+
+#if defined (MULTIMAX) || defined (n16)
+#if defined (UMAXV)
+  printf ("ns32k-encore-sysv\n"); exit (0);
+#else
+#if defined (CMU)
+  printf ("ns32k-encore-mach\n"); exit (0);
+#else
+  printf ("ns32k-encore-bsd\n"); exit (0);
+#endif
+#endif
+#endif
+
+#if defined (__386BSD__)
+  printf ("i386-pc-bsd\n"); exit (0);
+#endif
+
+#if defined (sequent)
+#if defined (i386)
+  printf ("i386-sequent-dynix\n"); exit (0);
+#endif
+#if defined (ns32000)
+  printf ("ns32k-sequent-dynix\n"); exit (0);
+#endif
+#endif
+
+#if defined (_SEQUENT_)
+    struct utsname un;
+
+    uname(&un);
+
+    if (strncmp(un.version, "V2", 2) == 0) {
+       printf ("i386-sequent-ptx2\n"); exit (0);
+    }
+    if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */
+       printf ("i386-sequent-ptx1\n"); exit (0);
+    }
+    printf ("i386-sequent-ptx\n"); exit (0);
+
+#endif
+
+#if defined (vax)
+#if !defined (ultrix)
+  printf ("vax-dec-bsd\n"); exit (0);
+#else
+  printf ("vax-dec-ultrix\n"); exit (0);
+#endif
+#endif
+
+#if defined (alliant) && defined (i860)
+  printf ("i860-alliant-bsd\n"); exit (0);
+#endif
+
+  exit (1);
+}
+EOF
+
+$CC_FOR_BUILD $dummy.c -o $dummy 2>/dev/null && ./$dummy && rm $dummy.c $dummy && exit 0
+rm -f $dummy.c $dummy
+
+# Apollos put the system type in the environment.
+
+test -d /usr/apollo && { echo ${ISP}-apollo-${SYSTYPE}; exit 0; }
+
+# Convex versions that predate uname can use getsysinfo(1)
+
+if [ -x /usr/convex/getsysinfo ]
+then
+    case `getsysinfo -f cpu_type` in
+    c1*)
+       echo c1-convex-bsd
+       exit 0 ;;
+    c2*)
+       if getsysinfo -f scalar_acc
+       then echo c32-convex-bsd
+       else echo c2-convex-bsd
+       fi
+       exit 0 ;;
+    c34*)
+       echo c34-convex-bsd
+       exit 0 ;;
+    c38*)
+       echo c38-convex-bsd
+       exit 0 ;;
+    c4*)
+       echo c4-convex-bsd
+       exit 0 ;;
+    esac
+fi
+
+#echo '(Unable to guess system type)' 1>&2
+
+exit 1
diff --git a/config.h.in b/config.h.in
new file mode 100644 (file)
index 0000000..972a579
--- /dev/null
@@ -0,0 +1,91 @@
+/* config.h.in.  Generated automatically from configure.in by autoheader.  */
+
+/* Define to empty if the keyword does not work.  */
+#undef const
+
+/* Define if you don't have vprintf but do have _doprnt.  */
+#undef HAVE_DOPRNT
+
+/* Define if you have the vprintf function.  */
+#undef HAVE_VPRINTF
+
+/* Define if you have the wait3 system call.  */
+#undef HAVE_WAIT3
+
+/* Define as the return type of signal handlers (int or void).  */
+#undef RETSIGTYPE
+
+/* Define if the `setpgrp' function takes no argument.  */
+#undef SETPGRP_VOID
+
+/* Define if you have the ANSI C header files.  */
+#undef STDC_HEADERS
+
+/* Define if you can safely include both <sys/time.h> and <time.h>.  */
+#undef TIME_WITH_SYS_TIME
+
+/* Define if you have the regcomp function.  */
+#undef HAVE_REGCOMP
+
+/* Define if you have the select function.  */
+#undef HAVE_SELECT
+
+/* Define if you have the socket function.  */
+#undef HAVE_SOCKET
+
+/* Define if you have the strcspn function.  */
+#undef HAVE_STRCSPN
+
+/* Define if you have the strdup function.  */
+#undef HAVE_STRDUP
+
+/* Define if you have the strstr function.  */
+#undef HAVE_STRSTR
+
+/* Define if you have the strtol function.  */
+#undef HAVE_STRTOL
+
+/* Define if you have the <fcntl.h> header file.  */
+#undef HAVE_FCNTL_H
+
+/* Define if you have the <malloc.h> header file.  */
+#undef HAVE_MALLOC_H
+
+/* Define if you have the <strings.h> header file.  */
+#undef HAVE_STRINGS_H
+
+/* Define if you have the <sys/file.h> header file.  */
+#undef HAVE_SYS_FILE_H
+
+/* Define if you have the <sys/ioctl.h> header file.  */
+#undef HAVE_SYS_IOCTL_H
+
+/* Define if you have the <sys/time.h> header file.  */
+#undef HAVE_SYS_TIME_H
+
+/* Define if you have the <syslog.h> header file.  */
+#undef HAVE_SYSLOG_H
+
+/* Define if you have the <unistd.h> header file.  */
+#undef HAVE_UNISTD_H
+
+/* Define if you have the c library (-lc).  */
+#undef HAVE_LIBC
+
+/* Define if you have the crypt library (-lcrypt).  */
+#undef HAVE_LIBCRYPT
+
+/* Define if you have the dl library (-ldl).  */
+#undef HAVE_LIBDL
+
+/* Define if you have the og library (-log).  */
+#undef HAVE_LIBOG
+
+/* Define if you have the pam library (-lpam).  */
+#undef HAVE_LIBPAM
+
+/* Define this if you have shadow passwords in /etc/passwd and
+ * /etc/shadow. Note that you usually need to be root to read
+ * /etc/shadow */
+#undef SHADOW_PASSWORDS
+
diff --git a/config.sub b/config.sub
new file mode 100644 (file)
index 0000000..28426bb
--- /dev/null
@@ -0,0 +1,1232 @@
+#! /bin/sh
+# Configuration validation subroutine script, version 1.1.
+#   Copyright (C) 1991, 92-97, 1998, 1999 Free Software Foundation, Inc.
+# This file is (in principle) common to ALL GNU software.
+# The presence of a machine in this file suggests that SOME GNU software
+# can handle that machine.  It does not imply ALL GNU software can.
+#
+# This file 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.
+
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# Configuration subroutine to validate and canonicalize a configuration type.
+# Supply the specified configuration type as an argument.
+# If it is invalid, we print an error message on stderr and exit with code 1.
+# Otherwise, we print the canonical config type on stdout and succeed.
+
+# This file is supposed to be the same for all GNU packages
+# and recognize all the CPU types, system types and aliases
+# that are meaningful with *any* GNU software.
+# Each package is responsible for reporting which valid configurations
+# it does not support.  The user should be able to distinguish
+# a failure to support a valid configuration from a meaningless
+# configuration.
+
+# The goal of this file is to map all the various variations of a given
+# machine specification into a single specification in the form:
+#      CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM
+# or in some cases, the newer four-part form:
+#      CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM
+# It is wrong to echo any other type of specification.
+
+if [ x$1 = x ]
+then
+       echo Configuration name missing. 1>&2
+       echo "Usage: $0 CPU-MFR-OPSYS" 1>&2
+       echo "or     $0 ALIAS" 1>&2
+       echo where ALIAS is a recognized configuration type. 1>&2
+       exit 1
+fi
+
+# First pass through any local machine types.
+case $1 in
+       *local*)
+               echo $1
+               exit 0
+               ;;
+       *)
+       ;;
+esac
+
+# Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any).
+# Here we must recognize all the valid KERNEL-OS combinations.
+maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'`
+case $maybe_os in
+  linux-gnu*)
+    os=-$maybe_os
+    basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'`
+    ;;
+  *)
+    basic_machine=`echo $1 | sed 's/-[^-]*$//'`
+    if [ $basic_machine != $1 ]
+    then os=`echo $1 | sed 's/.*-/-/'`
+    else os=; fi
+    ;;
+esac
+
+### Let's recognize common machines as not being operating systems so
+### that things like config.sub decstation-3100 work.  We also
+### recognize some manufacturers as not being operating systems, so we
+### can provide default operating systems below.
+case $os in
+       -sun*os*)
+               # Prevent following clause from handling this invalid input.
+               ;;
+       -dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \
+       -att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \
+       -unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \
+       -convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\
+       -c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \
+       -harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \
+       -apple)
+               os=
+               basic_machine=$1
+               ;;
+       -sim | -cisco | -oki | -wec | -winbond)
+               os=
+               basic_machine=$1
+               ;;
+       -scout)
+               ;;
+       -wrs)
+               os=-vxworks
+               basic_machine=$1
+               ;;
+       -hiux*)
+               os=-hiuxwe2
+               ;;
+       -sco5)
+               os=-sco3.2v5
+               basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+               ;;
+       -sco4)
+               os=-sco3.2v4
+               basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+               ;;
+       -sco3.2.[4-9]*)
+               os=`echo $os | sed -e 's/sco3.2./sco3.2v/'`
+               basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+               ;;
+       -sco3.2v[4-9]*)
+               # Don't forget version if it is 3.2v4 or newer.
+               basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+               ;;
+       -sco*)
+               os=-sco3.2v2
+               basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+               ;;
+       -udk*)
+               basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+               ;;
+       -isc)
+               os=-isc2.2
+               basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+               ;;
+       -clix*)
+               basic_machine=clipper-intergraph
+               ;;
+       -isc*)
+               basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+               ;;
+       -lynx*)
+               os=-lynxos
+               ;;
+       -ptx*)
+               basic_machine=`echo $1 | sed -e 's/86-.*/86-sequent/'`
+               ;;
+       -windowsnt*)
+               os=`echo $os | sed -e 's/windowsnt/winnt/'`
+               ;;
+       -psos*)
+               os=-psos
+               ;;
+       -mint | -mint[0-9]*)
+               basic_machine=m68k-atari
+               os=-mint
+               ;;
+esac
+
+# Decode aliases for certain CPU-COMPANY combinations.
+case $basic_machine in
+       # Recognize the basic CPU types without company name.
+       # Some are omitted here because they have special meanings below.
+       tahoe | i860 | ia64 | m32r | m68k | m68000 | m88k | ns32k | arc | arm \
+               | arme[lb] | pyramid | mn10200 | mn10300 | tron | a29k \
+               | 580 | i960 | h8300 \
+               | hppa | hppa1.0 | hppa1.1 | hppa2.0 | hppa2.0w | hppa2.0n \
+               | alpha | alphaev[4-7] | alphaev56 | alphapca5[67] \
+               | we32k | ns16k | clipper | i370 | sh | powerpc | powerpcle \
+               | 1750a | dsp16xx | pdp11 | mips16 | mips64 | mipsel | mips64el \
+               | mips64orion | mips64orionel | mipstx39 | mipstx39el \
+               | mips64vr4300 | mips64vr4300el | mips64vr4100 | mips64vr4100el \
+               | mips64vr5000 | miprs64vr5000el | mcore \
+               | sparc | sparclet | sparclite | sparc64 | sparcv9 | v850 | c4x \
+               | thumb | d10v | fr30)
+               basic_machine=$basic_machine-unknown
+               ;;
+       m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | z8k | v70 | h8500 | w65 | pj | pjl)
+               ;;
+
+       # We use `pc' rather than `unknown'
+       # because (1) that's what they normally are, and
+       # (2) the word "unknown" tends to confuse beginning users.
+       i[34567]86)
+         basic_machine=$basic_machine-pc
+         ;;
+       # Object if more than one company name word.
+       *-*-*)
+               echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2
+               exit 1
+               ;;
+       # Recognize the basic CPU types with company name.
+       # FIXME: clean up the formatting here.
+       vax-* | tahoe-* | i[34567]86-* | i860-* | ia64-* | m32r-* | m68k-* | m68000-* \
+             | m88k-* | sparc-* | ns32k-* | fx80-* | arc-* | arm-* | c[123]* \
+             | mips-* | pyramid-* | tron-* | a29k-* | romp-* | rs6000-* \
+             | power-* | none-* | 580-* | cray2-* | h8300-* | h8500-* | i960-* \
+             | xmp-* | ymp-* \
+             | hppa-* | hppa1.0-* | hppa1.1-* | hppa2.0-* | hppa2.0w-* | hppa2.0n-* \
+             | alpha-* | alphaev[4-7]-* | alphaev56-* | alphapca5[67]-* \
+             | we32k-* | cydra-* | ns16k-* | pn-* | np1-* | xps100-* \
+             | clipper-* | orion-* \
+             | sparclite-* | pdp11-* | sh-* | powerpc-* | powerpcle-* \
+             | sparc64-* | sparcv9-* | sparc86x-* | mips16-* | mips64-* | mipsel-* \
+             | mips64el-* | mips64orion-* | mips64orionel-* \
+             | mips64vr4100-* | mips64vr4100el-* | mips64vr4300-* | mips64vr4300el-* \
+             | mipstx39-* | mipstx39el-* | mcore-* \
+             | f301-* | armv*-* | t3e-* \
+             | m88110-* | m680[01234]0-* | m683?2-* | m68360-* | z8k-* | d10v-* \
+             | thumb-* | v850-* | d30v-* | tic30-* | c30-* | fr30-* )
+               ;;
+       # Recognize the various machine names and aliases which stand
+       # for a CPU type and a company and sometimes even an OS.
+       386bsd)
+               basic_machine=i386-unknown
+               os=-bsd
+               ;;
+       3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc)
+               basic_machine=m68000-att
+               ;;
+       3b*)
+               basic_machine=we32k-att
+               ;;
+       a29khif)
+               basic_machine=a29k-amd
+               os=-udi
+               ;;
+       adobe68k)
+               basic_machine=m68010-adobe
+               os=-scout
+               ;;
+       alliant | fx80)
+               basic_machine=fx80-alliant
+               ;;
+       altos | altos3068)
+               basic_machine=m68k-altos
+               ;;
+       am29k)
+               basic_machine=a29k-none
+               os=-bsd
+               ;;
+       amdahl)
+               basic_machine=580-amdahl
+               os=-sysv
+               ;;
+       amiga | amiga-*)
+               basic_machine=m68k-cbm
+               ;;
+       amigaos | amigados)
+               basic_machine=m68k-cbm
+               os=-amigaos
+               ;;
+       amigaunix | amix)
+               basic_machine=m68k-cbm
+               os=-sysv4
+               ;;
+       apollo68)
+               basic_machine=m68k-apollo
+               os=-sysv
+               ;;
+       apollo68bsd)
+               basic_machine=m68k-apollo
+               os=-bsd
+               ;;
+       aux)
+               basic_machine=m68k-apple
+               os=-aux
+               ;;
+       balance)
+               basic_machine=ns32k-sequent
+               os=-dynix
+               ;;
+       convex-c1)
+               basic_machine=c1-convex
+               os=-bsd
+               ;;
+       convex-c2)
+               basic_machine=c2-convex
+               os=-bsd
+               ;;
+       convex-c32)
+               basic_machine=c32-convex
+               os=-bsd
+               ;;
+       convex-c34)
+               basic_machine=c34-convex
+               os=-bsd
+               ;;
+       convex-c38)
+               basic_machine=c38-convex
+               os=-bsd
+               ;;
+       cray | ymp)
+               basic_machine=ymp-cray
+               os=-unicos
+               ;;
+       cray2)
+               basic_machine=cray2-cray
+               os=-unicos
+               ;;
+       [ctj]90-cray)
+               basic_machine=c90-cray
+               os=-unicos
+               ;;
+       crds | unos)
+               basic_machine=m68k-crds
+               ;;
+       da30 | da30-*)
+               basic_machine=m68k-da30
+               ;;
+       decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn)
+               basic_machine=mips-dec
+               ;;
+       delta | 3300 | motorola-3300 | motorola-delta \
+             | 3300-motorola | delta-motorola)
+               basic_machine=m68k-motorola
+               ;;
+       delta88)
+               basic_machine=m88k-motorola
+               os=-sysv3
+               ;;
+       dpx20 | dpx20-*)
+               basic_machine=rs6000-bull
+               os=-bosx
+               ;;
+       dpx2* | dpx2*-bull)
+               basic_machine=m68k-bull
+               os=-sysv3
+               ;;
+       ebmon29k)
+               basic_machine=a29k-amd
+               os=-ebmon
+               ;;
+       elxsi)
+               basic_machine=elxsi-elxsi
+               os=-bsd
+               ;;
+       encore | umax | mmax)
+               basic_machine=ns32k-encore
+               ;;
+       es1800 | OSE68k | ose68k | ose | OSE)
+               basic_machine=m68k-ericsson
+               os=-ose
+               ;;
+       fx2800)
+               basic_machine=i860-alliant
+               ;;
+       genix)
+               basic_machine=ns32k-ns
+               ;;
+       gmicro)
+               basic_machine=tron-gmicro
+               os=-sysv
+               ;;
+       h3050r* | hiux*)
+               basic_machine=hppa1.1-hitachi
+               os=-hiuxwe2
+               ;;
+       h8300hms)
+               basic_machine=h8300-hitachi
+               os=-hms
+               ;;
+       h8300xray)
+               basic_machine=h8300-hitachi
+               os=-xray
+               ;;
+       h8500hms)
+               basic_machine=h8500-hitachi
+               os=-hms
+               ;;
+       harris)
+               basic_machine=m88k-harris
+               os=-sysv3
+               ;;
+       hp300-*)
+               basic_machine=m68k-hp
+               ;;
+       hp300bsd)
+               basic_machine=m68k-hp
+               os=-bsd
+               ;;
+       hp300hpux)
+               basic_machine=m68k-hp
+               os=-hpux
+               ;;
+       hp3k9[0-9][0-9] | hp9[0-9][0-9])
+               basic_machine=hppa1.0-hp
+               ;;
+       hp9k2[0-9][0-9] | hp9k31[0-9])
+               basic_machine=m68000-hp
+               ;;
+       hp9k3[2-9][0-9])
+               basic_machine=m68k-hp
+               ;;
+       hp9k6[0-9][0-9] | hp6[0-9][0-9])
+               basic_machine=hppa1.0-hp
+               ;;
+       hp9k7[0-79][0-9] | hp7[0-79][0-9])
+               basic_machine=hppa1.1-hp
+               ;;
+       hp9k78[0-9] | hp78[0-9])
+               # FIXME: really hppa2.0-hp
+               basic_machine=hppa1.1-hp
+               ;;
+       hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893)
+               # FIXME: really hppa2.0-hp
+               basic_machine=hppa1.1-hp
+               ;;
+       hp9k8[0-9][13679] | hp8[0-9][13679])
+               basic_machine=hppa1.1-hp
+               ;;
+       hp9k8[0-9][0-9] | hp8[0-9][0-9])
+               basic_machine=hppa1.0-hp
+               ;;
+       hppa-next)
+               os=-nextstep3
+               ;;
+       hppaosf)
+               basic_machine=hppa1.1-hp
+               os=-osf
+               ;;
+       hppro)
+               basic_machine=hppa1.1-hp
+               os=-proelf
+               ;;
+       i370-ibm* | ibm*)
+               basic_machine=i370-ibm
+               ;;
+# I'm not sure what "Sysv32" means.  Should this be sysv3.2?
+       i[34567]86v32)
+               basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+               os=-sysv32
+               ;;
+       i[34567]86v4*)
+               basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+               os=-sysv4
+               ;;
+       i[34567]86v)
+               basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+               os=-sysv
+               ;;
+       i[34567]86sol2)
+               basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+               os=-solaris2
+               ;;
+       i386mach)
+               basic_machine=i386-mach
+               os=-mach
+               ;;
+       i386-vsta | vsta)
+               basic_machine=i386-unknown
+               os=-vsta
+               ;;
+       i386-go32 | go32)
+               basic_machine=i386-unknown
+               os=-go32
+               ;;
+       i386-mingw32 | mingw32)
+               basic_machine=i386-unknown
+               os=-mingw32
+               ;;
+       i386-qnx | qnx)
+               basic_machine=i386-qnx
+               ;;
+       iris | iris4d)
+               basic_machine=mips-sgi
+               case $os in
+                   -irix*)
+                       ;;
+                   *)
+                       os=-irix4
+                       ;;
+               esac
+               ;;
+       isi68 | isi)
+               basic_machine=m68k-isi
+               os=-sysv
+               ;;
+       m88k-omron*)
+               basic_machine=m88k-omron
+               ;;
+       magnum | m3230)
+               basic_machine=mips-mips
+               os=-sysv
+               ;;
+       merlin)
+               basic_machine=ns32k-utek
+               os=-sysv
+               ;;
+       miniframe)
+               basic_machine=m68000-convergent
+               ;;
+       *mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*)
+               basic_machine=m68k-atari
+               os=-mint
+               ;;
+       mipsel*-linux*)
+               basic_machine=mipsel-unknown
+               os=-linux-gnu
+               ;;
+       mips*-linux*)
+               basic_machine=mips-unknown
+               os=-linux-gnu
+               ;;
+       mips3*-*)
+               basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`
+               ;;
+       mips3*)
+               basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`-unknown
+               ;;
+       monitor)
+               basic_machine=m68k-rom68k
+               os=-coff
+               ;;
+       msdos)
+               basic_machine=i386-unknown
+               os=-msdos
+               ;;
+       mvs)
+               basic_machine=i370-ibm
+               os=-mvs
+               ;;
+       ncr3000)
+               basic_machine=i486-ncr
+               os=-sysv4
+               ;;
+       netbsd386)
+               basic_machine=i386-unknown
+               os=-netbsd
+               ;;
+       netwinder)
+               basic_machine=armv4l-rebel
+               os=-linux
+               ;;
+       news | news700 | news800 | news900)
+               basic_machine=m68k-sony
+               os=-newsos
+               ;;
+       news1000)
+               basic_machine=m68030-sony
+               os=-newsos
+               ;;
+       news-3600 | risc-news)
+               basic_machine=mips-sony
+               os=-newsos
+               ;;
+       necv70)
+               basic_machine=v70-nec
+               os=-sysv
+               ;;
+       next | m*-next )
+               basic_machine=m68k-next
+               case $os in
+                   -nextstep* )
+                       ;;
+                   -ns2*)
+                     os=-nextstep2
+                       ;;
+                   *)
+                     os=-nextstep3
+                       ;;
+               esac
+               ;;
+       nh3000)
+               basic_machine=m68k-harris
+               os=-cxux
+               ;;
+       nh[45]000)
+               basic_machine=m88k-harris
+               os=-cxux
+               ;;
+       nindy960)
+               basic_machine=i960-intel
+               os=-nindy
+               ;;
+       mon960)
+               basic_machine=i960-intel
+               os=-mon960
+               ;;
+       np1)
+               basic_machine=np1-gould
+               ;;
+       op50n-* | op60c-*)
+               basic_machine=hppa1.1-oki
+               os=-proelf
+               ;;
+       OSE68000 | ose68000)
+               basic_machine=m68000-ericsson
+               os=-ose
+               ;;
+       os68k)
+               basic_machine=m68k-none
+               os=-os68k
+               ;;
+       pa-hitachi)
+               basic_machine=hppa1.1-hitachi
+               os=-hiuxwe2
+               ;;
+       paragon)
+               basic_machine=i860-intel
+               os=-osf
+               ;;
+       pbd)
+               basic_machine=sparc-tti
+               ;;
+       pbb)
+               basic_machine=m68k-tti
+               ;;
+        pc532 | pc532-*)
+               basic_machine=ns32k-pc532
+               ;;
+       pentium | p5 | k5 | k6 | nexen)
+               basic_machine=i586-pc
+               ;;
+       pentiumpro | p6 | 6x86)
+               basic_machine=i686-pc
+               ;;
+       pentiumii | pentium2)
+               basic_machine=i786-pc
+               ;;
+       pentium-* | p5-* | k5-* | k6-* | nexen-*)
+               basic_machine=i586-`echo $basic_machine | sed 's/^[^-]*-//'`
+               ;;
+       pentiumpro-* | p6-* | 6x86-*)
+               basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'`
+               ;;
+       pentiumii-* | pentium2-*)
+               basic_machine=i786-`echo $basic_machine | sed 's/^[^-]*-//'`
+               ;;
+       pn)
+               basic_machine=pn-gould
+               ;;
+       power)  basic_machine=rs6000-ibm
+               ;;
+       ppc)    basic_machine=powerpc-unknown
+               ;;
+       ppc-*)  basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'`
+               ;;
+       ppcle | powerpclittle | ppc-le | powerpc-little)
+               basic_machine=powerpcle-unknown
+               ;;
+       ppcle-* | powerpclittle-*)
+               basic_machine=powerpcle-`echo $basic_machine | sed 's/^[^-]*-//'`
+               ;;
+       ps2)
+               basic_machine=i386-ibm
+               ;;
+       rom68k)
+               basic_machine=m68k-rom68k
+               os=-coff
+               ;;
+       rm[46]00)
+               basic_machine=mips-siemens
+               ;;
+       rtpc | rtpc-*)
+               basic_machine=romp-ibm
+               ;;
+       sa29200)
+               basic_machine=a29k-amd
+               os=-udi
+               ;;
+       sequent)
+               basic_machine=i386-sequent
+               ;;
+       sh)
+               basic_machine=sh-hitachi
+               os=-hms
+               ;;
+       sparclite-wrs)
+               basic_machine=sparclite-wrs
+               os=-vxworks
+               ;;
+       sps7)
+               basic_machine=m68k-bull
+               os=-sysv2
+               ;;
+       spur)
+               basic_machine=spur-unknown
+               ;;
+       st2000)
+               basic_machine=m68k-tandem
+               ;;
+       stratus)
+               basic_machine=i860-stratus
+               os=-sysv4
+               ;;
+       sun2)
+               basic_machine=m68000-sun
+               ;;
+       sun2os3)
+               basic_machine=m68000-sun
+               os=-sunos3
+               ;;
+       sun2os4)
+               basic_machine=m68000-sun
+               os=-sunos4
+               ;;
+       sun3os3)
+               basic_machine=m68k-sun
+               os=-sunos3
+               ;;
+       sun3os4)
+               basic_machine=m68k-sun
+               os=-sunos4
+               ;;
+       sun4os3)
+               basic_machine=sparc-sun
+               os=-sunos3
+               ;;
+       sun4os4)
+               basic_machine=sparc-sun
+               os=-sunos4
+               ;;
+       sun4sol2)
+               basic_machine=sparc-sun
+               os=-solaris2
+               ;;
+       sun3 | sun3-*)
+               basic_machine=m68k-sun
+               ;;
+       sun4)
+               basic_machine=sparc-sun
+               ;;
+       sun386 | sun386i | roadrunner)
+               basic_machine=i386-sun
+               ;;
+       symmetry)
+               basic_machine=i386-sequent
+               os=-dynix
+               ;;
+       t3e)
+               basic_machine=t3e-cray
+               os=-unicos
+               ;;
+       tx39)
+               basic_machine=mipstx39-unknown
+               ;;
+       tx39el)
+               basic_machine=mipstx39el-unknown
+               ;;
+       tower | tower-32)
+               basic_machine=m68k-ncr
+               ;;
+       udi29k)
+               basic_machine=a29k-amd
+               os=-udi
+               ;;
+       ultra3)
+               basic_machine=a29k-nyu
+               os=-sym1
+               ;;
+       v810 | necv810)
+               basic_machine=v810-nec
+               os=-none
+               ;;
+       vaxv)
+               basic_machine=vax-dec
+               os=-sysv
+               ;;
+       vms)
+               basic_machine=vax-dec
+               os=-vms
+               ;;
+       vpp*|vx|vx-*)
+               basic_machine=f301-fujitsu
+               ;;
+       vxworks960)
+               basic_machine=i960-wrs
+               os=-vxworks
+               ;;
+       vxworks68)
+               basic_machine=m68k-wrs
+               os=-vxworks
+               ;;
+       vxworks29k)
+               basic_machine=a29k-wrs
+               os=-vxworks
+               ;;
+       w65*)
+               basic_machine=w65-wdc
+               os=-none
+               ;;
+       w89k-*)
+               basic_machine=hppa1.1-winbond
+               os=-proelf
+               ;;
+       xmp)
+               basic_machine=xmp-cray
+               os=-unicos
+               ;;
+        xps | xps100)
+               basic_machine=xps100-honeywell
+               ;;
+       z8k-*-coff)
+               basic_machine=z8k-unknown
+               os=-sim
+               ;;
+       none)
+               basic_machine=none-none
+               os=-none
+               ;;
+
+# Here we handle the default manufacturer of certain CPU types.  It is in
+# some cases the only manufacturer, in others, it is the most popular.
+       w89k)
+               basic_machine=hppa1.1-winbond
+               ;;
+       op50n)
+               basic_machine=hppa1.1-oki
+               ;;
+       op60c)
+               basic_machine=hppa1.1-oki
+               ;;
+       mips)
+               if [ x$os = x-linux-gnu ]; then
+                       basic_machine=mips-unknown
+               else
+                       basic_machine=mips-mips
+               fi
+               ;;
+       romp)
+               basic_machine=romp-ibm
+               ;;
+       rs6000)
+               basic_machine=rs6000-ibm
+               ;;
+       vax)
+               basic_machine=vax-dec
+               ;;
+       pdp11)
+               basic_machine=pdp11-dec
+               ;;
+       we32k)
+               basic_machine=we32k-att
+               ;;
+       sparc | sparcv9)
+               basic_machine=sparc-sun
+               ;;
+        cydra)
+               basic_machine=cydra-cydrome
+               ;;
+       orion)
+               basic_machine=orion-highlevel
+               ;;
+       orion105)
+               basic_machine=clipper-highlevel
+               ;;
+       mac | mpw | mac-mpw)
+               basic_machine=m68k-apple
+               ;;
+       pmac | pmac-mpw)
+               basic_machine=powerpc-apple
+               ;;
+       c4x*)
+               basic_machine=c4x-none
+               os=-coff
+               ;;
+       *)
+               echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2
+               exit 1
+               ;;
+esac
+
+# Here we canonicalize certain aliases for manufacturers.
+case $basic_machine in
+       *-digital*)
+               basic_machine=`echo $basic_machine | sed 's/digital.*/dec/'`
+               ;;
+       *-commodore*)
+               basic_machine=`echo $basic_machine | sed 's/commodore.*/cbm/'`
+               ;;
+       *)
+               ;;
+esac
+
+# Decode manufacturer-specific aliases for certain operating systems.
+
+if [ x"$os" != x"" ]
+then
+case $os in
+        # First match some system type aliases
+        # that might get confused with valid system types.
+       # -solaris* is a basic system type, with this one exception.
+       -solaris1 | -solaris1.*)
+               os=`echo $os | sed -e 's|solaris1|sunos4|'`
+               ;;
+       -solaris)
+               os=-solaris2
+               ;;
+       -svr4*)
+               os=-sysv4
+               ;;
+       -unixware*)
+               os=-sysv4.2uw
+               ;;
+       -gnu/linux*)
+               os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'`
+               ;;
+       # First accept the basic system types.
+       # The portable systems comes first.
+       # Each alternative MUST END IN A *, to match a version number.
+       # -sysv* is not here because it comes later, after sysvr4.
+       -gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \
+             | -*vms* | -sco* | -esix* | -isc* | -aix* | -sunos | -sunos[34]*\
+             | -hpux* | -unos* | -osf* | -luna* | -dgux* | -solaris* | -sym* \
+             | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \
+             | -aos* \
+             | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \
+             | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \
+             | -hiux* | -386bsd* | -netbsd* | -openbsd* | -freebsd* | -riscix* \
+             | -lynxos* | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \
+             | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \
+             | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \
+             | -cygwin* | -pe* | -psos* | -moss* | -proelf* | -rtems* \
+             | -mingw32* | -linux-gnu* | -uxpv* | -beos* | -mpeix* | -udk* \
+             | -interix* | -uwin* | -rhapsody* | -opened* | -openstep* | -oskit*)
+       # Remember, each alternative MUST END IN *, to match a version number.
+               ;;
+       -sim | -es1800* | -hms* | -xray | -os68k* | -none* | -v88r* \
+             | -windows* | -osx | -abug | -netware* | -os9* | -beos* \
+             | -macos* | -mpw* | -magic* | -mon960* | -lnews*)
+               ;;
+       -mac*)
+               os=`echo $os | sed -e 's|mac|macos|'`
+               ;;
+       -linux*)
+               os=`echo $os | sed -e 's|linux|linux-gnu|'`
+               ;;
+       -sunos5*)
+               os=`echo $os | sed -e 's|sunos5|solaris2|'`
+               ;;
+       -sunos6*)
+               os=`echo $os | sed -e 's|sunos6|solaris3|'`
+               ;;
+       -opened*)
+               os=-openedition
+               ;;
+       -osfrose*)
+               os=-osfrose
+               ;;
+       -osf*)
+               os=-osf
+               ;;
+       -utek*)
+               os=-bsd
+               ;;
+       -dynix*)
+               os=-bsd
+               ;;
+       -acis*)
+               os=-aos
+               ;;
+       -386bsd)
+               os=-bsd
+               ;;
+       -ctix* | -uts*)
+               os=-sysv
+               ;;
+       -ns2 )
+               os=-nextstep2
+               ;;
+       # Preserve the version number of sinix5.
+       -sinix5.*)
+               os=`echo $os | sed -e 's|sinix|sysv|'`
+               ;;
+       -sinix*)
+               os=-sysv4
+               ;;
+       -triton*)
+               os=-sysv3
+               ;;
+       -oss*)
+               os=-sysv3
+               ;;
+        -qnx)
+               os=-qnx4
+               ;;
+       -svr4)
+               os=-sysv4
+               ;;
+       -svr3)
+               os=-sysv3
+               ;;
+       -sysvr4)
+               os=-sysv4
+               ;;
+       # This must come after -sysvr4.
+       -sysv*)
+               ;;
+       -ose*)
+               os=-ose
+               ;;
+       -es1800*)
+               os=-ose
+               ;;
+       -xenix)
+               os=-xenix
+               ;;
+        -*mint | -*MiNT)
+               os=-mint
+               ;;
+       -none)
+               ;;
+       *)
+               # Get rid of the `-' at the beginning of $os.
+               os=`echo $os | sed 's/[^-]*-//'`
+               echo Invalid configuration \`$1\': system \`$os\' not recognized 1>&2
+               exit 1
+               ;;
+esac
+else
+
+# Here we handle the default operating systems that come with various machines.
+# The value should be what the vendor currently ships out the door with their
+# machine or put another way, the most popular os provided with the machine.
+
+# Note that if you're going to try to match "-MANUFACTURER" here (say,
+# "-sun"), then you have to tell the case statement up towards the top
+# that MANUFACTURER isn't an operating system.  Otherwise, code above
+# will signal an error saying that MANUFACTURER isn't an operating
+# system, and we'll never get to this point.
+
+case $basic_machine in
+       *-acorn)
+               os=-riscix1.2
+               ;;
+       arm*-rebel)
+               os=-linux
+               ;;
+       arm*-semi)
+               os=-aout
+               ;;
+        pdp11-*)
+               os=-none
+               ;;
+       *-dec | vax-*)
+               os=-ultrix4.2
+               ;;
+       m68*-apollo)
+               os=-domain
+               ;;
+       i386-sun)
+               os=-sunos4.0.2
+               ;;
+       m68000-sun)
+               os=-sunos3
+               # This also exists in the configure program, but was not the
+               # default.
+               # os=-sunos4
+               ;;
+       m68*-cisco)
+               os=-aout
+               ;;
+       mips*-cisco)
+               os=-elf
+               ;;
+       mips*-*)
+               os=-elf
+               ;;
+       *-tti)  # must be before sparc entry or we get the wrong os.
+               os=-sysv3
+               ;;
+       sparc-* | *-sun)
+               os=-sunos4.1.1
+               ;;
+       *-be)
+               os=-beos
+               ;;
+       *-ibm)
+               os=-aix
+               ;;
+       *-wec)
+               os=-proelf
+               ;;
+       *-winbond)
+               os=-proelf
+               ;;
+       *-oki)
+               os=-proelf
+               ;;
+       *-hp)
+               os=-hpux
+               ;;
+       *-hitachi)
+               os=-hiux
+               ;;
+       i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent)
+               os=-sysv
+               ;;
+       *-cbm)
+               os=-amigaos
+               ;;
+       *-dg)
+               os=-dgux
+               ;;
+       *-dolphin)
+               os=-sysv3
+               ;;
+       m68k-ccur)
+               os=-rtu
+               ;;
+       m88k-omron*)
+               os=-luna
+               ;;
+       *-next )
+               os=-nextstep
+               ;;
+       *-sequent)
+               os=-ptx
+               ;;
+       *-crds)
+               os=-unos
+               ;;
+       *-ns)
+               os=-genix
+               ;;
+       i370-*)
+               os=-mvs
+               ;;
+       *-next)
+               os=-nextstep3
+               ;;
+        *-gould)
+               os=-sysv
+               ;;
+        *-highlevel)
+               os=-bsd
+               ;;
+       *-encore)
+               os=-bsd
+               ;;
+        *-sgi)
+               os=-irix
+               ;;
+        *-siemens)
+               os=-sysv4
+               ;;
+       *-masscomp)
+               os=-rtu
+               ;;
+       f301-fujitsu)
+               os=-uxpv
+               ;;
+       *-rom68k)
+               os=-coff
+               ;;
+       *-*bug)
+               os=-coff
+               ;;
+       *-apple)
+               os=-macos
+               ;;
+       *-atari*)
+               os=-mint
+               ;;
+       *)
+               os=-none
+               ;;
+esac
+fi
+
+# Here we handle the case where we know the os, and the CPU type, but not the
+# manufacturer.  We pick the logical manufacturer.
+vendor=unknown
+case $basic_machine in
+       *-unknown)
+               case $os in
+                       -riscix*)
+                               vendor=acorn
+                               ;;
+                       -sunos*)
+                               vendor=sun
+                               ;;
+                       -aix*)
+                               vendor=ibm
+                               ;;
+                       -beos*)
+                               vendor=be
+                               ;;
+                       -hpux*)
+                               vendor=hp
+                               ;;
+                       -mpeix*)
+                               vendor=hp
+                               ;;
+                       -hiux*)
+                               vendor=hitachi
+                               ;;
+                       -unos*)
+                               vendor=crds
+                               ;;
+                       -dgux*)
+                               vendor=dg
+                               ;;
+                       -luna*)
+                               vendor=omron
+                               ;;
+                       -genix*)
+                               vendor=ns
+                               ;;
+                       -mvs* | -opened*)
+                               vendor=ibm
+                               ;;
+                       -ptx*)
+                               vendor=sequent
+                               ;;
+                       -vxsim* | -vxworks*)
+                               vendor=wrs
+                               ;;
+                       -aux*)
+                               vendor=apple
+                               ;;
+                       -hms*)
+                               vendor=hitachi
+                               ;;
+                       -mpw* | -macos*)
+                               vendor=apple
+                               ;;
+                       -*mint | -*MiNT)
+                               vendor=atari
+                               ;;
+               esac
+               basic_machine=`echo $basic_machine | sed "s/unknown/$vendor/"`
+               ;;
+esac
+
+echo $basic_machine$os
diff --git a/configure b/configure
new file mode 100755 (executable)
index 0000000..7a31bfe
--- /dev/null
+++ b/configure
@@ -0,0 +1,2886 @@
+#! /bin/sh
+
+# Guess values for system-dependent variables and create Makefiles.
+# Generated automatically using autoconf version 2.13 
+# Copyright (C) 1992, 93, 94, 95, 96 Free Software Foundation, Inc.
+#
+# This configure script is free software; the Free Software Foundation
+# gives unlimited permission to copy, distribute and modify it.
+
+# Defaults:
+ac_help=
+ac_default_prefix=/usr/local
+# Any additions from configure.in:
+ac_help="$ac_help
+  --with-pam           With PAM Support   "
+ac_help="$ac_help
+  --with-ldap          With LDAP Support   "
+ac_help="$ac_help
+  --with-db            For DB Support   "
+ac_help="$ac_help
+  --with-mysql         With MySQL Support   "
+ac_help="$ac_help
+  --with-mysql-prefix=PREFIX  Mysql prefix [default=/usr]"
+ac_help="$ac_help
+  --with-pgsql         With PgSQL Support   "
+ac_help="$ac_help
+  --with-pgsql-prefix=PREFIX  PgSQL prefix [default=/usr]"
+ac_help="$ac_help
+  --with-tacuid=ID     If you like to run tac_plus other than root user (no default value) "
+ac_help="$ac_help
+  --with-tacgid=GID    If you like to run tac_plus other than root group(no default value) "
+ac_help="$ac_help
+  --enable-maxsess     Enable maxsess feature "
+ac_help="$ac_help
+  --with-tacplus_pid=PREFIX  Tac_plus pid file location [default=/var/run] "
+ac_help="$ac_help
+  --with-libwrap[=PATH]   Compile in libwrap (tcp_wrappers) support."
+
+# Initialize some variables set by options.
+# The variables have the same names as the options, with
+# dashes changed to underlines.
+build=NONE
+cache_file=./config.cache
+exec_prefix=NONE
+host=NONE
+no_create=
+nonopt=NONE
+no_recursion=
+prefix=NONE
+program_prefix=NONE
+program_suffix=NONE
+program_transform_name=s,x,x,
+silent=
+site=
+srcdir=
+target=NONE
+verbose=
+x_includes=NONE
+x_libraries=NONE
+bindir='${exec_prefix}/bin'
+sbindir='${exec_prefix}/sbin'
+libexecdir='${exec_prefix}/libexec'
+datadir='${prefix}/share'
+sysconfdir='${prefix}/etc'
+sharedstatedir='${prefix}/com'
+localstatedir='${prefix}/var'
+libdir='${exec_prefix}/lib'
+includedir='${prefix}/include'
+oldincludedir='/usr/include'
+infodir='${prefix}/info'
+mandir='${prefix}/man'
+
+# Initialize some other variables.
+subdirs=
+MFLAGS= MAKEFLAGS=
+SHELL=${CONFIG_SHELL-/bin/sh}
+# Maximum number of lines to put in a shell here document.
+ac_max_here_lines=12
+
+ac_prev=
+for ac_option
+do
+
+  # If the previous option needs an argument, assign it.
+  if test -n "$ac_prev"; then
+    eval "$ac_prev=\$ac_option"
+    ac_prev=
+    continue
+  fi
+
+  case "$ac_option" in
+  -*=*) ac_optarg=`echo "$ac_option" | sed 's/[-_a-zA-Z0-9]*=//'` ;;
+  *) ac_optarg= ;;
+  esac
+
+  # Accept the important Cygnus configure options, so we can diagnose typos.
+
+  case "$ac_option" in
+
+  -bindir | --bindir | --bindi | --bind | --bin | --bi)
+    ac_prev=bindir ;;
+  -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*)
+    bindir="$ac_optarg" ;;
+
+  -build | --build | --buil | --bui | --bu)
+    ac_prev=build ;;
+  -build=* | --build=* | --buil=* | --bui=* | --bu=*)
+    build="$ac_optarg" ;;
+
+  -cache-file | --cache-file | --cache-fil | --cache-fi \
+  | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c)
+    ac_prev=cache_file ;;
+  -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \
+  | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*)
+    cache_file="$ac_optarg" ;;
+
+  -datadir | --datadir | --datadi | --datad | --data | --dat | --da)
+    ac_prev=datadir ;;
+  -datadir=* | --datadir=* | --datadi=* | --datad=* | --data=* | --dat=* \
+  | --da=*)
+    datadir="$ac_optarg" ;;
+
+  -disable-* | --disable-*)
+    ac_feature=`echo $ac_option|sed -e 's/-*disable-//'`
+    # Reject names that are not valid shell variable names.
+    if test -n "`echo $ac_feature| sed 's/[-a-zA-Z0-9_]//g'`"; then
+      { echo "configure: error: $ac_feature: invalid feature name" 1>&2; exit 1; }
+    fi
+    ac_feature=`echo $ac_feature| sed 's/-/_/g'`
+    eval "enable_${ac_feature}=no" ;;
+
+  -enable-* | --enable-*)
+    ac_feature=`echo $ac_option|sed -e 's/-*enable-//' -e 's/=.*//'`
+    # Reject names that are not valid shell variable names.
+    if test -n "`echo $ac_feature| sed 's/[-_a-zA-Z0-9]//g'`"; then
+      { echo "configure: error: $ac_feature: invalid feature name" 1>&2; exit 1; }
+    fi
+    ac_feature=`echo $ac_feature| sed 's/-/_/g'`
+    case "$ac_option" in
+      *=*) ;;
+      *) ac_optarg=yes ;;
+    esac
+    eval "enable_${ac_feature}='$ac_optarg'" ;;
+
+  -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \
+  | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \
+  | --exec | --exe | --ex)
+    ac_prev=exec_prefix ;;
+  -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \
+  | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \
+  | --exec=* | --exe=* | --ex=*)
+    exec_prefix="$ac_optarg" ;;
+
+  -gas | --gas | --ga | --g)
+    # Obsolete; use --with-gas.
+    with_gas=yes ;;
+
+  -help | --help | --hel | --he)
+    # Omit some internal or obsolete options to make the list less imposing.
+    # This message is too long to be a string in the A/UX 3.1 sh.
+    cat << EOF
+Usage: configure [options] [host]
+Options: [defaults in brackets after descriptions]
+Configuration:
+  --cache-file=FILE       cache test results in FILE
+  --help                  print this message
+  --no-create             do not create output files
+  --quiet, --silent       do not print \`checking...' messages
+  --version               print the version of autoconf that created configure
+Directory and file names:
+  --prefix=PREFIX         install architecture-independent files in PREFIX
+                          [$ac_default_prefix]
+  --exec-prefix=EPREFIX   install architecture-dependent files in EPREFIX
+                          [same as prefix]
+  --bindir=DIR            user executables in DIR [EPREFIX/bin]
+  --sbindir=DIR           system admin executables in DIR [EPREFIX/sbin]
+  --libexecdir=DIR        program executables in DIR [EPREFIX/libexec]
+  --datadir=DIR           read-only architecture-independent data in DIR
+                          [PREFIX/share]
+  --sysconfdir=DIR        read-only single-machine data in DIR [PREFIX/etc]
+  --sharedstatedir=DIR    modifiable architecture-independent data in DIR
+                          [PREFIX/com]
+  --localstatedir=DIR     modifiable single-machine data in DIR [PREFIX/var]
+  --libdir=DIR            object code libraries in DIR [EPREFIX/lib]
+  --includedir=DIR        C header files in DIR [PREFIX/include]
+  --oldincludedir=DIR     C header files for non-gcc in DIR [/usr/include]
+  --infodir=DIR           info documentation in DIR [PREFIX/info]
+  --mandir=DIR            man documentation in DIR [PREFIX/man]
+  --srcdir=DIR            find the sources in DIR [configure dir or ..]
+  --program-prefix=PREFIX prepend PREFIX to installed program names
+  --program-suffix=SUFFIX append SUFFIX to installed program names
+  --program-transform-name=PROGRAM
+                          run sed PROGRAM on installed program names
+EOF
+    cat << EOF
+Host type:
+  --build=BUILD           configure for building on BUILD [BUILD=HOST]
+  --host=HOST             configure for HOST [guessed]
+  --target=TARGET         configure for TARGET [TARGET=HOST]
+Features and packages:
+  --disable-FEATURE       do not include FEATURE (same as --enable-FEATURE=no)
+  --enable-FEATURE[=ARG]  include FEATURE [ARG=yes]
+  --with-PACKAGE[=ARG]    use PACKAGE [ARG=yes]
+  --without-PACKAGE       do not use PACKAGE (same as --with-PACKAGE=no)
+  --x-includes=DIR        X include files are in DIR
+  --x-libraries=DIR       X library files are in DIR
+EOF
+    if test -n "$ac_help"; then
+      echo "--enable and --with options recognized:$ac_help"
+    fi
+    exit 0 ;;
+
+  -host | --host | --hos | --ho)
+    ac_prev=host ;;
+  -host=* | --host=* | --hos=* | --ho=*)
+    host="$ac_optarg" ;;
+
+  -includedir | --includedir | --includedi | --included | --include \
+  | --includ | --inclu | --incl | --inc)
+    ac_prev=includedir ;;
+  -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \
+  | --includ=* | --inclu=* | --incl=* | --inc=*)
+    includedir="$ac_optarg" ;;
+
+  -infodir | --infodir | --infodi | --infod | --info | --inf)
+    ac_prev=infodir ;;
+  -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*)
+    infodir="$ac_optarg" ;;
+
+  -libdir | --libdir | --libdi | --libd)
+    ac_prev=libdir ;;
+  -libdir=* | --libdir=* | --libdi=* | --libd=*)
+    libdir="$ac_optarg" ;;
+
+  -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \
+  | --libexe | --libex | --libe)
+    ac_prev=libexecdir ;;
+  -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \
+  | --libexe=* | --libex=* | --libe=*)
+    libexecdir="$ac_optarg" ;;
+
+  -localstatedir | --localstatedir | --localstatedi | --localstated \
+  | --localstate | --localstat | --localsta | --localst \
+  | --locals | --local | --loca | --loc | --lo)
+    ac_prev=localstatedir ;;
+  -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \
+  | --localstate=* | --localstat=* | --localsta=* | --localst=* \
+  | --locals=* | --local=* | --loca=* | --loc=* | --lo=*)
+    localstatedir="$ac_optarg" ;;
+
+  -mandir | --mandir | --mandi | --mand | --man | --ma | --m)
+    ac_prev=mandir ;;
+  -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*)
+    mandir="$ac_optarg" ;;
+
+  -nfp | --nfp | --nf)
+    # Obsolete; use --without-fp.
+    with_fp=no ;;
+
+  -no-create | --no-create | --no-creat | --no-crea | --no-cre \
+  | --no-cr | --no-c)
+    no_create=yes ;;
+
+  -no-recursion | --no-recursion | --no-recursio | --no-recursi \
+  | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r)
+    no_recursion=yes ;;
+
+  -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \
+  | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \
+  | --oldin | --oldi | --old | --ol | --o)
+    ac_prev=oldincludedir ;;
+  -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \
+  | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \
+  | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*)
+    oldincludedir="$ac_optarg" ;;
+
+  -prefix | --prefix | --prefi | --pref | --pre | --pr | --p)
+    ac_prev=prefix ;;
+  -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*)
+    prefix="$ac_optarg" ;;
+
+  -program-prefix | --program-prefix | --program-prefi | --program-pref \
+  | --program-pre | --program-pr | --program-p)
+    ac_prev=program_prefix ;;
+  -program-prefix=* | --program-prefix=* | --program-prefi=* \
+  | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*)
+    program_prefix="$ac_optarg" ;;
+
+  -program-suffix | --program-suffix | --program-suffi | --program-suff \
+  | --program-suf | --program-su | --program-s)
+    ac_prev=program_suffix ;;
+  -program-suffix=* | --program-suffix=* | --program-suffi=* \
+  | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*)
+    program_suffix="$ac_optarg" ;;
+
+  -program-transform-name | --program-transform-name \
+  | --program-transform-nam | --program-transform-na \
+  | --program-transform-n | --program-transform- \
+  | --program-transform | --program-transfor \
+  | --program-transfo | --program-transf \
+  | --program-trans | --program-tran \
+  | --progr-tra | --program-tr | --program-t)
+    ac_prev=program_transform_name ;;
+  -program-transform-name=* | --program-transform-name=* \
+  | --program-transform-nam=* | --program-transform-na=* \
+  | --program-transform-n=* | --program-transform-=* \
+  | --program-transform=* | --program-transfor=* \
+  | --program-transfo=* | --program-transf=* \
+  | --program-trans=* | --program-tran=* \
+  | --progr-tra=* | --program-tr=* | --program-t=*)
+    program_transform_name="$ac_optarg" ;;
+
+  -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+  | -silent | --silent | --silen | --sile | --sil)
+    silent=yes ;;
+
+  -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
+    ac_prev=sbindir ;;
+  -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
+  | --sbi=* | --sb=*)
+    sbindir="$ac_optarg" ;;
+
+  -sharedstatedir | --sharedstatedir | --sharedstatedi \
+  | --sharedstated | --sharedstate | --sharedstat | --sharedsta \
+  | --sharedst | --shareds | --shared | --share | --shar \
+  | --sha | --sh)
+    ac_prev=sharedstatedir ;;
+  -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \
+  | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \
+  | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \
+  | --sha=* | --sh=*)
+    sharedstatedir="$ac_optarg" ;;
+
+  -site | --site | --sit)
+    ac_prev=site ;;
+  -site=* | --site=* | --sit=*)
+    site="$ac_optarg" ;;
+
+  -srcdir | --srcdir | --srcdi | --srcd | --src | --sr)
+    ac_prev=srcdir ;;
+  -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*)
+    srcdir="$ac_optarg" ;;
+
+  -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \
+  | --syscon | --sysco | --sysc | --sys | --sy)
+    ac_prev=sysconfdir ;;
+  -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \
+  | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*)
+    sysconfdir="$ac_optarg" ;;
+
+  -target | --target | --targe | --targ | --tar | --ta | --t)
+    ac_prev=target ;;
+  -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*)
+    target="$ac_optarg" ;;
+
+  -v | -verbose | --verbose | --verbos | --verbo | --verb)
+    verbose=yes ;;
+
+  -version | --version | --versio | --versi | --vers)
+    echo "configure generated by autoconf version 2.13"
+    exit 0 ;;
+
+  -with-* | --with-*)
+    ac_package=`echo $ac_option|sed -e 's/-*with-//' -e 's/=.*//'`
+    # Reject names that are not valid shell variable names.
+    if test -n "`echo $ac_package| sed 's/[-_a-zA-Z0-9]//g'`"; then
+      { echo "configure: error: $ac_package: invalid package name" 1>&2; exit 1; }
+    fi
+    ac_package=`echo $ac_package| sed 's/-/_/g'`
+    case "$ac_option" in
+      *=*) ;;
+      *) ac_optarg=yes ;;
+    esac
+    eval "with_${ac_package}='$ac_optarg'" ;;
+
+  -without-* | --without-*)
+    ac_package=`echo $ac_option|sed -e 's/-*without-//'`
+    # Reject names that are not valid shell variable names.
+    if test -n "`echo $ac_package| sed 's/[-a-zA-Z0-9_]//g'`"; then
+      { echo "configure: error: $ac_package: invalid package name" 1>&2; exit 1; }
+    fi
+    ac_package=`echo $ac_package| sed 's/-/_/g'`
+    eval "with_${ac_package}=no" ;;
+
+  --x)
+    # Obsolete; use --with-x.
+    with_x=yes ;;
+
+  -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \
+  | --x-incl | --x-inc | --x-in | --x-i)
+    ac_prev=x_includes ;;
+  -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \
+  | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*)
+    x_includes="$ac_optarg" ;;
+
+  -x-libraries | --x-libraries | --x-librarie | --x-librari \
+  | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l)
+    ac_prev=x_libraries ;;
+  -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \
+  | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*)
+    x_libraries="$ac_optarg" ;;
+
+  -*) { echo "configure: error: $ac_option: invalid option; use --help to show usage" 1>&2; exit 1; }
+    ;;
+
+  *)
+    if test -n "`echo $ac_option| sed 's/[-a-z0-9.]//g'`"; then
+      echo "configure: warning: $ac_option: invalid host type" 1>&2
+    fi
+    if test "x$nonopt" != xNONE; then
+      { echo "configure: error: can only configure for one host and one target at a time" 1>&2; exit 1; }
+    fi
+    nonopt="$ac_option"
+    ;;
+
+  esac
+done
+
+if test -n "$ac_prev"; then
+  { echo "configure: error: missing argument to --`echo $ac_prev | sed 's/_/-/g'`" 1>&2; exit 1; }
+fi
+
+trap 'rm -fr conftest* confdefs* core core.* *.core $ac_clean_files; exit 1' 1 2 15
+
+# File descriptor usage:
+# 0 standard input
+# 1 file creation
+# 2 errors and warnings
+# 3 some systems may open it to /dev/tty
+# 4 used on the Kubota Titan
+# 6 checking for... messages and results
+# 5 compiler messages saved in config.log
+if test "$silent" = yes; then
+  exec 6>/dev/null
+else
+  exec 6>&1
+fi
+exec 5>./config.log
+
+echo "\
+This file contains any messages produced by compilers while
+running configure, to aid debugging if configure makes a mistake.
+" 1>&5
+
+# Strip out --no-create and --no-recursion so they do not pile up.
+# Also quote any args containing shell metacharacters.
+ac_configure_args=
+for ac_arg
+do
+  case "$ac_arg" in
+  -no-create | --no-create | --no-creat | --no-crea | --no-cre \
+  | --no-cr | --no-c) ;;
+  -no-recursion | --no-recursion | --no-recursio | --no-recursi \
+  | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) ;;
+  *" "*|*"     "*|*[\[\]\~\#\$\^\&\*\(\)\{\}\\\|\;\<\>\?]*)
+  ac_configure_args="$ac_configure_args '$ac_arg'" ;;
+  *) ac_configure_args="$ac_configure_args $ac_arg" ;;
+  esac
+done
+
+# NLS nuisances.
+# Only set these to C if already set.  These must not be set unconditionally
+# because not all systems understand e.g. LANG=C (notably SCO).
+# Fixing LC_MESSAGES prevents Solaris sh from translating var values in `set'!
+# Non-C LC_CTYPE values break the ctype check.
+if test "${LANG+set}"   = set; then LANG=C;   export LANG;   fi
+if test "${LC_ALL+set}" = set; then LC_ALL=C; export LC_ALL; fi
+if test "${LC_MESSAGES+set}" = set; then LC_MESSAGES=C; export LC_MESSAGES; fi
+if test "${LC_CTYPE+set}"    = set; then LC_CTYPE=C;    export LC_CTYPE;    fi
+
+# confdefs.h avoids OS command line length limits that DEFS can exceed.
+rm -rf conftest* confdefs.h
+# AIX cpp loses on an empty file, so make sure it contains at least a newline.
+echo > confdefs.h
+
+# A filename unique to this package, relative to the directory that
+# configure is in, which we can look for to find out if srcdir is correct.
+ac_unique_file=
+
+# Find the source files, if location was not specified.
+if test -z "$srcdir"; then
+  ac_srcdir_defaulted=yes
+  # Try the directory containing this script, then its parent.
+  ac_prog=$0
+  ac_confdir=`echo $ac_prog|sed 's%/[^/][^/]*$%%'`
+  test "x$ac_confdir" = "x$ac_prog" && ac_confdir=.
+  srcdir=$ac_confdir
+  if test ! -r $srcdir/$ac_unique_file; then
+    srcdir=..
+  fi
+else
+  ac_srcdir_defaulted=no
+fi
+if test ! -r $srcdir/$ac_unique_file; then
+  if test "$ac_srcdir_defaulted" = yes; then
+    { echo "configure: error: can not find sources in $ac_confdir or .." 1>&2; exit 1; }
+  else
+    { echo "configure: error: can not find sources in $srcdir" 1>&2; exit 1; }
+  fi
+fi
+srcdir=`echo "${srcdir}" | sed 's%\([^/]\)/*$%\1%'`
+
+# Prefer explicitly selected file to automatically selected ones.
+if test -z "$CONFIG_SITE"; then
+  if test "x$prefix" != xNONE; then
+    CONFIG_SITE="$prefix/share/config.site $prefix/etc/config.site"
+  else
+    CONFIG_SITE="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site"
+  fi
+fi
+for ac_site_file in $CONFIG_SITE; do
+  if test -r "$ac_site_file"; then
+    echo "loading site script $ac_site_file"
+    . "$ac_site_file"
+  fi
+done
+
+if test -r "$cache_file"; then
+  echo "loading cache $cache_file"
+  . $cache_file
+else
+  echo "creating cache $cache_file"
+  > $cache_file
+fi
+
+ac_ext=c
+# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options.
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5'
+ac_link='${CC-cc} -o conftest${ac_exeext} $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5'
+cross_compiling=$ac_cv_prog_cc_cross
+
+ac_exeext=
+ac_objext=o
+if (echo "testing\c"; echo 1,2,3) | grep c >/dev/null; then
+  # Stardent Vistra SVR4 grep lacks -e, says ghazi@caip.rutgers.edu.
+  if (echo -n testing; echo 1,2,3) | sed s/-n/xn/ | grep xn >/dev/null; then
+    ac_n= ac_c='
+' ac_t='       '
+  else
+    ac_n=-n ac_c= ac_t=
+  fi
+else
+  ac_n= ac_c='\c' ac_t=
+fi
+
+
+
+echo $ac_n "checking whether ${MAKE-make} sets \${MAKE}""... $ac_c" 1>&6
+echo "configure:551: checking whether ${MAKE-make} sets \${MAKE}" >&5
+set dummy ${MAKE-make}; ac_make=`echo "$2" | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_prog_make_${ac_make}_set'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  cat > conftestmake <<\EOF
+all:
+       @echo 'ac_maketemp="${MAKE}"'
+EOF
+# GNU make sometimes prints "make[1]: Entering...", which would confuse us.
+eval `${MAKE-make} -f conftestmake 2>/dev/null | grep temp=`
+if test -n "$ac_maketemp"; then
+  eval ac_cv_prog_make_${ac_make}_set=yes
+else
+  eval ac_cv_prog_make_${ac_make}_set=no
+fi
+rm -f conftestmake
+fi
+if eval "test \"`echo '$ac_cv_prog_make_'${ac_make}_set`\" = yes"; then
+  echo "$ac_t""yes" 1>&6
+  SET_MAKE=
+else
+  echo "$ac_t""no" 1>&6
+  SET_MAKE="MAKE=${MAKE-make}"
+fi
+
+# Extract the first word of "gcc", so it can be a program name with args.
+set dummy gcc; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:580: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+  IFS="${IFS=  }"; ac_save_ifs="$IFS"; IFS=":"
+  ac_dummy="$PATH"
+  for ac_dir in $ac_dummy; do
+    test -z "$ac_dir" && ac_dir=.
+    if test -f $ac_dir/$ac_word; then
+      ac_cv_prog_CC="gcc"
+      break
+    fi
+  done
+  IFS="$ac_save_ifs"
+fi
+fi
+CC="$ac_cv_prog_CC"
+if test -n "$CC"; then
+  echo "$ac_t""$CC" 1>&6
+else
+  echo "$ac_t""no" 1>&6
+fi
+
+if test -z "$CC"; then
+  # Extract the first word of "cc", so it can be a program name with args.
+set dummy cc; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:610: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+  IFS="${IFS=  }"; ac_save_ifs="$IFS"; IFS=":"
+  ac_prog_rejected=no
+  ac_dummy="$PATH"
+  for ac_dir in $ac_dummy; do
+    test -z "$ac_dir" && ac_dir=.
+    if test -f $ac_dir/$ac_word; then
+      if test "$ac_dir/$ac_word" = "/usr/ucb/cc"; then
+        ac_prog_rejected=yes
+       continue
+      fi
+      ac_cv_prog_CC="cc"
+      break
+    fi
+  done
+  IFS="$ac_save_ifs"
+if test $ac_prog_rejected = yes; then
+  # We found a bogon in the path, so make sure we never use it.
+  set dummy $ac_cv_prog_CC
+  shift
+  if test $# -gt 0; then
+    # We chose a different compiler from the bogus one.
+    # However, it has the same basename, so the bogon will be chosen
+    # first if we set CC to just the basename; use the full file name.
+    shift
+    set dummy "$ac_dir/$ac_word" "$@"
+    shift
+    ac_cv_prog_CC="$@"
+  fi
+fi
+fi
+fi
+CC="$ac_cv_prog_CC"
+if test -n "$CC"; then
+  echo "$ac_t""$CC" 1>&6
+else
+  echo "$ac_t""no" 1>&6
+fi
+
+  if test -z "$CC"; then
+    case "`uname -s`" in
+    *win32* | *WIN32*)
+      # Extract the first word of "cl", so it can be a program name with args.
+set dummy cl; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:661: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+  IFS="${IFS=  }"; ac_save_ifs="$IFS"; IFS=":"
+  ac_dummy="$PATH"
+  for ac_dir in $ac_dummy; do
+    test -z "$ac_dir" && ac_dir=.
+    if test -f $ac_dir/$ac_word; then
+      ac_cv_prog_CC="cl"
+      break
+    fi
+  done
+  IFS="$ac_save_ifs"
+fi
+fi
+CC="$ac_cv_prog_CC"
+if test -n "$CC"; then
+  echo "$ac_t""$CC" 1>&6
+else
+  echo "$ac_t""no" 1>&6
+fi
+ ;;
+    esac
+  fi
+  test -z "$CC" && { echo "configure: error: no acceptable cc found in \$PATH" 1>&2; exit 1; }
+fi
+
+echo $ac_n "checking whether the C compiler ($CC $CFLAGS $LDFLAGS) works""... $ac_c" 1>&6
+echo "configure:693: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) works" >&5
+
+ac_ext=c
+# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options.
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5'
+ac_link='${CC-cc} -o conftest${ac_exeext} $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5'
+cross_compiling=$ac_cv_prog_cc_cross
+
+cat > conftest.$ac_ext << EOF
+
+#line 704 "configure"
+#include "confdefs.h"
+
+main(){return(0);}
+EOF
+if { (eval echo configure:709: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+  ac_cv_prog_cc_works=yes
+  # If we can't run a trivial program, we are probably using a cross compiler.
+  if (./conftest; exit) 2>/dev/null; then
+    ac_cv_prog_cc_cross=no
+  else
+    ac_cv_prog_cc_cross=yes
+  fi
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  ac_cv_prog_cc_works=no
+fi
+rm -fr conftest*
+ac_ext=c
+# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options.
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5'
+ac_link='${CC-cc} -o conftest${ac_exeext} $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5'
+cross_compiling=$ac_cv_prog_cc_cross
+
+echo "$ac_t""$ac_cv_prog_cc_works" 1>&6
+if test $ac_cv_prog_cc_works = no; then
+  { echo "configure: error: installation or configuration problem: C compiler cannot create executables." 1>&2; exit 1; }
+fi
+echo $ac_n "checking whether the C compiler ($CC $CFLAGS $LDFLAGS) is a cross-compiler""... $ac_c" 1>&6
+echo "configure:735: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) is a cross-compiler" >&5
+echo "$ac_t""$ac_cv_prog_cc_cross" 1>&6
+cross_compiling=$ac_cv_prog_cc_cross
+
+echo $ac_n "checking whether we are using GNU C""... $ac_c" 1>&6
+echo "configure:740: checking whether we are using GNU C" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_gcc'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  cat > conftest.c <<EOF
+#ifdef __GNUC__
+  yes;
+#endif
+EOF
+if { ac_try='${CC-cc} -E conftest.c'; { (eval echo configure:749: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }; } | egrep yes >/dev/null 2>&1; then
+  ac_cv_prog_gcc=yes
+else
+  ac_cv_prog_gcc=no
+fi
+fi
+
+echo "$ac_t""$ac_cv_prog_gcc" 1>&6
+
+if test $ac_cv_prog_gcc = yes; then
+  GCC=yes
+else
+  GCC=
+fi
+
+ac_test_CFLAGS="${CFLAGS+set}"
+ac_save_CFLAGS="$CFLAGS"
+CFLAGS=
+echo $ac_n "checking whether ${CC-cc} accepts -g""... $ac_c" 1>&6
+echo "configure:768: checking whether ${CC-cc} accepts -g" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_cc_g'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  echo 'void f(){}' > conftest.c
+if test -z "`${CC-cc} -g -c conftest.c 2>&1`"; then
+  ac_cv_prog_cc_g=yes
+else
+  ac_cv_prog_cc_g=no
+fi
+rm -f conftest*
+
+fi
+
+echo "$ac_t""$ac_cv_prog_cc_g" 1>&6
+if test "$ac_test_CFLAGS" = set; then
+  CFLAGS="$ac_save_CFLAGS"
+elif test $ac_cv_prog_cc_g = yes; then
+  if test "$GCC" = yes; then
+    CFLAGS="-g -O2"
+  else
+    CFLAGS="-g"
+  fi
+else
+  if test "$GCC" = yes; then
+    CFLAGS="-O2"
+  else
+    CFLAGS=
+  fi
+fi
+
+
+ac_aux_dir=
+for ac_dir in $srcdir $srcdir/.. $srcdir/../..; do
+  if test -f $ac_dir/install-sh; then
+    ac_aux_dir=$ac_dir
+    ac_install_sh="$ac_aux_dir/install-sh -c"
+    break
+  elif test -f $ac_dir/install.sh; then
+    ac_aux_dir=$ac_dir
+    ac_install_sh="$ac_aux_dir/install.sh -c"
+    break
+  fi
+done
+if test -z "$ac_aux_dir"; then
+  { echo "configure: error: can not find install-sh or install.sh in $srcdir $srcdir/.. $srcdir/../.." 1>&2; exit 1; }
+fi
+ac_config_guess=$ac_aux_dir/config.guess
+ac_config_sub=$ac_aux_dir/config.sub
+ac_configure=$ac_aux_dir/configure # This should be Cygnus configure.
+
+
+# Do some error checking and defaulting for the host and target type.
+# The inputs are:
+#    configure --host=HOST --target=TARGET --build=BUILD NONOPT
+#
+# The rules are:
+# 1. You are not allowed to specify --host, --target, and nonopt at the
+#    same time.
+# 2. Host defaults to nonopt.
+# 3. If nonopt is not specified, then host defaults to the current host,
+#    as determined by config.guess.
+# 4. Target and build default to nonopt.
+# 5. If nonopt is not specified, then target and build default to host.
+
+# The aliases save the names the user supplied, while $host etc.
+# will get canonicalized.
+case $host---$target---$nonopt in
+NONE---*---* | *---NONE---* | *---*---NONE) ;;
+*) { echo "configure: error: can only configure for one host and one target at a time" 1>&2; exit 1; } ;;
+esac
+
+
+# Make sure we can run config.sub.
+if ${CONFIG_SHELL-/bin/sh} $ac_config_sub sun4 >/dev/null 2>&1; then :
+else { echo "configure: error: can not run $ac_config_sub" 1>&2; exit 1; }
+fi
+
+echo $ac_n "checking host system type""... $ac_c" 1>&6
+echo "configure:847: checking host system type" >&5
+
+host_alias=$host
+case "$host_alias" in
+NONE)
+  case $nonopt in
+  NONE)
+    if host_alias=`${CONFIG_SHELL-/bin/sh} $ac_config_guess`; then :
+    else { echo "configure: error: can not guess host type; you must specify one" 1>&2; exit 1; }
+    fi ;;
+  *) host_alias=$nonopt ;;
+  esac ;;
+esac
+
+host=`${CONFIG_SHELL-/bin/sh} $ac_config_sub $host_alias`
+host_cpu=`echo $host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\1/'`
+host_vendor=`echo $host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\2/'`
+host_os=`echo $host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'`
+echo "$ac_t""$host" 1>&6
+
+echo $ac_n "checking target system type""... $ac_c" 1>&6
+echo "configure:868: checking target system type" >&5
+
+target_alias=$target
+case "$target_alias" in
+NONE)
+  case $nonopt in
+  NONE) target_alias=$host_alias ;;
+  *) target_alias=$nonopt ;;
+  esac ;;
+esac
+
+target=`${CONFIG_SHELL-/bin/sh} $ac_config_sub $target_alias`
+target_cpu=`echo $target | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\1/'`
+target_vendor=`echo $target | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\2/'`
+target_os=`echo $target | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'`
+echo "$ac_t""$target" 1>&6
+
+echo $ac_n "checking build system type""... $ac_c" 1>&6
+echo "configure:886: checking build system type" >&5
+
+build_alias=$build
+case "$build_alias" in
+NONE)
+  case $nonopt in
+  NONE) build_alias=$host_alias ;;
+  *) build_alias=$nonopt ;;
+  esac ;;
+esac
+
+build=`${CONFIG_SHELL-/bin/sh} $ac_config_sub $build_alias`
+build_cpu=`echo $build | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\1/'`
+build_vendor=`echo $build | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\2/'`
+build_os=`echo $build | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'`
+echo "$ac_t""$build" 1>&6
+
+test "$host_alias" != "$target_alias" &&
+  test "$program_prefix$program_suffix$program_transform_name" = \
+    NONENONEs,x,x, &&
+  program_prefix=${target_alias}-
+
+
+case $host_os in 
+       *linux-gnu)
+       OS="-DLINUX -DGLIBC"
+       ;;
+       *solaris)
+       OS="-DSOLARIS"
+       ;;
+       *freebsd)
+       OS="-DFREEBSD"
+       ;;
+       *hpux)
+       OS="-DHPUX"
+       ;;
+       *aix)
+       OS="-DAIX"
+       ;;
+       *)
+       ;;
+esac
+       
+echo $ac_n "checking for main in -lnsl""... $ac_c" 1>&6
+echo "configure:930: checking for main in -lnsl" >&5
+ac_lib_var=`echo nsl'_'main | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  ac_save_LIBS="$LIBS"
+LIBS="-lnsl  $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 938 "configure"
+#include "confdefs.h"
+
+int main() {
+main()
+; return 0; }
+EOF
+if { (eval echo configure:945: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+  rm -rf conftest*
+  eval "ac_cv_lib_$ac_lib_var=yes"
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+  echo "$ac_t""yes" 1>&6
+    ac_tr_lib=HAVE_LIB`echo nsl | sed -e 's/[^a-zA-Z0-9_]/_/g' \
+    -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'`
+  cat >> confdefs.h <<EOF
+#define $ac_tr_lib 1
+EOF
+
+  LIBS="-lnsl $LIBS"
+
+else
+  echo "$ac_t""no" 1>&6
+fi
+
+echo $ac_n "checking for main in -log""... $ac_c" 1>&6
+echo "configure:973: checking for main in -log" >&5
+ac_lib_var=`echo og'_'main | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  ac_save_LIBS="$LIBS"
+LIBS="-log  $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 981 "configure"
+#include "confdefs.h"
+
+int main() {
+main()
+; return 0; }
+EOF
+if { (eval echo configure:988: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+  rm -rf conftest*
+  eval "ac_cv_lib_$ac_lib_var=yes"
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+  echo "$ac_t""yes" 1>&6
+    ac_tr_lib=HAVE_LIB`echo og | sed -e 's/[^a-zA-Z0-9_]/_/g' \
+    -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'`
+  cat >> confdefs.h <<EOF
+#define $ac_tr_lib 1
+EOF
+
+  LIBS="-log $LIBS"
+
+else
+  echo "$ac_t""no" 1>&6
+fi
+
+echo $ac_n "checking for main in -lldap""... $ac_c" 1>&6
+echo "configure:1016: checking for main in -lldap" >&5
+ac_lib_var=`echo ldap'_'main | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  ac_save_LIBS="$LIBS"
+LIBS="-lldap  $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 1024 "configure"
+#include "confdefs.h"
+
+int main() {
+main()
+; return 0; }
+EOF
+if { (eval echo configure:1031: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+  rm -rf conftest*
+  eval "ac_cv_lib_$ac_lib_var=yes"
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+  echo "$ac_t""yes" 1>&6
+    ac_tr_lib=HAVE_LIB`echo ldap | sed -e 's/[^a-zA-Z0-9_]/_/g' \
+    -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'`
+  cat >> confdefs.h <<EOF
+#define $ac_tr_lib 1
+EOF
+
+  LIBS="-lldap $LIBS"
+
+else
+  echo "$ac_t""no" 1>&6
+fi
+
+echo $ac_n "checking for main in -llber""... $ac_c" 1>&6
+echo "configure:1059: checking for main in -llber" >&5
+ac_lib_var=`echo lber'_'main | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  ac_save_LIBS="$LIBS"
+LIBS="-llber  $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 1067 "configure"
+#include "confdefs.h"
+
+int main() {
+main()
+; return 0; }
+EOF
+if { (eval echo configure:1074: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+  rm -rf conftest*
+  eval "ac_cv_lib_$ac_lib_var=yes"
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+  echo "$ac_t""yes" 1>&6
+    ac_tr_lib=HAVE_LIB`echo lber | sed -e 's/[^a-zA-Z0-9_]/_/g' \
+    -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'`
+  cat >> confdefs.h <<EOF
+#define $ac_tr_lib 1
+EOF
+
+  LIBS="-llber $LIBS"
+
+else
+  echo "$ac_t""no" 1>&6
+fi
+
+echo $ac_n "checking for main in -lsocket""... $ac_c" 1>&6
+echo "configure:1102: checking for main in -lsocket" >&5
+ac_lib_var=`echo socket'_'main | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  ac_save_LIBS="$LIBS"
+LIBS="-lsocket  $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 1110 "configure"
+#include "confdefs.h"
+
+int main() {
+main()
+; return 0; }
+EOF
+if { (eval echo configure:1117: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+  rm -rf conftest*
+  eval "ac_cv_lib_$ac_lib_var=yes"
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+  echo "$ac_t""yes" 1>&6
+    ac_tr_lib=HAVE_LIB`echo socket | sed -e 's/[^a-zA-Z0-9_]/_/g' \
+    -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'`
+  cat >> confdefs.h <<EOF
+#define $ac_tr_lib 1
+EOF
+
+  LIBS="-lsocket $LIBS"
+
+else
+  echo "$ac_t""no" 1>&6
+fi
+
+echo $ac_n "checking for crypt in -lcrypt""... $ac_c" 1>&6
+echo "configure:1145: checking for crypt in -lcrypt" >&5
+ac_lib_var=`echo crypt'_'crypt | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  ac_save_LIBS="$LIBS"
+LIBS="-lcrypt  $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 1153 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error.  */
+/* We use char because int might match the return type of a gcc2
+    builtin and then its argument prototype would still apply.  */
+char crypt();
+
+int main() {
+crypt()
+; return 0; }
+EOF
+if { (eval echo configure:1164: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+  rm -rf conftest*
+  eval "ac_cv_lib_$ac_lib_var=yes"
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+  echo "$ac_t""yes" 1>&6
+    ac_tr_lib=HAVE_LIB`echo crypt | sed -e 's/[^a-zA-Z0-9_]/_/g' \
+    -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'`
+  cat >> confdefs.h <<EOF
+#define $ac_tr_lib 1
+EOF
+
+  LIBS="-lcrypt $LIBS"
+
+else
+  echo "$ac_t""no" 1>&6
+fi
+
+echo $ac_n "checking for printf in -lc""... $ac_c" 1>&6
+echo "configure:1192: checking for printf in -lc" >&5
+ac_lib_var=`echo c'_'printf | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  ac_save_LIBS="$LIBS"
+LIBS="-lc  $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 1200 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error.  */
+/* We use char because int might match the return type of a gcc2
+    builtin and then its argument prototype would still apply.  */
+char printf();
+
+int main() {
+printf()
+; return 0; }
+EOF
+if { (eval echo configure:1211: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+  rm -rf conftest*
+  eval "ac_cv_lib_$ac_lib_var=yes"
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+  echo "$ac_t""yes" 1>&6
+    ac_tr_lib=HAVE_LIB`echo c | sed -e 's/[^a-zA-Z0-9_]/_/g' \
+    -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'`
+  cat >> confdefs.h <<EOF
+#define $ac_tr_lib 1
+EOF
+
+  LIBS="-lc $LIBS"
+
+else
+  echo "$ac_t""no" 1>&6
+fi
+
+
+
+
+
+echo $ac_n "checking for PAM support:""... $ac_c" 1>&6
+echo "configure:1243: checking for PAM support:" >&5
+echo
+# Check whether --with-pam or --without-pam was given.
+if test "${with_pam+set}" = set; then
+  withval="$with_pam"
+  :
+fi
+
+if test "x$with_pam" = "xyes";then
+       echo $ac_n "checking for dlopen in -ldl""... $ac_c" 1>&6
+echo "configure:1253: checking for dlopen in -ldl" >&5
+ac_lib_var=`echo dl'_'dlopen | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  ac_save_LIBS="$LIBS"
+LIBS="-ldl  $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 1261 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error.  */
+/* We use char because int might match the return type of a gcc2
+    builtin and then its argument prototype would still apply.  */
+char dlopen();
+
+int main() {
+dlopen()
+; return 0; }
+EOF
+if { (eval echo configure:1272: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+  rm -rf conftest*
+  eval "ac_cv_lib_$ac_lib_var=yes"
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+  echo "$ac_t""yes" 1>&6
+    ac_tr_lib=HAVE_LIB`echo dl | sed -e 's/[^a-zA-Z0-9_]/_/g' \
+    -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'`
+  cat >> confdefs.h <<EOF
+#define $ac_tr_lib 1
+EOF
+
+  LIBS="-ldl $LIBS"
+
+else
+  echo "$ac_t""no" 1>&6
+fi
+
+       echo $ac_n "checking for pam_start in -lpam""... $ac_c" 1>&6
+echo "configure:1300: checking for pam_start in -lpam" >&5
+ac_lib_var=`echo pam'_'pam_start | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  ac_save_LIBS="$LIBS"
+LIBS="-lpam  $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 1308 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error.  */
+/* We use char because int might match the return type of a gcc2
+    builtin and then its argument prototype would still apply.  */
+char pam_start();
+
+int main() {
+pam_start()
+; return 0; }
+EOF
+if { (eval echo configure:1319: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+  rm -rf conftest*
+  eval "ac_cv_lib_$ac_lib_var=yes"
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+  echo "$ac_t""yes" 1>&6
+    ac_tr_lib=HAVE_LIB`echo pam | sed -e 's/[^a-zA-Z0-9_]/_/g' \
+    -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'`
+  cat >> confdefs.h <<EOF
+#define $ac_tr_lib 1
+EOF
+
+  LIBS="-lpam $LIBS"
+
+else
+  echo "$ac_t""no" 1>&6
+fi
+
+       DEFINES="-DUSE_PAM $DEFINES";
+        echo "$ac_t""Pam support... yes" 1>&6
+else
+        echo "$ac_t""Pam support... no" 1>&6
+fi
+
+echo $ac_n "checking for LDAP support""... $ac_c" 1>&6
+echo "configure:1353: checking for LDAP support" >&5
+echo
+# Check whether --with-ldap or --without-ldap was given.
+if test "${with_ldap+set}" = set; then
+  withval="$with_ldap"
+  :
+fi
+
+
+if test "x$with_ldap" = "xyes";then
+   echo $ac_n "checking for ldap_simple_bind_s in -lldap""... $ac_c" 1>&6
+echo "configure:1364: checking for ldap_simple_bind_s in -lldap" >&5
+ac_lib_var=`echo ldap'_'ldap_simple_bind_s | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  ac_save_LIBS="$LIBS"
+LIBS="-lldap  $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 1372 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error.  */
+/* We use char because int might match the return type of a gcc2
+    builtin and then its argument prototype would still apply.  */
+char ldap_simple_bind_s();
+
+int main() {
+ldap_simple_bind_s()
+; return 0; }
+EOF
+if { (eval echo configure:1383: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+  rm -rf conftest*
+  eval "ac_cv_lib_$ac_lib_var=yes"
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+  echo "$ac_t""yes" 1>&6
+    ac_tr_lib=HAVE_LIB`echo ldap | sed -e 's/[^a-zA-Z0-9_]/_/g' \
+    -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'`
+  cat >> confdefs.h <<EOF
+#define $ac_tr_lib 1
+EOF
+
+  LIBS="-lldap $LIBS"
+
+else
+  echo "$ac_t""no" 1>&6
+fi
+
+   echo $ac_n "checking for ldap_init in -lldap""... $ac_c" 1>&6
+echo "configure:1411: checking for ldap_init in -lldap" >&5
+ac_lib_var=`echo ldap'_'ldap_init | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  ac_save_LIBS="$LIBS"
+LIBS="-lldap  $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 1419 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error.  */
+/* We use char because int might match the return type of a gcc2
+    builtin and then its argument prototype would still apply.  */
+char ldap_init();
+
+int main() {
+ldap_init()
+; return 0; }
+EOF
+if { (eval echo configure:1430: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+  rm -rf conftest*
+  eval "ac_cv_lib_$ac_lib_var=yes"
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+  echo "$ac_t""yes" 1>&6
+    ac_tr_lib=HAVE_LIB`echo ldap | sed -e 's/[^a-zA-Z0-9_]/_/g' \
+    -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'`
+  cat >> confdefs.h <<EOF
+#define $ac_tr_lib 1
+EOF
+
+  LIBS="-lldap $LIBS"
+
+else
+  echo "$ac_t""no" 1>&6
+fi
+
+   DEFINES="-DUSE_LDAP $DEFINES"
+        echo "$ac_t""LDAP support... yes" 1>&6
+else
+        echo "$ac_t""LDAP support... no" 1>&6
+fi
+
+echo $ac_n "checking for DB support""... $ac_c" 1>&6
+echo "configure:1465: checking for DB support" >&5
+echo 
+# Check whether --with-db or --without-db was given.
+if test "${with_db+set}" = set; then
+  withval="$with_db"
+  :
+fi
+
+if test "x$with_db" = "xyes";then
+       DB="$DB -DDB -DDB_NULL" 
+       echo "$ac_t""DB support... yes" 1>&6
+else
+       echo "$ac_t""DB support... no" 1>&6
+fi
+
+if test "x$with_db" = "xyes";then
+
+echo "Check for MySQL support:"
+
+# Check whether --with-mysql or --without-mysql was given.
+if test "${with_mysql+set}" = set; then
+  withval="$with_mysql"
+  :
+fi
+
+
+# Check whether --with-mysql-prefix or --without-mysql-prefix was given.
+if test "${with_mysql_prefix+set}" = set; then
+  withval="$with_mysql_prefix"
+  MYSQL_PREFIX=$withval
+else
+  MYSQL_PREFIX=/usr
+
+fi
+
+
+
+if test "x$with_mysql" = "xyes";then
+       
+       LDFLAGS="-L$MYSQL_PREFIX/lib/mysql $LDFLAGS"
+        LDFLAGS="-I$MYSQL_PREFIX/include/mysql $LDFLAGS"
+        echo $ac_n "checking for mysql_init in -lmysqlclient""... $ac_c" 1>&6
+echo "configure:1507: checking for mysql_init in -lmysqlclient" >&5
+ac_lib_var=`echo mysqlclient'_'mysql_init | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  ac_save_LIBS="$LIBS"
+LIBS="-lmysqlclient -lm $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 1515 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error.  */
+/* We use char because int might match the return type of a gcc2
+    builtin and then its argument prototype would still apply.  */
+char mysql_init();
+
+int main() {
+mysql_init()
+; return 0; }
+EOF
+if { (eval echo configure:1526: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+  rm -rf conftest*
+  eval "ac_cv_lib_$ac_lib_var=yes"
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+  echo "$ac_t""yes" 1>&6
+  LIBS="-lmysqlclient -lm $LIBS"
+else
+  echo "$ac_t""no" 1>&6
+{ echo "configure: error: *** couldn't find libmysqlclient" 1>&2; exit 1; }
+fi
+
+
+       DB="$DB -DDB_MYSQL";
+        echo "$ac_t""Mysql support... yes" 1>&6
+else
+        echo "$ac_t""Mysql support... no" 1>&6
+fi
+
+fi
+
+if test "x$with_db" = "xyes";then
+
+echo "Check for PgSQL support:"
+
+# Check whether --with-pgsql or --without-pgsql was given.
+if test "${with_pgsql+set}" = set; then
+  withval="$with_pgsql"
+  :
+fi
+
+
+# Check whether --with-pgsql-prefix or --without-pgsql-prefix was given.
+if test "${with_pgsql_prefix+set}" = set; then
+  withval="$with_pgsql_prefix"
+  PGSQL_PREFIX=$withval
+else
+  PGSQL_PREFIX=/usr
+
+fi
+
+
+
+if test "x$with_pgsql" = "xyes";then
+       
+       LDFLAGS="-L$PGSQL_PREFIX/lib/pgsql $LDFLAGS"
+        LDFLAGS="-I$PGSQL_PREFIX/include/pgsql $LDFLAGS"
+        echo $ac_n "checking for PQconnectdb  in -lpq""... $ac_c" 1>&6
+echo "configure:1583: checking for PQconnectdb  in -lpq" >&5
+ac_lib_var=`echo pq'_'PQconnectdb  | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  ac_save_LIBS="$LIBS"
+LIBS="-lpq  $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 1591 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error.  */
+/* We use char because int might match the return type of a gcc2
+    builtin and then its argument prototype would still apply.  */
+char PQconnectdb ();
+
+int main() {
+PQconnectdb ()
+; return 0; }
+EOF
+if { (eval echo configure:1602: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+  rm -rf conftest*
+  eval "ac_cv_lib_$ac_lib_var=yes"
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+  echo "$ac_t""yes" 1>&6
+  LIBS="-lpq $LIBS"
+else
+  echo "$ac_t""no" 1>&6
+{ echo "configure: error: *** couldn't find libpq" 1>&2; exit 1; }
+fi
+
+
+       DB="$DB -DDB_PGSQL";
+        echo "$ac_t""Pgsql support... yes" 1>&6
+else
+        echo "$ac_t""Pgsql support... no" 1>&6
+fi
+
+fi
+
+
+# Check whether --with-tacuid or --without-tacuid was given.
+if test "${with_tacuid+set}" = set; then
+  withval="$with_tacuid"
+  :
+fi
+
+# Check whether --with-tacgid or --without-tacgid was given.
+if test "${with_tacgid+set}" = set; then
+  withval="$with_tacgid"
+  :
+fi
+
+
+
+if (test "x$with_tacuid" != "x" && test "x$with_tacgid" != "x" && test "x$with_tacuid" != "xyes" && test "x$with_tacgid" != "xyes");then
+
+       DEFINES="-DTACPLUS_USERID=$with_tacuid -DTACPLUS_GROUPID=$with_tacgid $DEFINES";        
+       echo "$ac_t""tacacs+ work with given user and group id" 1>&6 
+fi
+
+echo $ac_n "checking whether to enable the maxsess feature""... $ac_c" 1>&6
+echo "configure:1654: checking whether to enable the maxsess feature" >&5
+# Check whether --enable-maxsess or --disable-maxsess was given.
+if test "${enable_maxsess+set}" = set; then
+  enableval="$enable_maxsess"
+  
+if test "$enableval" = "yes";then
+       DEFINES="-DMAXSESS $DEFINES";
+       echo "$ac_t""yes" 1>&6
+else 
+       echo "$ac_t""no" 1>&6
+fi
+
+else
+  
+       echo "$ac_t""no" 1>&6
+
+fi
+
+
+
+# Check whether --with-tacplus_pid or --without-tacplus_pid was given.
+if test "${with_tacplus_pid+set}" = set; then
+  withval="$with_tacplus_pid"
+  PIDFILE="-DTACPLUS_PIDFILE=\\\"$withval/tac_plus.pid\\\""
+else
+  PIDFILE="-DTACPLUS_PIDFILE=\\\"/var/run/tac_plus.pid\\\""
+
+fi
+
+
+echo $ac_n "checking whether to enable the libwrap feture""... $ac_c" 1>&6
+echo "configure:1685: checking whether to enable the libwrap feture" >&5
+
+# Check whether --with-libwrap or --without-libwrap was given.
+if test "${with_libwrap+set}" = set; then
+  withval="$with_libwrap"
+   case "$withval" in
+  no)
+    echo "$ac_t""no" 1>&6
+    ;;
+  yes)
+    echo "$ac_t""yes" 1>&6
+    echo $ac_n "checking for request_init in -lwrap""... $ac_c" 1>&6
+echo "configure:1697: checking for request_init in -lwrap" >&5
+ac_lib_var=`echo wrap'_'request_init | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  ac_save_LIBS="$LIBS"
+LIBS="-lwrap  $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 1705 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error.  */
+/* We use char because int might match the return type of a gcc2
+    builtin and then its argument prototype would still apply.  */
+char request_init();
+
+int main() {
+request_init()
+; return 0; }
+EOF
+if { (eval echo configure:1716: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+  rm -rf conftest*
+  eval "ac_cv_lib_$ac_lib_var=yes"
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+  echo "$ac_t""yes" 1>&6
+  
+        LIBS="-lwrap $LIBS"
+        DEFINES="-DTCPWRAPPER $DEFINES"
+else
+  echo "$ac_t""no" 1>&6
+fi
+
+    ;;
+  *)
+    echo "$ac_t""yes" 1>&6
+    if test -d "$withval"; then
+        LDFLAGS="-L$withval $LDFLAGS"
+       DEFINES="-DTCPWRAPPER $DEFINES"
+    fi
+    cat > conftest.$ac_ext <<EOF
+#line 1746 "configure"
+#include "confdefs.h"
+ int allow_severity; int deny_severity; 
+int main() {
+ hosts_access(); 
+; return 0; }
+EOF
+if { (eval echo configure:1753: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+  :
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+   { echo "configure: error: Could not find the $withval library.  You must first install tcp_wrappers." 1>&2; exit 1; } 
+fi
+rm -f conftest*
+    ;;
+  esac 
+else
+  echo "$ac_t""no" 1>&6
+
+fi
+
+
+
+
+
+
+
+echo $ac_n "checking how to run the C preprocessor""... $ac_c" 1>&6
+echo "configure:1776: checking how to run the C preprocessor" >&5
+# On Suns, sometimes $CPP names a directory.
+if test -n "$CPP" && test -d "$CPP"; then
+  CPP=
+fi
+if test -z "$CPP"; then
+if eval "test \"`echo '$''{'ac_cv_prog_CPP'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+    # This must be in double quotes, not single quotes, because CPP may get
+  # substituted into the Makefile and "${CC-cc}" will confuse make.
+  CPP="${CC-cc} -E"
+  # On the NeXT, cc -E runs the code through the compiler's parser,
+  # not just through cpp.
+  cat > conftest.$ac_ext <<EOF
+#line 1791 "configure"
+#include "confdefs.h"
+#include <assert.h>
+Syntax Error
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:1797: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+  :
+else
+  echo "$ac_err" >&5
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  CPP="${CC-cc} -E -traditional-cpp"
+  cat > conftest.$ac_ext <<EOF
+#line 1808 "configure"
+#include "confdefs.h"
+#include <assert.h>
+Syntax Error
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:1814: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+  :
+else
+  echo "$ac_err" >&5
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  CPP="${CC-cc} -nologo -E"
+  cat > conftest.$ac_ext <<EOF
+#line 1825 "configure"
+#include "confdefs.h"
+#include <assert.h>
+Syntax Error
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:1831: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+  :
+else
+  echo "$ac_err" >&5
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  CPP=/lib/cpp
+fi
+rm -f conftest*
+fi
+rm -f conftest*
+fi
+rm -f conftest*
+  ac_cv_prog_CPP="$CPP"
+fi
+  CPP="$ac_cv_prog_CPP"
+else
+  ac_cv_prog_CPP="$CPP"
+fi
+echo "$ac_t""$CPP" 1>&6
+
+echo $ac_n "checking for ANSI C header files""... $ac_c" 1>&6
+echo "configure:1856: checking for ANSI C header files" >&5
+if eval "test \"`echo '$''{'ac_cv_header_stdc'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  cat > conftest.$ac_ext <<EOF
+#line 1861 "configure"
+#include "confdefs.h"
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <float.h>
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:1869: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+  rm -rf conftest*
+  ac_cv_header_stdc=yes
+else
+  echo "$ac_err" >&5
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+if test $ac_cv_header_stdc = yes; then
+  # SunOS 4.x string.h does not declare mem*, contrary to ANSI.
+cat > conftest.$ac_ext <<EOF
+#line 1886 "configure"
+#include "confdefs.h"
+#include <string.h>
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+  egrep "memchr" >/dev/null 2>&1; then
+  :
+else
+  rm -rf conftest*
+  ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+  # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI.
+cat > conftest.$ac_ext <<EOF
+#line 1904 "configure"
+#include "confdefs.h"
+#include <stdlib.h>
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+  egrep "free" >/dev/null 2>&1; then
+  :
+else
+  rm -rf conftest*
+  ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+  # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi.
+if test "$cross_compiling" = yes; then
+  :
+else
+  cat > conftest.$ac_ext <<EOF
+#line 1925 "configure"
+#include "confdefs.h"
+#include <ctype.h>
+#define ISLOWER(c) ('a' <= (c) && (c) <= 'z')
+#define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c))
+#define XOR(e, f) (((e) && !(f)) || (!(e) && (f)))
+int main () { int i; for (i = 0; i < 256; i++)
+if (XOR (islower (i), ISLOWER (i)) || toupper (i) != TOUPPER (i)) exit(2);
+exit (0); }
+
+EOF
+if { (eval echo configure:1936: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null
+then
+  :
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -fr conftest*
+  ac_cv_header_stdc=no
+fi
+rm -fr conftest*
+fi
+
+fi
+fi
+
+echo "$ac_t""$ac_cv_header_stdc" 1>&6
+if test $ac_cv_header_stdc = yes; then
+  cat >> confdefs.h <<\EOF
+#define STDC_HEADERS 1
+EOF
+
+fi
+
+for ac_hdr in fcntl.h malloc.h strings.h sys/file.h sys/ioctl.h sys/time.h syslog.h unistd.h
+do
+ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'`
+echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6
+echo "configure:1963: checking for $ac_hdr" >&5
+if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  cat > conftest.$ac_ext <<EOF
+#line 1968 "configure"
+#include "confdefs.h"
+#include <$ac_hdr>
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:1973: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+  rm -rf conftest*
+  eval "ac_cv_header_$ac_safe=yes"
+else
+  echo "$ac_err" >&5
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  eval "ac_cv_header_$ac_safe=no"
+fi
+rm -f conftest*
+fi
+if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then
+  echo "$ac_t""yes" 1>&6
+    ac_tr_hdr=HAVE_`echo $ac_hdr | sed 'y%abcdefghijklmnopqrstuvwxyz./-%ABCDEFGHIJKLMNOPQRSTUVWXYZ___%'`
+  cat >> confdefs.h <<EOF
+#define $ac_tr_hdr 1
+EOF
+else
+  echo "$ac_t""no" 1>&6
+fi
+done
+
+for ac_hdr in shadow.h
+do
+ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'`
+echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6
+echo "configure:2003: checking for $ac_hdr" >&5
+if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  cat > conftest.$ac_ext <<EOF
+#line 2008 "configure"
+#include "confdefs.h"
+#include <$ac_hdr>
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:2013: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+  rm -rf conftest*
+  eval "ac_cv_header_$ac_safe=yes"
+else
+  echo "$ac_err" >&5
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  eval "ac_cv_header_$ac_safe=no"
+fi
+rm -f conftest*
+fi
+if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then
+  echo "$ac_t""yes" 1>&6
+    ac_tr_hdr=HAVE_`echo $ac_hdr | sed 'y%abcdefghijklmnopqrstuvwxyz./-%ABCDEFGHIJKLMNOPQRSTUVWXYZ___%'`
+  cat >> confdefs.h <<EOF
+#define $ac_tr_hdr 1
+EOF
+               if test -f /etc/shadow ; then
+                 cat >> confdefs.h <<\EOF
+#define SHADOW_PASSWORDS 1
+EOF
+               
+               fi 
+               
+else
+  echo "$ac_t""no" 1>&6
+fi
+done
+
+echo $ac_n "checking for working const""... $ac_c" 1>&6
+echo "configure:2047: checking for working const" >&5
+if eval "test \"`echo '$''{'ac_cv_c_const'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  cat > conftest.$ac_ext <<EOF
+#line 2052 "configure"
+#include "confdefs.h"
+
+int main() {
+
+/* Ultrix mips cc rejects this.  */
+typedef int charset[2]; const charset x;
+/* SunOS 4.1.1 cc rejects this.  */
+char const *const *ccp;
+char **p;
+/* NEC SVR4.0.2 mips cc rejects this.  */
+struct point {int x, y;};
+static struct point const zero = {0,0};
+/* AIX XL C 1.02.0.0 rejects this.
+   It does not let you subtract one const X* pointer from another in an arm
+   of an if-expression whose if-part is not a constant expression */
+const char *g = "string";
+ccp = &g + (g ? g-g : 0);
+/* HPUX 7.0 cc rejects these. */
+++ccp;
+p = (char**) ccp;
+ccp = (char const *const *) p;
+{ /* SCO 3.2v4 cc rejects this.  */
+  char *t;
+  char const *s = 0 ? (char *) 0 : (char const *) 0;
+
+  *t++ = 0;
+}
+{ /* Someone thinks the Sun supposedly-ANSI compiler will reject this.  */
+  int x[] = {25, 17};
+  const int *foo = &x[0];
+  ++foo;
+}
+{ /* Sun SC1.0 ANSI compiler rejects this -- but not the above. */
+  typedef const int *iptr;
+  iptr p = 0;
+  ++p;
+}
+{ /* AIX XL C 1.02.0.0 rejects this saying
+     "k.c", line 2.27: 1506-025 (S) Operand must be a modifiable lvalue. */
+  struct s { int j; const int *ap[3]; };
+  struct s *b; b->j = 5;
+}
+{ /* ULTRIX-32 V3.1 (Rev 9) vcc rejects this */
+  const int foo = 10;
+}
+
+; return 0; }
+EOF
+if { (eval echo configure:2101: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+  rm -rf conftest*
+  ac_cv_c_const=yes
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  ac_cv_c_const=no
+fi
+rm -f conftest*
+fi
+
+echo "$ac_t""$ac_cv_c_const" 1>&6
+if test $ac_cv_c_const = no; then
+  cat >> confdefs.h <<\EOF
+#define const 
+EOF
+
+fi
+
+echo $ac_n "checking whether time.h and sys/time.h may both be included""... $ac_c" 1>&6
+echo "configure:2122: checking whether time.h and sys/time.h may both be included" >&5
+if eval "test \"`echo '$''{'ac_cv_header_time'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  cat > conftest.$ac_ext <<EOF
+#line 2127 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#include <sys/time.h>
+#include <time.h>
+int main() {
+struct tm *tp;
+; return 0; }
+EOF
+if { (eval echo configure:2136: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+  rm -rf conftest*
+  ac_cv_header_time=yes
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  ac_cv_header_time=no
+fi
+rm -f conftest*
+fi
+
+echo "$ac_t""$ac_cv_header_time" 1>&6
+if test $ac_cv_header_time = yes; then
+  cat >> confdefs.h <<\EOF
+#define TIME_WITH_SYS_TIME 1
+EOF
+
+fi
+
+
+if test $ac_cv_prog_gcc = yes; then
+    echo $ac_n "checking whether ${CC-cc} needs -traditional""... $ac_c" 1>&6
+echo "configure:2159: checking whether ${CC-cc} needs -traditional" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_gcc_traditional'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+    ac_pattern="Autoconf.*'x'"
+  cat > conftest.$ac_ext <<EOF
+#line 2165 "configure"
+#include "confdefs.h"
+#include <sgtty.h>
+Autoconf TIOCGETP
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+  egrep "$ac_pattern" >/dev/null 2>&1; then
+  rm -rf conftest*
+  ac_cv_prog_gcc_traditional=yes
+else
+  rm -rf conftest*
+  ac_cv_prog_gcc_traditional=no
+fi
+rm -f conftest*
+
+
+  if test $ac_cv_prog_gcc_traditional = no; then
+    cat > conftest.$ac_ext <<EOF
+#line 2183 "configure"
+#include "confdefs.h"
+#include <termio.h>
+Autoconf TCGETA
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+  egrep "$ac_pattern" >/dev/null 2>&1; then
+  rm -rf conftest*
+  ac_cv_prog_gcc_traditional=yes
+fi
+rm -f conftest*
+
+  fi
+fi
+
+echo "$ac_t""$ac_cv_prog_gcc_traditional" 1>&6
+  if test $ac_cv_prog_gcc_traditional = yes; then
+    CC="$CC -traditional"
+  fi
+fi
+
+echo $ac_n "checking whether setpgrp takes no argument""... $ac_c" 1>&6
+echo "configure:2205: checking whether setpgrp takes no argument" >&5
+if eval "test \"`echo '$''{'ac_cv_func_setpgrp_void'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  if test "$cross_compiling" = yes; then
+  { echo "configure: error: cannot check setpgrp if cross compiling" 1>&2; exit 1; }
+else
+  cat > conftest.$ac_ext <<EOF
+#line 2213 "configure"
+#include "confdefs.h"
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+/*
+ * If this system has a BSD-style setpgrp, which takes arguments, exit
+ * successfully.
+ */
+main()
+{
+    if (setpgrp(1,1) == -1)
+       exit(0);
+    else
+       exit(1);
+}
+
+EOF
+if { (eval echo configure:2233: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null
+then
+  ac_cv_func_setpgrp_void=no
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -fr conftest*
+  ac_cv_func_setpgrp_void=yes
+fi
+rm -fr conftest*
+fi
+
+
+fi
+
+echo "$ac_t""$ac_cv_func_setpgrp_void" 1>&6
+if test $ac_cv_func_setpgrp_void = yes; then
+  cat >> confdefs.h <<\EOF
+#define SETPGRP_VOID 1
+EOF
+
+fi
+
+echo $ac_n "checking return type of signal handlers""... $ac_c" 1>&6
+echo "configure:2257: checking return type of signal handlers" >&5
+if eval "test \"`echo '$''{'ac_cv_type_signal'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  cat > conftest.$ac_ext <<EOF
+#line 2262 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#include <signal.h>
+#ifdef signal
+#undef signal
+#endif
+#ifdef __cplusplus
+extern "C" void (*signal (int, void (*)(int)))(int);
+#else
+void (*signal ()) ();
+#endif
+
+int main() {
+int i;
+; return 0; }
+EOF
+if { (eval echo configure:2279: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+  rm -rf conftest*
+  ac_cv_type_signal=void
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  ac_cv_type_signal=int
+fi
+rm -f conftest*
+fi
+
+echo "$ac_t""$ac_cv_type_signal" 1>&6
+cat >> confdefs.h <<EOF
+#define RETSIGTYPE $ac_cv_type_signal
+EOF
+
+
+echo $ac_n "checking for vprintf""... $ac_c" 1>&6
+echo "configure:2298: checking for vprintf" >&5
+if eval "test \"`echo '$''{'ac_cv_func_vprintf'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  cat > conftest.$ac_ext <<EOF
+#line 2303 "configure"
+#include "confdefs.h"
+/* System header to define __stub macros and hopefully few prototypes,
+    which can conflict with char vprintf(); below.  */
+#include <assert.h>
+/* Override any gcc2 internal prototype to avoid an error.  */
+/* We use char because int might match the return type of a gcc2
+    builtin and then its argument prototype would still apply.  */
+char vprintf();
+
+int main() {
+
+/* The GNU C library defines this for functions which it implements
+    to always fail with ENOSYS.  Some functions are actually named
+    something starting with __ and the normal name is an alias.  */
+#if defined (__stub_vprintf) || defined (__stub___vprintf)
+choke me
+#else
+vprintf();
+#endif
+
+; return 0; }
+EOF
+if { (eval echo configure:2326: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+  rm -rf conftest*
+  eval "ac_cv_func_vprintf=yes"
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  eval "ac_cv_func_vprintf=no"
+fi
+rm -f conftest*
+fi
+
+if eval "test \"`echo '$ac_cv_func_'vprintf`\" = yes"; then
+  echo "$ac_t""yes" 1>&6
+  cat >> confdefs.h <<\EOF
+#define HAVE_VPRINTF 1
+EOF
+
+else
+  echo "$ac_t""no" 1>&6
+fi
+
+if test "$ac_cv_func_vprintf" != yes; then
+echo $ac_n "checking for _doprnt""... $ac_c" 1>&6
+echo "configure:2350: checking for _doprnt" >&5
+if eval "test \"`echo '$''{'ac_cv_func__doprnt'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  cat > conftest.$ac_ext <<EOF
+#line 2355 "configure"
+#include "confdefs.h"
+/* System header to define __stub macros and hopefully few prototypes,
+    which can conflict with char _doprnt(); below.  */
+#include <assert.h>
+/* Override any gcc2 internal prototype to avoid an error.  */
+/* We use char because int might match the return type of a gcc2
+    builtin and then its argument prototype would still apply.  */
+char _doprnt();
+
+int main() {
+
+/* The GNU C library defines this for functions which it implements
+    to always fail with ENOSYS.  Some functions are actually named
+    something starting with __ and the normal name is an alias.  */
+#if defined (__stub__doprnt) || defined (__stub____doprnt)
+choke me
+#else
+_doprnt();
+#endif
+
+; return 0; }
+EOF
+if { (eval echo configure:2378: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+  rm -rf conftest*
+  eval "ac_cv_func__doprnt=yes"
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  eval "ac_cv_func__doprnt=no"
+fi
+rm -f conftest*
+fi
+
+if eval "test \"`echo '$ac_cv_func_'_doprnt`\" = yes"; then
+  echo "$ac_t""yes" 1>&6
+  cat >> confdefs.h <<\EOF
+#define HAVE_DOPRNT 1
+EOF
+
+else
+  echo "$ac_t""no" 1>&6
+fi
+
+fi
+
+echo $ac_n "checking for wait3 that fills in rusage""... $ac_c" 1>&6
+echo "configure:2403: checking for wait3 that fills in rusage" >&5
+if eval "test \"`echo '$''{'ac_cv_func_wait3_rusage'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  if test "$cross_compiling" = yes; then
+  ac_cv_func_wait3_rusage=no
+else
+  cat > conftest.$ac_ext <<EOF
+#line 2411 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <stdio.h>
+/* HP-UX has wait3 but does not fill in rusage at all.  */
+main() {
+  struct rusage r;
+  int i;
+  /* Use a field that we can force nonzero --
+     voluntary context switches.
+     For systems like NeXT and OSF/1 that don't set it,
+     also use the system CPU time.  And page faults (I/O) for Linux.  */
+  r.ru_nvcsw = 0;
+  r.ru_stime.tv_sec = 0;
+  r.ru_stime.tv_usec = 0;
+  r.ru_majflt = r.ru_minflt = 0;
+  switch (fork()) {
+  case 0: /* Child.  */
+    sleep(1); /* Give up the CPU.  */
+    _exit(0);
+  case -1: _exit(0); /* What can we do?  */
+  default: /* Parent.  */
+    wait3(&i, 0, &r);
+    sleep(2); /* Avoid "text file busy" from rm on fast HP-UX machines.  */
+    exit(r.ru_nvcsw == 0 && r.ru_majflt == 0 && r.ru_minflt == 0
+        && r.ru_stime.tv_sec == 0 && r.ru_stime.tv_usec == 0);
+  }
+}
+EOF
+if { (eval echo configure:2442: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null
+then
+  ac_cv_func_wait3_rusage=yes
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -fr conftest*
+  ac_cv_func_wait3_rusage=no
+fi
+rm -fr conftest*
+fi
+
+fi
+
+echo "$ac_t""$ac_cv_func_wait3_rusage" 1>&6
+if test $ac_cv_func_wait3_rusage = yes; then
+  cat >> confdefs.h <<\EOF
+#define HAVE_WAIT3 1
+EOF
+
+fi
+
+for ac_func in regcomp select socket strcspn strdup strtol
+do
+echo $ac_n "checking for $ac_func""... $ac_c" 1>&6
+echo "configure:2467: checking for $ac_func" >&5
+if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  cat > conftest.$ac_ext <<EOF
+#line 2472 "configure"
+#include "confdefs.h"
+/* System header to define __stub macros and hopefully few prototypes,
+    which can conflict with char $ac_func(); below.  */
+#include <assert.h>
+/* Override any gcc2 internal prototype to avoid an error.  */
+/* We use char because int might match the return type of a gcc2
+    builtin and then its argument prototype would still apply.  */
+char $ac_func();
+
+int main() {
+
+/* The GNU C library defines this for functions which it implements
+    to always fail with ENOSYS.  Some functions are actually named
+    something starting with __ and the normal name is an alias.  */
+#if defined (__stub_$ac_func) || defined (__stub___$ac_func)
+choke me
+#else
+$ac_func();
+#endif
+
+; return 0; }
+EOF
+if { (eval echo configure:2495: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+  rm -rf conftest*
+  eval "ac_cv_func_$ac_func=yes"
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  eval "ac_cv_func_$ac_func=no"
+fi
+rm -f conftest*
+fi
+
+if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then
+  echo "$ac_t""yes" 1>&6
+    ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'`
+  cat >> confdefs.h <<EOF
+#define $ac_tr_func 1
+EOF
+else
+  echo "$ac_t""no" 1>&6
+fi
+done
+
+
+trap '' 1 2 15
+cat > confcache <<\EOF
+# This file is a shell script that caches the results of configure
+# tests run on this system so they can be shared between configure
+# scripts and configure runs.  It is not useful on other systems.
+# If it contains results you don't want to keep, you may remove or edit it.
+#
+# By default, configure uses ./config.cache as the cache file,
+# creating it if it does not exist already.  You can give configure
+# the --cache-file=FILE option to use a different cache file; that is
+# what configure does when it calls configure scripts in
+# subdirectories, so they share the cache.
+# Giving --cache-file=/dev/null disables caching, for debugging configure.
+# config.status only pays attention to the cache file if you give it the
+# --recheck option to rerun configure.
+#
+EOF
+# The following way of writing the cache mishandles newlines in values,
+# but we know of no workaround that is simple, portable, and efficient.
+# So, don't put newlines in cache variables' values.
+# Ultrix sh set writes to stderr and can't be redirected directly,
+# and sets the high bit in the cache file unless we assign to the vars.
+(set) 2>&1 |
+  case `(ac_space=' '; set | grep ac_space) 2>&1` in
+  *ac_space=\ *)
+    # `set' does not quote correctly, so add quotes (double-quote substitution
+    # turns \\\\ into \\, and sed turns \\ into \).
+    sed -n \
+      -e "s/'/'\\\\''/g" \
+      -e "s/^\\([a-zA-Z0-9_]*_cv_[a-zA-Z0-9_]*\\)=\\(.*\\)/\\1=\${\\1='\\2'}/p"
+    ;;
+  *)
+    # `set' quotes correctly as required by POSIX, so do not add quotes.
+    sed -n -e 's/^\([a-zA-Z0-9_]*_cv_[a-zA-Z0-9_]*\)=\(.*\)/\1=${\1=\2}/p'
+    ;;
+  esac >> confcache
+if cmp -s $cache_file confcache; then
+  :
+else
+  if test -w $cache_file; then
+    echo "updating cache $cache_file"
+    cat confcache > $cache_file
+  else
+    echo "not updating unwritable cache $cache_file"
+  fi
+fi
+rm -f confcache
+
+trap 'rm -fr conftest* confdefs* core core.* *.core $ac_clean_files; exit 1' 1 2 15
+
+test "x$prefix" = xNONE && prefix=$ac_default_prefix
+# Let make expand exec_prefix.
+test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
+
+# Any assignment to VPATH causes Sun make to only execute
+# the first set of double-colon rules, so remove it if not needed.
+# If there is a colon in the path, we need to keep it.
+if test "x$srcdir" = x.; then
+  ac_vpsub='/^[        ]*VPATH[        ]*=[^:]*$/d'
+fi
+
+trap 'rm -f $CONFIG_STATUS conftest*; exit 1' 1 2 15
+
+DEFS=-DHAVE_CONFIG_H
+
+# Without the "./", some shells look in PATH for config.status.
+: ${CONFIG_STATUS=./config.status}
+
+echo creating $CONFIG_STATUS
+rm -f $CONFIG_STATUS
+cat > $CONFIG_STATUS <<EOF
+#! /bin/sh
+# Generated automatically by configure.
+# Run this file to recreate the current configuration.
+# This directory was configured as follows,
+# on host `(hostname || uname -n) 2>/dev/null | sed 1q`:
+#
+# $0 $ac_configure_args
+#
+# Compiler output produced by configure, useful for debugging
+# configure, is in ./config.log if it exists.
+
+ac_cs_usage="Usage: $CONFIG_STATUS [--recheck] [--version] [--help]"
+for ac_option
+do
+  case "\$ac_option" in
+  -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r)
+    echo "running \${CONFIG_SHELL-/bin/sh} $0 $ac_configure_args --no-create --no-recursion"
+    exec \${CONFIG_SHELL-/bin/sh} $0 $ac_configure_args --no-create --no-recursion ;;
+  -version | --version | --versio | --versi | --vers | --ver | --ve | --v)
+    echo "$CONFIG_STATUS generated by autoconf version 2.13"
+    exit 0 ;;
+  -help | --help | --hel | --he | --h)
+    echo "\$ac_cs_usage"; exit 0 ;;
+  *) echo "\$ac_cs_usage"; exit 1 ;;
+  esac
+done
+
+ac_given_srcdir=$srcdir
+
+trap 'rm -fr `echo "Makefile config.h" | sed "s/:[^ ]*//g"` conftest*; exit 1' 1 2 15
+EOF
+cat >> $CONFIG_STATUS <<EOF
+
+# Protect against being on the right side of a sed subst in config.status.
+sed 's/%@/@@/; s/@%/@@/; s/%g\$/@g/; /@g\$/s/[\\\\&%]/\\\\&/g;
+ s/@@/%@/; s/@@/@%/; s/@g\$/%g/' > conftest.subs <<\\CEOF
+$ac_vpsub
+$extrasub
+s%@SHELL@%$SHELL%g
+s%@CFLAGS@%$CFLAGS%g
+s%@CPPFLAGS@%$CPPFLAGS%g
+s%@CXXFLAGS@%$CXXFLAGS%g
+s%@FFLAGS@%$FFLAGS%g
+s%@DEFS@%$DEFS%g
+s%@LDFLAGS@%$LDFLAGS%g
+s%@LIBS@%$LIBS%g
+s%@exec_prefix@%$exec_prefix%g
+s%@prefix@%$prefix%g
+s%@program_transform_name@%$program_transform_name%g
+s%@bindir@%$bindir%g
+s%@sbindir@%$sbindir%g
+s%@libexecdir@%$libexecdir%g
+s%@datadir@%$datadir%g
+s%@sysconfdir@%$sysconfdir%g
+s%@sharedstatedir@%$sharedstatedir%g
+s%@localstatedir@%$localstatedir%g
+s%@libdir@%$libdir%g
+s%@includedir@%$includedir%g
+s%@oldincludedir@%$oldincludedir%g
+s%@infodir@%$infodir%g
+s%@mandir@%$mandir%g
+s%@SET_MAKE@%$SET_MAKE%g
+s%@CC@%$CC%g
+s%@host@%$host%g
+s%@host_alias@%$host_alias%g
+s%@host_cpu@%$host_cpu%g
+s%@host_vendor@%$host_vendor%g
+s%@host_os@%$host_os%g
+s%@target@%$target%g
+s%@target_alias@%$target_alias%g
+s%@target_cpu@%$target_cpu%g
+s%@target_vendor@%$target_vendor%g
+s%@target_os@%$target_os%g
+s%@build@%$build%g
+s%@build_alias@%$build_alias%g
+s%@build_cpu@%$build_cpu%g
+s%@build_vendor@%$build_vendor%g
+s%@build_os@%$build_os%g
+s%@DEFINES@%$DEFINES%g
+s%@PIDFILE@%$PIDFILE%g
+s%@DB@%$DB%g
+s%@OS@%$OS%g
+s%@CPP@%$CPP%g
+
+CEOF
+EOF
+
+cat >> $CONFIG_STATUS <<\EOF
+
+# Split the substitutions into bite-sized pieces for seds with
+# small command number limits, like on Digital OSF/1 and HP-UX.
+ac_max_sed_cmds=90 # Maximum number of lines to put in a sed script.
+ac_file=1 # Number of current file.
+ac_beg=1 # First line for current file.
+ac_end=$ac_max_sed_cmds # Line after last line for current file.
+ac_more_lines=:
+ac_sed_cmds=""
+while $ac_more_lines; do
+  if test $ac_beg -gt 1; then
+    sed "1,${ac_beg}d; ${ac_end}q" conftest.subs > conftest.s$ac_file
+  else
+    sed "${ac_end}q" conftest.subs > conftest.s$ac_file
+  fi
+  if test ! -s conftest.s$ac_file; then
+    ac_more_lines=false
+    rm -f conftest.s$ac_file
+  else
+    if test -z "$ac_sed_cmds"; then
+      ac_sed_cmds="sed -f conftest.s$ac_file"
+    else
+      ac_sed_cmds="$ac_sed_cmds | sed -f conftest.s$ac_file"
+    fi
+    ac_file=`expr $ac_file + 1`
+    ac_beg=$ac_end
+    ac_end=`expr $ac_end + $ac_max_sed_cmds`
+  fi
+done
+if test -z "$ac_sed_cmds"; then
+  ac_sed_cmds=cat
+fi
+EOF
+
+cat >> $CONFIG_STATUS <<EOF
+
+CONFIG_FILES=\${CONFIG_FILES-"Makefile"}
+EOF
+cat >> $CONFIG_STATUS <<\EOF
+for ac_file in .. $CONFIG_FILES; do if test "x$ac_file" != x..; then
+  # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in".
+  case "$ac_file" in
+  *:*) ac_file_in=`echo "$ac_file"|sed 's%[^:]*:%%'`
+       ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;;
+  *) ac_file_in="${ac_file}.in" ;;
+  esac
+
+  # Adjust a relative srcdir, top_srcdir, and INSTALL for subdirectories.
+
+  # Remove last slash and all that follows it.  Not all systems have dirname.
+  ac_dir=`echo $ac_file|sed 's%/[^/][^/]*$%%'`
+  if test "$ac_dir" != "$ac_file" && test "$ac_dir" != .; then
+    # The file is in a subdirectory.
+    test ! -d "$ac_dir" && mkdir "$ac_dir"
+    ac_dir_suffix="/`echo $ac_dir|sed 's%^\./%%'`"
+    # A "../" for each directory in $ac_dir_suffix.
+    ac_dots=`echo $ac_dir_suffix|sed 's%/[^/]*%../%g'`
+  else
+    ac_dir_suffix= ac_dots=
+  fi
+
+  case "$ac_given_srcdir" in
+  .)  srcdir=.
+      if test -z "$ac_dots"; then top_srcdir=.
+      else top_srcdir=`echo $ac_dots|sed 's%/$%%'`; fi ;;
+  /*) srcdir="$ac_given_srcdir$ac_dir_suffix"; top_srcdir="$ac_given_srcdir" ;;
+  *) # Relative path.
+    srcdir="$ac_dots$ac_given_srcdir$ac_dir_suffix"
+    top_srcdir="$ac_dots$ac_given_srcdir" ;;
+  esac
+
+
+  echo creating "$ac_file"
+  rm -f "$ac_file"
+  configure_input="Generated automatically from `echo $ac_file_in|sed 's%.*/%%'` by configure."
+  case "$ac_file" in
+  *Makefile*) ac_comsub="1i\\
+# $configure_input" ;;
+  *) ac_comsub= ;;
+  esac
+
+  ac_file_inputs=`echo $ac_file_in|sed -e "s%^%$ac_given_srcdir/%" -e "s%:% $ac_given_srcdir/%g"`
+  sed -e "$ac_comsub
+s%@configure_input@%$configure_input%g
+s%@srcdir@%$srcdir%g
+s%@top_srcdir@%$top_srcdir%g
+" $ac_file_inputs | (eval "$ac_sed_cmds") > $ac_file
+fi; done
+rm -f conftest.s*
+
+# These sed commands are passed to sed as "A NAME B NAME C VALUE D", where
+# NAME is the cpp macro being defined and VALUE is the value it is being given.
+#
+# ac_d sets the value in "#define NAME VALUE" lines.
+ac_dA='s%^\([  ]*\)#\([        ]*define[       ][      ]*\)'
+ac_dB='\([     ][      ]*\)[^  ]*%\1#\2'
+ac_dC='\3'
+ac_dD='%g'
+# ac_u turns "#undef NAME" with trailing blanks into "#define NAME VALUE".
+ac_uA='s%^\([  ]*\)#\([        ]*\)undef\([    ][      ]*\)'
+ac_uB='\([     ]\)%\1#\2define\3'
+ac_uC=' '
+ac_uD='\4%g'
+# ac_e turns "#undef NAME" without trailing blanks into "#define NAME VALUE".
+ac_eA='s%^\([  ]*\)#\([        ]*\)undef\([    ][      ]*\)'
+ac_eB='$%\1#\2define\3'
+ac_eC=' '
+ac_eD='%g'
+
+if test "${CONFIG_HEADERS+set}" != set; then
+EOF
+cat >> $CONFIG_STATUS <<EOF
+  CONFIG_HEADERS="config.h"
+EOF
+cat >> $CONFIG_STATUS <<\EOF
+fi
+for ac_file in .. $CONFIG_HEADERS; do if test "x$ac_file" != x..; then
+  # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in".
+  case "$ac_file" in
+  *:*) ac_file_in=`echo "$ac_file"|sed 's%[^:]*:%%'`
+       ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;;
+  *) ac_file_in="${ac_file}.in" ;;
+  esac
+
+  echo creating $ac_file
+
+  rm -f conftest.frag conftest.in conftest.out
+  ac_file_inputs=`echo $ac_file_in|sed -e "s%^%$ac_given_srcdir/%" -e "s%:% $ac_given_srcdir/%g"`
+  cat $ac_file_inputs > conftest.in
+
+EOF
+
+# Transform confdefs.h into a sed script conftest.vals that substitutes
+# the proper values into config.h.in to produce config.h.  And first:
+# Protect against being on the right side of a sed subst in config.status.
+# Protect against being in an unquoted here document in config.status.
+rm -f conftest.vals
+cat > conftest.hdr <<\EOF
+s/[\\&%]/\\&/g
+s%[\\$`]%\\&%g
+s%#define \([A-Za-z_][A-Za-z0-9_]*\) *\(.*\)%${ac_dA}\1${ac_dB}\1${ac_dC}\2${ac_dD}%gp
+s%ac_d%ac_u%gp
+s%ac_u%ac_e%gp
+EOF
+sed -n -f conftest.hdr confdefs.h > conftest.vals
+rm -f conftest.hdr
+
+# This sed command replaces #undef with comments.  This is necessary, for
+# example, in the case of _POSIX_SOURCE, which is predefined and required
+# on some systems where configure will not decide to define it.
+cat >> conftest.vals <<\EOF
+s%^[   ]*#[    ]*undef[        ][      ]*[a-zA-Z_][a-zA-Z_0-9]*%/* & */%
+EOF
+
+# Break up conftest.vals because some shells have a limit on
+# the size of here documents, and old seds have small limits too.
+
+rm -f conftest.tail
+while :
+do
+  ac_lines=`grep -c . conftest.vals`
+  # grep -c gives empty output for an empty file on some AIX systems.
+  if test -z "$ac_lines" || test "$ac_lines" -eq 0; then break; fi
+  # Write a limited-size here document to conftest.frag.
+  echo '  cat > conftest.frag <<CEOF' >> $CONFIG_STATUS
+  sed ${ac_max_here_lines}q conftest.vals >> $CONFIG_STATUS
+  echo 'CEOF
+  sed -f conftest.frag conftest.in > conftest.out
+  rm -f conftest.in
+  mv conftest.out conftest.in
+' >> $CONFIG_STATUS
+  sed 1,${ac_max_here_lines}d conftest.vals > conftest.tail
+  rm -f conftest.vals
+  mv conftest.tail conftest.vals
+done
+rm -f conftest.vals
+
+cat >> $CONFIG_STATUS <<\EOF
+  rm -f conftest.frag conftest.h
+  echo "/* $ac_file.  Generated automatically by configure.  */" > conftest.h
+  cat conftest.in >> conftest.h
+  rm -f conftest.in
+  if cmp -s $ac_file conftest.h 2>/dev/null; then
+    echo "$ac_file is unchanged"
+    rm -f conftest.h
+  else
+    # Remove last slash and all that follows it.  Not all systems have dirname.
+      ac_dir=`echo $ac_file|sed 's%/[^/][^/]*$%%'`
+      if test "$ac_dir" != "$ac_file" && test "$ac_dir" != .; then
+      # The file is in a subdirectory.
+      test ! -d "$ac_dir" && mkdir "$ac_dir"
+    fi
+    rm -f $ac_file
+    mv conftest.h $ac_file
+  fi
+fi; done
+
+EOF
+cat >> $CONFIG_STATUS <<EOF
+
+EOF
+cat >> $CONFIG_STATUS <<\EOF
+echo timestamp > stamp-h
+exit 0
+EOF
+chmod +x $CONFIG_STATUS
+rm -fr confdefs* $ac_clean_files
+test "$no_create" = yes || ${CONFIG_SHELL-/bin/sh} $CONFIG_STATUS || exit 1
+
diff --git a/configure.in b/configure.in
new file mode 100644 (file)
index 0000000..6b3eb13
--- /dev/null
@@ -0,0 +1,250 @@
+dnl This file writen by Devrim SERAL for tac_plus daemon
+
+AC_INIT()
+
+dnl Checks for programs.
+AC_PROG_MAKE_SET
+AC_PROG_CC
+
+dnl Check for Host information
+dnl AC_CANONICAL_HOST()
+AC_CANONICAL_SYSTEM()
+
+case $host_os in 
+       *linux-gnu)
+       OS="-DLINUX -DGLIBC"
+       ;;
+       *solaris)
+       OS="-DSOLARIS"
+       ;;
+       *freebsd)
+       OS="-DFREEBSD"
+       ;;
+       *hpux)
+       OS="-DHPUX"
+       ;;
+       *aix)
+       OS="-DAIX"
+       ;;
+       *)
+       ;;
+esac
+       
+dnl Checks for libraries.
+dnl Replace `main' with a function in -lnsl:
+AC_CHECK_LIB(nsl, main)
+dnl Replace `main' with a function in -log:
+AC_CHECK_LIB(og, main)
+dnl Replace `main' with a function in -lldap:
+AC_CHECK_LIB(ldap, main)
+dnl Replace `main' with a function in -llber:
+AC_CHECK_LIB(lber, main)
+dnl Replace `main' with a function in -lsocket:
+AC_CHECK_LIB(socket, main)
+dnl Check for Crypt function
+AC_CHECK_LIB(crypt, crypt)
+AC_CHECK_LIB(c,printf)
+
+
+dnl Devrim Added 
+AC_CONFIG_HEADER(config.h)
+
+dnl For PAM support
+AC_MSG_CHECKING(for PAM support:)
+echo
+AC_ARG_WITH(pam,
+       [  --with-pam           With PAM Support   ],,)
+if test "x$with_pam" = "xyes";then
+       AC_CHECK_LIB(dl, dlopen)
+       AC_CHECK_LIB(pam, pam_start)
+       DEFINES="-DUSE_PAM $DEFINES";
+        AC_MSG_RESULT(Pam support... yes)
+else
+        AC_MSG_RESULT(Pam support... no)
+fi
+
+dnl For LDAP Support
+AC_MSG_CHECKING(for LDAP support)
+echo
+AC_ARG_WITH(ldap,
+        [  --with-ldap         With LDAP Support   ],,)
+
+if test "x$with_ldap" = "xyes";then
+   AC_CHECK_LIB(ldap, ldap_simple_bind_s)
+   AC_CHECK_LIB(ldap, ldap_init)
+   DEFINES="-DUSE_LDAP $DEFINES"
+        AC_MSG_RESULT(LDAP support... yes)
+else
+        AC_MSG_RESULT(LDAP support... no)
+fi
+
+dnl For DB Support
+AC_MSG_CHECKING(for DB support)
+echo 
+AC_ARG_WITH(db,
+        [  --with-db           For DB Support   ],,)
+if test "x$with_db" = "xyes";then
+       DB="$DB -DDB -DDB_NULL" 
+       AC_MSG_RESULT(DB support... yes)
+else
+       AC_MSG_RESULT(DB support... no)
+fi
+
+dnl For MySQL support
+if test "x$with_db" = "xyes";then
+
+echo "Check for MySQL support:"
+
+AC_ARG_WITH(mysql,
+       [  --with-mysql         With MySQL Support   ],,)
+
+AC_ARG_WITH(mysql-prefix,
+        [  --with-mysql-prefix=PREFIX  Mysql prefix [default=/usr]],
+       MYSQL_PREFIX=$withval,
+       MYSQL_PREFIX=/usr
+)
+
+
+if test "x$with_mysql" = "xyes";then
+       
+       LDFLAGS="-L$MYSQL_PREFIX/lib/mysql $LDFLAGS"
+        LDFLAGS="-I$MYSQL_PREFIX/include/mysql $LDFLAGS"
+        AC_CHECK_LIB(mysqlclient, mysql_init,
+                        LIBS="-lmysqlclient -lm $LIBS",
+                        AC_MSG_ERROR(*** couldn't find libmysqlclient),
+                        -lm)
+
+       DB="$DB -DDB_MYSQL";
+        AC_MSG_RESULT(Mysql support... yes)
+else
+        AC_MSG_RESULT(Mysql support... no)
+fi
+
+fi
+
+dnl For PgSQL support
+if test "x$with_db" = "xyes";then
+
+echo "Check for PgSQL support:"
+
+AC_ARG_WITH(pgsql,
+       [  --with-pgsql         With PgSQL Support   ],,)
+
+AC_ARG_WITH(pgsql-prefix,
+        [  --with-pgsql-prefix=PREFIX  PgSQL prefix [default=/usr]],
+       PGSQL_PREFIX=$withval,
+       PGSQL_PREFIX=/usr
+)
+
+
+if test "x$with_pgsql" = "xyes";then
+       
+       LDFLAGS="-L$PGSQL_PREFIX/lib/pgsql $LDFLAGS"
+        LDFLAGS="-I$PGSQL_PREFIX/include/pgsql $LDFLAGS"
+        AC_CHECK_LIB(pq,PQconnectdb ,
+                        LIBS="-lpq $LIBS",
+                        AC_MSG_ERROR(*** couldn't find libpq))
+
+       DB="$DB -DDB_PGSQL";
+        AC_MSG_RESULT(Pgsql support... yes)
+else
+        AC_MSG_RESULT(Pgsql support... no)
+fi
+
+fi
+
+dnl Tacuid & tac guid 
+
+AC_ARG_WITH(tacuid,
+               [  --with-tacuid=ID     If you like to run tac_plus other than root user (no default value) ],,)
+AC_ARG_WITH(tacgid,
+               [  --with-tacgid=GID    If you like to run tac_plus other than root group(no default value) ],,)
+
+
+if (test "x$with_tacuid" != "x" && test "x$with_tacgid" != "x" && test "x$with_tacuid" != "xyes" && test "x$with_tacgid" != "xyes");then
+
+       DEFINES="-DTACPLUS_USERID=$with_tacuid -DTACPLUS_GROUPID=$with_tacgid $DEFINES";        
+       AC_MSG_RESULT(tacacs+ work with given user and group id) 
+fi
+
+AC_MSG_CHECKING(whether to enable the maxsess feature)
+AC_ARG_ENABLE(maxsess,
+              [  --enable-maxsess      Enable maxsess feature ],
+[
+if test "$enableval" = "yes";then
+       DEFINES="-DMAXSESS $DEFINES";
+       AC_MSG_RESULT(yes)
+else 
+       AC_MSG_RESULT(no)
+fi
+],
+[
+       AC_MSG_RESULT(no)
+])
+
+dnl Enable tacacs.pid file directory 
+
+AC_ARG_WITH(tacplus_pid,
+        [  --with-tacplus_pid=PREFIX  Tac_plus pid file location [default=/var/run] ],
+        PIDFILE="-DTACPLUS_PIDFILE=\\\"$withval/tac_plus.pid\\\"",
+        PIDFILE="-DTACPLUS_PIDFILE=\\\"/var/run/tac_plus.pid\\\""
+)
+
+dnl For libwrap check
+AC_MSG_CHECKING(whether to enable the libwrap feture)
+
+AC_ARG_WITH(libwrap,
+[  --with-libwrap[=PATH]   Compile in libwrap (tcp_wrappers) support.],
+[ case "$withval" in
+  no)
+    AC_MSG_RESULT(no)
+    ;;
+  yes)
+    AC_MSG_RESULT(yes)
+    AC_CHECK_LIB(wrap, request_init, [
+        LIBS="-lwrap $LIBS"
+        DEFINES="-DTCPWRAPPER $DEFINES"])
+    ;;
+  *)
+    AC_MSG_RESULT(yes)
+    if test -d "$withval"; then
+        LDFLAGS="-L$withval $LDFLAGS"
+       DEFINES="-DTCPWRAPPER $DEFINES"
+    fi
+    AC_TRY_LINK([ int allow_severity; int deny_severity; ],
+                [ hosts_access(); ],
+                [],
+                [ AC_MSG_ERROR(Could not find the $withval library.  You must first install tcp_wrappers.) ])
+    ;;
+  esac ],
+  AC_MSG_RESULT(no)
+)
+
+dnl insert defines to Makefile 
+AC_SUBST(DEFINES)
+AC_SUBST(PIDFILE)
+AC_SUBST(DB)
+AC_SUBST(OS)
+
+dnl Checks for header files.
+AC_HEADER_STDC
+AC_CHECK_HEADERS(fcntl.h malloc.h strings.h sys/file.h sys/ioctl.h sys/time.h syslog.h unistd.h)
+AC_CHECK_HEADERS(shadow.h,[
+               if test -f /etc/shadow ; then
+                 AC_DEFINE(SHADOW_PASSWORDS)           
+               fi 
+               ],)
+dnl Checks for typedefs, structures, and compiler characteristics.
+AC_C_CONST
+AC_HEADER_TIME
+
+dnl Checks for library functions.
+AC_PROG_GCC_TRADITIONAL
+AC_FUNC_SETPGRP
+AC_TYPE_SIGNAL
+AC_FUNC_VPRINTF
+AC_FUNC_WAIT3
+AC_CHECK_FUNCS(regcomp select socket strcspn strdup strtol)
+
+AC_OUTPUT(Makefile,echo timestamp > stamp-h)
diff --git a/convert.pl b/convert.pl
new file mode 100755 (executable)
index 0000000..eb0b5c2
--- /dev/null
@@ -0,0 +1,146 @@
+#! /usr/bin/perl
+
+# convert a passwd(5) and optional supplementary file into the new
+# file format
+
+# Please NOTE:  None of the TACACS code available here comes with any
+# warranty or support.
+# Copyright (c) 1995-1998 by Cisco systems, Inc.
+# 
+# Permission to use, copy, modify, and distribute this software for any
+# purpose and without fee is hereby granted, provided that this
+# copyright and permission notice appear on all copies of the software and
+# supporting documentation, the name of Cisco Systems, Inc. not be used
+# in advertising or publicity pertaining to distribution of the
+# program without specific prior permission, and notice be given
+# in supporting documentation that modification, copying and distribution is by
+# permission of Cisco Systems, Inc.   
+# 
+# Cisco Systems, Inc. makes no representations about the suitability of this
+# software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS IS''
+# AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT
+# LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE. 
+
+die 'Usage: convert.pl <password file> [-g] [ <supplementary file> ]' 
+    if $#ARGV < 0;
+
+$pwfile = '';
+$supfile  = '';
+%sup = ();
+$acl_valid = 0;  # is acl valid in gid field?
+
+$pwfile = shift(@ARGV);
+while ($#ARGV >= 0) {
+    local($arg) = shift(@ARGV);
+    $acl_valid++, next if ($arg eq '-g');
+    $supfile = $arg;
+}
+
+if ($supfile) {
+    open(SUP, $supfile) || die "Can't read $supfile -- $!";
+    while(<SUP>) {
+       next if /^#/;
+       chop;
+
+       local($user, $inacl, $outacl, $arap, $chap) = split(/:/);
+
+       if (defined $sup{$user,'user'}) {
+           die "User $user is multiply defined on lines $sup{$user,'user'} and $. of $supfile";
+       }
+       $users{$user} = 1;
+       $sup{$user,'user'} = $.;
+       $sup{$user,'inacl'} = $inacl;
+       $sup{$user,'outacl'} = $outacl;
+       $sup{$user,'arap'} = $arap;
+       $sup{$user,'chap'} = $chap;
+    }
+    close(SUP);
+}
+
+open(PASSWD, $pwfile) || die "Can't read $pwfile -- $!";
+
+while(<PASSWD>) {
+    chop;
+    next if ($_ eq '');
+
+    local($user, $pass, $uid, $gid, $gcos, $home, $exp) = split(/:/);
+
+    $users{$user} = 2;
+
+    print "user = $user {\n";
+    print "    login = des $pass\n";
+    if (!$acl_valid) {
+       print "    member = $gid\n";
+       $groups{$gid}++;
+    }
+    if ($gcos) {
+       if ($gcos =~ /[         ]/) {
+           print "    name = \"$gcos\"\n";
+       } else {
+           print "    name = $gcos\n";
+       }
+    }
+
+    if ($exp =~ /\S+\s+\d+\s+\d+/) {
+       print "    expires = \"$exp\"\n";
+    }
+
+    if ($acl_valid) {
+       print "    service = exec {\n";
+       print "        acl = $gid\n";
+       print "    }\n";
+    }
+
+    local($outacl) = $sup{$user,'outacl'};
+    local($inacl) = $sup{$user,'inacl'};
+    if ($inacl ne '' || $outacl ne '') {
+       print "    service = slip {\n";
+       print " inacl = $inacl\n" if $inacl ne '';
+       print " outacl = $outacl\n" if $outacl ne '';
+       print "    }\n";
+
+       print "    service = ppp protocol = ip {\n";
+       print " inacl = $inacl\n" if $inacl ne '';
+       print " outacl = $outacl\n" if $outacl ne '';
+       print "    }\n";
+    }
+
+    print "    arap = $sup{$user,'arap'}\n" if $sup{$user,'arap'} ne '';
+    print "    chap = $sup{$user,'chap'}\n" if $sup{$user,'chap'} ne '';
+    print "}\n";
+}
+
+close(PASSWD);
+
+foreach $user (keys %users) {
+    next if $users{$user} != 1;
+    # This user only in supfile
+    print "user = $user {\n";
+    local($outacl) = $sup{$user,'outacl'};
+    local($inacl) = $sup{$user,'inacl'};
+    if ($inacl ne '' || $outacl ne '') {
+       print "    service = slip {\n";
+       print " inacl = $inacl\n" if $inacl ne '';
+       print " outacl = $outacl\n" if $outacl ne '';
+       print "    }\n";
+
+       print "    service = ppp protocol = ip {\n";
+       print " inacl = $inacl\n" if $inacl ne '';
+       print " outacl = $outacl\n" if $outacl ne '';
+       print "    }\n";
+    }
+
+    print "    arap = $sup{$user,'arap'}\n" if $sup{$user,'arap'} ne '';
+    print "    chap = $sup{$user,'chap'}\n" if $sup{$user,'chap'} ne '';
+    print "}\n";
+}
+
+exit 0 if ($acl_valid);
+
+foreach $group (keys %groups) {
+    print "group = $group { }\n";
+}
+
+
+
diff --git a/db.c b/db.c
new file mode 100644 (file)
index 0000000..9cbbbe1
--- /dev/null
+++ b/db.c
@@ -0,0 +1,403 @@
+/*
+     Verify that this user/password is valid per a database.
+     Return 1 if verified, 0 otherwise.
+     
+     Format of connection string (look like internet URL):
+
+       db://user:password@hostname/table?name&passwd
+
+     Example connect to Oracle RDBMS at user 'roger' with password
+     'tiger', to 'host.domain.com' database server, fields name in
+     table is 'name' and 'passwd' in 'oshadow' table:
+
+       oracle://roger:tiger@host.domain.com/oshadow?name&passwd
+
+     DONE:
+     12-nov-1998 Created
+                 Add DB support to 'login = db <string>'
+     14-nov-1998 Change Tacacs+ version from 0.95 to 3.0.9
+     18-nov-1998 Added code for Oracle [version 8.0.5]
+     27-nov-1998 Tested with 30'000 usernames Oracle database
+                 Added DB support to global configuration
+                 'default authentication = db <string>'
+     28-nov-1998 Added code for NULL database %)
+     14-dec-1999 Add code for MySQL and also more check
+     
+     FUTURE:
+     Make *_db_verify() the functions is reenterable
+     More security for connection to database
+     GDBM support
+     Separate debug logging
+     Perfomance testing on 10000 records in Oracle database
+     (in guide sayd about 3 auth/sec on Ultra 2 - hmm)
+     
+     -------------------------------------------------------
+     fil@artelecom.ru                   http://twister.pp.ru
+ ****************************************************************************
+                                   PART II
+
+   I am added some extra extension. Like MySQL and PostgreSQL database support
+   And change most of lines for use dynamic memory allocation. db_accounting 
+   added by me.
+  
+   devrim(devrim@gazi.edu.tr)
+*/
+
+#if defined(DB)
+#include <stdio.h>
+#include "tac_plus.h"
+/* The databases  recognized by this function */
+#define DEFINED_DB {"null","mysql","pgsql"}
+
+char *find_attr_value(); 
+
+int
+db_verify(user, users_passwd, str_conn)
+char *user, *users_passwd;      /* Username and gived password   */
+char *str_conn;                 /* String connection to database */
+{
+    char *buffer;
+    char *db_pref, *db_user, *db_password;
+    char *db_hostname, *db_table,*db_name,*dbfield_name, *dbfield_passwd;
+    int ret;
+
+    if (debug & DEBUG_PASSWD_FLAG)
+       report(LOG_DEBUG, "verify %s by database at %s", user, str_conn);
+
+    buffer = db_pref = (char *)malloc( strlen(str_conn) + 1 );
+    if( buffer == NULL ){
+       report(LOG_DEBUG, "Error allocation memory");
+        return(0);
+    }
+
+    strcpy( buffer, str_conn );
+
+    db_user = (char *)strstr( db_pref, "://" );
+    if( db_user == NULL ){
+       if (debug & DEBUG_PASSWD_FLAG)
+           report(LOG_DEBUG, "Error parse db_user");
+           free(buffer);
+           return(0);
+    }
+    *db_user = '\0'; 
+
+       /* For recognize db authentication database */
+    
+    if (check_db_type(db_pref)) {
+       report(LOG_DEBUG, "%s DB authentication scheme didn't recognize by tac_plus",db_pref);
+       free(buffer);
+        return(0);
+    }
+
+    db_user += 3;
+
+    db_password = (char *)strstr( db_user, ":" );
+    if( db_password == NULL ){
+        if (debug & DEBUG_PASSWD_FLAG)
+            report(LOG_DEBUG, "Error parse db_password");
+       free(buffer);
+        return(0);
+    }
+    *db_password = '\0';
+    db_password++;
+    
+    db_hostname = (char *)strstr( db_password, "@" );
+    if( db_hostname == NULL ){
+       if (debug & DEBUG_PASSWD_FLAG)
+           report(LOG_DEBUG, "Error parse db_hostname");
+       free(buffer);
+        return(0);
+    }
+    *db_hostname = '\0';
+    db_hostname++;
+    
+    db_name = (char *)strstr( db_hostname, "/" );
+    if( db_name == NULL ){
+        if (debug & DEBUG_PASSWD_FLAG)
+           report(LOG_DEBUG, "Error parse db_name");
+       free(buffer);
+        return(0);
+    } 
+    *db_name = '\0';
+    db_name++;
+    
+    db_table = (char *)strstr( db_name, "/" );
+    if( db_table == NULL ){
+        if (debug & DEBUG_PASSWD_FLAG)
+           report(LOG_DEBUG, "Error parse db_table");
+       free(buffer);
+        return(0);
+    } 
+    *db_table = '\0';
+    db_table++;
+    
+    dbfield_name = (char *)strstr( db_table, "?" );
+    if( dbfield_name == NULL){
+       if (debug & DEBUG_PASSWD_FLAG)
+           report(LOG_DEBUG, "Error parse dbfield_name");
+       free(buffer);
+        return(0);
+    }
+    *dbfield_name = '\0';
+    dbfield_name++;
+
+
+    dbfield_passwd = (char *)strstr( dbfield_name, "&" );
+    if( dbfield_passwd == NULL){
+       if (debug & DEBUG_PASSWD_FLAG)
+           report(LOG_DEBUG, "Error parse dbfield_passwd");
+       free(buffer);
+        return(0);
+    }
+    *dbfield_passwd = '\0';
+    dbfield_passwd++;
+    
+
+    /* Parse database connection string */
+       if (debug & DEBUG_PASSWD_FLAG)
+           report(LOG_DEBUG, "db_verify: db_pref=%s, db_user=%s, db_password=%s db_hostname=%s, db_name=%s ,db_table=%s, dbfield_name=%s, dbfield_passwd=%s", db_pref, db_user, db_password, db_hostname, db_name,db_table, dbfield_name, dbfield_passwd);
+
+    /* Check for empty passwords */
+   if (users_passwd == NULL || *users_passwd == '\0' ||
+        db_password == NULL || *db_password == '\0' ) {
+        if (debug & DEBUG_PASSWD_FLAG)
+           report(LOG_DEBUG, "One from passwords is empty");
+       free(buffer);
+       return (0);
+    }
+
+    ret = 0;
+
+    /* Run database depend function */
+#if defined(DB_ORACLE)
+    if (!strcmp(db_pref, "oracle")) {
+       ret = oracle_db_verify(
+           user, users_passwd,
+           db_user, db_password, db_hostname, db_table,
+           dbfield_name, dbfield_passwd);
+    }
+#endif
+
+#if defined(DB_MYSQL)
+    if (!strcmp(db_pref, "mysql")) {
+       ret = mysql_db_verify(
+           user, users_passwd,
+           db_user, db_password, db_hostname, db_name, db_table,
+           dbfield_name, dbfield_passwd);
+    }
+#endif
+
+#if defined(DB_PGSQL)
+    if (!strcmp(db_pref, "pgsql")) {
+       ret = pgsql_db_verify(
+           user, users_passwd,
+           db_user, db_password, db_hostname,db_name, db_table,
+           dbfield_name, dbfield_passwd);
+    }
+#endif
+
+#if defined(DB_NULL)
+    if (!strcmp(db_pref, "null")) {
+        ret = null_db_verify(
+           user, users_passwd,
+           db_user, db_password, db_hostname ,db_table,
+           dbfield_name, dbfield_passwd);
+    }
+#endif
+
+#if defined(DB_GDBM)
+    if (!strcmp(db_pref, "gdbm")) {
+        gdb_db_verify();
+    }
+#endif
+    free(buffer); /* Free unused memory */
+    return (ret); /* error */
+}
+
+
+/* Db accounting routine */
+int
+db_acct(rec)
+struct acct_rec *rec;
+{
+    char *buffer;
+    char *db_pref, *db_user, *db_password;
+    char *db_hostname, *db_name,*db_table;
+    char *a_username,*s_name,*c_name,*elapsed_time,*bytes_in,*bytes_out;
+    int ret;
+
+    buffer = db_pref = (char *)malloc( strlen(session.db_acct) + 1 );
+       
+    if( buffer == NULL ){
+       report(LOG_DEBUG, "Error allocation memory");
+        return(0);
+    }
+
+    strcpy( buffer, session.db_acct);
+
+    db_user = (char *)strstr( db_pref, "://" );
+    if( db_user == NULL ){
+       if (debug & DEBUG_PASSWD_FLAG)
+           report(LOG_DEBUG, "Error parse db_user");
+        free(buffer);
+       return(0);
+    }
+    *db_user = '\0'; 
+
+       /* For recognize db accouting database */
+    
+    if( check_db_type(db_pref) ) {
+       report(LOG_DEBUG, "%s DB accounting scheme didn't recognize by tac_plus",db_pref);
+        free(buffer);
+        return(0);
+    }
+
+    db_user += 3;
+
+    db_password = (char *)strstr( db_user, ":" );
+    if( db_password == NULL ){
+        if (debug & DEBUG_PASSWD_FLAG)
+           report(LOG_DEBUG, "Error parse db_password");
+        free(buffer);
+        return(0);
+    }
+    *db_password = '\0';
+    db_password++;
+    
+    db_hostname = (char *)strstr( db_password, "@" );
+    if( db_hostname == NULL ){
+       if (debug & DEBUG_PASSWD_FLAG)
+           report(LOG_DEBUG, "Error parse db_hostname");
+        free(buffer);
+        return(0);
+    }
+    *db_hostname = '\0';
+    db_hostname++;
+    
+    db_name = (char *)strstr( db_hostname, "/" );
+    if( db_name == NULL ){
+        if (debug & DEBUG_PASSWD_FLAG)
+           report(LOG_DEBUG, "Error parse db_name");
+        free(buffer);
+        return(0);
+    } 
+    *db_name = '\0';
+    db_name++;
+    
+    db_table = (char *)strstr( db_name, "/" );
+    if( db_table == NULL ){
+        if (debug & DEBUG_PASSWD_FLAG)
+           report(LOG_DEBUG, "Error parse db_table");
+        free(buffer);
+        return(0);
+    } 
+    *db_table = '\0';
+    db_table++;
+
+/* Find some attributes  for accounting */
+    a_username=rec->identity->username;
+       if (a_username==NULL ) {
+               if (debug & DEBUG_PASSWD_FLAG) 
+                       report(LOG_DEBUG,"db_acct: Can't find username!");
+                       free(buffer);
+                       return(0);
+       }
+    s_name=rec->identity->NAS_name;
+       if (s_name==NULL) {
+               if (debug & DEBUG_PASSWD_FLAG) 
+                       report(LOG_DEBUG,"db_acct: Can't find NAS name!");
+                       free(buffer);
+                       return(0);
+       }
+    c_name=find_attr_value("addr", rec->args, rec->num_args);
+       if (c_name==NULL) {
+               if (debug & DEBUG_PASSWD_FLAG) 
+                       report(LOG_DEBUG,"db_acct: Can't find client adress!");
+       /* Can't find client adress so give NAC_address attribute value */ 
+               c_name=rec->identity->NAC_address;
+       }
+    elapsed_time=find_attr_value("elapsed_time", rec->args, rec->num_args);
+       if (elapsed_time==NULL) {
+               if (debug & DEBUG_PASSWD_FLAG) 
+                       report(LOG_DEBUG,"db_acct: Can't get elapsed time!");
+                       free(buffer);
+                       return(0);
+       }
+    bytes_in=find_attr_value("bytes_in", rec->args, rec->num_args);
+       if (bytes_in==NULL) bytes_in="0";
+    bytes_out=find_attr_value("bytes_out", rec->args, rec->num_args);
+       if (bytes_out==NULL) bytes_out="0";
+
+
+    /* Parse database connection string */
+       if (debug & DEBUG_PASSWD_FLAG)
+           report(LOG_DEBUG, "db_verify: db_pref=%s, db_user=%s,db_password=%s , db_hostname=%s, db_name=%s ,db_table=%s ",
+               db_pref, db_user, db_password,
+               db_hostname, db_name,db_table );
+
+    /* Check for empty passwords */
+   if (db_user == NULL || db_password == '\0' ) {
+        if (debug & DEBUG_PASSWD_FLAG)
+           report(LOG_DEBUG, "One from passwords is empty");
+        free(buffer);
+       return (0);
+    }
+
+    ret = 0;
+    /* Run database depend function */
+#if defined(DB_ORACLE)
+    if (!strcmp(db_pref, "oracle")) {
+       ret = oracle_db_acct(
+           db_user, db_password, db_hostname, db_name, db_table);
+    }
+#endif
+
+#if defined(DB_MYSQL)
+    if (!strcmp(db_pref, "mysql")) {
+       ret = mysql_db_acct(
+           db_user, db_password, db_hostname, db_name, db_table,s_name,c_name,a_username,elapsed_time,bytes_in,bytes_out);
+    }
+#endif
+
+#if defined(DB_PGSQL)
+    if (!strcmp(db_pref, "pgsql")) {
+        ret = pgsql_db_acct(
+            db_user, db_password, db_hostname, db_name, db_table,s_name,c_name,a_username,elapsed_time,bytes_in,bytes_out);
+    }
+#endif
+
+#if defined(DB_NULL)
+    if (!strcmp(db_pref, "null")) {
+        ret = null_db_acct(
+           db_user, db_password, db_hostname, db_name, db_table,s_name,c_name,a_username,elapsed_time,bytes_in,bytes_out);
+    }
+#endif
+#if defined(DB_GDBM)
+    if (!strcmp(db_pref, "gdbm")) {
+        gdb_db_acct();
+    }
+#endif
+
+    free(buffer); /* Free unused memory */
+    return (ret); /* error */
+
+}
+
+/* For checking DB type */
+int 
+check_db_type(db_type)
+char *db_type;
+{
+char *dbp[]=DEFINED_DB;
+int ret=1,i;
+
+for (i=0; dbp[i] ; i++ ) {
+       if(!strcmp(db_type,dbp[i])) {
+               ret=0;
+               break;
+       }
+}
+return ret;
+}
+#endif /* DB */
diff --git a/db_mysql.c b/db_mysql.c
new file mode 100644 (file)
index 0000000..8aef6d4
--- /dev/null
@@ -0,0 +1,216 @@
+#if defined(DB_MYSQL) && defined(DB)
+
+/*
+Writen by Devrim SERAL(devrim@tef.gazi.edu.tr)
+*/
+
+#include "tac_plus.h"
+#include <stdio.h>
+#include "mysql.h"
+#define SQLCMDL 1024
+#define AUTHSQL "SELECT %s FROM %s WHERE %s=\"%s\""
+#define ACCTSQL "INSERT INTO %s (usern,s_name,c_name,elapsed_time,bytes_in,bytes_out,fin_t) VALUES (\"%s\",\"%s\",\"%s\",%s,%s,%s,NOW())"  
+
+MYSQL mysqldb;
+MYSQL_RES *res;
+MYSQL_ROW row;
+MYSQL_FIELD *table_field;
+
+int mysql_db_verify(user, users_passwd, db_user, db_password,
+       db_hostname,db_name, db_table, dbfield_name, dbfield_passwd)
+
+
+char *user, *users_passwd;      /* Username and gived password   */
+char *db_user;                  /* db's parameters               */
+char *db_password;
+char *db_hostname;
+char *db_name;
+char *db_table;
+char *dbfield_name;
+char *dbfield_passwd;
+
+{
+
+char *real_passwd;
+char *mysqlcmd;
+int sql_len;
+
+   if (debug & DEBUG_AUTHEN_FLAG)
+       report(LOG_DEBUG, "MySQL: verify %s", user);
+       
+/* Connect database server */
+
+   if ( !( mysql_connect(&mysqldb,db_hostname,db_user,db_password) ) )
+       {
+               if (debug & DEBUG_AUTHEN_FLAG)
+                   report(LOG_DEBUG, "MySQL: cannot connect as %s", db_user);
+               return(0);
+       }
+
+/*Select tacacs db */
+
+    if ( mysql_select_db(&mysqldb,db_name) )
+       {
+               if (debug & DEBUG_AUTHEN_FLAG)
+                  report(LOG_DEBUG, "MySQL: cannot find database named %s",db_name);
+               return(0);
+       }
+
+/* Check select string length */
+
+sql_len=strlen(dbfield_passwd)+strlen(dbfield_name)+strlen(db_table)+strlen(user)+strlen(AUTHSQL);
+
+  if ( sql_len> SQLCMDL )
+        {
+               if (debug & DEBUG_AUTHEN_FLAG)
+                    report(LOG_DEBUG, "MySQL: Sql cmd exceed alowed limits");
+               return(0);
+        }
+
+/* Prepare select string */
+
+mysqlcmd=(char *) malloc(sql_len);
+
+if(mysqlcmd==NULL) { 
+       if (debug & DEBUG_AUTHEN_FLAG)
+               report(LOG_ERR, "mysql_db_verify: mysqlcmd malloc error");
+       return(0);
+}
+
+sprintf(mysqlcmd,AUTHSQL,dbfield_passwd,db_table,dbfield_name,user);
+
+/*  Query database */
+
+    if (mysql_query(&mysqldb,mysqlcmd))
+       {
+       if (debug & DEBUG_AUTHEN_FLAG)
+               report(LOG_DEBUG, "MySQL: cannot query database ");
+       free(mysqlcmd);
+        return(0);
+       }
+
+    free(mysqlcmd);
+    
+    if (!(res = mysql_store_result(&mysqldb)))
+       {
+       if (debug & DEBUG_AUTHEN_FLAG)
+               report(LOG_DEBUG, "MySQL: cannot store result");
+        return(0);
+       }  
+   
+   if(!(row = mysql_fetch_row(res)))
+       {
+       if (debug & DEBUG_AUTHEN_FLAG)
+               report(LOG_DEBUG, "MySQL: cannot fetch row");
+        return(0);
+        }  
+  
+   if (strlen(row[0]) <=0 )
+        {
+       if (debug & DEBUG_AUTHEN_FLAG)
+               report(LOG_DEBUG, "MySQL: DB passwd entry is NULL");
+        return(0);
+        }
+  /* Allocate memory for real_passwd */
+       real_passwd=(char *) malloc(strlen(row[0])+1);
+       strcpy(real_passwd,row[0]);
+   if (!mysql_eof(res))
+       {
+       if (debug & DEBUG_AUTHEN_FLAG)
+               report(LOG_DEBUG, "MySQL:  Result not end!!");
+        return(0);
+        }
+
+    mysql_free_result(res);
+    mysql_close(&mysqldb);
+  
+if (debug & DEBUG_AUTHEN_FLAG)   
+     report(LOG_DEBUG, "MySQL: verify password '%s' to DES encrypted string '%s'", users_passwd, real_passwd);
+
+    /* Try to verify the password */
+    if (!des_verify(users_passwd, real_passwd)) {
+        free(real_passwd);
+       return (0);
+    }
+    free(real_passwd);
+    return (1); /* Return 1 if verified, 0 otherwise. */
+}
+
+int 
+mysql_db_acct(db_user,db_password,db_hostname,db_name,db_table,s_name,c_name,a_username,elapsed_time,bytes_in,bytes_out)
+
+char *db_user;                 /* db's parameters              */
+char *db_password;
+char *db_hostname;
+char *db_name;
+char *db_table;
+char *s_name, *c_name,*a_username,*elapsed_time,*bytes_in,*bytes_out;
+
+{
+
+char *mysqlcmd;
+int sql_len;
+       
+/* Connect database server */
+
+   if (!(mysql_connect(&mysqldb,db_hostname,db_user,db_password)))
+       {
+       if (debug & DEBUG_ACCT_FLAG)
+               report(LOG_DEBUG, "MySQL: cannot connect as %s", db_user);
+               return(0);
+       }
+
+/*Select tacacs db */
+
+    if (mysql_select_db(&mysqldb,db_name))
+       {
+       if (debug & DEBUG_ACCT_FLAG)
+               report(LOG_DEBUG, "MySQL: cannot find database named %s",db_name);
+               return(0);
+       }
+
+/* Check buffer overflow for select string */
+sql_len=strlen(db_table)+strlen(a_username)+strlen(s_name)+strlen(c_name)+strlen(elapsed_time)+strlen(bytes_in)+strlen(bytes_out)+strlen(ACCTSQL);  
+
+if ( sql_len >SQLCMDL)
+        {
+        if (debug & DEBUG_ACCT_FLAG)
+               report(LOG_DEBUG, "MySQL: Sql cmd exceed alowed limits");
+               return(0);
+        }
+
+/* Prepare select string */
+mysqlcmd=(char *) malloc(sql_len);
+
+if(mysqlcmd==NULL) { 
+       if (debug & DEBUG_ACCT_FLAG)
+               report(LOG_ERR, "mysql_db_acct: mysqlcmd malloc error");
+       return(0);
+}
+
+sprintf(mysqlcmd,ACCTSQL,db_table,a_username,s_name,c_name,elapsed_time,bytes_in,bytes_out);
+
+/*  Query database */
+
+    if (mysql_query(&mysqldb,mysqlcmd))
+       {
+       if (debug & DEBUG_ACCT_FLAG)
+               report(LOG_DEBUG, "MySQL: cannot query database");
+       free(mysqlcmd);
+       return(0);
+        }
+
+       free(mysqlcmd);
+
+/* Check if accounting is sucess */
+    if ( mysql_affected_rows( &mysqldb ) < 0 )
+       {
+       if (debug & DEBUG_ACCT_FLAG)
+               report(LOG_DEBUG, "MySQL: Insert isn't sucess");
+        return(0);
+        }
+       return (1); /* Return 1 if verified, 0 otherwise. */
+}
+#endif
diff --git a/db_null.c b/db_null.c
new file mode 100644 (file)
index 0000000..40252d8
--- /dev/null
+++ b/db_null.c
@@ -0,0 +1,57 @@
+/*
+**  Simple NULL driver for database interface. I created this for testing
+**  db_* on my notebook by home. There i dont have Oracle or any database
+**  server.                                               Fil/27-nov-1998
+**
+**  DO_NOT_USE_THIS_FOR_WORK!
+*/
+
+#if defined(DB_NULL) && defined(DB)
+#include "tac_plus.h"
+
+int null_db_verify(user, users_passwd, db_user, db_password, db_hostname,
+           db_table, dbfield_name, dbfield_passwd)
+           
+char *user, *users_passwd;      /* Username and gived password   */
+char *db_user;                 /* db's parametr's               */
+char *db_password;
+char *db_hostname;
+char *db_table;
+char *dbfield_name;
+char *dbfield_passwd;
+
+{
+//report(LOG_DEBUG, "DB_NULL(%u) - ok", __LINE__);
+
+    /* Try to verify the password
+       Successful if username and password equal */
+    if (strcmp(user, users_passwd)) {
+       return (0);
+    }
+    if (debug & DEBUG_PASSWD_FLAG)
+               report(LOG_DEBUG, "DB Null: verify password '%s'", users_passwd);
+
+    return (1); /* Return 1 if verified, 0 otherwise. */
+}
+
+/*     Null Database Accounting        */
+
+int 
+null_db_acct(db_user, db_password, db_hostname,db_name,db_table,s_name,c_name,a_username,elapsed_time,bytes_in,bytes_out)
+char *db_user;                 /* db's parametr's               */
+char *db_password;
+char *db_hostname;
+char *db_name;
+char *db_table;
+char *s_name;
+char *c_name;
+char *a_username;
+char *elapsed_time;char *bytes_in;char *bytes_out;
+{
+report(LOG_INFO,"Db accounting user=%s pass=%s host=%s 
+db_name=%s table=%s servern=%s clientn=%s username=%s et=%s bi=%s bo=%s",db_user,db_password,db_hostname,
+db_name,db_table,s_name,c_name,a_username,elapsed_time,bytes_in,bytes_out);
+return (1);
+}
+#endif
+
diff --git a/db_pgsql.c b/db_pgsql.c
new file mode 100644 (file)
index 0000000..22725fe
--- /dev/null
@@ -0,0 +1,218 @@
+#if defined(DB_PGSQL) && defined(DB)
+
+/*
+Writen by Devrim SERAL(devrim@tef.gazi.edu.tr)
+For PostgreSQL Authentication And Accounting
+               28-01-2001
+This program protected with GPL License. 
+*/
+
+#include "tac_plus.h"
+#include <stdio.h>
+#include "libpq-fe.h" 
+#define SQLCMDL 1024
+#define PWLEN  13
+#define AUTHSQL "SELECT %s FROM %s WHERE %s='%s'"
+#define ACCTSQL "INSERT INTO %s (usern,s_name,c_name,elapsed_time,bytes_in,bytes_out,fin_t) VALUES ('%s','%s','%s',%s,%s,%s,NOW())"  
+
+PGconn     *conn;
+PGresult   *res;
+
+int pgsql_db_verify(user, users_passwd, db_user, db_password,
+       db_hostname,db_name, db_table, dbfield_name, dbfield_passwd)
+
+
+char *user, *users_passwd;      /* Username and gived password   */
+char *db_user;                  /* db's parameters               */
+char *db_password;
+char *db_hostname;
+char *db_name;
+char *db_table;
+char *dbfield_name;
+char *dbfield_passwd;
+
+{
+
+char *real_passwd;
+char *pgsqlcmd;
+int sql_len;
+int nrow;
+
+if (debug & DEBUG_AUTHEN_FLAG)
+        report(LOG_DEBUG, "PGSQL: verify %s", user);
+       
+/* Connect database server */
+
+conn=PQsetdbLogin(db_hostname,NULL,NULL,NULL,db_name,db_user,db_password);
+
+if ( PQstatus(conn) == CONNECTION_BAD ) 
+{
+    if (debug & DEBUG_AUTHEN_FLAG)
+       report(LOG_DEBUG, "PGSQL: Connection to database %s failed", db_name);
+       return(0);
+}
+
+/* Check select string length */
+
+sql_len=strlen(dbfield_passwd)+strlen(dbfield_name)+strlen(db_table)+strlen(user)+strlen(AUTHSQL);
+
+if ( sql_len> SQLCMDL )
+{
+    if (debug & DEBUG_AUTHEN_FLAG)
+               report(LOG_DEBUG, "PGSQL: Sql cmd exceed alowed limits");
+               return(0);
+}
+
+/* Prepare select string */
+
+pgsqlcmd=(char *) malloc(sql_len);
+
+if(pgsqlcmd==NULL) 
+{ 
+    if (debug & DEBUG_AUTHEN_FLAG)
+       report(LOG_ERR, "pgsql_db_verify: pgsqlcmd malloc error");
+       return(0);
+}
+
+sprintf(pgsqlcmd,AUTHSQL,dbfield_passwd,db_table,dbfield_name,user);
+
+/*  Query database */
+res=PQexec(conn,pgsqlcmd);
+
+if (!res || PQresultStatus(res) != PGRES_TUPLES_OK)
+{
+   if (debug & DEBUG_AUTHEN_FLAG) {
+       report(LOG_DEBUG, "PGSQL: cannot query database ");
+       report(LOG_DEBUG, "PGSQL: Error message->%s", PQerrorMessage(conn) );
+   }
+       free(pgsqlcmd);
+        exit_nicely(conn,res);
+       return(0);
+}
+
+free(pgsqlcmd);
+
+if( nrow=PQntuples(res)!=1) 
+{  
+    if (debug & DEBUG_AUTHEN_FLAG)
+        report(LOG_DEBUG, "PGSQL: Have we got more than one password!!");
+        exit_nicely(conn,res);
+       return(0);
+}  
+  
+if ( PQgetisnull(res,0,PQfnumber(res,dbfield_passwd)) ) 
+{
+    if (debug & DEBUG_AUTHEN_FLAG)
+        report(LOG_DEBUG, "PGSQL: DB passwd entry is NULL");
+        exit_nicely(conn,res);
+       return(0);
+}
+
+  /* Allocate memory for real_passwd */
+       real_passwd=(char *) malloc(PWLEN+1);
+       strncpy(real_passwd,PQgetvalue(res,0,PQfnumber(res,dbfield_passwd)),PWLEN);
+        real_passwd[PWLEN]='\0';
+exit_nicely(conn,res);
+  
+if (debug & DEBUG_AUTHEN_FLAG)
+        report(LOG_DEBUG, "PGSQL: verify password '%s' to DES encrypted string '%s'", users_passwd, real_passwd);
+       /* Try to verify the password */
+       if (!des_verify(users_passwd, real_passwd)) 
+       {
+        return (0);
+       }
+
+    return (1); /* Return 1 if verified, 0 otherwise. */
+}
+
+/*     PGSQL ACCOUNTING function       */ 
+
+int pgsql_db_acct(db_user,db_password,db_hostname,db_name,db_table,s_name,c_name,a_username,elapsed_time,bytes_in,bytes_out)
+
+char *db_user;                  /* db's parameters              */
+char *db_password;
+char *db_hostname;
+char *db_name;
+char *db_table;
+char *s_name, *c_name,*a_username,*elapsed_time,*bytes_in,*bytes_out;
+
+{
+
+char *pgsqlcmd;
+int sql_len;
+
+  if (debug & DEBUG_ACCT_FLAG)
+       report(LOG_DEBUG, "PGSQL: Accounting for %s begin", a_username);
+       
+/* Connect database server */
+
+conn=PQsetdbLogin(db_hostname,NULL,NULL,NULL,db_name,db_user,db_password);
+
+if ( PQstatus(conn) == CONNECTION_BAD ) 
+{
+   if (debug & DEBUG_ACCT_FLAG) {
+       report(LOG_DEBUG, "PGSQL: Connection to database %s failed", db_name);
+       report(LOG_DEBUG, "PGSQL: Error message->%s", PQerrorMessage(conn) );
+   }
+       return(0);
+}
+
+/* Check select string length */
+
+sql_len=strlen(db_table)+strlen(a_username)+strlen(s_name)+strlen(c_name)+strlen(elapsed_time)+strlen(bytes_in)+strlen(bytes_out)+strlen(ACCTSQL); 
+
+if ( sql_len> SQLCMDL )
+{
+   if (debug & DEBUG_ACCT_FLAG) 
+               report(LOG_DEBUG, "PGSQL: Sql cmd exceed alowed limits");
+               return(0);
+}
+
+/* Prepare select string */
+
+pgsqlcmd=(char *) malloc(sql_len);
+
+if(pgsqlcmd==NULL) 
+{
+if (debug & DEBUG_ACCT_FLAG) 
+       report(LOG_ERR, "pgsql_db_verify: pgsqlcmd malloc error");
+       return(0);
+}
+
+sprintf(pgsqlcmd,ACCTSQL,db_table,a_username,s_name,c_name,elapsed_time,bytes_in,bytes_out);
+/*  Query database */
+res=PQexec(conn,pgsqlcmd);
+
+if (!res || PQresultStatus(res) != PGRES_COMMAND_OK )
+{
+ if (debug & DEBUG_ACCT_FLAG) { 
+       report(LOG_DEBUG, "PGSQL: cannot establish database query");
+       report(LOG_DEBUG, "PGSQL: Error message->%s", PQerrorMessage(conn) );
+}
+       free(pgsqlcmd);
+        exit_nicely(conn,res);
+       return(0);
+}
+
+free(pgsqlcmd);
+    
+/* Flush all result and close connection */
+exit_nicely(conn,res);
+
+    if (debug & DEBUG_ACCT_FLAG)
+       report(LOG_DEBUG, "PGSQL: Accounting for %s finished", a_username);
+  
+    return (1); /* Return 1 if verified, 0 otherwise. */
+}
+
+int
+exit_nicely(PGconn *cn,PGresult *r)
+{
+    PQclear(r);
+    PQfinish(cn);
+}
+
+#endif
diff --git a/default_fn.c b/default_fn.c
new file mode 100644 (file)
index 0000000..f97e9e2
--- /dev/null
@@ -0,0 +1,786 @@
+/*
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+#include "tac_plus.h"
+#include "expire.h"
+#include "md5.h"
+
+#ifdef MSCHAP
+#include "md4.h"
+#include "mschap.h"
+
+#ifdef MSCHAP_DES
+#include "arap_des.h"
+#endif
+#endif /* MSCHAP */
+
+#ifdef ARAP_DES
+#include "arap_des.h"
+#endif
+
+/* internal state variables */
+#define STATE_AUTHEN_START   0 /* no requests issued */
+#define STATE_AUTHEN_GETUSER 1 /* username has been requested */
+#define STATE_AUTHEN_GETPASS 2 /* password has been requested */
+
+struct private_data {
+    char password[MAX_PASSWD_LEN + 1];
+    int state;
+};
+
+static void chap_verify();
+#ifdef MSCHAP
+static void mschap_verify();
+#endif /* MSCHAP */
+static void arap_verify();
+static void pap_verify();
+static void tac_login();
+
+/*
+ * Default tacacs login authentication function. Wants a username
+ * and a password, and tries to verify them.
+ *
+ * Choose_authen will ensure that we already have a username before this
+ * gets called.
+ *
+ * We will query for a password and keep it in the method_data.
+ *
+ * Any strings returned via pointers in authen_data must come from the
+ * heap. They will get freed by the caller.
+ *
+ * Return 0 if data->status is valid, otherwise 1
+ */
+
+int
+default_fn(data)
+struct authen_data *data;
+{
+    struct private_data *p;
+    char *name = data->NAS_id->username;
+
+    p = (struct private_data *) data->method_data;
+
+    /* An abort has been received. Clean up and return */
+    if (data->flags & TAC_PLUS_CONTINUE_FLAG_ABORT) {
+       if (data->method_data)
+           free(data->method_data);
+       data->method_data = NULL;
+       return (1);
+    }
+    /* Initialise method_data if first time through */
+    if (!p) {
+       p = (struct private_data *) tac_malloc(sizeof(struct private_data));
+       bzero(p, sizeof(struct private_data));
+       data->method_data = p;
+       p->state = STATE_AUTHEN_START;
+    }
+    if (STREQ(name, DEFAULT_USERNAME)) {
+       /* Never authenticate this user. It's for authorization only */
+       data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+       if (debug) {
+           report(LOG_DEBUG,
+                  "authentication query for '%s' %s from %s rejected",
+                  name && name[0] ? name : "unknown",
+                  session.port, session.peer);
+       }
+       return (0);
+    }
+    if (data->action != TAC_PLUS_AUTHEN_LOGIN) {
+       data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+    } else {
+       switch (data->type) {
+       case TAC_PLUS_AUTHEN_TYPE_CHAP:
+           /* set status inside chap_verify */
+           chap_verify(data);
+
+           if (debug) {
+               report(LOG_DEBUG, "chap-login query for '%s' %s from %s %s",
+                      name && name[0] ? name : "unknown",
+                      session.port, session.peer,
+                      (data->status == TAC_PLUS_AUTHEN_STATUS_PASS) ?
+                      "accepted" : "rejected");
+           }
+           break;
+
+#ifdef MSCHAP
+       case TAC_PLUS_AUTHEN_TYPE_MSCHAP:
+           /* set status inside mschap_verify */
+           mschap_verify(data);
+
+           if (debug) {
+               report(LOG_DEBUG, "mschap-login query for '%s' %s from %s %s",
+                      name && name[0] ? name : "unknown",
+                      session.port, session.peer,
+                      (data->status == TAC_PLUS_AUTHEN_STATUS_PASS) ?
+                      "accepted" : "rejected");
+           }
+           break;
+#endif /* MSCHAP */
+
+       case TAC_PLUS_AUTHEN_TYPE_ARAP:
+           /* set status inside arap_verify */
+           arap_verify(data);
+
+           if (debug) {
+               report(LOG_DEBUG, "arap query for '%s' %s from %s %s",
+                      name && name[0] ? name : "unknown",
+                      session.port, session.peer,
+                      (data->status == TAC_PLUS_AUTHEN_STATUS_PASS) ?
+                      "accepted" : "rejected");
+           }
+           break;
+
+       case TAC_PLUS_AUTHEN_TYPE_PAP:
+           pap_verify(data);
+
+           if (debug) {
+               report(LOG_INFO, "pap-login query for '%s' %s from %s %s",
+                      name && name[0] ? name : "unknown",
+                      session.port,
+                      session.peer,
+                      (data->status == TAC_PLUS_AUTHEN_STATUS_PASS) ?
+                      "accepted" : "rejected");
+           }
+           break;
+
+       case TAC_PLUS_AUTHEN_TYPE_ASCII:
+           tac_login(data, p);
+           switch (data->status) {
+           case TAC_PLUS_AUTHEN_STATUS_GETPASS:
+           case TAC_PLUS_AUTHEN_STATUS_GETUSER:
+           case TAC_PLUS_AUTHEN_STATUS_GETDATA:
+               /* Authentication still in progress. More data required */
+               return (0);
+
+           default:
+               /* Authentication finished */
+               if (debug)
+                   report(LOG_INFO, "login query for '%s' %s from %s %s",
+                          name && name[0] ? name : "unknown",
+                          session.port,
+                          session.peer,
+                          (data->status == TAC_PLUS_AUTHEN_STATUS_PASS) ?
+                          "accepted" : "rejected");
+           }
+           break;
+
+       default:
+           data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+           break;
+       }
+    }
+
+    if (data->method_data)
+       free(data->method_data);
+    data->method_data = NULL;
+
+    switch (data->status) {
+    case TAC_PLUS_AUTHEN_STATUS_ERROR:
+    case TAC_PLUS_AUTHEN_STATUS_FAIL:
+    case TAC_PLUS_AUTHEN_STATUS_PASS:
+       return (0);
+
+    default:
+       report(LOG_ERR, "%s %s: default_fn set bogus status value %d",
+              session.peer, session.port, data->status);
+       data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+       return (0);
+    }
+}
+
+/* Do a login requiring a username & password. We already know the
+ * username. We may return GETPASS to get a password if we need it.
+ * The password will be stored in the private data
+ *
+ */
+
+static void
+tac_login(data, p)
+struct authen_data *data;
+struct private_data *p;
+{
+    char *name, *passwd;
+    int pwlen;
+
+    name = data->NAS_id->username;
+
+    if (!name[0]) {
+       /* something awful has happened. Give up and die */
+       report(LOG_ERR, "%s %s: no username for login",
+              session.peer, session.port);
+       data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+       return;
+    }
+    /* Do we have a password? */
+    passwd = p->password;
+
+    if (!passwd[0]) {
+
+       /* no password yet. Either we need to ask for one and expect to get
+        * called again when it's supplied, or we already asked for one and
+        * we should have a reply. */
+
+       switch (p->state) {
+       case STATE_AUTHEN_GETPASS:
+           /* We already asked for a password. This should be the reply */
+           if (data->client_msg) {
+               pwlen = MIN((int) strlen(data->client_msg), MAX_PASSWD_LEN);
+           } else {
+               pwlen = 0;
+           }
+           strncpy(passwd, data->client_msg, pwlen);
+           passwd[pwlen] = '\0';
+           break;
+
+       case STATE_AUTHEN_START:
+           /* if we're at the username stage, and the user has
+            * nopasswd defined, then return a PASS
+            */
+           if (cfg_get_user_nopasswd(name, TAC_PLUS_RECURSE)) {
+               data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
+               return;
+           }
+           /* FALL-THRU */
+       default:
+           data->flags = TAC_PLUS_AUTHEN_FLAG_NOECHO;
+           data->server_msg = tac_strdup("Password: ");
+           data->status = TAC_PLUS_AUTHEN_STATUS_GETPASS;
+           p->state = STATE_AUTHEN_GETPASS;
+           return;
+       }
+    }
+    /* Now we have a username and password. Try validating */
+
+    /* Assume the worst */
+    data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+    verify(name, passwd, data, TAC_PLUS_RECURSE);
+    return;
+}
+
+/*
+ * Process an inbound PAP login. The username & password should be in
+ * the START packet.
+ */
+
+static void
+pap_verify(data)
+struct authen_data *data;
+{
+    char *name, *passwd;
+
+    name = data->NAS_id->username;
+
+    if (!name[0]) {
+       /* something awful has happened. Give up and die */
+       report(LOG_ERR, "%s %s: no username for inbound PAP login",
+              session.peer, session.port);
+       data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+       return;
+    }
+    /* get the password */
+    passwd = tac_malloc(data->client_dlen + 1);
+    bcopy(data->client_data, passwd, data->client_dlen);
+    passwd[data->client_dlen] = '\0';
+
+    /* Assume the worst */
+    data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+    verify(name, passwd, data, TAC_PLUS_RECURSE);
+    free(passwd);
+}
+
+
+/* Verify the challenge and id against the response by looking up the
+ * chap secret in the config file. Set data->status appropriately.
+ */
+static void
+chap_verify(data)
+struct authen_data *data;
+{
+    char *name, *secret, *chal, digest[MD5_LEN];
+    char *exp_date, *p;
+    u_char *mdp;
+    char id;
+    int chal_len, inlen;
+    MD5_CTX mdcontext;
+
+    if (!(char) data->NAS_id->username[0]) {
+       report(LOG_ERR, "%s %s: no username for chap_verify",
+              session.peer, session.port);
+       data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+       return;
+    }
+    name = data->NAS_id->username;
+
+    id = data->client_data[0];
+
+    chal_len = data->client_dlen - 1 - MD5_LEN;
+    if (chal_len < 0) {
+       data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+       return;
+    }
+    if (debug & DEBUG_AUTHEN_FLAG) {
+       report(LOG_DEBUG, "%s %s: chap user=%s, id=%d chal_len=%d",
+              session.peer, session.port, name, (int) id, chal_len);
+
+       /* report_hex(LOG_DEBUG, (u_char *)data->client_data + 1, chal_len); */
+    }
+    /* Assume failure */
+    data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+
+    /* Get the secret */
+    secret = cfg_get_chap_secret(name, TAC_PLUS_RECURSE);
+
+    /* If there is no chap password for this user, see if there is a global
+     * password for her that we can use */
+    if (!secret) {
+       secret = cfg_get_global_secret(name, TAC_PLUS_RECURSE);
+    }
+    if (!secret) {
+       /* No secret. Fail */
+       if (debug & DEBUG_AUTHEN_FLAG) {
+           report(LOG_DEBUG, "%s %s: No chap or global secret for %s",
+                  session.peer, session.port, name);
+       }
+       data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+       return;
+    }
+    p = tac_find_substring("cleartext ", secret);
+    if (!p) {
+       report(LOG_ERR, "%s %s: %s chap secret %s is not cleartext",
+              session.peer, session.port, name, secret);
+       data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+       return;
+    }
+    secret = p;
+
+    /* We now have the secret, the id, and the challenge value. Put them all
+     * together, and run them through the MD5 digest algorithm. */
+
+    inlen = sizeof(u_char) + strlen(secret) + chal_len;
+    mdp = (u_char *) tac_malloc(inlen);
+    mdp[0] = id;
+    bcopy(secret, &mdp[1], strlen(secret));
+    chal = data->client_data + 1;
+    bcopy(chal, mdp + strlen(secret) + 1, chal_len);
+    MD5Init(&mdcontext);
+    MD5Update(&mdcontext, mdp, inlen);
+    MD5Final((u_char *) digest, &mdcontext);
+    free(mdp);
+
+    /* Now compare the received response value with the just calculated
+     * digest value.  If they are equal, it's a pass, otherwise it's a
+     * failure */
+
+    if (bcmp(digest, data->client_data + 1 + chal_len, MD5_LEN)) {
+       data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+    } else {
+       data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
+    }
+
+    exp_date = cfg_get_expires(name, TAC_PLUS_RECURSE);
+    set_expiration_status(exp_date, data);
+}
+
+
+/*
+ * Force the "parity" bit to zero on a password before passing it to
+ * des. This is not documented anywhere. (I believe forcing the parity
+ * to zero reduces the integrity of the encrypted keys but this is
+ * what Apple chose to do).
+ */
+void 
+pw_bitshift(pw)
+char *pw;
+{
+    int i;
+    unsigned char pws[8];
+
+    /* key is 0 padded */
+    for (i = 0; i < 8; i++)
+       pws[i] = 0;
+
+    /* parity bit is always zero (this seem bogus) */
+    for (i = 0; i < 8 && pw[i]; i++)
+       pws[i] = pw[i] << 1;
+
+    bcopy(pws, pw, 8);
+}
+
+
+static void
+arap_verify(data)
+struct authen_data *data;
+{
+    char nas_chal[8], r_chal[8], r_resp[8], secret[8];
+    char *name, *cfg_secret, *exp_date, *p;
+
+    if (!(char) data->NAS_id->username[0]) {
+       report(LOG_ERR, "%s %s: no username for arap_verify",
+              session.peer, session.port);
+       data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+       return;
+    }
+    name = data->NAS_id->username;
+
+    bcopy(data->client_data, nas_chal, 8);
+    bcopy(data->client_data + 8, r_chal, 8);
+    bcopy(data->client_data + 8 + 8, r_resp, 8);
+
+    /* Assume failure */
+    data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+
+    /* Get the secret */
+    cfg_secret = cfg_get_arap_secret(name, TAC_PLUS_RECURSE);
+
+    /* If there is no arap password for this user, see if there is a global
+     * password for her that we can use */
+    if (!cfg_secret) {
+       cfg_secret = cfg_get_global_secret(name, TAC_PLUS_RECURSE);
+    }
+    if (!cfg_secret) {
+       /* No secret. Fail */
+       if (debug & DEBUG_AUTHEN_FLAG) {
+           report(LOG_DEBUG, "%s %s: No arap or global secret for %s",
+                  session.peer, session.port, name);
+       }
+       data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+       return;
+    }
+    p = tac_find_substring("cleartext ", cfg_secret);
+    if (!p) {
+       report(LOG_ERR, "%s %s: %s arap secret %s is not cleartext",
+              session.peer, session.port, name, cfg_secret);
+       data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+       return;
+    }
+    /* need to allocate 8 bytes for secret, even if it's actually shorter */
+    bzero(secret, sizeof(secret));
+    strcpy(secret, p);
+
+    pw_bitshift(secret);
+
+#ifdef ARAP_DES
+    des_init(0);
+    des_setkey(secret);
+    des_endes(nas_chal);
+    des_done();
+#endif                         /* ARAP_DES */
+
+    /* Now compare the remote's response value with the just calculated one
+     * value.  If they are equal, it's a pass, otherwise it's a failure */
+
+    if (bcmp(nas_chal, r_resp, 8)) {
+       data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+    } else {
+       data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
+    }
+
+#ifdef ARAP_DES
+    /* Now calculate the response to the remote's challenge */
+    des_init(0);
+    des_setkey(secret);
+    des_endes(r_chal);
+    des_done();
+#endif                         /* ARAP_DES */
+
+    data->server_data = tac_malloc(8);
+    data->server_dlen = 8;
+    bcopy(r_chal, data->server_data, 8);
+
+    exp_date = cfg_get_expires(name, TAC_PLUS_RECURSE);
+    set_expiration_status(exp_date, data);
+}
+
+
+#ifdef MSCHAP
+
+/* Following code is added for ms-chap */
+static void
+mschap_desencrypt(clear, str, cypher)
+char *clear;
+unsigned char *str;
+unsigned char *cypher;
+{
+    unsigned char key[8];
+
+    /* des_state_type *des_state = NULL; */
+
+    memset(key, 0, 8);
+
+    /* Copy the key inserting parity bits */
+
+#ifdef old
+    /* This method makes it obvious what we are doing */
+
+#define getbit(bit,array) ((array[bit/8] & (1 <<  (7-(bit%8)))) !=0)
+#define setbit(bit,array) (array[bit/8] |= (1 <<  (7-(bit%8))))
+
+    {
+       int i, j;
+
+       j = 0;
+       for (i = 0; i < 56; i++) {
+           if (i && (i % 7 == 0)) {
+               j++;
+           }
+           if (getbit(i, str))
+               setbit(j, key);
+           j++;
+       }
+    }
+#else
+    /* this is a little more cryptic, but faster basicly we are insering a
+     * bit into the stream after every 7 bits */
+
+    key[0] = ((str[0] & 0xfe));
+    key[1] = ((str[0] & 0x01) << 7) | ((str[1] & 0x0fc) >> 1);
+    key[2] = ((str[1] & 0x03) << 6) | ((str[2] & 0x0f8) >> 2);
+    key[3] = ((str[2] & 0x07) << 5) | ((str[3] & 0x0f0) >> 3);
+    key[4] = ((str[3] & 0x0f) << 4) | ((str[4] & 0x0e0) >> 4);
+    key[5] = ((str[4] & 0x1f) << 3) | ((str[5] & 0x0c0) >> 5);
+    key[6] = ((str[5] & 0x3f) << 2) | ((str[6] & 0x080) >> 6);
+    key[7] = ((str[6] & 0x7f) << 1);
+
+#endif
+
+    /* copy clear to cypher, cause our des encrypts in place */
+    memcpy(cypher, clear, 8);
+/*
+    des_init(0,&des_state);
+    des_setkey(des_state,key);
+    des_endes(des_state,cypher);
+    des_done(des_state);
+*/
+#ifdef MSCHAP_DES
+    des_init(0);
+    des_setkey(key);
+    des_endes(cypher);
+    des_done();
+#endif                         /* MSCHAP_DES */
+}
+
+
+static void
+mschap_deshash(clear, cypher)
+char *clear;
+char *cypher;
+{
+    mschap_desencrypt(MSCHAP_KEY, clear, cypher);
+}
+
+
+static void
+mschap_lmpasswordhash(password, passwordhash)
+char *password;
+char *passwordhash;
+{
+    unsigned char upassword[15];
+    int i = 0;
+
+    memset(upassword, 0, 15);
+    while (password[i]) {
+       upassword[i] = toupper(password[i]);
+       i++;
+    };
+
+    mschap_deshash(&upassword[0], &passwordhash[0]);
+    mschap_deshash(&upassword[7], &passwordhash[8]);
+}
+
+
+static void
+mschap_challengeresponse(challenge, passwordhash, response)
+char *challenge;
+char *passwordhash;
+char *response;
+{
+    char zpasswordhash[21];
+
+    memset(zpasswordhash, 0, 21);
+    memcpy(zpasswordhash, passwordhash, 16);
+
+    mschap_desencrypt(challenge, &zpasswordhash[0], &response[0]);
+    mschap_desencrypt(challenge, &zpasswordhash[7], &response[8]);
+    mschap_desencrypt(challenge, &zpasswordhash[14], &response[16]);
+}
+
+
+void
+mschap_lmchallengeresponse(challenge, password, response)
+char *challenge;
+char *password;
+char *response;
+{
+    char passwordhash[16];
+
+    mschap_lmpasswordhash(password, passwordhash);
+    mschap_challengeresponse(challenge, passwordhash, response);
+}
+
+
+static int
+mschap_unicode_len(password)
+char *password;
+{
+    int i;
+
+    i = 0;
+    while ((password[i] || password[i + 1]) && (i < 512)) {
+       i += 2;
+    }
+
+    return i;
+}
+
+
+static void
+mschap_ntpasswordhash(password, passwordhash)
+char *password;
+char *passwordhash;
+{
+    MD4_CTX context;
+    int i;
+    char *cp;
+    unsigned char unicode_password[512];
+
+    memset(unicode_password, 0, 512);
+
+    i = 0;
+    memset(unicode_password, 0, 512);
+    cp = password;
+    while (*cp) {
+       unicode_password[i++] = *cp++;
+       unicode_password[i++] = '\0';
+    }
+
+    MD4Init(&context);
+    MD4Update(&context, unicode_password,
+             mschap_unicode_len(unicode_password));
+    MD4Final(passwordhash, &context);
+}
+
+
+void
+mschap_ntchallengeresponse(challenge,
+                          password,
+                          response)
+char *challenge;
+char *password;
+char *response;
+{
+    char passwordhash[16];
+
+    mschap_ntpasswordhash(password, passwordhash);
+    mschap_challengeresponse(challenge, passwordhash, response);
+}
+
+
+/* Verify the challenge and id against the response by looking up the
+ * ms-chap secret in the config file. Set data->status appropriately.
+ */
+static void
+mschap_verify(data)
+struct authen_data *data;
+{
+    char *name, *secret, *chal, *resp;
+    char *exp_date, *p;
+    char id;
+    int chal_len;
+    char lmresponse[24];
+    char ntresponse[24];
+    int bcmp_status;
+
+    if (!(char) data->NAS_id->username[0]) {
+       report(LOG_ERR, "%s %s: no username for mschap_verify",
+              session.peer, session.port);
+       data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+       return;
+    }
+    name = data->NAS_id->username;
+
+    id = data->client_data[0];
+
+    chal_len = data->client_dlen - 1 - MSCHAP_DIGEST_LEN;
+    if (data->client_dlen <= (MSCHAP_DIGEST_LEN + 2)) {
+       /* Invalid packet or NULL challenge */
+       data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+       return;
+    }
+    if (debug & DEBUG_AUTHEN_FLAG) {
+       report(LOG_DEBUG, "%s %s: ms-chap user=%s, id=%d chal_len=%d",
+              session.peer, session.port, name, (int) id, chal_len);
+
+       /* report_hex(LOG_DEBUG, (u_char *)data->client_data + 1, chal_len); */
+    }
+    /* Assume failure */
+    data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+
+    /* Get the secret */
+    secret = cfg_get_mschap_secret(name, TAC_PLUS_RECURSE);
+
+    /* If there is no ms-chap password for this user, see if there is a
+     * global password for her that we can use */
+    if (!secret) {
+       secret = cfg_get_global_secret(name, TAC_PLUS_RECURSE);
+    }
+    if (!secret) {
+       /* No secret. Fail */
+       if (debug & DEBUG_AUTHEN_FLAG) {
+           report(LOG_DEBUG, "%s %s: No ms-chap or global secret for %s",
+                  session.peer, session.port, name);
+       }
+       data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+       return;
+    }
+    p = tac_find_substring("cleartext ", secret);
+    if (!p) {
+       report(LOG_ERR, "%s %s: %s ms-chap secret %s is not cleartext",
+              session.peer, session.port, name, secret);
+       data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+       return;
+    }
+    secret = p;
+
+    /* We now have the secret, the id, and the challenge value. Put them all
+     * together, and run them through the MD4 digest algorithm. */
+    chal = data->client_data + 1;
+    resp = data->client_data + 1 + chal_len;
+
+    mschap_lmchallengeresponse(chal, secret, lmresponse);
+    mschap_ntchallengeresponse(chal, secret, ntresponse);
+
+    /* Now compare the received response value with the just calculated
+     * digest value.  If they are equal, it's a pass, otherwise it's a
+     * failure */
+    if (resp[48])
+       bcmp_status = bcmp(ntresponse, &resp[24], 24);
+    else
+       bcmp_status = bcmp(lmresponse, &resp[0], 24);
+
+    if (bcmp_status) {
+       data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+    } else {
+       data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
+    }
+
+    exp_date = cfg_get_expires(name, TAC_PLUS_RECURSE);
+    set_expiration_status(exp_date, data);
+}
+
+#endif /* MSCHAP */
diff --git a/default_v0_fn.c b/default_v0_fn.c
new file mode 100644 (file)
index 0000000..96e0c1d
--- /dev/null
@@ -0,0 +1,188 @@
+/*
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+#include "tac_plus.h"
+#include "expire.h"
+
+/* internal state variables */
+#define STATE_AUTHEN_START   0 /* no requests issued */
+#define STATE_AUTHEN_GETUSER 1 /* username has been requested */
+#define STATE_AUTHEN_GETPASS 2 /* password has been requested */
+
+struct private_data {
+    char password[MAX_PASSWD_LEN + 1];
+    int state;
+};
+
+/*
+ * Default tacacs login authentication function. Wants a username
+ * and a password, and tries to verify them.
+ *
+ * Choose_authen will ensure that we already have a username before this
+ * gets called.
+ *
+ * We will query for a password and keep it in the method_data.
+ *
+ * Any strings returned via pointers in authen_data must come from the
+ * heap. They will get freed by the caller.
+ *
+ * Return 0 if data->status is valid, otherwise 1
+ */
+
+int
+default_v0_fn(data)
+struct authen_data *data;
+{
+    char *name, *passwd;
+    struct private_data *p;
+    char *prompt;
+
+    p = (struct private_data *) data->method_data;
+
+    /* An abort has been received. Clean up and return */
+    if (data->flags & TAC_PLUS_CONTINUE_FLAG_ABORT) {
+       if (data->method_data)
+           free(data->method_data);
+       data->method_data = NULL;
+       return (1);
+    }
+    /* Initialise method_data if first time through */
+    if (!p) {
+       p = (struct private_data *) tac_malloc(sizeof(struct private_data));
+       bzero(p, sizeof(struct private_data));
+       data->method_data = p;
+       p->state = STATE_AUTHEN_START;
+    }
+
+    /* Unless we're enabling, we need a username */
+    if (data->service != TAC_PLUS_AUTHEN_SVC_ENABLE &&
+       !(char) data->NAS_id->username[0]) {
+       switch (p->state) {
+
+       case STATE_AUTHEN_GETUSER:
+           /* we have previously asked for a username but none came back.
+            * This is a gross error */
+           data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+           report(LOG_ERR, "%s: No username supplied after GETUSER",
+                  session.peer);
+           return (0);
+
+       case STATE_AUTHEN_START:
+           /* No username. Try requesting one */
+           data->status = TAC_PLUS_AUTHEN_STATUS_GETUSER;
+           if (data->service == TAC_PLUS_AUTHEN_SVC_LOGIN) {
+               prompt = "\nUser Access Verification\n\nUsername: ";
+           } else {
+               prompt = "Username: ";
+           }
+           data->server_msg = tac_strdup(prompt);
+           p->state = STATE_AUTHEN_GETUSER;
+           return (0);
+
+       default:
+           /* something awful has happened. Give up and die */
+           report(LOG_ERR, "%s: default_fn bad state %d", 
+                  session.peer, p->state);
+           return (1);
+       }
+    }
+
+    /* we now have a username if we needed one */
+    name = data->NAS_id->username;
+
+    /* Do we have a password? */
+    passwd = p->password;
+
+    if (!passwd[0]) {
+
+       /* no password yet. Either we need to ask for one and expect to get
+        * called again, or we asked but nothing came back, which is fatal */
+
+       switch (p->state) {
+       case STATE_AUTHEN_GETPASS:
+           /* We already asked for a password. This should be the reply */
+           strncpy(passwd, data->client_msg, MAX_PASSWD_LEN);
+           passwd[MAX_PASSWD_LEN + 1] = '\0';
+           break;
+
+       default:
+           data->flags = TAC_PLUS_AUTHEN_FLAG_NOECHO;
+           data->server_msg = tac_strdup("Password: ");
+           data->status = TAC_PLUS_AUTHEN_STATUS_GETPASS;
+           p->state = STATE_AUTHEN_GETPASS;
+           return (0);
+       }
+    }
+
+    /* We have a username and password. Try validating */
+
+    if (STREQ(name, DEFAULT_USERNAME)) {
+       /* Never authenticate this user. It's for authorization only */
+       data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+       if (debug) {
+           report(LOG_DEBUG, 
+                  "authentication query for '%s' %s from %s rejected",
+                  name && name[0] ? name : "unknown",
+                  session.port, session.peer);
+       }
+       return(0);
+    }
+
+    /* Assume the worst */
+    data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+
+    switch (data->service) {
+    case TAC_PLUS_AUTHEN_SVC_NASI:
+    case TAC_PLUS_AUTHEN_SVC_LOGIN:
+    case TAC_PLUS_AUTHEN_SVC_PPP:
+       verify(name, passwd, data, TAC_PLUS_RECURSE);
+       if (debug)
+           report(LOG_INFO, "login query for '%s' %s from %s %s",
+                  name && name[0] ? name : "unknown",
+                  data->NAS_id->NAS_port && data->NAS_id->NAS_port[0] ?
+                      data->NAS_id->NAS_port : "unknown",
+                  session.peer,
+                  (data->status == TAC_PLUS_AUTHEN_STATUS_PASS) ?
+                  "accepted" : "rejected");
+       break;
+
+    default:
+       data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+       report(LOG_ERR, "%s: Bogus service value %d from packet", 
+              session.peer, data->service);
+       break;
+    }
+
+    if (data->method_data)
+       free(data->method_data);
+    data->method_data = NULL;
+
+    switch (data->status) {
+    case TAC_PLUS_AUTHEN_STATUS_ERROR:
+    case TAC_PLUS_AUTHEN_STATUS_FAIL:
+    case TAC_PLUS_AUTHEN_STATUS_PASS:
+       return (0);
+    default:
+       report(LOG_ERR, "%s: default_v0_fn can't set status %d",
+              session.peer, data->status);
+       data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+       return (1);
+    }
+}
+
diff --git a/do_acct.c b/do_acct.c
new file mode 100644 (file)
index 0000000..dee8eaa
--- /dev/null
+++ b/do_acct.c
@@ -0,0 +1,310 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+#include "tac_plus.h"
+
+static int acctfd = 0;
+
+/* Make a acct entry into the accounting file for accounting. 
+   Return 1 on error  */
+
+static int
+acct_write(string)
+    char *string;
+{
+    if (write(acctfd, string, strlen(string)) != strlen(string)) {
+       report(LOG_ERR, "%s: couldn't write acct file %s %s",
+              session.peer,
+              session.acctfile, sys_errlist[errno]);
+       return(1);
+    }
+    
+    if (debug & DEBUG_ACCT_FLAG)
+       report(LOG_DEBUG, "'%s'", string);
+
+    return(0);
+}
+
+/* Write a string or "unknown" into the accounting file.
+   Return 1 on error  */
+static int
+acct_write_field(string)
+    char *string;
+{
+    if (string && string[0]) {
+       if (acct_write(string))
+           return(1);
+    } else {
+       if (acct_write("unknown"))
+           return(1);
+    }
+    return(0);
+}
+
+int
+do_acct(rec)
+struct acct_rec *rec;
+{
+    int i, errors;
+    time_t t = time(NULL);
+    char *ct = ctime(&t);
+
+    ct[24] = '\0';
+
+    if (!acctfd) {
+       acctfd = open(session.acctfile, O_CREAT | O_WRONLY | O_APPEND,0640);
+       if (acctfd < 0) {
+           report(LOG_ERR, "Can't open acct file %s -- %s",
+                  session.acctfile, sys_errlist[errno]);
+           return(1);
+       }
+    }
+   if (!tac_lockfd(session.acctfile, acctfd)) {
+       rec->admin_msg = tac_strdup("Cannot lock log file");
+       report(LOG_ERR, "%s: Cannot lock %s", 
+              session.peer, session.acctfile);
+       return(1);
+    }
+
+    errors = 0;
+
+    errors += acct_write(ct);
+    errors += acct_write("\t");
+
+    errors += acct_write_field(rec->identity->NAS_name);
+    errors += acct_write("\t");
+
+    errors += acct_write_field(rec->identity->username);
+    errors += acct_write("\t");
+
+    errors += acct_write_field(rec->identity->NAS_port);
+    errors += acct_write("\t");
+
+    errors += acct_write_field(rec->identity->NAC_address);
+    errors += acct_write("\t");
+
+    switch(rec->acct_type) {
+    case ACCT_TYPE_UPDATE:
+       errors += acct_write("update\t");
+       break;
+    case ACCT_TYPE_START:
+       errors += acct_write("start\t");
+       break;
+    case ACCT_TYPE_STOP:
+       errors += acct_write("stop\t");
+       break;
+    default:
+       errors += acct_write("unknown\t");
+       break;
+    }
+
+    for (i=0; i < rec->num_args; i++) {
+       errors += acct_write(rec->args[i]);
+       if (i < (rec->num_args-1)) 
+           errors += acct_write("\t");
+    }
+    errors += acct_write("\n");
+
+    close(acctfd);
+    acctfd = 0;
+
+    if (errors) {
+       return(1);
+    }
+    return (0);
+}
+
+int
+wtmp_entry (line, name, host, utime)
+    char *line, *name, *host;
+    time_t utime;
+{
+    struct utmp entry;
+
+    if (!wtmpfile) {
+       return(1);
+    }
+
+    bzero(&entry, sizeof entry);
+
+    if (strlen(line) < sizeof entry.ut_line)
+       strcpy(entry.ut_line, line);
+    else bcopy(line, entry.ut_line, sizeof entry.ut_line);
+
+    if (strlen(name) < sizeof entry.ut_name)
+       strcpy(entry.ut_name, name);
+    else bcopy(name, entry.ut_name, sizeof entry.ut_name);
+
+#ifndef SOLARIS
+    if (strlen(host) < sizeof entry.ut_host)
+       strcpy(entry.ut_host, host);
+    else bcopy(host, entry.ut_host, sizeof entry.ut_host);
+#endif
+    entry.ut_time = utime;
+
+    wtmpfd = open(wtmpfile, O_CREAT | O_WRONLY | O_APPEND | O_SYNC, 0644);     
+    if (wtmpfd < 0) {
+       report(LOG_ERR, "Can't open wtmp file %s -- %s",
+              wtmpfile, sys_errlist[errno]);
+       return(1);
+    }
+
+    if (!tac_lockfd(wtmpfile, wtmpfd)) {
+       report(LOG_ERR, "%s: Cannot lock %s", session.peer, wtmpfile);
+       return(1);
+    }
+
+    if (write(wtmpfd, &entry, sizeof entry) != (sizeof entry)) {
+       report(LOG_ERR, "%s: couldn't write wtmp file %s %s",
+              session.peer, wtmpfile, sys_errlist[errno]);
+       return(1);
+    } 
+
+    close(wtmpfd);
+
+    if (debug & DEBUG_ACCT_FLAG) {
+       report(LOG_DEBUG, "wtmp: %s, %s %s %d", line, name, host, utime);
+    }
+    
+    return(0);
+}
+
+char *
+find_attr_value (attr, args, cnt)
+    char *attr, **args;
+    int cnt;
+{
+    int i;
+
+    for (i=0; i < cnt; i++) {
+       if (!strncmp(attr, args[i], strlen(attr))) {
+           char *ptr;
+
+           for (ptr = args[i]; ptr && *ptr; ptr++) {
+               if ((*ptr == '*') || (*ptr == '=')) {
+                   return(ptr+1);
+               }
+           }
+           return(NULL);
+       }
+    }
+    return(NULL);
+}
+
+int
+do_wtmp(rec)
+    struct acct_rec *rec;
+{
+    time_t now = time(NULL);
+    char *service;
+    char *elapsed_time, *start_time;
+    time_t start_utime = 0, stop_utime = 0, elapsed_utime = 0;
+    
+
+    switch(rec->acct_type) {
+    case ACCT_TYPE_START:
+    case ACCT_TYPE_STOP:
+       break;
+
+    case ACCT_TYPE_UPDATE:
+    default:
+       return(0);
+    }
+
+    service = find_attr_value("service", rec->args, rec->num_args);
+
+    if (!service) {
+       /* An error */
+       return(1);
+    }
+
+    if (STREQ(service, "system")) {
+       if (rec->acct_type == ACCT_TYPE_START) {
+           /* A reload */
+           wtmp_entry("~", "", session.peer, now);
+       }
+       return(0);
+    }
+       
+    if (rec->acct_type != ACCT_TYPE_STOP) {
+       return(0);
+    }
+
+    /* 
+     * Since xtacacs logged start records containing the peer address
+     * for a connection, we have to generate them from T+ stop records.
+     * Might as well do this for exec records too.
+     */
+    
+    elapsed_time = find_attr_value("elapsed_time", rec->args, rec->num_args);
+
+    if (elapsed_time) {
+       elapsed_utime = strtol(elapsed_time, NULL, 10);
+    }
+
+    start_time = find_attr_value("start_time", rec->args, rec->num_args);
+
+    /* 
+     * Use the start_time if there is one. If not (e.g. the NAS may
+     * not know the time), assume the stop time is now, and calculate
+     * the rest
+     */
+
+    if (start_time) {
+       start_utime = strtol(start_time, NULL, 10);
+       stop_utime  = start_utime + elapsed_utime;
+    } else {
+       start_utime = now - elapsed_utime;
+       stop_utime  = now;
+    }  
+
+    if (STREQ(service, "slip") || STREQ(service, "ppp")) {
+       char *dest_addr = find_attr_value("addr", rec->args, rec->num_args);
+
+       /* The start record */
+       wtmp_entry(rec->identity->NAS_port,
+                  rec->identity->username,
+                  dest_addr,
+                  start_utime);
+
+       /* The stop record */
+       wtmp_entry(rec->identity->NAS_port,
+                  "",
+                  dest_addr,
+                  stop_utime);
+       return(0);
+    }
+
+    if (STREQ(service, "shell")) {
+       /* Start */
+       wtmp_entry(rec->identity->NAS_port,
+                  rec->identity->username,
+                  session.peer,
+                  start_utime);
+
+       /* Stop */
+       wtmp_entry(rec->identity->NAS_port,
+                  "",
+                  session.peer,
+                  stop_utime);
+       return(0);
+    }
+    return(0);
+}
diff --git a/do_author.c b/do_author.c
new file mode 100644 (file)
index 0000000..574736f
--- /dev/null
@@ -0,0 +1,1256 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+#include "tac_plus.h"
+#include "regexp.h"
+
+static int get_nas_svc();
+static int authorize_cmd();
+static int authorize_exec();
+static int authorize_svc();
+static void post_authorization();
+static int pre_authorization();
+
+/* Return 0 is data->status is valid */
+int
+do_author(data)
+struct author_data *data;
+{
+    char *username = data->id->username;
+    int status;
+    int svc;
+    char *cmd, *protocol, *svcname;
+#ifdef USE_PAM
+    char *pam_service= NULL;
+#endif
+    protocol = NULL;
+
+    data->status = AUTHOR_STATUS_FAIL; /* for safety */
+
+    data->output_args = NULL;
+    data->num_out_args = 0;
+
+    /* If this user doesn't exist in our configs, do the default */
+
+    if (!cfg_user_exists(username) && !cfg_user_exists(DEFAULT_USERNAME)) {
+       
+       if (cfg_no_user_permitted()) {
+           if (debug & DEBUG_AUTHOR_FLAG)
+               report(LOG_DEBUG, 
+                      "user '%s' or '%s' not found, permitted by default", 
+                      username, DEFAULT_USERNAME);
+
+           data->status = AUTHOR_STATUS_PASS_ADD;
+           data->output_args = NULL;
+           data->num_out_args = 0;
+           return (0);
+       }
+
+       if (debug & DEBUG_AUTHOR_FLAG)
+           report(LOG_DEBUG, 
+                  "user '%s' or '%s' not found, denied by default", 
+                  username, DEFAULT_USERNAME);
+       data->status = AUTHOR_STATUS_FAIL;
+       return (0);
+    }
+
+    if (!cfg_user_exists(username) && cfg_user_exists(DEFAULT_USERNAME)) {
+       if (debug & DEBUG_AUTHOR_FLAG) {
+           report(LOG_DEBUG, "Authorizing user '%s' instead of '%s'", 
+                  DEFAULT_USERNAME, username);
+       }
+       username = DEFAULT_USERNAME;
+    }
+
+    /* See if there's a program defined which will do authorization for us */
+    if (pre_authorization(username, data)) 
+       return(0);
+    
+    /* 
+     * Decide what kind of authorization request this is. Currently
+     * one of: exec, cmd, slip, arap, ppp or <string>
+     * 
+     * If it's a command typed to the exec, return its text in cmd.
+     * 
+     * If it's a ppp request, return the protocol name in protocol.
+     */
+
+    svc = get_nas_svc(data, &cmd, &protocol, &svcname);
+
+    if (!svc) {
+       /* if we can't identify the service in the request it's an error */
+       data->status = AUTHOR_STATUS_ERROR;
+       data->admin_msg =
+        tac_strdup("No identifiable service/protocol in authorization request");
+       if (debug & DEBUG_AUTHOR_FLAG) {
+           report(LOG_DEBUG, "user %s %s", username, data->admin_msg);
+       }
+       return (0);
+    }
+
+    if (debug & DEBUG_AUTHOR_FLAG)
+       report(LOG_DEBUG, "user '%s' found", username);
+
+#ifdef MAXSESS
+    /* Never permit if they're going over their session limit */
+    switch (svc) {
+    case N_svc_arap:
+    case N_svc_ppp:
+    case N_svc_slip:
+    case N_svc_exec:
+/*    case N_svc: */
+       if (maxsess_check_count(username, data)) {
+           return(0);
+       }
+
+    default:
+       break;
+    }
+#endif /* MAXSESS */
+
+#ifdef USE_PAM
+       /* Check  PAM Authorization */
+     switch (svc) {
+    case N_svc_ppp:
+    case N_svc_exec:
+if (pam_service=cfg_get_pam_service(data->id->username,TAC_PLUS_RECURSE) ) {
+
+       if (debug & DEBUG_AUTHOR_FLAG)
+               report(LOG_DEBUG, "PAM Authorization begin for user %s",data->id->username);
+       if(tac_pam_authorization(data->id->username,data,pam_service))
+       {
+               if (debug & DEBUG_AUTHOR_FLAG)
+                       report(LOG_DEBUG, "PAM Authorization Fail");
+               return(0);
+       }
+} /* Pam_service */
+    default:
+       break;
+    }
+#endif 
+
+    switch(svc) {
+    default:
+       report(LOG_ERR, "%s: Bad service type %d", session.peer, svc);
+       data->status = AUTHOR_STATUS_FAIL;
+       return(0);
+
+    case N_svc_cmd:
+       /* A command authorisation request */
+       status = authorize_cmd(username, cmd, data);
+       break;
+    case N_svc_exec:
+       if (authorize_exec(username, data)) 
+           return(0);
+       /* FALLTHRU */
+
+    case N_svc_arap:
+    case N_svc_ppp:
+    case N_svc_slip:
+       status = authorize_svc(username, svc, protocol, NULL, data);
+       break;
+
+    case N_svc:
+       status = authorize_svc(username, svc, protocol, svcname, data);
+       break;
+    }
+
+    post_authorization(username, data);
+    return(status);    
+}
+
+/* If an before-authorization program has been specified, call it. 
+
+   A return value of 1 means no further authorization is required
+*/
+
+static int
+pre_authorization(username, data) 
+char *username;
+struct author_data *data;
+{
+    int status;
+    char **out_args;
+    int out_cnt, i;
+    char *cmd;
+    char error_str[255];
+    int error_len = 255;
+
+    out_cnt = 0;
+    out_args = NULL;
+
+    /* If a before-authorization program exists, call it to see how to
+       proceed */
+
+    cmd = cfg_get_pvalue(username, TAC_IS_USER, 
+                        S_before, TAC_PLUS_RECURSE);
+    if (!cmd) 
+       return(0);
+
+    if (debug & DEBUG_AUTHOR_FLAG)
+       report(LOG_DEBUG, "Before authorization call: %s", cmd);
+
+    status = call_pre_process(cmd, data, &out_args, &out_cnt, error_str,
+                             error_len);
+
+    switch (status) {
+    default:
+       if (debug & DEBUG_AUTHOR_FLAG)
+           report(LOG_DEBUG, "cmd %s returns %d (unrecognised value)", 
+                  cmd, status);
+
+       data->status = AUTHOR_STATUS_ERROR;
+       data->admin_msg = 
+           tac_strdup("Illegal return status from pre-authorization command");
+       data->msg = tac_strdup(error_str);
+       data->num_out_args = 0;
+       data->output_args = NULL;
+       /* throw away out_args */
+       for(i=0; i < out_cnt; i++) {
+           free(out_args[i]);
+       }
+       if (out_args) {
+           free(out_args);
+       }
+       return(1);
+
+    case 0: /* Permit */
+       if (debug & DEBUG_AUTHOR_FLAG)
+           report(LOG_DEBUG, "cmd %s returns 0 (unconditional permit)", cmd);
+
+       data->status = AUTHOR_STATUS_PASS_ADD;
+       data->num_out_args = 0;
+       data->output_args = NULL;
+
+       /* throw away out_args */
+       for(i=0; i < out_cnt; i++) {
+           free(out_args[i]);
+       }
+       if (out_args) {
+           free(out_args);
+       }
+       return(1);
+           
+    case 1: /* Deny */
+       if (debug & DEBUG_AUTHOR_FLAG)
+           report(LOG_DEBUG, "cmd %s returns %d (unconditional deny)", 
+                  cmd, status);
+
+       data->status = AUTHOR_STATUS_FAIL;
+       data->msg = tac_strdup(error_str);
+       data->num_out_args = 0;
+       data->output_args = NULL;
+
+       /* throw away out_args */
+       for(i=0; i < out_cnt; i++) {
+           free(out_args[i]);
+       }
+       if (out_args) {
+           free(out_args);
+       }
+       return(1);
+
+    case 2: /* Use replacement AV pairs from program as final result */
+       if (debug & DEBUG_AUTHOR_FLAG) {
+           report(LOG_DEBUG, "cmd %s returns %d (permitted, args replaced)", 
+                  cmd, status);
+           for(i=0; i < out_cnt; i++)
+               report(LOG_DEBUG, "%s", out_args[i]); 
+       }
+
+       /* and install the new set of AV pairs as output */
+       data->output_args = out_args;
+       data->num_out_args = out_cnt;
+       data->status = AUTHOR_STATUS_PASS_REPL;
+       return(1); /* no more processing required */
+
+    case 3: /* deny, but return attributes and server-msg to NAS */
+       if (debug & DEBUG_AUTHOR_FLAG) {
+           report(LOG_DEBUG, "cmd %s returns %d (deny, args replaced)", 
+                  cmd, status);
+           for(i=0; i < out_cnt; i++)
+               report(LOG_DEBUG, "%s", out_args[i]); 
+       }
+
+       /* and install the new set of AV pairs as output */
+       data->output_args = out_args;
+       data->num_out_args = out_cnt;
+       data->msg = tac_strdup(error_str);
+       data->status = AUTHOR_STATUS_FAIL;
+       return(1); /* no more processing required */
+    }
+}
+
+/* If an after-authorization program has been specified, call it. It
+   can rewrite the output arguments in the authorization data, or
+   change the authorization status by calling an external program.
+*/
+
+static void
+post_authorization(username, data) 
+    char *username;
+    struct author_data *data;
+{
+    char **out_args;
+    int out_cnt, i;
+    int status;
+    char *after = cfg_get_pvalue(username, TAC_IS_USER, 
+                               S_after, TAC_PLUS_RECURSE);
+    if (!after) 
+       return;
+
+    if (debug & DEBUG_AUTHOR_FLAG)
+       report(LOG_DEBUG, "After authorization call: %s", after);
+
+    status = call_post_process(after, data, &out_args, &out_cnt);
+
+    if (status != 2) {
+       /* throw away out_args */
+       for(i=0; i < out_cnt; i++) {
+           free(out_args[i]);
+       }
+       free(out_args);
+    }
+
+    switch (status) {
+    default:
+       if (debug & DEBUG_AUTHOR_FLAG)
+           report(LOG_DEBUG, 
+                  "cmd %s returns %d (Error)", after, status);
+
+       data->status = AUTHOR_STATUS_ERROR;
+       return;
+
+    case 0:                            /* Permit */
+       if (debug & DEBUG_AUTHOR_FLAG)
+           report(LOG_DEBUG, "cmd %s returns 0 (no change)", after);
+       return;
+           
+    case 1:                            /* Deny */
+       if (debug & DEBUG_AUTHOR_FLAG)
+           report(LOG_DEBUG, "cmd %s returns %d (unconditional deny)", 
+                  after, status);
+
+       data->status = AUTHOR_STATUS_FAIL;
+       return;
+
+    case 2:
+       /* Use replacement AV pairs from program */
+       if (debug & DEBUG_AUTHOR_FLAG)
+           report(LOG_DEBUG, "cmd %s returns 2 (replace & continue)", 
+                  after);
+
+       /* Free any existing AV output pairs */
+       if (data->num_out_args) {
+           for(i=0; i < data->num_out_args; i++) {
+               free(data->output_args[i]);
+           }
+           free(data->output_args);
+           data->output_args = NULL;
+       }
+
+       if (debug & DEBUG_AUTHOR_FLAG) {
+           report(LOG_DEBUG, "status is now AUTHOR_STATUS_PASS_REPL");
+       }
+
+       data->status = AUTHOR_STATUS_PASS_REPL;
+       data->output_args = out_args;
+       data->num_out_args = out_cnt;
+       return;
+    }
+}
+
+
+/* Return a pointer to the value part of an attr=value string */
+static char *
+value(s)
+char *s;
+{
+    while (*s && *s != '=' && *s != '*')
+       s++;
+    if (*s)
+       return (++s);
+    return (NULL);
+}
+
+/* Reassemble the command arguments as typed by the user, out of the
+   array of args we received. Return "" if there are no arguments */
+
+static char *
+assemble_args(data)
+struct author_data *data;
+{
+    char *buf;
+    int i;
+    char *nas_arg, *v;
+    int len;
+
+    len = 0;
+    for (i = 0; i < data->num_in_args; i++) {
+       nas_arg = data->input_args[i];
+       if (strncmp(nas_arg, "cmd-arg", strlen("cmd-arg"))==0)
+           len += strlen(value(nas_arg)) + 1;
+    }
+
+    if (len <= 0) {
+       return(tac_strdup(""));
+    }
+
+    buf = tac_malloc(len);
+    buf[0] = '\0';
+
+    for (i = 0; i < data->num_in_args; i++) {
+       nas_arg = data->input_args[i];
+       if (strncmp(nas_arg, "cmd-arg", strlen("cmd-arg")))
+           continue;
+
+       v = value(nas_arg);
+       if (!v) {
+           free(buf);
+           return (NULL);
+       }
+       strcat(buf, v);
+       if (i < (data->num_in_args - 1))
+           strcat(buf, " ");
+    }
+    return (buf);
+}
+
+
+/* See if an exec is authorized. Either the user has explicitly
+   authorized the exec, or she has authorized some commands (which
+   implicitly authorizes an exec), or the default is permit.
+
+   If she has explicitly authorized an exec, we need to process its
+   attribute=value pairs. We indicate this by returning zero to the
+   caller.
+
+   Otherwise, we return 1, indicating no further processing is
+   required for this request. */
+
+static int
+authorize_exec(user, data)
+char *user;
+struct author_data *data;
+{
+    NODE *svc;
+
+    if (debug & DEBUG_AUTHOR_FLAG) 
+       report(LOG_DEBUG, "exec authorization request for %s", user);
+
+    /* Is an exec explicitly configured? If so, return 0 so we know to
+       process its attributes */
+
+    svc = cfg_get_svc_node(user, N_svc_exec, NULL, NULL, TAC_PLUS_RECURSE);
+    if (svc) {
+       if (debug & DEBUG_AUTHOR_FLAG) 
+           report(LOG_DEBUG, "exec is explicitly permitted by line %d",
+                  svc->line);
+       return (0);
+    }
+
+    /* No exec is configured. Are any commands configured? */
+    svc = cfg_get_svc_node(user, N_svc_cmd, NULL, NULL, TAC_PLUS_RECURSE);
+    if (svc) {
+       if (debug & DEBUG_AUTHOR_FLAG) 
+           report(LOG_DEBUG, "exec permitted because commands are configured");
+
+       data->status = AUTHOR_STATUS_PASS_ADD;
+       data->output_args = NULL;
+       data->num_out_args = 0;
+       return (1);
+    }
+
+    /* No exec or commands configured. What's the default? */
+    if (cfg_user_svc_default_is_permit(user)) {
+
+       if (debug & DEBUG_AUTHOR_FLAG) 
+           report(LOG_DEBUG, "exec permitted by default");
+
+       data->status = AUTHOR_STATUS_PASS_ADD;
+       data->output_args = NULL;
+       data->num_out_args = 0;
+       return (1);
+    }
+
+    if (debug & DEBUG_AUTHOR_FLAG) 
+       report(LOG_DEBUG, "exec denied by default");
+
+    data->status = AUTHOR_STATUS_FAIL;
+    data->num_out_args = 0;
+    return(1);
+}
+
+/* Is an exec command authorized per our database(s)?  
+   Return 0 if status is valid */
+
+static int
+authorize_cmd(user, cmd, data)
+char *user, *cmd;
+struct author_data *data;
+{
+    NODE *node;
+    char *args;
+    int match;
+
+    args = assemble_args(data);
+
+    if (!cmd) {
+       data->status = AUTHOR_STATUS_ERROR;
+       data->admin_msg = tac_strdup("No command found");
+       report(LOG_ERR, "%s: %s %s", session.peer, cmd, data->admin_msg);
+       data->num_out_args = 0;
+       return (0);
+    }
+
+    node = cfg_get_cmd_node(user, cmd, TAC_PLUS_RECURSE);
+
+    /* The command does not exist. Do the default */
+    if (!node) {
+
+       if (cfg_user_svc_default_is_permit(user)) {
+
+           if (debug & DEBUG_AUTHOR_FLAG) 
+               report(LOG_DEBUG, "cmd %s does not exist, permitted by default",
+                      cmd);
+           
+           data->status = AUTHOR_STATUS_PASS_ADD;
+           data->num_out_args = 0;
+           if (args)
+               free(args);
+           return(0);
+       }
+
+       if (debug & DEBUG_AUTHOR_FLAG) 
+           report(LOG_DEBUG, "cmd %s does not exist, denied by default",
+                  cmd);
+
+       data->status = AUTHOR_STATUS_FAIL;
+       data->num_out_args = 0;
+       if (args)
+           free(args);
+       return(0);
+    }
+
+    /* The command exists. The default if nothing matches is DENY */
+    data->status = AUTHOR_STATUS_FAIL;
+    data->num_out_args = 0;
+
+    for (node=node->value1; node && args; node = node->next) {
+       match = regexec((regexp *) node->value1, args);
+
+       if (debug & DEBUG_AUTHOR_FLAG) {
+           report(LOG_INFO, "line %d compare %s %s '%s' & '%s' %smatch",
+                  node->line, cmd,
+                  node->type == N_permit ? "permit" : "deny",
+                  node->value, args, (match ? "" : "no "));
+       }
+
+       if (!match)
+           continue;
+
+       switch (node->type) {
+       case N_permit:
+           if (debug & DEBUG_AUTHOR_FLAG) {
+               report(LOG_DEBUG, "%s %s permitted by line %d", 
+                      cmd, args, node->line);
+           }
+           data->status = AUTHOR_STATUS_PASS_ADD;
+           data->num_out_args = 0;
+           break;
+       case N_deny:
+           if (debug & DEBUG_AUTHOR_FLAG) {
+               report(LOG_DEBUG, "%s %s denied by line %d", 
+                      cmd, args, node->line);
+           }
+           data->status = AUTHOR_STATUS_FAIL;
+           data->num_out_args = 0;
+           break;
+       default:
+           data->status = AUTHOR_STATUS_ERROR;
+           data->admin_msg = tac_strdup("Server error illegal configuration node");
+           report(LOG_ERR, "%s: %s %s %s", 
+                  session.peer, cmd, args, data->admin_msg);
+           break;
+       }
+       if (args)
+           free(args);
+       args = NULL;
+       return (0);
+    }
+    if (args)
+       free(args);
+    return (0);
+}
+
+static int
+is_separator(ch)
+char ch;
+{
+    return (ch == '=' || ch == '*');
+}
+
+/* check an attr=value pair for well-formedness */
+static int
+arg_ok(arg)
+char *arg;
+{
+    char *p = arg;
+
+    /* It must contain an attribute */
+    if (!*p)
+       return(0);
+
+    for(p=arg; *p; p++) {
+       if (is_separator(*p)) {
+           if (p == arg) /* no attribute */
+               return(0);
+           return(1);
+       }
+    }
+    /* no separator */
+    return(0);
+}
+
+
+/* return 1 if attrs match, 0 otherwise */
+static int
+match_attrs(nas_arg, server_arg)
+char *nas_arg, *server_arg;
+{
+    while (*nas_arg && *server_arg) {
+       if (is_separator(*nas_arg) && is_separator(*server_arg)) {
+           return (1);
+       }
+       if (*nas_arg != *server_arg)
+           return (0);
+       nas_arg++;
+       server_arg++;
+    }
+    return (0);
+}
+
+/* return 1 if values match, 0 otherwise */
+static int
+match_values(nas_arg, server_arg)
+char *nas_arg, *server_arg;
+{
+    while (*nas_arg && 
+          *server_arg &&
+          !is_separator(*nas_arg)) {
+       nas_arg++;
+       server_arg++;
+    }
+
+    if (!*nas_arg)
+       return(0);
+
+    /* skip separator */
+    nas_arg++;
+    if (*server_arg)
+       server_arg++;
+    
+    /* compare values */
+    return(STREQ(nas_arg, server_arg));
+}
+
+/* Return 1 if arg is mandatory, 0 otherwise */
+static int
+mandatory(arg)
+char *arg;
+{
+    char *p = arg;
+
+    while (*p && !is_separator(*p))
+       p++;
+
+    /* if we're not at the end, this must be the separator */
+    if (*p && !is_separator(*p)) {
+       report(LOG_ERR, "%s: Error on arg %s cannot find separator", 
+              session.peer, arg);
+       return (0);
+    }
+    return (*p == '=');
+}
+
+static int
+optional(arg)
+char *arg;
+{
+    return (!mandatory(arg));
+}
+
+/* PPP-LCP requests are a special case. If they are not explicitly
+   configured, but there are other ppp services explicitly configured,
+   we admit (return 0) any PPP-LCP request */
+
+static int 
+ppp_lcp_allowed(svc, protocol, user)
+    int svc;
+    char *user, *protocol;
+{
+    /* This is not a ppp/lcp request. Just Say No */
+    if (!(svc == N_svc_ppp &&
+         protocol &&
+         STREQ(protocol, "lcp")))
+       return(0);
+
+    /* It is an LCP request. Are there PPP services configured */
+    if (cfg_ppp_is_configured(user, TAC_PLUS_RECURSE)) {
+       if (debug & DEBUG_AUTHOR_FLAG) {
+           report(LOG_DEBUG, 
+                  "ppp/lcp request permitted (ppp is configured for %s)",
+                  user);
+       }
+       return(1);
+    }
+    
+    /* It is an LCP request but no PPP services are configured */
+    if (debug & DEBUG_AUTHOR_FLAG) {
+       report(LOG_DEBUG, "ppp/lcp request denied (ppp not configured for %s)",
+              user);
+    }
+    return(0);
+}
+
+/* Return 0 means data->status is valid */
+static int
+authorize_svc(user, svc, protocol, svcname, data)
+    char *user;
+    int svc;
+    char *svcname;
+    struct author_data *data;
+    char *protocol; /* valid only if svc == ppp */
+{
+    int max_args;
+    char **out_args, **outp;
+    char *nas_arg, *cfg_arg;
+    int i, j;
+    char **cfg_args;
+    char **cfg_argp;
+    int deny_by_default;
+    NODE *node;
+
+    int replaced = 0;
+    int added = 0;
+    int cfg_cnt;
+
+    /* Does this service exist? */
+    node = cfg_get_svc_node(user, svc, protocol, svcname, TAC_PLUS_RECURSE);
+
+    if (!node) {
+       /* Service not found. If the default is permit, or this is an
+          PPP/LCP request and other ppp services are configured,
+          we'll allow it. */
+
+       if (cfg_user_svc_default_is_permit(user)) {
+
+           if (debug & DEBUG_AUTHOR_FLAG)
+               report(LOG_DEBUG, 
+              "svc=%s protocol=%s svcname=%s not found, permitted by default", 
+                      cfg_nodestring(svc), 
+                      protocol ? protocol : "",
+                      svcname ? svcname : "");
+
+           data->status = AUTHOR_STATUS_PASS_ADD;
+           data->num_out_args = 0;
+           data->output_args = NULL;
+           return(0);
+       }
+
+       if (ppp_lcp_allowed(svc, protocol, user)) {
+           data->status = AUTHOR_STATUS_PASS_ADD;
+           data->num_out_args = 0;
+           data->output_args = NULL;
+           return(0);
+       }
+
+       if (debug & DEBUG_AUTHOR_FLAG)
+           report(LOG_DEBUG, "svc=%s protocol=%s not found, denied by default",
+                  cfg_nodestring(svc), protocol ? protocol : "");
+
+       data->status = AUTHOR_STATUS_FAIL;
+       data->num_out_args = 0;
+       data->output_args = NULL;
+       return(0);
+    }
+    
+    /* Get server args configured in the config file. */
+    cfg_args = cfg_get_svc_attrs(node, &deny_by_default);
+
+    /* Check the nas args for well-formedness */
+    for (i = 0; i < data->num_in_args; i++) {
+       if (!arg_ok(data->input_args[i])) {
+           char buf[MAX_INPUT_LINE_LEN+50];
+           sprintf(buf, "Illegal arg %s from NAS", data->input_args[i]);
+           data->status = AUTHOR_STATUS_ERROR;
+           data->admin_msg = tac_strdup(buf);
+           report(LOG_ERR, "%s: Error %s", session.peer, buf);
+
+           /* free any server arguments */
+           for(cfg_argp = cfg_args; cfg_args && *cfg_argp; cfg_argp++)
+               free(*cfg_argp);
+           free(cfg_args);
+           return (0);
+       }
+    }
+
+    /* How many configured AV pairs are there ? */
+    for (cfg_cnt = 0; cfg_args && cfg_args[cfg_cnt];)
+       cfg_cnt++;
+
+    /* Allocate space for in + out args */
+    max_args = cfg_cnt + data->num_in_args;
+    out_args = (char **) tac_malloc((max_args + 1) * sizeof(char *));
+    outp = out_args;
+    data->num_out_args = 0;
+
+    bzero(out_args, (max_args + 1) * sizeof(char *));
+
+    for (i = 0; i < data->num_in_args; i++) {
+       nas_arg = data->input_args[i];
+
+       /* always pass these pairs through unchanged */
+       if (match_attrs(nas_arg, "service=") ||
+           match_attrs(nas_arg, "protocol=") ||
+           match_attrs(nas_arg, "cmd=")) {
+
+           if (debug & DEBUG_AUTHOR_FLAG) {
+               report(LOG_DEBUG, "nas:%s (passed thru)", nas_arg);
+           }
+           *outp++ = tac_strdup(nas_arg);
+           data->num_out_args++;
+           continue;
+       }
+
+       /* NAS AV pair is mandatory */
+       if (mandatory(nas_arg)) {
+
+           /* a). look for an exact attribute,value match in the daemon's
+              mandatory list. If found, add the AV pair to the output */
+
+           for (j = 0; j < cfg_cnt; j++) {
+               cfg_arg = cfg_args[j];
+               if (optional(cfg_arg))
+                   continue;
+
+               if (STREQ(nas_arg, cfg_arg)) {
+                   if (debug & DEBUG_AUTHOR_FLAG) {
+                       report(LOG_DEBUG, "nas:%s, svr:%s -> add %s (a)",
+                              nas_arg, cfg_arg, nas_arg);
+                   }
+                   *outp++ = tac_strdup(nas_arg);
+                   data->num_out_args++;
+                   goto next_nas_arg;
+               }
+           }
+
+           /* b). If an exact match doesn't exist, look in the
+              daemon's optional list for the first attribute
+              match. If found, add the NAS AV pair to the output */
+
+           for (j = 0; j < cfg_cnt; j++) {
+               cfg_arg = cfg_args[j];
+               if (mandatory(cfg_arg))
+                   continue;
+
+               if (match_attrs(nas_arg, cfg_arg)) {
+                   if (debug & DEBUG_AUTHOR_FLAG) {
+                       report(LOG_DEBUG, "nas:%s, svr:%s -> add %s (b)",
+                              nas_arg, cfg_arg, nas_arg);
+                   }
+                   *outp++ = tac_strdup(nas_arg);
+                   data->num_out_args++;
+                   goto next_nas_arg;
+               }
+           }
+
+           /* c). If no attribute match exists, deny the command if the
+              default is to deny */
+
+           if (deny_by_default) {
+               data->status = AUTHOR_STATUS_FAIL;
+               if (debug & DEBUG_AUTHOR_FLAG) {
+                   report(LOG_DEBUG, "nas:%s svr:absent, default=deny -> denied (c)",
+                          nas_arg);
+               }
+               if (out_args) {
+                   for (i = 0; i < data->num_out_args; i++)
+                       free(out_args[i]);
+                   free(out_args);
+               }
+               
+               data->num_out_args = 0;
+               data->output_args = NULL;
+               
+               /* free the server arguments */
+               for(cfg_argp = cfg_args; *cfg_argp; cfg_argp++)
+                   free(*cfg_argp);
+               free(cfg_args);
+               return (0);
+           }
+
+           /* d). If the default is permit, add the NAS AV pair to
+              the output */
+
+           if (debug & DEBUG_AUTHOR_FLAG) {
+               report(LOG_DEBUG, 
+                      "nas:%s, svr:absent, default=permit -> add %s (d)",
+                      nas_arg, nas_arg);
+           }
+           *outp++ = tac_strdup(nas_arg);
+           data->num_out_args++;
+           goto next_nas_arg;
+
+       } else {
+
+           /* NAS AV pair is Optional */
+
+           /* e). look for an exact attribute,value match in the mandatory
+              list. If found, add DAEMON's AV pair to output */
+
+           for (j = 0; j < cfg_cnt; j++) {
+               cfg_arg = cfg_args[j];
+               if (optional(cfg_arg))
+                   continue;
+
+               if (match_attrs(nas_arg, cfg_arg) &&
+                   match_values(nas_arg, cfg_arg)) {
+
+                   if (debug & DEBUG_AUTHOR_FLAG) {
+                       report(LOG_DEBUG, "nas:%s svr:%s -> replace with %s (e)",
+                              nas_arg, cfg_arg, cfg_arg);
+                   }
+                   *outp++ = tac_strdup(cfg_arg);
+                   data->num_out_args++;
+                   replaced++;
+                   goto next_nas_arg;
+               }
+           }
+
+           /* f). If not found, look for the first attribute match in
+              the mandatory list. If found, add DAEMONS's AV pair to
+              output */
+           
+           for (j = 0; j < cfg_cnt; j++) {
+               cfg_arg = cfg_args[j];
+               if (optional(cfg_arg))
+                   continue;
+
+               if (match_attrs(nas_arg, cfg_arg)) {
+                   if (debug & DEBUG_AUTHOR_FLAG) {
+                       report(LOG_DEBUG, "nas:%s svr:%s -> replace with %s (f)",
+                              nas_arg, cfg_arg, cfg_arg);
+                   }
+                   *outp++ = tac_strdup(cfg_arg);
+                   data->num_out_args++;
+                   replaced++;
+                   goto next_nas_arg;
+               }
+           }
+
+           /* g). If no mandatory match exists, look for an exact
+              attribute,value pair match among the daemon's optional AV
+              pairs. If found add the DAEMON's matching AV pair to the
+              output.
+              */
+
+           for (j = 0; j < cfg_cnt; j++) {
+               cfg_arg = cfg_args[j];
+               if (!optional(cfg_arg))
+                   continue;
+               
+               if (match_attrs(nas_arg, cfg_arg) && 
+                   match_values(nas_arg, cfg_arg)) {
+                   if (debug & DEBUG_AUTHOR_FLAG) {
+                       report(LOG_DEBUG, "nas:%s svr:%s -> replace with %s (g)",
+                              nas_arg, cfg_arg, cfg_arg);
+                   }
+                   *outp++ = tac_strdup(cfg_arg);
+                   data->num_out_args++;
+                   replaced++;
+                   goto next_nas_arg;
+               }
+           }
+
+           /* h). If no exact match exists, locate the first
+              attribute match among the daemon's optional AV
+              pairs. If found add the DAEMON's matching AV pair to
+              the output */
+
+           for (j = 0; j < cfg_cnt; j++) {
+               cfg_arg = cfg_args[j];
+               if (!optional(cfg_arg))
+                   continue;
+               
+               if (match_attrs(nas_arg, cfg_arg)) {
+                   if (debug & DEBUG_AUTHOR_FLAG) {
+                       report(LOG_DEBUG, "nas:%s svr:%s -> replace with %s (h)",
+                              nas_arg, cfg_arg, cfg_arg);
+                   }
+                   *outp++ = tac_strdup(cfg_arg);
+                   data->num_out_args++;
+                   replaced++;
+                   goto next_nas_arg;
+               }
+           }
+
+
+           /* i). If no match is found, delete the AV pair if default is
+              deny */
+
+           if (deny_by_default) {
+               if (debug & DEBUG_AUTHOR_FLAG) {
+                   report(LOG_DEBUG, "nas:%s svr:absent/deny -> delete %s (i)",
+                          nas_arg, nas_arg);
+               }
+               replaced++;
+               goto next_nas_arg;
+           }
+
+           /* j). If the default is permit add the NAS AV pair to the output */
+
+           if (debug & DEBUG_AUTHOR_FLAG) {
+               report(LOG_DEBUG, "nas:%s svr:absent/permit -> add %s (j)",
+                      nas_arg, nas_arg);
+           }
+           *outp++ = tac_strdup(nas_arg);
+           data->num_out_args++;
+           goto next_nas_arg;
+       }
+    next_nas_arg:;
+
+    }
+
+    
+    /* k). After all AV pairs have been processed, for each mandatory
+       DAEMON AV pair, if there is no attribute match already in the
+       output list, add the AV pair (add only one AV pair for each
+       mandatory attribute) */
+
+    for (i = 0; i < cfg_cnt; i++) {
+       cfg_arg = cfg_args[i];
+
+       if (!mandatory(cfg_arg))
+           continue;   
+
+       for (j = 0; j < data->num_out_args; j++) {
+           char *output_arg = out_args[j];
+
+           if (match_attrs(cfg_arg, output_arg)) {
+               goto next_cfg_arg;
+           }
+       }
+
+       /* Attr is required by daemon but not present in
+          output. Add it */
+
+       if (debug & DEBUG_AUTHOR_FLAG) {
+           report(LOG_DEBUG, "nas:absent, server:%s -> add %s (k)",
+                  cfg_arg, cfg_arg);
+       }
+       added++;
+       *outp++ = tac_strdup(cfg_arg);
+       data->num_out_args++;
+
+    next_cfg_arg:;
+       
+    }
+       
+    /* If we replaced or deleted some pairs we must return the entire
+       list we've constructed */
+
+    if (replaced) {
+       if (debug & DEBUG_AUTHOR_FLAG) {
+           report(LOG_DEBUG, "replaced %d args", replaced);
+       }
+       data->status = AUTHOR_STATUS_PASS_REPL;
+       data->output_args = out_args;
+
+       /* free the server arguments */
+       for(cfg_argp = cfg_args; *cfg_argp; cfg_argp++)
+           free(*cfg_argp);
+       free(cfg_args);
+
+       return (0);
+    }
+
+    /* We added something not on the original nas list, but didn't
+       replace or delete anything. We should return only the
+       additions */
+
+    if (added) {
+
+       if (debug & DEBUG_AUTHOR_FLAG) 
+           report(LOG_DEBUG, "added %d args", added);
+
+       /* throw away output args which are just copies of the input args */
+       for (i = 0; i < data->num_in_args; i++) {
+           if (debug & DEBUG_AUTHOR_FLAG) {
+               report(LOG_DEBUG, "out_args[%d] = %s input copy discarded", 
+                      i, out_args[i]);
+           }
+           free(out_args[i]);
+           out_args[i] = NULL;
+       }
+
+       /* Now compact the new args added to the end of the array down
+           to the beginning */
+
+       j = 0;
+       for (i = data->num_in_args; i < data->num_out_args; i++) {
+           if (out_args[j]) /* we goofed */
+               report(LOG_ERR, "%s: out_args[%d] should be NULL", 
+                      session.peer, j);
+           if (!out_args[i]) /* we goofed */
+               report(LOG_ERR, "%s: out_args[%d] should not be NULL",
+                      session.peer, i);
+
+           if (debug & DEBUG_AUTHOR_FLAG) {
+               report(LOG_DEBUG, "out_args[%d] = %s compacted to out_args[%d]", 
+                      i, out_args[i], j);
+           }
+           out_args[j++] = out_args[i];
+           out_args[i] = NULL;
+       }
+       data->num_out_args = j;
+       if (debug & DEBUG_AUTHOR_FLAG) {
+           report(LOG_DEBUG, "%d output args", data->num_out_args);
+       }
+
+       /* should/could do a realloc here but it won't matter */
+       data->status = AUTHOR_STATUS_PASS_ADD;
+       data->output_args = out_args;
+
+       /* free the server arguments */
+       for(cfg_argp = cfg_args; *cfg_argp; cfg_argp++)
+           free(*cfg_argp);
+       free(cfg_args);
+
+       return (0);
+    }
+
+    /* no additions or replacements. Input and output are
+       identical. Don't need to return anything */
+
+    if (debug & DEBUG_AUTHOR_FLAG) {
+       report(LOG_DEBUG, "added %d", added);
+    }
+    data->status = AUTHOR_STATUS_PASS_ADD;
+    if (out_args) {
+       for (i = 0; i < data->num_out_args; i++) {
+           free(out_args[i]);
+       }
+       free(out_args);
+    }
+
+    /* Final sanity check */
+    if (data->num_out_args != data->num_in_args) {
+       data->status = AUTHOR_STATUS_ERROR;
+       data->admin_msg = tac_strdup("Bad output arg cnt from do_author");
+       report(LOG_ERR, "%s: Error %s", session.peer, data->admin_msg);
+
+       /* free the server arguments */
+       for(cfg_argp = cfg_args; *cfg_argp; cfg_argp++)
+           free(*cfg_argp);
+       free(cfg_args);
+
+       return (0);
+    }
+
+    data->num_out_args = 0;
+    data->output_args = NULL;
+
+    /* free the server arguments */
+    for(cfg_argp = cfg_args; *cfg_argp; cfg_argp++)
+       free(*cfg_argp);
+    free(cfg_args);
+
+    return (0);
+}
+
+/* Return an integer indicating which kind of service is being
+   requested. 
+
+   Conveniently this integer is one of our node types. If the service
+   is a command authorisation request, also return the command name in
+   cmdname. 
+
+   If this service is a ppp request, return the protocol name in
+   protocol.
+
+   If this service is not one of the standard, known ones, return its
+   name in svcname.
+*/
+
+static int
+get_nas_svc(data, cmdname, protocol, svcname)
+struct author_data *data;
+char **cmdname, **protocol, **svcname;
+{
+    int i;
+    char *nas_arg;
+    
+    *cmdname = NULL;
+    *protocol = NULL;
+    *svcname = NULL;
+
+    for (i = 0; i < data->num_in_args; i++) {
+       nas_arg = data->input_args[i];
+
+       if (STREQ(nas_arg, "service=shell")) {
+           for (i = 0; i < data->num_in_args; i++) {
+               nas_arg = data->input_args[i];
+               if (strncmp(nas_arg, "cmd", strlen("cmd")) == 0) {
+                   /* A cmd=<nothing> means we are authorising exec startup */
+                   if ((int)strlen(nas_arg) <= 4)
+                       return (N_svc_exec);
+
+                   /* A non-null command means we are authorising a command */
+                   *cmdname = nas_arg + strlen("cmd") + 1;
+                   return (N_svc_cmd);
+               }
+           }
+           return(0);
+       }
+
+       if (STREQ(nas_arg, "service=slip")) {
+           return (N_svc_slip);
+       }
+       if (STREQ(nas_arg, "service=arap")) {
+           return (N_svc_arap);
+       }
+       if (STREQ(nas_arg, "service=ppp")) {
+           for (i = 0; i < data->num_in_args; i++) {
+               nas_arg = data->input_args[i];
+               if (strncmp(nas_arg, "protocol", strlen("protocol")) == 0) {
+                   *protocol = nas_arg + strlen("protocol") + 1;
+                   return(N_svc_ppp);
+               }
+           }
+       }
+
+       if (strncmp(nas_arg, "service=", strlen("service=")) ==0 ) {
+           *svcname = nas_arg + strlen("service=");
+           return(N_svc);
+       }
+    }
+    return (0);
+}
diff --git a/dump.c b/dump.c
new file mode 100644 (file)
index 0000000..ae58e5e
--- /dev/null
+++ b/dump.c
@@ -0,0 +1,518 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+#include "tac_plus.h"
+
+/* Routines for dumping packets to stderr */
+char *
+summarise_outgoing_packet_type(pak)
+u_char *pak;
+{
+    HDR *hdr;
+    struct authen_reply *authen;
+    struct author_reply *author;
+    char *p;
+
+    hdr = (HDR *) pak;
+
+    switch (hdr->type) {
+    case TAC_PLUS_AUTHEN:
+       authen = (struct authen_reply *) (pak + TAC_PLUS_HDR_SIZE);
+
+       switch (authen->status) {
+       case TAC_PLUS_AUTHEN_STATUS_PASS:
+           p = "AUTHEN/SUCCEED";
+           break;
+       case TAC_PLUS_AUTHEN_STATUS_FAIL:
+           p = "AUTHEN/FAIL";
+           break;
+       case TAC_PLUS_AUTHEN_STATUS_GETDATA:
+           p = "AUTHEN/GETDATA";
+           break;
+       case TAC_PLUS_AUTHEN_STATUS_GETUSER:
+           p = "AUTHEN/GETUSER";
+           break;
+       case TAC_PLUS_AUTHEN_STATUS_GETPASS:
+           p = "AUTHEN/GETPASS";
+           break;
+       case TAC_PLUS_AUTHEN_STATUS_ERROR:
+           p = "AUTHEN/ERROR";
+           break;
+       default:
+           p = "AUTHEN/UNKNOWN";
+           break;
+       }
+       break;
+
+    case TAC_PLUS_AUTHOR:
+       author = (struct author_reply *) (pak + TAC_PLUS_HDR_SIZE);
+       switch (author->status) {
+       case AUTHOR_STATUS_PASS_ADD:
+           p = "AUTHOR/PASS_ADD";
+           break;
+       case AUTHOR_STATUS_FAIL:
+           p = "AUTHOR/FAIL";
+           break;
+       case AUTHOR_STATUS_PASS_REPL:
+           p = "AUTHOR/PASS_REPL";
+           break;
+       case AUTHOR_STATUS_ERROR:
+           p = "AUTHOR/ERROR";
+           break;
+       default:
+           p = "AUTHOR/UNKNOWN";
+           break;
+       }
+       break;
+    case TAC_PLUS_ACCT:
+       p = "ACCT";
+       break;
+    default:
+       p = "UNKNOWN";
+       break;
+    }
+    return (p);
+}
+
+void
+dump_header(pak)
+u_char *pak;
+{
+    HDR *hdr;
+    u_char *data;
+
+    hdr = (HDR *) pak;
+
+    report(LOG_DEBUG, "PACKET: key=%s", session.key ? session.key : "<NULL>");
+    report(LOG_DEBUG, "version %d (0x%x), type %d, seq no %d, encryption %d",
+          hdr->version, hdr->version, 
+          hdr->type, hdr->seq_no, hdr->encryption);
+    report(LOG_DEBUG, "session_id %u (0x%x), Data length %d (0x%x)",
+          ntohl(hdr->session_id), ntohl(hdr->session_id),
+          ntohl(hdr->datalength), ntohl(hdr->datalength));
+
+    report(LOG_DEBUG, "End header");
+
+    if (debug & DEBUG_HEX_FLAG) {
+       report(LOG_DEBUG, "Packet body hex dump:");
+       data = (u_char *) (pak + TAC_PLUS_HDR_SIZE);
+       report_hex(LOG_DEBUG, data, ntohl(hdr->datalength));
+    }
+}
+
+
+/* Dump packets originated by a NAS */
+dump_nas_pak(pak)
+u_char *pak;
+{
+    struct authen_start *start;
+    struct authen_cont *cont;
+    struct author *author;
+    struct acct *acct;
+    int i;
+    HDR *hdr;
+    u_char *p, *argsizep;
+    int seq;
+
+    dump_header(pak);
+
+    hdr = (HDR *) pak;
+
+    seq = hdr->seq_no;
+    if (seq % 2 != 1) {
+       report(LOG_DEBUG, "nas packets should be odd numbered seq=%d",
+              seq);
+       exit(1);
+    }
+    switch (hdr->type) {
+
+    case TAC_PLUS_AUTHEN:
+       start = (struct authen_start *) (pak + TAC_PLUS_HDR_SIZE);
+
+       switch (hdr->seq_no) {
+
+       case 1:
+           report(LOG_DEBUG, "type=AUTHEN/START, priv_lvl = %d",
+                  start->priv_lvl);
+
+           switch (start->action) {
+           case TAC_PLUS_AUTHEN_LOGIN:
+               report(LOG_DEBUG, "action=login");
+               break;
+           case TAC_PLUS_AUTHEN_CHPASS:
+               report(LOG_DEBUG, "action=chpass");
+               break;
+           case TAC_PLUS_AUTHEN_SENDPASS:
+               report(LOG_DEBUG, "action=sendpass");
+               break;
+           case TAC_PLUS_AUTHEN_SENDAUTH:
+               report(LOG_DEBUG, "action=sendauth");
+               break;
+           default:
+               report(LOG_DEBUG, "action=UNKNOWN %d", start->action);
+               break;
+           }
+
+           switch(start->authen_type) {
+           case TAC_PLUS_AUTHEN_TYPE_ASCII:
+               report(LOG_DEBUG, "authen_type=ascii");
+               break;
+           case TAC_PLUS_AUTHEN_TYPE_PAP:
+               report(LOG_DEBUG, "authen_type=pap");
+               break;
+           case TAC_PLUS_AUTHEN_TYPE_CHAP:
+               report(LOG_DEBUG, "authen_type=chap");
+               break;
+           case TAC_PLUS_AUTHEN_TYPE_ARAP:
+               report(LOG_DEBUG, "authen_type=arap");
+               break;
+           default:
+               report(LOG_DEBUG, "authen_type=unknown %d", start->authen_type);
+               break;
+           }
+
+           switch(start->service) {
+               
+           case TAC_PLUS_AUTHEN_SVC_LOGIN:
+               report(LOG_DEBUG, "service=login");
+               break;
+           case TAC_PLUS_AUTHEN_SVC_ENABLE:
+               report(LOG_DEBUG, "service=enable");
+               break;
+           case TAC_PLUS_AUTHEN_SVC_PPP:
+               report(LOG_DEBUG, "service=ppp");
+               break;
+           case TAC_PLUS_AUTHEN_SVC_ARAP:
+               report(LOG_DEBUG, "service=arap");
+               break;
+           case TAC_PLUS_AUTHEN_SVC_PT:
+               report(LOG_DEBUG, "service=pt");
+               break;
+           case TAC_PLUS_AUTHEN_SVC_RCMD:
+               report(LOG_DEBUG, "service=rcmd");
+               break;
+           case TAC_PLUS_AUTHEN_SVC_X25:
+               report(LOG_DEBUG, "service=x25");
+               break;
+           case TAC_PLUS_AUTHEN_SVC_NASI:
+               report(LOG_DEBUG, "service=nasi");
+               break;
+           default:
+               report(LOG_DEBUG, "service=unknown %d", start->service);
+               break;
+           }
+
+           report(LOG_DEBUG, 
+                  "user_len=%d port_len=%d (0x%x), rem_addr_len=%d (0x%x)",
+                  start->user_len, start->port_len, start->port_len,
+                  start->rem_addr_len, start->rem_addr_len);
+
+           report(LOG_DEBUG, "data_len=%d", start->data_len);
+
+           /* start of variable length data is here */
+           p = pak + TAC_PLUS_HDR_SIZE + TAC_AUTHEN_START_FIXED_FIELDS_SIZE;
+
+           report(LOG_DEBUG, "User: ");
+           report_string(LOG_DEBUG, p, start->user_len);
+           p += start->user_len;
+
+           report(LOG_DEBUG, "port: ");
+           report_string(LOG_DEBUG, p, start->port_len);
+           p += start->port_len;
+
+           report(LOG_DEBUG, "rem_addr: ");
+           report_string(LOG_DEBUG, p, start->rem_addr_len);
+           p += start->rem_addr_len;
+
+           report(LOG_DEBUG, "data: ");
+           report_string(LOG_DEBUG, p, start->data_len);
+
+           report(LOG_DEBUG, "End packet");
+           return;
+
+       default:
+           cont = (struct authen_cont *) (pak + TAC_PLUS_HDR_SIZE);
+           report(LOG_DEBUG, "type=AUTHEN/CONT");
+           report(LOG_DEBUG, "user_msg_len %d (0x%x), user_data_len %d (0x%x)",
+                  cont->user_msg_len, cont->user_msg_len,
+                  cont->user_data_len, cont->user_data_len);
+           report(LOG_DEBUG, "flags=0x%x", cont->flags);
+
+           /* start of variable length data is here */
+           p = pak + TAC_PLUS_HDR_SIZE +
+               TAC_AUTHEN_CONT_FIXED_FIELDS_SIZE;
+
+           report(LOG_DEBUG, "User msg: ");
+           report_string(LOG_DEBUG, p, cont->user_msg_len);
+           p += cont->user_msg_len;
+
+           report(LOG_DEBUG, "User data: ");
+           report_string(LOG_DEBUG, p, cont->user_data_len);
+
+           report(LOG_DEBUG, "End packet");
+           return;
+       }
+
+    case TAC_PLUS_AUTHOR:
+       author = (struct author *) (pak + TAC_PLUS_HDR_SIZE);
+
+       report(LOG_DEBUG, "type=AUTHOR, priv_lvl=%d, authen=%d",
+              author->priv_lvl, 
+              author->authen_type);
+
+       switch(author->authen_method) {
+       case AUTHEN_METH_NONE:
+               report(LOG_DEBUG, "method=none");
+               break;      
+       case AUTHEN_METH_KRB5:
+               report(LOG_DEBUG, "method=krb5");
+               break;      
+       case AUTHEN_METH_LINE:
+               report(LOG_DEBUG, "method=line");
+               break;      
+       case AUTHEN_METH_ENABLE:
+               report(LOG_DEBUG, "method=enable");
+               break;      
+       case AUTHEN_METH_LOCAL:
+               report(LOG_DEBUG, "method=local");
+               break;      
+       case AUTHEN_METH_TACACSPLUS:
+               report(LOG_DEBUG, "method=tacacs+");
+               break;      
+       case AUTHEN_METH_RCMD:
+               report(LOG_DEBUG, "method=rcmd");
+               break;      
+       default:
+               report(LOG_DEBUG, "method=unknown %d", author->authen_method);
+               break;      
+       }
+
+       report(LOG_DEBUG, "svc=%d user_len=%d port_len=%d rem_addr_len=%d",
+              author->service, author->user_len, 
+              author->port_len, author->rem_addr_len);
+
+       report(LOG_DEBUG, "arg_cnt=%d", author->arg_cnt);
+
+       /* variable length data start here */
+       p = pak + TAC_PLUS_HDR_SIZE + TAC_AUTHOR_REQ_FIXED_FIELDS_SIZE;
+       argsizep = p;
+       p += author->arg_cnt;
+
+       report(LOG_DEBUG, "User: ");
+       report_string(LOG_DEBUG, p, author->user_len);
+       p += author->user_len;
+
+       report(LOG_DEBUG, "port: ");
+       report_string(LOG_DEBUG, p, author->port_len);
+       p += author->port_len;
+
+       report(LOG_DEBUG, "rem_addr: ");
+       report_string(LOG_DEBUG, p, author->rem_addr_len);
+       p += author->rem_addr_len;
+
+       for (i = 0; i < (int) author->arg_cnt; i++) {
+           report(LOG_DEBUG, "arg[%d]: size=%d ", i, *argsizep);
+           report_string(LOG_DEBUG, p, *argsizep);
+           p += *argsizep;
+           argsizep++;
+       }
+       break;
+
+    case TAC_PLUS_ACCT:
+       acct = (struct acct *) (pak + TAC_PLUS_HDR_SIZE);
+       report(LOG_DEBUG, "ACCT, flags=0x%x method=%d priv_lvl=%d",
+              acct->flags, acct->authen_method, acct->priv_lvl);
+       report(LOG_DEBUG, "type=%d svc=%d",
+              acct->authen_type, acct->authen_service);
+       report(LOG_DEBUG, "user_len=%d port_len=%d rem_addr_len=%d",
+              acct->user_len, acct->port_len, acct->rem_addr_len);
+       report(LOG_DEBUG, "arg_cnt=%d", acct->arg_cnt);
+
+       p = pak + TAC_PLUS_HDR_SIZE + TAC_ACCT_REQ_FIXED_FIELDS_SIZE;
+       argsizep = p;
+       p += acct->arg_cnt;
+
+       report(LOG_DEBUG, "User: ");
+       report_string(LOG_DEBUG, p, acct->user_len);
+       p += acct->user_len;
+
+       report(LOG_DEBUG, "port: ");
+       report_string(LOG_DEBUG, p, acct->port_len);
+       p += acct->port_len;
+
+       report(LOG_DEBUG, "rem_addr: ");
+       report_string(LOG_DEBUG, p, acct->rem_addr_len);
+       p += acct->rem_addr_len;
+
+       for (i = 0; i < (int) acct->arg_cnt; i++) {
+           report(LOG_DEBUG, "arg[%d]: size=%d ", i, *argsizep);
+           report_string(LOG_DEBUG, p, *argsizep);
+           p += *argsizep;
+           argsizep++;
+       }
+       break;
+
+    default:
+       report(LOG_DEBUG, "dump_nas_pak: unrecognized header type %d", hdr->type);
+    }
+    report(LOG_DEBUG, "End packet");
+}
+
+/* Dump packets originated by Tacacsd  */
+
+dump_tacacs_pak(pak)
+u_char *pak;
+{
+    struct authen_reply *authen;
+    struct author_reply *author;
+    struct acct_reply *acct;
+    HDR *hdr;
+    u_char *p, *argsizep;
+    int i;
+    int seq;
+
+    dump_header(pak);
+
+    hdr = (HDR *) pak;
+    seq = hdr->seq_no;
+
+    if (seq % 2 != 0) {
+       report(LOG_ERR, "%s: Bad sequence number %d should be even", 
+              session.peer, seq);
+       tac_exit(1);
+    }
+    switch (hdr->type) {
+
+    case TAC_PLUS_AUTHEN:
+       authen = (struct authen_reply *) (pak + TAC_PLUS_HDR_SIZE);
+
+       report(LOG_DEBUG, "type=AUTHEN status=%d (%s) flags=0x%x",
+              authen->status, summarise_outgoing_packet_type(pak),
+              authen->flags);
+
+       report(LOG_DEBUG, "msg_len=%d, data_len=%d",
+              authen->msg_len, authen->data_len);
+
+       /* start of variable length data is here */
+       p = pak + TAC_PLUS_HDR_SIZE + TAC_AUTHEN_REPLY_FIXED_FIELDS_SIZE;
+
+       report(LOG_DEBUG, "msg: ");
+       report_string(LOG_DEBUG, p, authen->msg_len);
+       p += authen->msg_len;
+
+       report(LOG_DEBUG, "data: ");
+       report_string(LOG_DEBUG, p, authen->data_len);
+
+       report(LOG_DEBUG, "End packet");
+       return;
+
+    case TAC_PLUS_AUTHOR:
+       author = (struct author_reply *) (pak + TAC_PLUS_HDR_SIZE);
+
+       report(LOG_DEBUG, "type=AUTHOR/REPLY status=%d (%s) ",
+              author->status, summarise_outgoing_packet_type(pak));
+       report(LOG_DEBUG, "msg_len=%d, data_len=%d arg_cnt=%d",
+              author->msg_len, author->data_len, author->arg_cnt);
+
+       /* start of variable length data is here */
+       p = pak + TAC_PLUS_HDR_SIZE + TAC_AUTHOR_REPLY_FIXED_FIELDS_SIZE;
+
+       /* arg sizes come next */
+       argsizep = p;
+
+       p += author->arg_cnt;
+
+       report(LOG_DEBUG, "msg: ");
+       report_string(LOG_DEBUG, p, author->msg_len);
+       p += author->msg_len;
+
+       report(LOG_DEBUG, "data: ");
+       report_string(LOG_DEBUG, p, author->data_len);
+       p += author->data_len;
+
+       /* args follow */
+       for (i = 0; i < (int) author->arg_cnt; i++) {
+           int size = argsizep[i];
+
+           report(LOG_DEBUG, "arg[%d] size=%d ", i, size);
+           report_string(LOG_DEBUG, p, size);
+           p += size;
+       }
+       break;
+
+    case TAC_PLUS_ACCT:
+       acct = (struct acct_reply *) (pak + TAC_PLUS_HDR_SIZE);
+       report(LOG_DEBUG, "ACCT/REPLY status=%d", acct->status);
+
+       report(LOG_DEBUG, "msg_len=%d data_len=%d",
+              acct->msg_len, acct->data_len);
+
+       p = pak + TAC_PLUS_HDR_SIZE + TAC_ACCT_REPLY_FIXED_FIELDS_SIZE;
+
+       report(LOG_DEBUG, "msg: ");
+       
+       report_string(LOG_DEBUG, p, acct->msg_len);
+       p += acct->msg_len;
+
+       report(LOG_DEBUG, "data: ");
+       report_string(LOG_DEBUG, p, acct->data_len);
+
+       break;
+
+    default:
+       report(LOG_DEBUG, "dump_tacacs_pak: unrecognized header type %d",
+              hdr->type);
+    }
+    report(LOG_DEBUG, "End packet");
+}
+
+/* summarise packet types for logging routines. */
+char *
+summarise_incoming_packet_type(pak)
+u_char *pak;
+{
+    HDR *hdr;
+    char *p;
+
+    hdr = (HDR *) pak;
+
+    switch (hdr->type) {
+    case TAC_PLUS_AUTHEN:
+       switch (hdr->seq_no) {
+       case 1:
+           p = "AUTHEN/START";
+           break;
+       default:
+           p = "AUTHEN/CONT";
+           break;
+       }
+       return (p);
+
+    case TAC_PLUS_AUTHOR:
+       p = "AUTHOR";
+       break;
+    case TAC_PLUS_ACCT:
+       p = "ACCT";
+       break;
+    default:
+       p = "UNKNOWN";
+       break;
+    }
+    return (p);
+}
diff --git a/enable.c b/enable.c
new file mode 100644 (file)
index 0000000..63a17eb
--- /dev/null
+++ b/enable.c
@@ -0,0 +1,187 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+#include "tac_plus.h"
+#include "expire.h"
+
+/* internal state variables */
+#define STATE_AUTHEN_START   0 /* no requests issued */
+#define STATE_AUTHEN_GETUSER 1 /* username has been requested */
+#define STATE_AUTHEN_GETPASS 2 /* password has been requested */
+
+struct private_data {
+    char password[MAX_PASSWD_LEN + 1];
+    int state;
+};
+
+static void
+enable(passwd, data)
+char *passwd;
+struct authen_data *data;
+{
+    int level = data->NAS_id->priv_lvl;
+
+    /* sanity check */
+    if (level < TAC_PLUS_PRIV_LVL_MIN || level > TAC_PLUS_PRIV_LVL_MAX) {
+       data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+       data->server_msg = tac_strdup("Invalid privilege level in packet");
+       report(LOG_ERR, "%s level=%d %s", session.peer, level, data->server_msg);
+       return;
+    }
+    /* 0 <= level <= 14: look for $enab<n>$ and verify */
+    if (level < TAC_PLUS_PRIV_LVL_MAX) {
+       char buf[11];
+
+       sprintf(buf, "$enab%d$", level);
+       if (!verify(buf, passwd, data, TAC_PLUS_NORECURSE))
+           data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+       return;
+    }
+
+    /* 2). level=15. Try $enab15$ or $enable$ (for backwards
+       compatibility) and verify */
+
+    if (verify("$enable$", passwd, data, TAC_PLUS_NORECURSE) ||
+       verify("$enab15$", passwd, data, TAC_PLUS_NORECURSE)) {
+       return;
+    }
+
+    /* return fail */
+    data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+
+    return;
+}
+
+
+/*
+ * Tacacs enable authentication function. Wants an enable
+ * password, and tries to verify it.
+ *
+ * Choose_authen will ensure that we already have a username before this
+ * gets called.
+ *
+ * We will query for a password and keep it in the method_data.
+ *
+ * Any strings returned via pointers in authen_data must come from the
+ * heap. They will get freed by the caller.
+ *
+ * Return 0 if data->status is valid, otherwise 1
+ */
+
+int
+enable_fn(data)
+struct authen_data *data;
+{
+    char *passwd;
+    struct private_data *p;
+    int pwlen;
+
+    p = (struct private_data *) data->method_data;
+
+    /* An abort has been received. Clean up and return */
+    if (data->flags & TAC_PLUS_CONTINUE_FLAG_ABORT) {
+       if (data->method_data)
+           free(data->method_data);
+       data->method_data = NULL;
+       return (1);
+    }
+    /* Initialise method_data if first time through */
+    if (!p) {
+       p = (struct private_data *) tac_malloc(sizeof(struct private_data));
+       bzero(p, sizeof(struct private_data));
+       data->method_data = p;
+       p->state = STATE_AUTHEN_START;
+    }
+
+    /* As we're enabling, we don't need a username, but do we have a
+       password? */
+
+    passwd = p->password;
+
+    if (!passwd[0]) {
+
+       /* No password. Either we need to ask for one and expect to get
+        * called again, or we asked but nothing came back, which is fatal */
+
+       switch (p->state) {
+       case STATE_AUTHEN_GETPASS:
+           /* We already asked for a password. This should be the
+               reply */
+           if (data->client_msg) {
+               pwlen = MIN((int)strlen(data->client_msg), MAX_PASSWD_LEN);
+           } else {
+               pwlen = 0;
+           }
+           strncpy(passwd, data->client_msg, pwlen);
+           passwd[pwlen] = '\0';
+           break;
+
+       default:
+           /* Request a password */
+           data->flags = TAC_PLUS_AUTHEN_FLAG_NOECHO;
+           data->server_msg = tac_strdup("Password: ");
+           data->status = TAC_PLUS_AUTHEN_STATUS_GETPASS;
+           p->state = STATE_AUTHEN_GETPASS;
+           return (0);
+       }
+    }
+
+    /* We have a password. Try validating */
+
+    /* Assume the worst */
+    data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+
+    switch (data->service) {
+    case TAC_PLUS_AUTHEN_SVC_ENABLE:
+       enable(passwd, data);
+       if (debug) {
+           char *name = data->NAS_id->username;
+
+           report(LOG_INFO, "enable query for '%s' %s from %s %s",
+                  name && name[0] ? name : "unknown",
+                  data->NAS_id->NAS_port && data->NAS_id->NAS_port[0] ?
+                      data->NAS_id->NAS_port : "unknown",
+                  session.peer, 
+                  (data->status == TAC_PLUS_AUTHEN_STATUS_PASS) ?
+                  "accepted" : "rejected");
+       }
+       break;
+    default:
+       data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+       report(LOG_ERR, "%s: Bogus service value %d from packet", 
+              session.peer, data->service);
+       break;
+    }
+
+    if (data->method_data)
+       free(data->method_data);
+    data->method_data = NULL;
+
+    switch (data->status) {
+    case TAC_PLUS_AUTHEN_STATUS_ERROR:
+    case TAC_PLUS_AUTHEN_STATUS_FAIL:
+    case TAC_PLUS_AUTHEN_STATUS_PASS:
+       return (0);
+    default:
+       report(LOG_ERR, "%s: authenticate_fn can't set status %d",
+              session.peer, data->status);
+       data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+       return (1);
+    }
+}
diff --git a/encrypt.c b/encrypt.c
new file mode 100644 (file)
index 0000000..60ba5e9
--- /dev/null
+++ b/encrypt.c
@@ -0,0 +1,163 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+#include "tac_plus.h"
+#include "md5.h"
+
+/*
+ * create_md5_hash(): create an md5 hash of the "session_id", "the user's
+ * key", "the version number", the "sequence number", and an optional
+ * 16 bytes of data (a previously calculated hash). If not present, this
+ * should be NULL pointer.
+ *
+ * Write resulting hash into the array pointed to by "hash".
+ *
+ * The caller must allocate sufficient space for the resulting hash
+ * (which is 16 bytes long). The resulting hash can safely be used as
+ * input to another call to create_md5_hash, as its contents are copied
+ * before the new hash is generated.
+ *
+ *
+ */
+
+void
+create_md5_hash(session_id, key, version, seq_no, prev_hash, hash)
+int session_id;
+char *key;
+u_char version;
+u_char seq_no;
+u_char *prev_hash;
+u_char *hash;
+{
+    u_char *md_stream, *mdp;
+    int md_len;
+    MD5_CTX mdcontext;
+
+    md_len = sizeof(session_id) + strlen(key) + sizeof(version) +
+       sizeof(seq_no);
+
+    if (prev_hash) {
+       md_len += MD5_LEN;
+    }
+    mdp = md_stream = (u_char *) tac_malloc(md_len);
+    bcopy(&session_id, mdp, sizeof(session_id));
+    mdp += sizeof(session_id);
+
+    bcopy(key, mdp, strlen(key));
+    mdp += strlen(key);
+
+    bcopy(&version, mdp, sizeof(version));
+    mdp += sizeof(version);
+
+    bcopy(&seq_no, mdp, sizeof(seq_no));
+    mdp += sizeof(seq_no);
+
+    if (prev_hash) {
+       bcopy(prev_hash, mdp, MD5_LEN);
+       mdp += MD5_LEN;
+    }
+    MD5Init(&mdcontext);
+    MD5Update(&mdcontext, md_stream, md_len);
+    MD5Final(hash, &mdcontext);
+    free(md_stream);
+    return;
+}
+
+/*
+ * Overwrite input data with en/decrypted version by generating an MD5 hash and
+ * xor'ing data with it.
+ *
+ * When more than 16 bytes of hash is needed, the MD5 hash is performed
+ * again with the same values as before, but with the previous hash value
+ * appended to the MD5 input stream.
+ *
+ * Return 0 on success, -1 on failure.
+ */
+
+md5_xor(hdr, data, key)
+HDR *hdr;
+u_char *data;
+char *key;
+{
+    int i, j;
+    u_char hash[MD5_LEN];      /* the md5 hash */
+    u_char last_hash[MD5_LEN]; /* the last hash we generated */
+    u_char *prev_hashp = (u_char *) NULL;      /* pointer to last created
+                                                * hash */
+    int data_len;
+    int session_id;
+    u_char version;
+    u_char seq_no;
+
+    data_len = ntohl(hdr->datalength);
+    session_id = hdr->session_id; /* always in network order for hashing */
+    version = hdr->version;
+    seq_no = hdr->seq_no;
+
+    if (!key)
+       return (0);
+
+    for (i = 0; i < data_len; i += 16) {
+
+       create_md5_hash(session_id, key, version, seq_no, prev_hashp, hash);
+
+       if (debug & DEBUG_MD5_HASH_FLAG) {
+           int k;
+
+           report(LOG_DEBUG, 
+                  "hash: session_id=%u, key=%s, version=%d, seq_no=%d",
+                  session_id, key, version, seq_no);
+           if (prev_hashp) {
+               report(LOG_DEBUG, "prev_hash:");
+               for (k = 0; k < MD5_LEN; k++)
+                   report(LOG_DEBUG, "0x%x", prev_hashp[k]);
+           } else {
+               report(LOG_DEBUG, "no prev. hash");
+           }
+
+           report(LOG_DEBUG, "hash: ");
+           for (k = 0; k < MD5_LEN; k++)
+               report(LOG_DEBUG, "0x%x", hash[k]);
+       }                       /* debug */
+       bcopy(hash, last_hash, MD5_LEN);
+       prev_hashp = last_hash;
+
+       for (j = 0; j < 16; j++) {
+
+           if ((i + j) >= data_len) {
+               hdr->encryption = (hdr->encryption == TAC_PLUS_CLEAR) 
+                   ? TAC_PLUS_ENCRYPTED : TAC_PLUS_CLEAR;
+               return (0);
+           }
+           if (debug & DEBUG_XOR_FLAG) {
+               report(LOG_DEBUG,
+                  "data[%d] = 0x%x, xor'ed with hash[%d] = 0x%x -> 0x%x\n",
+                      i + j,
+                      data[i + j],
+                      j,
+                      hash[j],
+                      data[i + j] ^ hash[j]);
+           }                   /* debug */
+           data[i + j] ^= hash[j];
+       }
+    }
+    hdr->encryption = (hdr->encryption == TAC_PLUS_CLEAR) 
+       ? TAC_PLUS_ENCRYPTED : TAC_PLUS_CLEAR;
+    return (0);
+}
diff --git a/expire.c b/expire.c
new file mode 100644 (file)
index 0000000..1ef745c
--- /dev/null
+++ b/expire.c
@@ -0,0 +1,84 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+#include "tac_plus.h"
+#include "expire.h"
+
+/*
+ * check a date for expiry. If the field specifies
+ * a shell return PW_OK
+ *
+ * Return PW_OK if not expired
+ * Return PW_EXPIRING if expiry is coming soon
+ * Return PW_EXPIRED  if already expired
+ */
+
+#define SEC_IN_DAY (24*60*60)
+#define WARNING_PERIOD 14
+
+static char *monthname[] = {"JAN", "FEB", "MAR", "APR", "MAY", "JUN",
+"JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
+static long days_ere_month[] = {0, 31, 59, 90, 120, 151,
+181, 212, 243, 273, 304, 334};
+
+int
+check_expiration(date)
+char *date;
+{
+    long day, month, year, leaps, now, expiration, warning;
+    char monthstr[10];
+    int i;
+
+    monthstr[0] = '\0';
+
+    /* If no date or a shell, let it pass.  (Backward compatibility.) */
+    if (!date || (strlen(date) == 0) || (*date == '/'))
+       return (PW_OK);
+
+    /* Parse date string.  Fail it upon error. */
+    if (sscanf(date, "%s %d %d", monthstr, &day, &year) != 3)
+       return (PW_EXPIRED);
+
+    for(i=0; i < 3; i++) {
+       monthstr[i] = toupper(monthstr[i]);
+    }
+
+    /* Compute the expiration date in days. */
+    for (month = 0; month < 12; month++)
+       if (strncmp(monthstr, monthname[month], 3) == 0)
+           break;
+
+    if (month > 11)
+       return (PW_EXPIRED);
+
+    leaps = (year - 1969) / 4 + (((year % 4) == 0) && (month > 2));
+    expiration = (((year - 1970) * 365) + days_ere_month[month] + (day - 1) + leaps);
+    warning = expiration - WARNING_PERIOD;
+
+    /* Get the current time (to the day) */
+    now = time(NULL) / SEC_IN_DAY;
+
+    if (now > expiration)
+       return (PW_EXPIRED);
+
+    if (now > warning)
+       return (PW_EXPIRING);
+
+    return (PW_OK);
+}
diff --git a/expire.h b/expire.h
new file mode 100644 (file)
index 0000000..c7df48d
--- /dev/null
+++ b/expire.h
@@ -0,0 +1,26 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+#define PW_OK       0          /* pw not expired and not due to expire soon */
+#define PW_EXPIRED  1          /* pw has expired */
+#define PW_EXPIRING 2          /* pw will expire soon */
+
+#define MAX_PASSWD_LEN 256
+
+extern int check_expiration();
diff --git a/generate_passwd.c b/generate_passwd.c
new file mode 100644 (file)
index 0000000..509e48a
--- /dev/null
@@ -0,0 +1,90 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+/* Program to des encrypt a password like Unix 
+   It prompts for the password to encrypt. 
+   You can optionally supply a salt to verify a password.
+   Usage: a.out [salt]
+*/
+
+#define NULL 0
+
+main(argc, argv)
+char **argv;
+{
+    char *crypt();
+    char pass[25], *salt, buf[24];
+    char *result;
+    int n;
+    char *prompt = "Password to be encrypted: ";
+
+    salt = NULL;
+
+    if (argc == 2) {
+       salt = argv[1];
+    }
+
+    write(1, prompt, strlen(prompt));
+    n = read(0, pass, sizeof(pass));
+    pass[n-1] = NULL;
+
+    if (!salt) {
+       int i, r, r1, r2;
+
+       srand(time(0));
+
+       for(i=0; i <= 1; i++) {
+
+           r = rand();
+
+           r = r & 127;
+
+           if (r < 46)
+               r += 46;
+
+           if (r > 57 && r < 65)
+               r += 7;
+
+           if (r > 90 && r < 97) 
+               r += 6;
+
+           if (r > 122)
+               r -= 5;
+
+           if (i == 0)
+               r1 = r;
+
+           if (i == 1)
+               r2 = r;
+       }
+
+       sprintf(buf, "%c%c", r1, r2);
+       salt = buf;
+    }
+
+    result = crypt(pass, salt);
+
+    write(1, result, strlen(result));
+    write(1, "\n", 1);
+}
+
+
+
+
+
diff --git a/hash.c b/hash.c
new file mode 100644 (file)
index 0000000..443d42b
--- /dev/null
+++ b/hash.c
@@ -0,0 +1,125 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+#include "tac_plus.h"
+
+struct entry {
+    char *name;
+    void *hash;
+};
+
+typedef struct entry ENTRY;
+
+/* Calculate hash value from a string */
+static int
+calculate_hash(name)
+char *name;
+{
+    int i;
+    int len = strlen(name);
+    int hashval = 0;
+
+    for (i = 0; i < len; i++) {
+       hashval += name[i] * (i + 1);
+    }
+    hashval += name[0];
+    hashval = hashval > 0 ? hashval : -hashval;
+    return (hashval);
+}
+
+/* Lookup a name in a hash table.  Return its node if it exists, NULL
+   otherwise */
+void *
+hash_lookup(hashtab, name)
+void **hashtab;
+char *name;
+{
+    ENTRY *entry;
+    int hashval = calculate_hash(name);
+
+    entry = hashtab[hashval % HASH_TAB_SIZE];
+
+    while (entry) {
+       if (STREQ(name, entry->name))
+           /* Node exists in table. return it */
+           return (entry);
+       entry = entry->hash;
+    }
+    return (NULL);
+}
+
+/* Add a node to a hash table.  Return node if it exists, NULL
+   otherwise */
+void *
+hash_add_entry(hashtab, newentry)
+void **hashtab;
+ENTRY *newentry;
+{
+    ENTRY *entry;
+    int hashval;
+
+    entry = hash_lookup(hashtab, newentry->name);
+    if (entry)
+       return (entry);
+
+    /* Node does not exist in table. Add it */
+    hashval = calculate_hash(newentry->name);
+    newentry->hash = hashtab[hashval % HASH_TAB_SIZE];
+    hashtab[hashval % HASH_TAB_SIZE] = newentry;
+    return (NULL);
+}
+
+
+/* Return an array of pointers to all the entries in a hash table */
+void **
+hash_get_entries(hashtab)
+void **hashtab;
+{
+    int i;
+    int cnt;
+    ENTRY *entry;
+    void **entries, **p;
+    int n, longest;
+
+    longest = 0;
+    cnt = 0;
+    for (i = 0; i < HASH_TAB_SIZE; i++) {
+       entry = hashtab[i];
+       n = 0;
+       while (entry) {
+           cnt++;
+           n++;
+           entry = entry->hash;
+       }
+       if (n > longest)
+           longest = n;
+    }
+    cnt++;                     /* Add space for NULL entry at end */
+
+    p = entries = (void **) tac_malloc(cnt * sizeof(void *));
+    for (i = 0; i < HASH_TAB_SIZE; i++) {
+       entry = hashtab[i];
+       while (entry) {
+           *p++ = entry;
+           entry = entry->hash;
+       }
+    }
+    *p++ = NULL;
+    return (entries);
+}
diff --git a/install-sh b/install-sh
new file mode 100644 (file)
index 0000000..1a24852
--- /dev/null
@@ -0,0 +1 @@
+#!/bin/sh
diff --git a/ldap.c b/ldap.c
new file mode 100644 (file)
index 0000000..ca1a8ef
--- /dev/null
+++ b/ldap.c
@@ -0,0 +1,119 @@
+/*
+     Verify that this user/password is valid per a database LDAP server
+     Return 1 if verified, 0 otherwise.
+     
+     Format of connection string (look like internet URL):
+
+       ldap://LDAP-hostname
+     
+     -------------------------------------------------------
+     patrick.harpes@tudor.lu            http://www.santel.lu
+                                        http://www.tudor.lu
+     
+
+
+     Dependencies: You need to get the OpenLDAP libraries
+                   from http://www.openldap.org
+      License: tac_ldap 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,
+               or (at your option) any later version.
+--------------------------------------------------------------------------
+                               Changes:
+ Ok i am back again..:)
+ I changed lot of thing.. First off all i add port feature to ldap string.
+ And also add more check for buffer overflows.
+
+Connect format would be:
+       ldap://LDAP-hostname:100
+
+Port name isn't required.. I would like to change format with : 
+       ldap://LDAP-hostname:100/dn_for_user&dn_for_passwd
+
+ devrim seral <devrim@gazi.edu.tr> 
+
+*/ 
+
+
+#if defined(USE_LDAP)
+#include <stdio.h>
+#include <string.h>
+#include <lber.h>
+#include <ldap.h>
+#include <ldap_cdefs.h>
+
+#include "tac_plus.h"
+#include "ldap.h"
+
+
+int
+ldap_verify(user, users_passwd, str_conn)
+char *user, *users_passwd;      /* Username and gived password   */
+char *str_conn;                 /* String connection to database */
+{
+  char *buf;
+  char *ldapServer;
+  char *ldap_port;
+  LDAP *ld;
+  int port;
+  int err;
+
+/* Don't allow null username and passwd */ 
+  if ( *user == '0' || *users_passwd == '0' ) return (1);
+
+  buf=(char *)malloc(strlen(str_conn)+1);
+  if (buf == NULL ){ 
+       report(LOG_DEBUG, "Error can't allocate memory");
+        return(1);
+  }
+  
+  strcpy(buf,str_conn);
+  ldapServer=strstr(buf, "://");
+  
+  if(ldapServer == NULL && strlen(ldapServer) <4 ) {
+       if (debug) {
+               report(LOG_DEBUG, "Error parse ldap server");
+               return(1);
+       }
+  } 
+  
+ ldapServer=ldapServer+3;
+
+ ldap_port=(char *)strstr(ldapServer, ":");
+
+ if (ldap_port != NULL ) {
+               *ldap_port='\0';
+               port=atoi(++ldap_port);
+ } else {
+       port = LDAP_PORT;
+ }
+ if ( debug & DEBUG_AUTHEN_FLAG ) 
+  report(LOG_DEBUG, "In verify_ldap : Before ldap_init : ldapserver = %s port= %d", ldapServer, port);
+
+
+  if( (ld = ldap_init(ldapServer, port)) == NULL)
+    {
+      report(LOG_DEBUG, "Unable to connect to LDAP server:%s port:%d",ldapServer, port);
+      return 1;
+    }
+  
+  err=ldap_simple_bind_s(ld, user, users_passwd);
+  
+  if(err != LDAP_SUCCESS)
+    {
+      if ( debug & DEBUG_AUTHEN_FLAG ) 
+       report(LOG_DEBUG,"Error while bind : %d %s",err, ldap_err2string(err) );
+      return 1;
+    }         
+  else
+    {
+      /* Success */
+     if ( debug & DEBUG_AUTHEN_FLAG ) 
+               report(LOG_DEBUG, "LDAP authentication Sucess ");
+     ldap_unbind_s(ld); 
+     return 0;
+    }
+}
+#endif /* LDAP */
diff --git a/ldap.h b/ldap.h
new file mode 100644 (file)
index 0000000..6479426
--- /dev/null
+++ b/ldap.h
@@ -0,0 +1 @@
+int ldap_verify();
diff --git a/ldap_author.c b/ldap_author.c
new file mode 100644 (file)
index 0000000..ca1a8ef
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+     Verify that this user/password is valid per a database LDAP server
+     Return 1 if verified, 0 otherwise.
+     
+     Format of connection string (look like internet URL):
+
+       ldap://LDAP-hostname
+     
+     -------------------------------------------------------
+     patrick.harpes@tudor.lu            http://www.santel.lu
+                                        http://www.tudor.lu
+     
+
+
+     Dependencies: You need to get the OpenLDAP libraries
+                   from http://www.openldap.org
+      License: tac_ldap 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,
+               or (at your option) any later version.
+--------------------------------------------------------------------------
+                               Changes:
+ Ok i am back again..:)
+ I changed lot of thing.. First off all i add port feature to ldap string.
+ And also add more check for buffer overflows.
+
+Connect format would be:
+       ldap://LDAP-hostname:100
+
+Port name isn't required.. I would like to change format with : 
+       ldap://LDAP-hostname:100/dn_for_user&dn_for_passwd
+
+ devrim seral <devrim@gazi.edu.tr> 
+
+*/ 
+
+
+#if defined(USE_LDAP)
+#include <stdio.h>
+#include <string.h>
+#include <lber.h>
+#include <ldap.h>
+#include <ldap_cdefs.h>
+
+#include "tac_plus.h"
+#include "ldap.h"
+
+
+int
+ldap_verify(user, users_passwd, str_conn)
+char *user, *users_passwd;      /* Username and gived password   */
+char *str_conn;                 /* String connection to database */
+{
+  char *buf;
+  char *ldapServer;
+  char *ldap_port;
+  LDAP *ld;
+  int port;
+  int err;
+
+/* Don't allow null username and passwd */ 
+  if ( *user == '0' || *users_passwd == '0' ) return (1);
+
+  buf=(char *)malloc(strlen(str_conn)+1);
+  if (buf == NULL ){ 
+       report(LOG_DEBUG, "Error can't allocate memory");
+        return(1);
+  }
+  
+  strcpy(buf,str_conn);
+  ldapServer=strstr(buf, "://");
+  
+  if(ldapServer == NULL && strlen(ldapServer) <4 ) {
+       if (debug) {
+               report(LOG_DEBUG, "Error parse ldap server");
+               return(1);
+       }
+  } 
+  
+ ldapServer=ldapServer+3;
+
+ ldap_port=(char *)strstr(ldapServer, ":");
+
+ if (ldap_port != NULL ) {
+               *ldap_port='\0';
+               port=atoi(++ldap_port);
+ } else {
+       port = LDAP_PORT;
+ }
+ if ( debug & DEBUG_AUTHEN_FLAG ) 
+  report(LOG_DEBUG, "In verify_ldap : Before ldap_init : ldapserver = %s port= %d", ldapServer, port);
+
+
+  if( (ld = ldap_init(ldapServer, port)) == NULL)
+    {
+      report(LOG_DEBUG, "Unable to connect to LDAP server:%s port:%d",ldapServer, port);
+      return 1;
+    }
+  
+  err=ldap_simple_bind_s(ld, user, users_passwd);
+  
+  if(err != LDAP_SUCCESS)
+    {
+      if ( debug & DEBUG_AUTHEN_FLAG ) 
+       report(LOG_DEBUG,"Error while bind : %d %s",err, ldap_err2string(err) );
+      return 1;
+    }         
+  else
+    {
+      /* Success */
+     if ( debug & DEBUG_AUTHEN_FLAG ) 
+               report(LOG_DEBUG, "LDAP authentication Sucess ");
+     ldap_unbind_s(ld); 
+     return 0;
+    }
+}
+#endif /* LDAP */
diff --git a/ldap_author.h b/ldap_author.h
new file mode 100644 (file)
index 0000000..6479426
--- /dev/null
@@ -0,0 +1 @@
+int ldap_verify();
diff --git a/maxsess.c b/maxsess.c
new file mode 100644 (file)
index 0000000..83e0815
--- /dev/null
+++ b/maxsess.c
@@ -0,0 +1,692 @@
+/*
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+#include "tac_plus.h"
+
+#ifdef MAXSESS
+char *wholog = WHOLOG_DEFAULT;
+/*
+ * initialize wholog file for tracking of user logins/logouts from
+ * accounting records.
+ */
+void
+maxsess_loginit()
+{
+    int fd;
+
+    fd = open(wholog, O_CREAT | O_RDWR, 0600);
+    if (fd < 0) {
+       report(LOG_ERR, "Can't create: %s", wholog);
+    } else {
+       if (debug & DEBUG_MAXSESS_FLAG) {
+           report(LOG_DEBUG, "Initialize %s", wholog);
+       }
+       close(fd);
+    }
+}
+
+/*
+ * Given a port description, return it in a canonical format.
+ *
+ * This piece of goo is to cover the fact that an async line in EXEC
+ * mode is known as "ttyXX", but the same line doing PPP or SLIP is
+ * known as "AsyncXX".
+ */
+static char *
+portname(oldport)
+char *oldport;
+{
+    char *p = oldport;
+
+    if (!strncmp(p, "Async", 5) || !strncmp(p, "tty", 3)) {
+       while (!isdigit(*p) && *p) {
+           ++p;
+       }
+    }
+    if (!*p) {
+       if (debug & DEBUG_ACCT_FLAG)
+           report(LOG_DEBUG, "Maxsess -- Malformed portname: %s", oldport);
+       return (oldport);
+    }
+    return (p);
+}
+
+/*
+ * Seek to offset and write a buffer into the file pointed to by fp
+ */
+static void
+write_record(name, fp, buf, size, offset)
+FILE *fp;
+long offset;
+int size;
+void *buf;
+char *name;
+{
+    if (fseek(fp, offset, SEEK_SET) < 0) {
+       report(LOG_ERR, "%s fd=%d Cannot seek to %d %s",
+              name, fileno(fp), offset, sys_errlist[errno]);
+    }
+    if (fwrite(buf, size, 1, fp) != 1) {
+       report(LOG_ERR, "%s fd=%d Cannot write %d bytes",
+              name, fileno(fp), size);
+    }
+}
+
+static void
+process_stop_record(idp)
+struct identity *idp;
+{
+    int recnum;
+    struct peruser pu;
+    FILE *fp;
+    char *nasport = portname(idp->NAS_port);
+
+    /* If we can't access the file, skip all checks. */
+    fp = fopen(wholog, "r+");
+    if (fp == NULL) {
+       report(LOG_ERR, "Can't open %s for updating", wholog);
+       return;
+    }
+    tac_lockfd(wholog, fileno(fp));
+
+    for (recnum = 0; 1; recnum++) {
+
+       fseek(fp, recnum * sizeof(struct peruser), SEEK_SET);
+
+       if (fread(&pu, sizeof(pu), 1, fp) <= 0) {
+           break;
+       }
+
+       /* A match for this record? */
+       if (!(STREQ(pu.NAS_name, idp->NAS_name) &&
+             STREQ(pu.NAS_port, nasport))) {
+           continue;
+       }
+
+       /* A match. Zero out this record */
+       bzero(&pu, sizeof(pu));
+
+       write_record(wholog, fp, &pu, sizeof(pu), 
+                    recnum * sizeof(struct peruser));
+
+       if (debug & DEBUG_MAXSESS_FLAG) {
+           report(LOG_DEBUG, "STOP record -- clear %s entry %d for %s/%s",
+                  wholog, recnum, idp->username, nasport);
+       }
+    }
+    fclose(fp);
+}
+
+static void
+process_start_record(idp)
+struct identity *idp;
+{
+    int recnum;
+    int foundrec = -1;
+    int freerec = -1;
+    char *nasport = portname(idp->NAS_port);
+    struct peruser pu;
+    FILE *fp;
+
+    /* If we can't access the file, skip all checks. */
+    fp = fopen(wholog, "r+");
+    if (fp == NULL) {
+       report(LOG_ERR, "Can't open %s for updating", wholog);
+       return;
+    }
+    tac_lockfd(wholog, fileno(fp));
+
+    for (recnum = 0; (fread(&pu, sizeof(pu), 1, fp) > 0); recnum++) {
+       /* Match for this NAS/Port record? */
+       if (STREQ(pu.NAS_name, idp->NAS_name) && STREQ(pu.NAS_port, nasport)) {
+           foundrec = recnum;
+           break;
+       }
+       /* Found a free slot on the way */
+       if (pu.username[0] == '\0') {
+           freerec = recnum;
+       }
+    }
+
+    /* This is a START record, so write a new record or update the existing
+     * one.  Note that we bzero(), so the strncpy()'s will truncate long
+     * names and always leave a null-terminated string. 
+     */
+
+    bzero(&pu, sizeof(pu));
+    strncpy(pu.username, idp->username, sizeof(pu.username) - 1);
+    strncpy(pu.NAS_name, idp->NAS_name, sizeof(pu.NAS_name) - 1);
+    strncpy(pu.NAS_port, nasport, sizeof(pu.NAS_port) - 1);
+    strncpy(pu.NAC_address, idp->NAC_address, sizeof(pu.NAC_address) - 1);
+
+    /* Already in DB? */
+    if (foundrec >= 0) {
+
+       if (debug & DEBUG_MAXSESS_FLAG) {
+           report(LOG_DEBUG,
+             "START record -- overwrite existing %s entry %d for %s %s/%s",
+                  wholog, foundrec, pu.NAS_name, pu.username, pu.NAS_port);
+       }
+       write_record(wholog, fp, &pu, sizeof(pu),
+                    foundrec * sizeof(struct peruser));
+       fclose(fp);
+       return;
+    }
+
+    /* Not found in DB, but we have a free slot */
+    if (freerec >= 0) {
+
+       write_record(wholog, fp, &pu, sizeof(pu),
+                    freerec * sizeof(struct peruser));
+
+       if (debug & DEBUG_MAXSESS_FLAG) {
+           report(LOG_DEBUG, "START record -- %s entry %d for %s %s/%s added",
+                  wholog, freerec, pu.NAS_name, pu.username, pu.NAS_port);
+       }
+       fclose(fp);
+       return;
+    }
+
+    /* No free slot. Add record at the end */
+    write_record(wholog, fp, &pu, sizeof(pu),
+                recnum * sizeof(struct peruser));
+
+    if (debug & DEBUG_MAXSESS_FLAG) {
+       report(LOG_DEBUG, "START record -- %s entry %d for %s %s/%s added",
+              wholog, recnum, pu.NAS_name, pu.username, pu.NAS_port);
+    }
+    fclose(fp);
+}
+
+
+/*
+ * Given a start or a stop accounting record, update the file of
+ * records which tracks who's logged on and where.
+ */
+loguser(rec)
+struct acct_rec *rec;
+{
+    struct identity *idp;
+    int i;
+
+    /* We're only interested in start/stop records */
+    if ((rec->acct_type != ACCT_TYPE_START) &&
+       (rec->acct_type != ACCT_TYPE_STOP)) {
+       return;
+    }
+    /* ignore command accounting records */
+    for (i = 0; i < rec->num_args; i++) {
+       char *avpair = rec->args[i];
+       if ((strncmp(avpair, "cmd=", 4) == 0) && strlen(avpair) > 4) {
+           return;
+       }
+    }
+
+    /* Extract and store just the port number, since the port names are
+     * different depending on whether this is an async interface or an exec
+     * line. */
+    idp = rec->identity;
+
+    switch (rec->acct_type) {
+    case ACCT_TYPE_START:
+       process_start_record(idp);
+       return;
+
+    case ACCT_TYPE_STOP:
+       process_stop_record(idp);
+       return;
+    }
+}
+
+/* Read up to n bytes from descriptor fd into array ptr with timeout t
+ * seconds.
+ *
+ * Return -1 on error, eof or timeout. Otherwise return number of
+ * bytes read. */
+
+int
+timed_read(fd, ptr, nbytes, timeout)
+int fd;
+u_char *ptr;
+int nbytes;
+int timeout;
+{
+    int nread;
+    fd_set readfds, exceptfds;
+    struct timeval tout;
+
+    tout.tv_sec = timeout;
+    tout.tv_usec = 0;
+
+    FD_ZERO(&readfds);
+    FD_SET(fd, &readfds);
+
+    FD_ZERO(&exceptfds);
+    FD_SET(fd, &exceptfds);
+
+    while (1) {
+       int status = select(fd + 1, &readfds, (fd_set *) NULL,
+                           &exceptfds, &tout);
+
+       if (status == 0) {
+           report(LOG_DEBUG, "%s: timeout reading fd %d", session.peer, fd);
+           return (-1);
+       }
+       if (status < 0) {
+           if (errno == EINTR)
+               continue;
+           report(LOG_DEBUG, "%s: error in select %s fd %d",
+                  session.peer, sys_errlist[errno], fd);
+           return (-1);
+       }
+       if (FD_ISSET(fd, &exceptfds)) {
+           report(LOG_DEBUG, "%s: exception on fd %d",
+                  session.peer, fd);
+           return (-1);
+       }
+       if (!FD_ISSET(fd, &readfds)) {
+           report(LOG_DEBUG, "%s: spurious return from select",
+                  session.peer);
+           continue;
+       }
+       nread = read(fd, ptr, nbytes);
+
+       if (nread < 0) {
+           if (errno == EINTR) {
+               continue;
+           }
+           report(LOG_DEBUG, "%s %s: error reading fd %d nread=%d %s",
+                session.peer, session.port, fd, nread, sys_errlist[errno]);
+           return (-1);        /* error */
+       }
+       if (nread == 0) {
+           return (-1);        /* eof */
+       }
+       return (nread);
+    }
+    /* NOTREACHED */
+}
+
+/*
+ * Contact a NAS (using finger) to check how many sessions this USER
+ * is currently running on it.
+ *
+ * Note that typically you run this code when you are in the middle of
+ * trying to login to a Cisco NAS on a given port. Because you are
+ * part way through a login when you do this, you can get inconsistent
+ * reports for that particular port about whether the user is
+ * currently logged in on it or not, so we ignore output which claims
+ * that the user is using that line currently.
+ *
+ * This is extremely Cisco specific -- finger formats appear to vary wildly.
+ * The format we're expecting is:
+
+    Line     User      Host(s)               Idle Location
+   0 con 0             idle                 never
+  18 vty 0   usr0      idle                    30 barley.cisco.com
+  19 vty 1   usr0      Virtual Exec             2
+  20 vty 2             idle                     0 barley.cisco.com
+
+ * Column zero contains a space or an asterisk character.  The line number
+ * starts at column 1 and is 3 digits wide.  User names start at column 13,
+ * with a maximum possible width of 10.
+ */
+
+static int
+ckfinger(user, nas, idp)
+char *user, *nas;
+struct identity *idp;
+{
+    struct sockaddr_in sin;
+    struct servent *serv;
+    int count, s, bufsize;
+    char *buf, *p, *pn;
+    int incr = 4096, slop = 32;
+    u_long inaddr;
+    char *curport = portname(idp->NAS_port);
+    char *name;
+
+    /* The finger service, aka port 79 */
+    serv = getservbyname("finger", "tcp");
+    if (serv) {
+       sin.sin_port = serv->s_port;
+    } else {
+       sin.sin_port = 79;
+    }
+
+    /* Get IP addr for the NAS */
+    inaddr = inet_addr(nas);
+    if (inaddr != -1) {
+       /* A dotted decimal address */
+       bcopy(&inaddr, &sin.sin_addr, sizeof(inaddr));
+       sin.sin_family = AF_INET;
+    } else {
+       struct hostent *host = gethostbyname(nas);
+
+       if (host == NULL) {
+           report(LOG_ERR, "ckfinger: gethostbyname %s failure: %s",
+                  nas, sys_errlist[errno]);
+           return (0);
+       }
+       bcopy(host->h_addr, &sin.sin_addr, host->h_length);
+       sin.sin_family = host->h_addrtype;
+    }
+
+    s = socket(AF_INET, SOCK_STREAM, 0);
+    if (s < 0) {
+       report(LOG_ERR, "ckfinger: socket: %s", sys_errlist[errno]);
+       return (0);
+    }
+    if (connect(s, (struct sockaddr *) & sin, sizeof(sin)) < 0) {
+       report(LOG_ERR, "ckfinger: connect failure %s", sys_errlist[errno]);
+       close(s);
+       return (0);
+    }
+    /* Read in the finger output into a single flat buffer */
+    buf = NULL;
+    bufsize = 0;
+    for (;;) {
+       int x;
+
+       buf = tac_realloc(buf, bufsize + incr + slop);
+       x = timed_read(s, buf + bufsize, incr, 10);
+       if (x <= 0) {
+           break;
+       }
+       bufsize += x;
+    }
+
+    /* Done talking here */
+    close(s);
+    buf[bufsize] = '\0';
+
+    if (bufsize <= 0) {
+       report(LOG_ERR, "ckfinger: finger failure");
+       free(buf);
+       return (0);
+    }
+    /* skip first line in buffer */
+    p = strchr(buf, '\n');
+    if (p) {
+       p++;
+    }
+    p = strchr(p, '\n');
+    if (p) {
+       p++;
+    }
+    /* Tally each time this user appears */
+    for (count = 0; p && *p; p = pn) {
+       int i, len, nmlen;
+       char nmbuf[11];
+
+       /* Find next line */
+       pn = strchr(p, '\n');
+       if (pn) {
+           ++pn;
+       }
+       /* Calculate line length */
+       if (pn) {
+           len = pn - p;
+       } else {
+           len = strlen(p);
+       }
+
+       /* Line too short -> ignore */
+       if (len < 14) {
+           continue;
+       }
+       /* Always ignore the NAS/port we're currently trying to login on. */
+       if (isdigit(*curport)) {
+           int thisport;
+
+           if (sscanf(p + 1, " %d", &thisport) == 1) {
+               if ((atoi(curport) == thisport) &&
+                   !strcmp(idp->NAS_name, nas)) {
+
+                   if (debug & DEBUG_MAXSESS_FLAG) {
+                       report(LOG_DEBUG, "%s session on %s/%s discounted",
+                              user, idp->NAS_name, idp->NAS_port);
+                   }
+                   continue;
+               }
+           }
+       }
+       /* Extract username, up to 10 chars wide, starting at char 13 */
+       nmlen = 0;
+       name = p + 13;
+       for (i = 0; *name && !isspace(*name) && (i < 10); i++) {
+           nmbuf[nmlen++] = *name++;
+       }
+       nmbuf[nmlen++] = '\0';
+
+       /* If name matches, up the count */
+       if (STREQ(user, nmbuf)) {
+           count++;
+
+           if (debug & DEBUG_MAXSESS_FLAG) {
+               char c = *pn;
+
+               *pn = '\0';
+               report(LOG_DEBUG, "%s matches: %s", user, p);
+               *pn = c;
+           }
+       }
+    }
+    free(buf);
+    return (count);
+}
+
+/*
+ * Verify how many sessions a user has according to the wholog file.
+ * Use finger to contact each NAS that wholog says has this user
+ * logged on.
+ */
+static int
+countusers_by_finger(idp)
+struct identity *idp;
+{
+    FILE *fp;
+    struct peruser pu;
+    int x, naddr, nsess, n;
+    char **addrs, *uname;
+
+    fp = fopen(wholog, "r+");
+    if (fp == NULL) {
+       return (0);
+    }
+    uname = idp->username;
+
+    /* Count sessions */
+    tac_lockfd(wholog, fileno(fp));
+    nsess = 0;
+    naddr = 0;
+    addrs = NULL;
+
+    while (fread(&pu, sizeof(pu), 1, fp) > 0) {
+       int dup;
+
+       /* Ignore records for everyone except this user */
+       if (strcmp(pu.username, uname)) {
+           continue;
+       }
+       /* Only check a given NAS once */
+       for (dup = 0, x = 0; x < naddr; ++x) {
+           if (STREQ(addrs[x], pu.NAS_name)) {
+               dup = 1;
+               break;
+           }
+       }
+       if (dup) {
+           continue;
+       }
+       /* Add this address to our list */
+       addrs = (char **) tac_realloc((char *) addrs,
+                                     (naddr + 1) * sizeof(char *));
+       addrs[naddr] = tac_strdup(pu.NAS_name);
+       naddr += 1;
+
+       /* Validate via finger */
+       if (debug & DEBUG_MAXSESS_FLAG) {
+           report(LOG_DEBUG, "Running finger on %s for user %s/%s",
+                  pu.NAS_name, uname, idp->NAS_port);
+       }
+       n = ckfinger(uname, pu.NAS_name, idp);
+
+       if (debug & DEBUG_MAXSESS_FLAG) {
+           report(LOG_DEBUG, "finger reports %d active session%s for %s on %s",
+                  n, (n == 1 ? "" : "s"), uname, pu.NAS_name);
+       }
+       nsess += n;
+    }
+
+    /* Clean up and return */
+    fclose(fp);
+    for (x = 0; x < naddr; ++x) {
+       free(addrs[x]);
+    }
+    free(addrs);
+
+    return (nsess);
+}
+
+/*
+ * Estimate how many sessions a named user currently owns by looking in
+ * the wholog file.
+ */
+static int
+countuser(idp)
+struct identity *idp;
+{
+    FILE *fp;
+    struct peruser pu;
+    int nsess;
+
+    /* Access log */
+    fp = fopen(wholog, "r+");
+    if (fp == NULL) {
+       return (0);
+    }
+    /* Count sessions. Skip any session associated with the current port. */
+    tac_lockfd(wholog, fileno(fp));
+    nsess = 0;
+    while (fread(&pu, sizeof(pu), 1, fp) > 0) {
+
+       /* Current user */
+       if (strcmp(pu.username, idp->username)) {
+           continue;
+       }
+       /* skip current port on current NAS */
+       if (STREQ(portname(pu.NAS_port), portname(idp->NAS_port)) &&
+           STREQ(pu.NAS_name, idp->NAS_name)) {
+           continue;
+       }
+       nsess += 1;
+    }
+
+    /* Clean up and return */
+    fclose(fp);
+    return (nsess);
+}
+
+/*
+ * is_async()
+ * Tell if the named NAS port is an async-like device.
+ *
+ * Finger reports async users, but not ISDN ones (yay).  So we can do
+ * a "slow" double check for async, but not ISDN.
+ */
+static int
+is_async(portname)
+char *portname;
+{
+    if (isdigit(*portname) || !strncmp(portname, "Async", 5) ||
+       !strncmp(portname, "tty", 3)) {
+       return (1);
+    }
+    return (0);
+}
+
+/*
+ * See if this user can have more sessions.
+ */
+int
+maxsess_check_count(user, data)
+char *user;
+struct author_data *data;
+{
+    int sess, maxsess;
+    struct identity *id;
+
+    /* No max session configured--don't check */
+    id = data->id;
+
+    maxsess = cfg_get_intvalue(user, TAC_IS_USER, S_maxsess, TAC_PLUS_RECURSE);
+    if (!maxsess) {
+       if (debug & (DEBUG_MAXSESS_FLAG | DEBUG_AUTHOR_FLAG)) {
+           report(LOG_DEBUG, "%s may run an unlimited number of sessions",
+                  user);
+       }
+       return (0);
+    }
+    /* Count sessions for this user by looking in our wholog file */
+    sess = countuser(id);
+
+    if (debug & (DEBUG_MAXSESS_FLAG | DEBUG_AUTHOR_FLAG)) {
+       report(LOG_DEBUG,
+              "user %s is running %d out of a maximum of %d sessions",
+              user, sess, maxsess);
+    }
+    if ((sess >= maxsess) && is_async(id->NAS_port)) {
+       /* If we have finger available, double check this count by contacting
+        * the NAS */
+       sess = countusers_by_finger(id);
+    }
+    /* If it's really too high, don't authorize more services */
+    if (sess >= maxsess) {
+       char buf[80];
+
+       sprintf(buf,
+               "Login failed; too many active sessions (%d maximum)",
+               maxsess);
+
+       data->msg = tac_strdup(buf);
+
+       if (debug & (DEBUG_AUTHOR_FLAG | DEBUG_MAXSESS_FLAG)) {
+           report(LOG_DEBUG, data->msg);
+       }
+       data->status = AUTHOR_STATUS_FAIL;
+       data->output_args = NULL;
+       data->num_out_args = 0;
+       return (1);
+    }
+    return (0);
+}
+
+#else                          /* MAXSESS */
+
+/*
+ * The following code is not needed or used. It exists solely to
+ * prevent compilers from "helpfully" complaining that this source
+ * file is empty when MAXSESS is not defined. This upsets novices
+ * building the software, and I get complaints
+ */
+
+static int dummy = 0;
+
+#endif                         /* MAXSESS */
diff --git a/md4.c b/md4.c
new file mode 100644 (file)
index 0000000..d99c84c
--- /dev/null
+++ b/md4.c
@@ -0,0 +1,303 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+/* MD4C.C - RSA Data Security, Inc., MD4 message-digest algorithm
+ * $Id$
+ */
+
+/* Copyright (C) 1990-2, RSA Data Security, Inc. All rights reserved.
+
+   License to copy and use this software is granted provided that it
+   is identified as the "RSA Data Security, Inc. MD4 Message-Digest
+   Algorithm" in all material mentioning or referencing this software
+   or this function.
+
+   License is also granted to make and use derivative works provided
+   that such works are identified as "derived from the RSA Data
+   Security, Inc. MD4 Message-Digest Algorithm" in all material
+   mentioning or referencing the derived work.
+
+   RSA Data Security, Inc. makes no representations concerning either
+   the merchantability of this software or the suitability of this
+   software for any particular purpose. It is provided "as is"
+   without express or implied warranty of any kind.
+
+   These notices must be retained in any copies of any part of this
+   documentation and/or software.
+ */
+
+
+#include <string.h>
+#include "md4.h"
+/*
+#include "master.h"
+#include <ciscolib.h>
+*/
+
+typedef unsigned char *POINTER;
+typedef unsigned short int UINT2;
+typedef unsigned long int UINT4;
+
+#define PROTO_LIST(list) ()
+#define const 
+
+/* Constants for MD4Transform routine.
+ */
+#define S11 3
+#define S12 7
+#define S13 11
+#define S14 19
+#define S21 3
+#define S22 5
+#define S23 9
+#define S24 13
+#define S31 3
+#define S32 9
+#define S33 11
+#define S34 15
+
+static void MD4Transform PROTO_LIST ((UINT4 [4], const unsigned char [64]));
+static void Encode PROTO_LIST
+  ((unsigned char *, UINT4 *, unsigned int));
+static void Decode PROTO_LIST
+  ((UINT4 *, const unsigned char *, unsigned int));
+
+static unsigned char PADDING[64] = {
+  0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+/* F, G and H are basic MD4 functions.
+ */
+#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
+#define G(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z)))
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+
+/* ROTATE_LEFT rotates x left n bits.
+ */
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))
+
+/* FF, GG and HH are transformations for rounds 1, 2 and 3 */
+/* Rotation is separate from addition to prevent recomputation */
+#define FF(a, b, c, d, x, s) { \
+    (a) += F ((b), (c), (d)) + (x); \
+    (a) = ROTATE_LEFT ((a), (s)); \
+  }
+#define GG(a, b, c, d, x, s) { \
+    (a) += G ((b), (c), (d)) + (x) + (UINT4)0x5a827999; \
+    (a) = ROTATE_LEFT ((a), (s)); \
+  }
+#define HH(a, b, c, d, x, s) { \
+    (a) += H ((b), (c), (d)) + (x) + (UINT4)0x6ed9eba1; \
+    (a) = ROTATE_LEFT ((a), (s)); \
+  }
+
+/* MD4 initialization. Begins an MD4 operation, writing a new context.
+ */
+void MD4Init (context)
+MD4_CTX *context;                                        /* context */
+{
+  context->count[0] = context->count[1] = 0;
+
+  /* Load magic initialization constants.
+   */
+  context->state[0] = 0x67452301;
+  context->state[1] = 0xefcdab89;
+  context->state[2] = 0x98badcfe;
+  context->state[3] = 0x10325476;
+}
+
+/* MD4 block update operation. Continues an MD4 message-digest
+     operation, processing another message block, and updating the
+     context.
+ */
+void MD4Update (context, input, inputLen)
+MD4_CTX *context;                                        /* context */
+const unsigned char *input;                                /* input block */
+unsigned int inputLen;                     /* length of input block */
+{
+  unsigned int i, index, partLen;
+
+  /* Compute number of bytes mod 64 */
+  index = (unsigned int)((context->count[0] >> 3) & 0x3F);
+  /* Update number of bits */
+  if ((context->count[0] += ((UINT4)inputLen << 3))
+      < ((UINT4)inputLen << 3))
+    context->count[1]++;
+  context->count[1] += ((UINT4)inputLen >> 29);
+
+  partLen = 64 - index;
+  /* Transform as many times as possible.
+   */
+  if (inputLen >= partLen) {
+    memcpy
+      ((POINTER)&context->buffer[index], (POINTER)input, partLen);
+    MD4Transform (context->state, context->buffer);
+
+    for (i = partLen; i + 63 < inputLen; i += 64)
+      MD4Transform (context->state, &input[i]);
+
+    index = 0;
+  }
+  else
+    i = 0;
+
+  /* Buffer remaining input */
+  memcpy
+    ((POINTER)&context->buffer[index], (POINTER)&input[i],
+     inputLen-i);
+}
+
+/* MD4 finalization. Ends an MD4 message-digest operation, writing the
+     the message digest and zeroizing the context.
+ */
+void MD4Final (digest, context)
+unsigned char digest[16];                         /* message digest */
+MD4_CTX *context;                                        /* context */
+{
+  unsigned char bits[8];
+  unsigned int index, padLen;
+
+  /* Save number of bits */
+  Encode (bits, context->count, 8);
+
+  /* Pad out to 56 mod 64.
+   */
+  index = (unsigned int)((context->count[0] >> 3) & 0x3f);
+  padLen = (index < 56) ? (56 - index) : (120 - index);
+  MD4Update (context, PADDING, padLen);
+
+  /* Append length (before padding) */
+  MD4Update (context, bits, 8);
+  /* Store state in digest */
+  Encode (digest, context->state, 16);
+
+  /* Zeroize sensitive information.
+   */
+  memset ((POINTER)context, 0, sizeof (*context));
+}
+
+/* MD4 basic transformation. Transforms state based on block.
+ */
+static void MD4Transform (state, block)
+UINT4 state[4];
+const unsigned char block[64];
+{
+  UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16];
+
+  Decode (x, block, 64);
+
+  /* Round 1 */
+  FF (a, b, c, d, x[ 0], S11); /* 1 */
+  FF (d, a, b, c, x[ 1], S12); /* 2 */
+  FF (c, d, a, b, x[ 2], S13); /* 3 */
+  FF (b, c, d, a, x[ 3], S14); /* 4 */
+  FF (a, b, c, d, x[ 4], S11); /* 5 */
+  FF (d, a, b, c, x[ 5], S12); /* 6 */
+  FF (c, d, a, b, x[ 6], S13); /* 7 */
+  FF (b, c, d, a, x[ 7], S14); /* 8 */
+  FF (a, b, c, d, x[ 8], S11); /* 9 */
+  FF (d, a, b, c, x[ 9], S12); /* 10 */
+  FF (c, d, a, b, x[10], S13); /* 11 */
+  FF (b, c, d, a, x[11], S14); /* 12 */
+  FF (a, b, c, d, x[12], S11); /* 13 */
+  FF (d, a, b, c, x[13], S12); /* 14 */
+  FF (c, d, a, b, x[14], S13); /* 15 */
+  FF (b, c, d, a, x[15], S14); /* 16 */
+
+  /* Round 2 */
+  GG (a, b, c, d, x[ 0], S21); /* 17 */
+  GG (d, a, b, c, x[ 4], S22); /* 18 */
+  GG (c, d, a, b, x[ 8], S23); /* 19 */
+  GG (b, c, d, a, x[12], S24); /* 20 */
+  GG (a, b, c, d, x[ 1], S21); /* 21 */
+  GG (d, a, b, c, x[ 5], S22); /* 22 */
+  GG (c, d, a, b, x[ 9], S23); /* 23 */
+  GG (b, c, d, a, x[13], S24); /* 24 */
+  GG (a, b, c, d, x[ 2], S21); /* 25 */
+  GG (d, a, b, c, x[ 6], S22); /* 26 */
+  GG (c, d, a, b, x[10], S23); /* 27 */
+  GG (b, c, d, a, x[14], S24); /* 28 */
+  GG (a, b, c, d, x[ 3], S21); /* 29 */
+  GG (d, a, b, c, x[ 7], S22); /* 30 */
+  GG (c, d, a, b, x[11], S23); /* 31 */
+  GG (b, c, d, a, x[15], S24); /* 32 */
+
+  /* Round 3 */
+  HH (a, b, c, d, x[ 0], S31); /* 33 */
+  HH (d, a, b, c, x[ 8], S32); /* 34 */
+  HH (c, d, a, b, x[ 4], S33); /* 35 */
+  HH (b, c, d, a, x[12], S34); /* 36 */
+  HH (a, b, c, d, x[ 2], S31); /* 37 */
+  HH (d, a, b, c, x[10], S32); /* 38 */
+  HH (c, d, a, b, x[ 6], S33); /* 39 */
+  HH (b, c, d, a, x[14], S34); /* 40 */
+  HH (a, b, c, d, x[ 1], S31); /* 41 */
+  HH (d, a, b, c, x[ 9], S32); /* 42 */
+  HH (c, d, a, b, x[ 5], S33); /* 43 */
+  HH (b, c, d, a, x[13], S34); /* 44 */
+  HH (a, b, c, d, x[ 3], S31); /* 45 */
+  HH (d, a, b, c, x[11], S32); /* 46 */
+  HH (c, d, a, b, x[ 7], S33); /* 47 */
+  HH (b, c, d, a, x[15], S34); /* 48 */
+
+  state[0] += a;
+  state[1] += b;
+  state[2] += c;
+  state[3] += d;
+
+  /* Zeroize sensitive information.
+   */
+  memset ((POINTER)x, 0, sizeof (x));
+}
+
+/* Encodes input (UINT4) into output (unsigned char). Assumes len is
+     a multiple of 4.
+ */
+static void Encode (output, input, len)
+unsigned char *output;
+UINT4 *input;
+unsigned int len;
+{
+  unsigned int i, j;
+
+  for (i = 0, j = 0; j < len; i++, j += 4) {
+    output[j] = (unsigned char)(input[i] & 0xff);
+    output[j+1] = (unsigned char)((input[i] >> 8) & 0xff);
+    output[j+2] = (unsigned char)((input[i] >> 16) & 0xff);
+    output[j+3] = (unsigned char)((input[i] >> 24) & 0xff);
+  }
+}
+
+/* Decodes input (unsigned char) into output (UINT4). Assumes len is
+     a multiple of 4.
+ */
+static void Decode (output, input, len)
+
+UINT4 *output;
+const unsigned char *input;
+unsigned int len;
+{
+  unsigned int i, j;
+
+  for (i = 0, j = 0; j < len; i++, j += 4)
+    output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) |
+      (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24);
+}
diff --git a/md4.h b/md4.h
new file mode 100644 (file)
index 0000000..d821229
--- /dev/null
+++ b/md4.h
@@ -0,0 +1,61 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+/* MD4.H - header file for MD4C.C
+ * $Id$
+ */
+
+/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
+   rights reserved.
+
+   License to copy and use this software is granted provided that it
+   is identified as the "RSA Data Security, Inc. MD4 Message-Digest
+   Algorithm" in all material mentioning or referencing this software
+   or this function.
+   License is also granted to make and use derivative works provided
+   that such works are identified as "derived from the RSA Data
+   Security, Inc. MD4 Message-Digest Algorithm" in all material
+   mentioning or referencing the derived work.
+
+   RSA Data Security, Inc. makes no representations concerning either
+   the merchantability of this software or the suitability of this
+   software for any particular purpose. It is provided "as is"
+   without express or implied warranty of any kind.
+
+   These notices must be retained in any copies of any part of this
+   documentation and/or software.
+ */
+
+#ifndef _MD4_H_
+#define _MD4_H_
+/* MD4 context. */
+typedef struct MD4Context {
+  unsigned long int state[4];  /* state (ABCD) */
+  unsigned long int count[2];  /* number of bits, modulo 2^64 (lsb first) */
+  unsigned char buffer[64];    /* input buffer */
+} MD4_CTX;
+
+void   MD4Init();
+void   MD4Update();
+void   MD4Final();
+char * MD4End();
+char * MD4File();
+char * MD4Data();
+
+#endif /* _MD4_H_ */
diff --git a/md5.c b/md5.c
new file mode 100644 (file)
index 0000000..06225b0
--- /dev/null
+++ b/md5.c
@@ -0,0 +1,375 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+/* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm
+ */
+
+/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
+   rights reserved.
+
+   License to copy and use this software is granted provided that it
+   is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+   Algorithm" in all material mentioning or referencing this software
+   or this function.
+
+   License is also granted to make and use derivative works provided
+   that such works are identified as "derived from the RSA Data
+   Security, Inc. MD5 Message-Digest Algorithm" in all material
+   mentioning or referencing the derived work.
+
+   RSA Data Security, Inc. makes no representations concerning either
+   the merchantability of this software or the suitability of this
+   software for any particular purpose. It is provided "as is"
+   without express or implied warranty of any kind.
+
+   These notices must be retained in any copies of any part of this
+   documentation and/or software.
+ */
+
+/*
+ * RFC 1321 version #includes global.h, but md5.h has been locally modified
+ * to contain all the information that RFC 1321's global.h contains.
+ */
+
+#include "md5.h"
+
+/* Constants for MD5Transform routine.
+ */
+
+
+#define S11 7
+#define S12 12
+#define S13 17
+#define S14 22
+#define S21 5
+#define S22 9
+#define S23 14
+#define S24 20
+#define S31 4
+#define S32 11
+#define S33 16
+#define S34 23
+#define S41 6
+#define S42 10
+#define S43 15
+#define S44 21
+
+static void MD5Transform PROTO_LIST((UINT4[4], unsigned char[64]));
+static void Encode PROTO_LIST
+ ((unsigned char *, UINT4 *, unsigned int));
+static void Decode PROTO_LIST
+ ((UINT4 *, unsigned char *, unsigned int));
+
+#if !defined(MD5_NEED_MEM_FUNCS)
+
+#define MD5_memcpy(out,in,len) memcpy(out, in, len)
+#define MD5_memset(ptr,val,len) memset(ptr, val, len)
+
+#else                          /* !defined(MD5_NEED_MEM_FUNCS) */
+
+static void MD5_memcpy PROTO_LIST((POINTER, POINTER, unsigned int));
+static void MD5_memset PROTO_LIST((POINTER, int, unsigned int));
+
+#endif                         /* !defined(MD5_NEED_MEM_FUNCS) */
+
+static unsigned char PADDING[64] = {
+    0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+/* F, G, H and I are basic MD5 functions.
+ */
+#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
+#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define I(x, y, z) ((y) ^ ((x) | (~z)))
+
+/* ROTATE_LEFT rotates x left n bits.
+ */
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))
+
+/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
+Rotation is separate from addition to prevent recomputation.
+ */
+#define FF(a, b, c, d, x, s, ac) { \
+ (a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+  }
+#define GG(a, b, c, d, x, s, ac) { \
+ (a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+  }
+#define HH(a, b, c, d, x, s, ac) { \
+ (a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+  }
+#define II(a, b, c, d, x, s, ac) { \
+ (a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+  }
+
+/* MD5 initialization. Begins an MD5 operation, writing a new context.
+ */
+void
+MD5Init(context)
+MD5_CTX *context;              /* context */
+{
+    context->count[0] = context->count[1] = 0;
+    /* Load magic initialization constants. */
+    context->state[0] = 0x67452301;
+    context->state[1] = 0xefcdab89;
+    context->state[2] = 0x98badcfe;
+    context->state[3] = 0x10325476;
+}
+
+/* MD5 block update operation. Continues an MD5 message-digest
+   operation, processing another message block, and updating the
+   context.
+   */
+void
+MD5Update(context, input, inputLen)
+MD5_CTX *context;              /* context */
+unsigned char *input;          /* input block */
+unsigned int inputLen;         /* length of input block */
+{
+    unsigned int i, index, partLen;
+
+    /* Compute number of bytes mod 64 */
+    index = (unsigned int) ((context->count[0] >> 3) & 0x3F);
+
+    /* Update number of bits */
+    if ((context->count[0] += ((UINT4) inputLen << 3))
+       < ((UINT4) inputLen << 3))
+       context->count[1]++;
+    context->count[1] += ((UINT4) inputLen >> 29);
+
+    partLen = 64 - index;
+
+    /* Transform as many times as possible. */
+    if (inputLen >= partLen) {
+       MD5_memcpy
+           ((POINTER) & context->buffer[index], (POINTER) input, partLen);
+       MD5Transform(context->state, context->buffer);
+
+       for (i = partLen; i + 63 < inputLen; i += 64)
+           MD5Transform(context->state, &input[i]);
+
+       index = 0;
+    } else
+       i = 0;
+
+    /* Buffer remaining input */
+    MD5_memcpy
+       ((POINTER) & context->buffer[index], (POINTER) & input[i],
+        inputLen - i);
+}
+
+/* MD5 finalization. Ends an MD5 message-digest operation, writing the
+   the message digest and zeroizing the context.
+   */
+void
+MD5Final(digest, context)
+unsigned char digest[16];      /* message digest */
+MD5_CTX *context;              /* context */
+{
+    unsigned char bits[8];
+    unsigned int index, padLen;
+
+    /* Save number of bits */
+    Encode(bits, context->count, 8);
+
+    /* Pad out to 56 mod 64. */
+    index = (unsigned int) ((context->count[0] >> 3) & 0x3f);
+    padLen = (index < 56) ? (56 - index) : (120 - index);
+    MD5Update(context, PADDING, padLen);
+
+    /* Append length (before padding) */
+    MD5Update(context, bits, 8);
+
+    /* Store state in digest */
+    Encode(digest, context->state, 16);
+
+    /* Zeroize sensitive information. */
+    MD5_memset((POINTER) context, 0, sizeof(*context));
+}
+
+/* MD5 basic transformation. Transforms state based on block.
+ */
+static void
+MD5Transform(state, block)
+UINT4 state[4];
+unsigned char block[64];
+{
+    UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16];
+
+    Decode(x, block, 64);
+
+    /* Round 1 */
+    FF(a, b, c, d, x[0], S11, 0xd76aa478);     /* 1 */
+    FF(d, a, b, c, x[1], S12, 0xe8c7b756);     /* 2 */
+    FF(c, d, a, b, x[2], S13, 0x242070db);     /* 3 */
+    FF(b, c, d, a, x[3], S14, 0xc1bdceee);     /* 4 */
+    FF(a, b, c, d, x[4], S11, 0xf57c0faf);     /* 5 */
+    FF(d, a, b, c, x[5], S12, 0x4787c62a);     /* 6 */
+    FF(c, d, a, b, x[6], S13, 0xa8304613);     /* 7 */
+    FF(b, c, d, a, x[7], S14, 0xfd469501);     /* 8 */
+    FF(a, b, c, d, x[8], S11, 0x698098d8);     /* 9 */
+    FF(d, a, b, c, x[9], S12, 0x8b44f7af);     /* 10 */
+    FF(c, d, a, b, x[10], S13, 0xffff5bb1);    /* 11 */
+    FF(b, c, d, a, x[11], S14, 0x895cd7be);    /* 12 */
+    FF(a, b, c, d, x[12], S11, 0x6b901122);    /* 13 */
+    FF(d, a, b, c, x[13], S12, 0xfd987193);    /* 14 */
+    FF(c, d, a, b, x[14], S13, 0xa679438e);    /* 15 */
+    FF(b, c, d, a, x[15], S14, 0x49b40821);    /* 16 */
+
+    /* Round 2 */
+    GG(a, b, c, d, x[1], S21, 0xf61e2562);     /* 17 */
+    GG(d, a, b, c, x[6], S22, 0xc040b340);     /* 18 */
+    GG(c, d, a, b, x[11], S23, 0x265e5a51);    /* 19 */
+    GG(b, c, d, a, x[0], S24, 0xe9b6c7aa);     /* 20 */
+    GG(a, b, c, d, x[5], S21, 0xd62f105d);     /* 21 */
+    GG(d, a, b, c, x[10], S22, 0x2441453);     /* 22 */
+    GG(c, d, a, b, x[15], S23, 0xd8a1e681);    /* 23 */
+    GG(b, c, d, a, x[4], S24, 0xe7d3fbc8);     /* 24 */
+    GG(a, b, c, d, x[9], S21, 0x21e1cde6);     /* 25 */
+    GG(d, a, b, c, x[14], S22, 0xc33707d6);    /* 26 */
+    GG(c, d, a, b, x[3], S23, 0xf4d50d87);     /* 27 */
+    GG(b, c, d, a, x[8], S24, 0x455a14ed);     /* 28 */
+    GG(a, b, c, d, x[13], S21, 0xa9e3e905);    /* 29 */
+    GG(d, a, b, c, x[2], S22, 0xfcefa3f8);     /* 30 */
+    GG(c, d, a, b, x[7], S23, 0x676f02d9);     /* 31 */
+    GG(b, c, d, a, x[12], S24, 0x8d2a4c8a);    /* 32 */
+
+    /* Round 3 */
+    HH(a, b, c, d, x[5], S31, 0xfffa3942);     /* 33 */
+    HH(d, a, b, c, x[8], S32, 0x8771f681);     /* 34 */
+    HH(c, d, a, b, x[11], S33, 0x6d9d6122);    /* 35 */
+    HH(b, c, d, a, x[14], S34, 0xfde5380c);    /* 36 */
+    HH(a, b, c, d, x[1], S31, 0xa4beea44);     /* 37 */
+    HH(d, a, b, c, x[4], S32, 0x4bdecfa9);     /* 38 */
+    HH(c, d, a, b, x[7], S33, 0xf6bb4b60);     /* 39 */
+    HH(b, c, d, a, x[10], S34, 0xbebfbc70);    /* 40 */
+    HH(a, b, c, d, x[13], S31, 0x289b7ec6);    /* 41 */
+    HH(d, a, b, c, x[0], S32, 0xeaa127fa);     /* 42 */
+    HH(c, d, a, b, x[3], S33, 0xd4ef3085);     /* 43 */
+    HH(b, c, d, a, x[6], S34, 0x4881d05);      /* 44 */
+    HH(a, b, c, d, x[9], S31, 0xd9d4d039);     /* 45 */
+    HH(d, a, b, c, x[12], S32, 0xe6db99e5);    /* 46 */
+    HH(c, d, a, b, x[15], S33, 0x1fa27cf8);    /* 47 */
+    HH(b, c, d, a, x[2], S34, 0xc4ac5665);     /* 48 */
+
+    /* Round 4 */
+    II(a, b, c, d, x[0], S41, 0xf4292244);     /* 49 */
+    II(d, a, b, c, x[7], S42, 0x432aff97);     /* 50 */
+    II(c, d, a, b, x[14], S43, 0xab9423a7);    /* 51 */
+    II(b, c, d, a, x[5], S44, 0xfc93a039);     /* 52 */
+    II(a, b, c, d, x[12], S41, 0x655b59c3);    /* 53 */
+    II(d, a, b, c, x[3], S42, 0x8f0ccc92);     /* 54 */
+    II(c, d, a, b, x[10], S43, 0xffeff47d);    /* 55 */
+    II(b, c, d, a, x[1], S44, 0x85845dd1);     /* 56 */
+    II(a, b, c, d, x[8], S41, 0x6fa87e4f);     /* 57 */
+    II(d, a, b, c, x[15], S42, 0xfe2ce6e0);    /* 58 */
+    II(c, d, a, b, x[6], S43, 0xa3014314);     /* 59 */
+    II(b, c, d, a, x[13], S44, 0x4e0811a1);    /* 60 */
+    II(a, b, c, d, x[4], S41, 0xf7537e82);     /* 61 */
+    II(d, a, b, c, x[11], S42, 0xbd3af235);    /* 62 */
+    II(c, d, a, b, x[2], S43, 0x2ad7d2bb);     /* 63 */
+    II(b, c, d, a, x[9], S44, 0xeb86d391);     /* 64 */
+
+    state[0] += a;
+    state[1] += b;
+    state[2] += c;
+    state[3] += d;
+
+    /* Zeroize sensitive information. */
+    MD5_memset((POINTER) x, 0, sizeof(x));
+}
+
+/* Encodes input (UINT4) into output (unsigned char). Assumes len is
+   a multiple of 4.
+   */
+static void
+Encode(output, input, len)
+unsigned char *output;
+UINT4 *input;
+unsigned int len;
+{
+    unsigned int i, j;
+
+    for (i = 0, j = 0; j < len; i++, j += 4) {
+       output[j] = (unsigned char) (input[i] & 0xff);
+       output[j + 1] = (unsigned char) ((input[i] >> 8) & 0xff);
+       output[j + 2] = (unsigned char) ((input[i] >> 16) & 0xff);
+       output[j + 3] = (unsigned char) ((input[i] >> 24) & 0xff);
+    }
+}
+
+/* Decodes input (unsigned char) into output (UINT4). Assumes len is
+   a multiple of 4.
+   */
+static void
+Decode(output, input, len)
+UINT4 *output;
+unsigned char *input;
+unsigned int len;
+{
+    unsigned int i, j;
+
+    for (i = 0, j = 0; j < len; i++, j += 4)
+       output[i] = ((UINT4) input[j]) | (((UINT4) input[j + 1]) << 8) |
+           (((UINT4) input[j + 2]) << 16) | (((UINT4) input[j + 3]) << 24);
+}
+
+#if defined(MD5_NEED_MEM_FUNC)
+
+/* Note: Replace "for loop" with standard memcpy if possible.
+ */
+static void
+MD5_memcpy(output, input, len)
+POINTER output;
+POINTER input;
+unsigned int len;
+{
+    unsigned int i;
+
+    for (i = 0; i < len; i++)
+       output[i] = input[i];
+}
+
+
+/* Note: Replace "for loop" with standard memset if possible.
+ */
+static void
+MD5_memset(output, value, len)
+POINTER output;
+int value;
+unsigned int len;
+{
+    unsigned int i;
+
+    for (i = 0; i < len; i++)
+       ((char *) output)[i] = (char) value;
+}
+
+#endif                         /* defined(MD5_NEED_MEM_FUNC) */
diff --git a/md5.h b/md5.h
new file mode 100644 (file)
index 0000000..097156e
--- /dev/null
+++ b/md5.h
@@ -0,0 +1,81 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+
+/*
+ * MD5.H - header file for MD5C.C
+ */
+
+/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
+ * rights reserved.
+ *
+ * License to copy and use this software is granted provided that it
+ * is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+ * Algorithm" in all material mentioning or referencing this software
+ * or this function.
+ *
+ * License is also granted to make and use derivative works provided
+ * that such works are identified as "derived from the RSA Data
+ * Security, Inc. MD5 Message-Digest Algorithm" in all material
+ * mentioning or referencing the derived work.
+ *
+ * RSA Data Security, Inc. makes no representations concerning either
+ * the merchantability of this software or the suitability of this
+ * software for any particular purpose. It is provided "as is"
+ * without express or implied warranty of any kind.
+ *
+ * These notices must be retained in any copies of any part of this
+ * documentation and/or software.
+ */
+
+#ifndef _MD5_H
+#define _MD5_H
+
+/* delineate the cisco changes to the RSA supplied module */
+#define CISCO_MD5_MODS
+
+#if defined(CISCO_MD5_MODS)
+
+/* typedef a 32-bit type */
+typedef unsigned long int UINT4;
+
+/* typedef a generic pointer type */
+typedef unsigned char *POINTER;
+
+/* enable prototyping */
+/* #define PROTO_LIST(x) x */
+/* disable prototyping */
+#define PROTO_LIST(x) ()
+
+#endif /* defined(CISCO_MD5_MODS) */
+
+/* MD5 context. */
+typedef struct {
+  UINT4 state[4];                                   /* state (ABCD) */
+  UINT4 count[2];        /* number of bits, modulo 2^64 (lsb first) */
+  unsigned char buffer[64];                         /* input buffer */
+} MD5_CTX;
+
+void MD5Init PROTO_LIST ((MD5_CTX *));
+void MD5Update PROTO_LIST
+  ((MD5_CTX *, unsigned char *, unsigned int));
+void MD5Final PROTO_LIST ((unsigned char [16], MD5_CTX *));
+
+
+#endif                          /* _MD5_H */
diff --git a/mschap.h b/mschap.h
new file mode 100644 (file)
index 0000000..192d9d2
--- /dev/null
+++ b/mschap.h
@@ -0,0 +1,20 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+#define MSCHAP_KEY "Contact Microsoft for the MSCHAP key"
diff --git a/packet.c b/packet.c
new file mode 100644 (file)
index 0000000..f3f7023
--- /dev/null
+++ b/packet.c
@@ -0,0 +1,552 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+#include "tac_plus.h"
+
+/* Everything to do with reading and writing packets */
+
+/* send an accounting response packet */
+send_acct_reply(status, msg, data)
+    u_char status;
+    char *msg, *data;
+{
+    u_char *pak, *p;
+    HDR *hdr;
+    int len;
+    struct acct_reply *reply;
+    int msg_len, data_len;
+
+    msg_len = msg ? strlen(msg) : 0;
+    data_len = data ? strlen(data) : 0;
+
+    len = TAC_PLUS_HDR_SIZE + TAC_ACCT_REPLY_FIXED_FIELDS_SIZE + msg_len + data_len;
+
+    pak = (u_char *) tac_malloc(len);
+    reply = (struct acct_reply *) (pak + TAC_PLUS_HDR_SIZE);
+    hdr = (HDR *) pak;
+
+    bzero(pak, len);
+
+    hdr->version = TAC_PLUS_VER_0;
+    hdr->type = TAC_PLUS_ACCT;
+    hdr->seq_no = ++session.seq_no;
+    hdr->encryption = TAC_PLUS_CLEAR;
+    hdr->session_id = htonl(session.session_id);
+    hdr->datalength = htonl(len - TAC_PLUS_HDR_SIZE);
+
+    reply->status = status;
+    reply->msg_len  = msg_len;
+    reply->data_len = data_len;
+
+    p = pak + TAC_PLUS_HDR_SIZE + TAC_ACCT_REPLY_FIXED_FIELDS_SIZE;
+    bcopy(msg, p, msg_len);
+    p += msg_len;
+
+    bcopy(data, p, data_len);
+
+    if (debug & DEBUG_PACKET_FLAG) {
+       report(LOG_DEBUG, "Writing %s size=%d",
+              summarise_outgoing_packet_type(pak), len);
+       dump_tacacs_pak(pak);
+    }
+
+    reply->msg_len = ntohs(reply->msg_len);
+    reply->data_len = ntohs(reply->data_len);
+
+    write_packet(pak);
+    free(pak);
+}
+
+/* send an authorization reply packet */
+send_author_reply(status, msg, data, arg_cnt, args)
+u_char status;
+char *msg;
+char *data;
+int arg_cnt;
+char **args;
+{
+    u_char *pak, *p;
+    HDR *hdr;
+    struct author_reply *reply;
+    int msg_len;
+    int len;
+    int data_len;
+    int i;
+
+    data_len = (data ? strlen(data) : 0);
+    msg_len  = (msg  ? strlen(msg)  : 0);
+
+    /* start calculating final packet size */
+    len = TAC_PLUS_HDR_SIZE + TAC_AUTHOR_REPLY_FIXED_FIELDS_SIZE + msg_len +
+       data_len;
+
+    for (i=0; i < arg_cnt; i++) {
+       /* space for the arg and its length */
+       len += strlen(args[i]) + 1;
+    }
+
+    pak = (u_char *) tac_malloc(len);
+
+    bzero(pak, len);
+
+    hdr = (HDR *) pak;
+
+    reply = (struct author_reply *) (pak + TAC_PLUS_HDR_SIZE);
+
+    hdr->version = TAC_PLUS_VER_0;
+    hdr->type = TAC_PLUS_AUTHOR;
+    hdr->seq_no = ++session.seq_no;
+    hdr->encryption = TAC_PLUS_CLEAR;
+    hdr->session_id = htonl(session.session_id);
+    hdr->datalength = htonl(len - TAC_PLUS_HDR_SIZE);
+
+    reply->status   = status;
+    reply->msg_len  = msg_len;
+    reply->data_len = data_len;
+    reply->arg_cnt  = arg_cnt;
+
+    p = pak + TAC_PLUS_HDR_SIZE + TAC_AUTHOR_REPLY_FIXED_FIELDS_SIZE;
+
+    /* place arg sizes into packet  */
+    for (i=0; i < arg_cnt; i++) {
+       *p++ = strlen(args[i]);
+    }
+
+    bcopy(msg, p, msg_len);
+    p += msg_len;
+
+    bcopy(data, p, data_len);
+    p += data_len;
+
+    /* copy arg bodies into packet */
+    for (i=0; i < arg_cnt; i++) {
+       int arglen = strlen(args[i]);
+
+       bcopy(args[i], p, arglen);
+       p += arglen;
+    }
+
+    if (debug & DEBUG_PACKET_FLAG) {
+       report(LOG_DEBUG, "Writing %s size=%d",
+              summarise_outgoing_packet_type(pak), len);
+       dump_tacacs_pak(pak); 
+    }
+    
+    reply->msg_len  = htons(reply->msg_len);
+    reply->data_len = htons(reply->data_len);
+
+    write_packet(pak);
+    free(pak);
+}
+
+
+/* Send an authentication reply packet indicating an error has
+   occurred. msg is a null terminated character string */
+
+send_authen_error(msg)
+char *msg;
+{
+    char buf[255];
+
+    sprintf(buf, "%s %s: %s", session.peer, session.port, msg);
+    report(LOG_ERR, buf);
+    send_authen_reply(TAC_PLUS_AUTHEN_STATUS_ERROR,
+                     buf,
+                     strlen(buf),
+                     NULL,
+                     0,
+                     0);
+}
+
+/* create and send an authentication reply packet from tacacs+ to a NAS */
+
+send_authen_reply(status, msg, msg_len, data, data_len, flags)
+int status;
+char *msg;
+u_short msg_len;
+char *data;
+u_short data_len;
+u_char flags;
+{
+    u_char *pak, *p;
+    HDR *hdr;
+    struct authen_reply *reply;
+
+    int len = TAC_PLUS_HDR_SIZE + TAC_AUTHEN_REPLY_FIXED_FIELDS_SIZE + msg_len + data_len;
+
+    pak = (u_char *) tac_malloc(len);
+    bzero(pak, len);
+
+    hdr = (HDR *) pak;
+    reply = (struct authen_reply *) (pak + TAC_PLUS_HDR_SIZE);
+
+    hdr->version = session.version;
+    hdr->type = TAC_PLUS_AUTHEN;
+    hdr->seq_no = ++session.seq_no;
+    hdr->encryption = TAC_PLUS_CLEAR;
+    hdr->session_id = htonl(session.session_id);
+    hdr->datalength = htonl(TAC_AUTHEN_REPLY_FIXED_FIELDS_SIZE + msg_len + data_len);
+
+    reply->status = status;
+    reply->msg_len = msg_len;
+    reply->data_len = data_len;
+    reply->flags = flags;
+
+    p = pak + TAC_PLUS_HDR_SIZE + TAC_AUTHEN_REPLY_FIXED_FIELDS_SIZE;
+
+    bcopy(msg, p, msg_len);
+    p += msg_len;
+    bcopy(data, p, data_len);
+
+    if (debug & DEBUG_PACKET_FLAG) {
+       report(LOG_DEBUG, "Writing %s size=%d",
+              summarise_outgoing_packet_type(pak), len);
+       dump_tacacs_pak(pak);
+    }
+
+    reply->msg_len = htons(reply->msg_len);
+    reply->data_len = htons(reply->data_len);
+
+    write_packet(pak);
+    free(pak);
+}
+
+
+/* read an authentication GETDATA packet from a NAS. Return 0 on failure */
+u_char *
+get_authen_continue()
+{
+    HDR *hdr;
+    u_char *pak, *read_packet();
+    struct authen_cont *cont;
+    char msg[255];
+
+    pak = read_packet();
+    if (!pak)
+       return(NULL);
+    hdr = (HDR *) pak;
+    cont = (struct authen_cont *) (pak + TAC_PLUS_HDR_SIZE);
+
+    if ((hdr->type != TAC_PLUS_AUTHEN) || (hdr->seq_no <= 1)) {
+       sprintf(msg,
+         "%s: Bad packet type=%d/seq no=%d when expecting authentication cont",
+               session.peer, hdr->type, hdr->seq_no);
+       report(LOG_ERR, msg);
+       send_authen_error(msg);
+       return(NULL);
+    }
+
+    cont->user_msg_len  = ntohs(cont->user_msg_len);
+    cont->user_data_len = ntohs(cont->user_data_len);
+
+    if (TAC_AUTHEN_CONT_FIXED_FIELDS_SIZE + 
+       cont->user_msg_len + 
+       cont->user_data_len !=
+       ntohl(hdr->datalength)) {
+       char *m = "Illegally sized authentication cont packet";
+       report(LOG_ERR, "%s: %s", session.peer, m);
+       send_authen_error(m);
+       return(NULL);
+    }
+
+    if (debug & DEBUG_PACKET_FLAG)
+       dump_nas_pak(pak);
+
+    return (pak);
+}
+
+/* Read n bytes from descriptor fd into array ptr with timeout t
+ * seconds. Note the timeout is applied to each read, not for the
+ * overall operation.
+ *
+ * Return -1 on error, eof or timeout. Otherwise return number of
+ * bytes read. */
+
+int
+sockread(fd, ptr, nbytes, timeout)
+int fd;
+u_char *ptr;
+int nbytes;
+int timeout;
+{
+    int nleft, nread;
+    fd_set readfds, exceptfds;
+    struct timeval tout;
+
+    tout.tv_sec = timeout;
+    tout.tv_usec = 0;
+
+    FD_ZERO(&readfds);
+    FD_SET(fd, &readfds);
+
+    FD_ZERO(&exceptfds);
+    FD_SET(fd, &exceptfds);
+
+    nleft = nbytes;
+
+    while (nleft > 0) {
+       int status = select(fd + 1, &readfds, (fd_set *) NULL, 
+                           &exceptfds, &tout);
+
+       if (status == 0) {
+           report(LOG_DEBUG, "%s: timeout reading fd %d", session.peer, fd);
+           return(-1);
+       }
+       if (status < 0) {
+           if (errno == EINTR)
+               continue;
+           report(LOG_DEBUG, "%s: error in select %s fd %d", 
+                  session.peer, sys_errlist[errno], fd);
+           return (-1);
+       }
+       if (FD_ISSET(fd, &exceptfds)) {
+           report(LOG_DEBUG, "%s: exception on fd %d",
+                  session.peer, fd);
+           return (-1);
+       }
+       if (!FD_ISSET(fd, &readfds)) {
+           report(LOG_DEBUG, "%s: spurious return from select",
+                  session.peer);
+           continue;
+       }
+    again:
+       nread = read(fd, ptr, nleft);
+
+       if (nread < 0) {
+           if (errno == EINTR)
+               goto again;
+           report(LOG_DEBUG, "%s %s: error reading fd %d nread=%d %s", 
+                  session.peer, session.port, fd, nread, sys_errlist[errno]);
+           return (-1);        /* error */
+
+       } else if (nread == 0) {
+           report(LOG_DEBUG, "%s %s: fd %d eof (connection closed)", 
+                  session.peer, session.port, fd);
+           return (-1);        /* eof */
+       }
+       nleft -= nread;
+       if (nleft) 
+           ptr += nread;
+    }
+    return (nbytes - nleft);
+}
+
+/* Write n bytes to descriptor fd from array ptr with timeout t
+ * seconds. Note the timeout is applied to each write, not for the
+ * overall operation.
+ *
+ * Return -1 on error, eof or timeout. Otherwise return number of
+ * bytes written. */
+
+int
+sockwrite(fd, ptr, bytes, timeout)
+int fd;
+u_char *ptr;
+int bytes;
+int timeout;
+{
+    int remaining, sent;
+    fd_set writefds, exceptfds;
+    struct timeval tout;
+
+    sent = 0;
+
+    tout.tv_sec = timeout;
+    tout.tv_usec = 0;
+
+    FD_ZERO(&writefds);
+    FD_SET(fd, &writefds);
+
+    FD_ZERO(&exceptfds);
+    FD_SET(fd, &exceptfds);
+
+    remaining = bytes;
+
+    while (remaining > 0) {
+       int status = select(fd + 1, (fd_set *) NULL, 
+                           &writefds, &exceptfds, &tout);
+
+       if (status == 0) {
+           report(LOG_DEBUG, "%s: timeout writing to fd %d", 
+                  session.peer, fd);
+           return (-1);
+       }
+       if (status < 0) {
+           report(LOG_DEBUG, "%s: error in select fd %d", 
+                  session.peer, fd);
+           return (-1);
+       }
+       if (FD_ISSET(fd, &exceptfds)) {
+           report(LOG_DEBUG, "%s: exception on fd %d",
+                  session.peer, fd);
+           return (sent);      /* error */
+       }
+
+       if (!FD_ISSET(fd, &writefds)) {
+           report(LOG_DEBUG, "%s: spurious return from select",
+                  session.peer);
+           continue;
+       }
+       sent = write(fd, ptr, remaining);
+
+       if (sent <= 0) {
+           report(LOG_DEBUG, "%s: error writing fd %d sent=%d", 
+                  session.peer, fd, sent);
+           return (sent);      /* error */
+       }
+       remaining -= sent;
+       ptr += sent;
+    }
+    return (bytes - remaining);
+}
+
+/* read a packet from the wire, and decrypt it. Increment the global
+ seq_no return NULL on failure */
+
+u_char *
+read_packet()
+{
+    HDR hdr;
+    u_char *pkt, *data;
+    int len;
+    char *tkey;
+
+    if (debug & DEBUG_PACKET_FLAG)
+       report(LOG_DEBUG, "Waiting for packet");
+
+    /* read a packet header */
+    len = sockread(session.sock, (u_char *) & hdr, TAC_PLUS_HDR_SIZE, TAC_PLUS_READ_TIMEOUT);
+    if (len != TAC_PLUS_HDR_SIZE) {
+       report(LOG_DEBUG, "Read %d bytes from %s %s, expecting %d", 
+              len, session.peer, session.port, TAC_PLUS_HDR_SIZE);
+       return(NULL);
+    }
+
+    if ((hdr.version & TAC_PLUS_MAJOR_VER_MASK) != TAC_PLUS_MAJOR_VER) {
+       report(LOG_ERR, 
+              "%s: Illegal major version specified: found %d wanted %d\n",
+              session.peer, hdr.version, TAC_PLUS_MAJOR_VER);
+       return(NULL);
+    }
+
+    /* get memory for the packet */
+    len = TAC_PLUS_HDR_SIZE + ntohl(hdr.datalength);
+    if ((ntohl(hdr.datalength) & ~0xffffUL) ||
+       len < TAC_PLUS_HDR_SIZE || len > 0x10000) {
+       report(LOG_ERR,
+              "%s: Illegal data size: %lu\n",
+              session.peer, ntohl(hdr.datalength));
+       return(NULL);
+    }
+    pkt = (u_char *) tac_malloc(len);
+
+    /* initialise the packet */
+    bcopy(&hdr, pkt, TAC_PLUS_HDR_SIZE);
+
+    /* the data start here */
+    data = pkt + TAC_PLUS_HDR_SIZE;
+
+    /* read the rest of the packet data */
+    if (sockread(session.sock, data, ntohl(hdr.datalength), 
+                TAC_PLUS_READ_TIMEOUT) !=
+       ntohl(hdr.datalength)) {
+       report(LOG_ERR, "%s: start_session: bad socket read", session.peer);
+       return (NULL);
+    }
+    session.seq_no++;          /* should now equal that of incoming packet */
+    session.last_exch = time(NULL);
+
+    if (session.seq_no != hdr.seq_no) {
+       report(LOG_ERR, "%s: Illegal session seq # %d != packet seq # %d",
+              session.peer,
+              session.seq_no, hdr.seq_no);
+       return (NULL);
+    }
+
+    /* decrypt the data portion */
+    if ( !(tkey=(char *)cfg_get_host_key(session.peer)) )
+               tkey = session.key;     
+
+    if (md5_xor((HDR *)pkt, data, tkey)) {
+       report(LOG_ERR, "%s: start_session error decrypting data",
+              session.peer);
+       return (NULL);
+    }
+
+    if (debug & DEBUG_PACKET_FLAG)
+       report(LOG_DEBUG, "Read %s size=%d",
+              summarise_incoming_packet_type(pkt), len);
+
+    session.version = hdr.version;
+
+    return (pkt);
+}
+
+/* write a packet to the wire, encrypting it */
+write_packet(pak)
+u_char *pak;
+{
+    HDR *hdr = (HDR *) pak;
+    u_char *data;
+    int len;
+    char *tkey;
+
+    len = TAC_PLUS_HDR_SIZE + ntohl(hdr->datalength);
+
+    /* the data start here */
+    data = pak + TAC_PLUS_HDR_SIZE;
+
+    /* encrypt the data portion */
+   if ( !(tkey=(char *)cfg_get_host_key(session.peer)) )
+                tkey = session.key;
+
+   if (md5_xor((HDR *)pak, data, tkey)) {
+       report(LOG_ERR, "%s: write_packet: error encrypting data", session.peer);
+       return (-1);
+    }
+
+    if (sockwrite(session.sock, pak, len, TAC_PLUS_WRITE_TIMEOUT) != len) {
+       return (-1);
+    }
+    session.last_exch = time(NULL);
+    return (0);
+}
+
+send_error_reply(type, msg)
+int type;
+char *msg;
+{
+    switch (type) {
+    case TAC_PLUS_AUTHEN:
+       send_authen_error(msg);
+       return;
+
+    case TAC_PLUS_AUTHOR:
+       send_author_reply(AUTHOR_STATUS_ERROR, msg, NULL, 0, NULL);
+       return;
+
+    case TAC_PLUS_ACCT:
+       send_acct_reply(TAC_PLUS_ACCT_STATUS_ERROR, msg, NULL);
+       return;
+
+    default:
+       report(LOG_ERR, "Illegal type %d for send_error_reply", type);
+       return;
+    }
+}
diff --git a/parse.c b/parse.c
new file mode 100644 (file)
index 0000000..6ac6908
--- /dev/null
+++ b/parse.c
@@ -0,0 +1,254 @@
+/*
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+/* Keywords of the configuration language */
+
+#include "tac_plus.h"
+
+static void *wordtable[HASH_TAB_SIZE]; /* Table of keyword declarations */
+
+struct keyword {
+    char *word;
+    void *hash;
+    u_char value;
+};
+
+typedef struct keyword KEYWORD;
+
+static void
+declare(name, value)
+    char *name;
+    int value;
+{
+    KEYWORD *n;
+    KEYWORD *k = (KEYWORD *)tac_malloc(sizeof(KEYWORD));
+
+    k->word = tac_strdup(name);
+    k->value = value;
+
+    n = hash_add_entry(wordtable, (void *) k);
+
+    if (n) {
+       report(LOG_ERR, "Attempt to multiply define keyword %s",
+              name);
+       tac_exit(1);
+    }
+}
+
+/* Declare keywords of the "configuration language". */
+
+void 
+parser_init()
+{
+    bzero(wordtable, sizeof(wordtable));
+
+    declare("access", S_access);
+    declare("accounting", S_accounting);
+    declare("after", S_after);
+    declare("arap", S_arap);
+    declare("attribute", S_attr);
+    declare("authentication", S_authentication);
+    declare("authorization", S_authorization);
+    declare("before", S_before);
+    declare("chap", S_chap);
+#ifdef MSCHAP
+    declare("ms-chap", S_mschap);
+#endif /* MSCHAP */
+    declare("cleartext", S_cleartext);
+#ifdef USE_PAM
+    declare("pam", S_pam);
+#endif /*USE_PAM */
+    declare("nopassword", S_nopasswd);
+    declare("cmd", S_cmd);
+    declare("default", S_default);
+    declare("deny", S_deny);
+    declare("des", S_des);
+    declare("exec", S_exec);
+    declare("expires", S_expires);
+    declare("file", S_file);
+    declare("group", S_group);
+    declare("global", S_global);
+    declare("host", S_host);
+    declare("type", S_type);
+    declare("ip", S_ip);
+    declare("ipx", S_ipx);
+    declare("key", S_key);
+    declare("lcp", S_lcp);
+#ifdef MAXSESS
+    declare("maxsess", S_maxsess);
+#endif
+#ifdef DB
+    declare("db", S_db);
+    declare("db_accounting",S_db_accounting);
+#endif
+#ifdef USE_LDAP
+    declare ("ldap", S_ldap);
+#endif
+    declare("member", S_member);
+    declare("message", S_message);
+    declare("name", S_name);
+    declare("optional", S_optional);
+    declare("login", S_login);
+    declare("permit", S_permit);
+    declare("pap", S_pap);
+    declare("opap", S_opap);
+    declare("ppp", S_ppp);
+    declare("protocol", S_protocol);
+    declare("skey", S_skey);
+    declare("slip", S_slip);
+    declare("service", S_svc);
+    declare("user", S_user);
+    declare("time", S_time);
+}
+
+/* Return a keyword code if a keyword is recognized. 0 otherwise */
+int
+keycode(keyword)
+char *keyword;
+{
+    KEYWORD *k = hash_lookup(wordtable, keyword);
+
+    if (k)
+       return (k->value);
+    return (S_unknown);
+}
+
+char *
+codestring(type)
+int type;
+{
+    switch (type) {
+    default:
+       return ("<unknown symbol>");
+    case S_eof:
+       return ("end-of-file");
+    case S_unknown:
+       return ("unknown");
+    case S_separator:
+       return ("=");
+    case S_string:
+       return ("<string>");
+    case S_openbra:
+       return ("{");
+    case S_closebra:
+       return ("}");
+    case S_key:
+       return ("key");
+    case S_user:
+       return ("user");
+    case S_group:
+       return ("group");
+    case S_host:
+       return ("host");
+    case S_type:
+       return ("type");
+    case S_file:
+       return ("file");
+    case S_skey:
+       return ("skey");
+    case S_name:
+       return ("name");
+    case S_login:
+       return ("login");
+    case S_member:
+       return ("member");
+#ifdef MAXSESS
+    case S_maxsess:
+       return ("maxsess");
+#endif
+#ifdef DB
+    case S_db:
+        return ("db");
+    case S_db_accounting:
+       return ("db_accounting");
+#endif
+   case S_expires:
+       return ("expires");
+    case S_after:
+       return ("after");
+    case S_before:
+       return ("before");
+    case S_message:
+       return ("message");
+    case S_arap:
+       return ("arap");
+    case S_global:
+       return ("global");
+    case S_chap:
+       return ("chap");
+#ifdef MSCHAP
+    case S_mschap:
+       return ("ms-chap");
+#endif /* MSCHAP */
+    case S_pap:
+       return ("pap");
+    case S_opap:
+       return ("opap");
+    case S_cleartext:
+       return ("cleartext");
+#ifdef USE_PAM
+    case S_pam:
+       return ("pam");
+#endif /*USE_PAM */    
+    case S_nopasswd:
+       return("nopassword");
+    case S_des:
+       return("des");
+    case S_svc:
+       return ("service");
+    case S_default:
+       return ("default");
+    case S_access:
+       return ("access");
+    case S_deny:
+       return ("deny");
+    case S_permit:
+       return ("permit");
+    case S_exec:
+       return ("exec");
+    case S_protocol:
+       return ("protocol");
+    case S_optional:
+       return ("optional");
+    case S_ip:
+       return ("ip");
+    case S_ipx:
+       return ("ipx");
+    case S_slip:
+       return ("slip");
+    case S_ppp:
+       return ("ppp");
+    case S_authentication:
+       return ("authentication");
+    case S_authorization:
+       return ("authorization");
+    case S_cmd:
+       return ("cmd");
+    case S_attr:
+       return ("attribute");
+    case S_svc_dflt:
+       return ("svc_dflt");
+    case S_accounting:
+       return ("accounting");
+    case S_lcp:
+       return("lcp");
+    case S_time:
+       return("time");
+    }
+}
diff --git a/parse.h b/parse.h
new file mode 100644 (file)
index 0000000..e5be7f7
--- /dev/null
+++ b/parse.h
@@ -0,0 +1,90 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+/* Dummy password, if nopasswd is specified */
+extern char *nopasswd_str;
+
+/* Keywords & values */
+
+#define S_eof             99
+#define S_unknown         101
+#define S_separator       104
+#define S_string          106
+#define S_openbra         107
+#define S_closebra        108
+#define S_svc_dflt        109
+
+#define S_key             1
+#define S_user            2
+#define S_group           3
+#define S_host            4
+#define S_accounting      5
+#define S_name            7
+#define S_login           8
+#define S_member          9
+#define S_expires         10
+#define S_cleartext       11
+#define S_message         12
+#define S_arap            13
+#define S_chap            14
+#define S_after                  15
+#define S_pap             16
+#define S_svc             17
+#define S_before          18
+#define S_default         19
+#define S_access          20
+#define S_deny            21
+#define S_permit          22
+#define S_exec            23
+#define S_protocol        24
+#define S_optional        25
+#define S_ip              26
+#define S_ipx             27
+#define S_slip            28
+#define S_ppp             29
+#define S_file            30
+#define S_skey            31
+#define S_authorization   32
+#define S_authentication  33
+#define S_cmd             34
+#define S_attr            35
+#define S_lcp            36
+#define S_global         37
+#define S_des            38
+#define S_opap            39
+#ifdef MAXSESS
+#define S_maxsess        40
+#endif
+#define S_nopasswd        41
+#ifdef MSCHAP
+#define S_mschap          42
+#endif /* MSCHAP */
+#ifdef USE_PAM
+#define S_pam            43
+#endif  /*USE_PAM */
+#ifdef DB
+#define S_db             44
+#define S_db_accounting          45
+#endif  /*DB*/
+#define S_type           46
+#ifdef USE_LDAP
+#define S_ldap            47
+#endif /* LDAP */
+#define S_time           48
+
diff --git a/programs.c b/programs.c
new file mode 100644 (file)
index 0000000..bce0178
--- /dev/null
@@ -0,0 +1,491 @@
+/*
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+/* Routines to fork children and communicate with them via pipes */
+
+#include "tac_plus.h"
+#include "sys/wait.h"
+#include <unistd.h>
+#include "signal.h"
+
+/* Support for dollar variables.  Look in the authorization data and
+return strings representing values found there.  If not found, return
+"unknown". Recognized strings and their interpolated value types are:
+
+user    -- user name
+name    -- NAS name
+port    -- NAS port
+address -- NAC address (remote user location)
+priv    -- privilege level (0 to 15)
+method  -- (1 to 4)
+type    -- (1 to 4)
+service -- (1 to 7)
+status  -- (pass, fail, error, unknown) */
+
+static char *
+lookup(sym, data)
+char *sym;
+struct author_data *data;
+{
+    static char buf[5];
+
+    if (STREQ(sym, "user")) {
+       return (tac_strdup(data->id->username));
+    }
+    if (STREQ(sym, "name")) {
+       return (tac_strdup(data->id->NAS_name));
+    }
+    if (STREQ(sym, "port")) {
+       return (tac_strdup(data->id->NAS_port));
+    }
+    if (STREQ(sym, "port")) {
+       return (tac_strdup(data->id->NAS_port));
+    }
+    if (STREQ(sym, "address")) {
+       return (tac_strdup(data->id->NAC_address));
+    }
+    if (STREQ(sym, "priv")) {
+       sprintf(buf, "%d", data->id->priv_lvl);
+       return (tac_strdup(buf));
+    }
+    if (STREQ(sym, "method")) {
+       sprintf(buf, "%d", data->authen_method);
+       return (tac_strdup(buf));
+    }
+    if (STREQ(sym, "type")) {
+       sprintf(buf, "%d", data->authen_type);
+       return (tac_strdup(buf));
+    }
+    if (STREQ(sym, "service")) {
+       sprintf(buf, "%d", data->service);
+       return (tac_strdup(buf));
+    }
+    if (STREQ(sym, "status")) {
+       switch (data->status) {
+       default:
+           return (tac_strdup("unknown"));
+       case AUTHOR_STATUS_PASS_ADD:
+       case AUTHOR_STATUS_PASS_REPL:
+           return (tac_strdup("pass"));
+       case AUTHOR_STATUS_FAIL:
+           return (tac_strdup("fail"));
+       case AUTHOR_STATUS_ERROR:
+           return (tac_strdup("error"));
+       }
+    }
+    return (tac_strdup("unknown"));
+}
+
+/* Interpolate values of dollar variables into a string.  Determine
+   values for the various $ variables by looking in the authorization
+   data */
+
+static char *
+substitute(string, data)
+char *string;
+struct author_data *data;
+{
+    char *cp;
+    char out[MAX_INPUT_LINE_LEN], *outp;
+    char sym[MAX_INPUT_LINE_LEN], *symp;
+    char *value, *valuep;
+
+    if (debug & DEBUG_AUTHOR_FLAG)
+       report(LOG_DEBUG, "substitute: %s", string);
+
+    cp = string;
+    outp = out;
+
+    while (*cp) {
+       if (*cp != DOLLARSIGN) {
+           *outp++ = *cp++;
+           continue;
+       }
+       cp++;                   /* skip dollar sign */
+       symp = sym;
+
+       /* does it have curly braces e.g. ${foo} ? */
+       if (*cp == '{') {
+           cp++;               /* skip { */
+           while (*cp && *cp != '}')
+               *symp++ = *cp++;
+           cp++;               /* skip } */
+
+       } else {
+           /* copy symbol into sym */
+           while (*cp && isalpha(*cp))
+               *symp++ = *cp++;
+       }
+
+       *symp = '\0';
+       /* lookup value */
+
+       if (debug & DEBUG_SUBST_FLAG)
+           report(LOG_DEBUG, "Lookup %s", sym);
+
+       valuep = value = lookup(sym, data);
+
+       if (debug & DEBUG_SUBST_FLAG)
+           report(LOG_DEBUG, "Expands to: %s", value);
+
+       /* copy value into output */
+       while (valuep && *valuep)
+           *outp++ = *valuep++;
+       free(value);
+    }
+    *outp++ = '\0';
+
+    if (debug & DEBUG_AUTHOR_FLAG)
+       report(LOG_DEBUG, "Dollar substitution: %s", out);
+
+    return (tac_strdup(out));
+}
+
+/* Wait for a (child) pid to terminate. Return its status. Probably
+   horribly implementation dependent. */
+
+static int
+waitfor(pid)
+int pid;
+{
+    int ret;
+
+#ifdef UNIONWAIT
+    union wait status;
+#else
+    int status;
+#endif /* UNIONWAIT */
+
+    ret = waitpid(pid, &status, 0);
+
+    if (ret < 0) {
+       report(LOG_ERR, "%s: pid %d no child exists", session.peer, pid);
+       return (-1);
+    }
+    if (!WIFEXITED(status)) {
+       report(LOG_ERR, "%s: pid %d child in illegal state", session.peer, pid);
+       return (-1);
+    }
+    if (debug & DEBUG_AUTHOR_FLAG)
+       report(LOG_DEBUG, "pid %d child exited status %d",
+              pid, WEXITSTATUS(status));
+
+    return (WEXITSTATUS(status));
+}
+
+/* Write an argv array of strings to fd, adding a newline to each one */
+static int
+write_args(fd, args, arg_cnt)
+int fd, arg_cnt;
+char **args;
+{
+    int i, m;
+
+    for (i = 0; i < arg_cnt; i++) {
+       int n = strlen(args[i]);
+
+       m = write(fd, args[i], n);
+       m += write(fd, "\n", 1);
+
+       if (m != (n + 1)) {
+           report(LOG_ERR, "%s: Process write failure", session.peer);
+           return (-1);
+       }
+    }
+    return (0);
+}
+
+/* Close the three given file-descruptors */
+static void
+close_fds(fd1, fd2, fd3)
+ int fd1, fd2, fd3;
+{
+    if (fd1 >= 0) {
+       close(fd1);
+    }
+    if (fd2 >= 0) {
+       close(fd2);
+    }
+    if (fd3 >= 0) {
+       close(fd3);
+    }
+}
+
+/* Fork a command. Return read and write file descriptors in readfdp
+   and writefdp. Return the pid or -1 if unsuccessful */
+
+static int
+my_popen(cmd, readfdp, writefdp, errorfdp)
+char *cmd;
+int *readfdp, *writefdp, *errorfdp;
+{
+    int fd1[2], fd2[2], fd3[2];
+    int pid;
+
+    fd1[0] = fd1[1] = fd2[0] = fd2[1] = fd3[0] = fd3[1] = -1;
+    *readfdp = *writefdp = *errorfdp = -1;
+    
+    if (pipe(fd1) < 0 || pipe(fd2) < 0 || pipe(fd3) < 0) {
+       report(LOG_ERR, "%s: Cannot create pipes", session.peer);
+       close_fds(fd1[0], fd2[0], fd3[0]);
+       close_fds(fd1[1], fd2[1], fd3[1]);
+       return (-1);
+    }
+
+    /* The parent who forked us is set to reap all children
+       automatically. We disable this so we can explicitly reap our
+       children to read their status */
+
+    signal(SIGCHLD, SIG_DFL);
+
+    pid = fork();
+
+    if (pid < 0) {
+       report(LOG_ERR, "%s: fork failure", session.peer);
+       close_fds(fd1[0], fd2[0], fd3[0]);
+       close_fds(fd1[1], fd2[1], fd3[1]);
+       return (-1);
+    }
+    if (pid > 0) {
+       /* parent */
+       close_fds(fd1[0], fd2[1], fd3[1]);
+
+       *writefdp = fd1[1];
+       *readfdp = fd2[0];
+       *errorfdp = fd3[0];
+
+       return (pid);
+    }
+    /* child */
+    closelog();
+    close(session.sock);
+    close_fds(fd1[1], fd2[0], fd3[0]);
+
+    if (fd1[0] != STDIN_FILENO) {
+       if (dup2(fd1[0], STDIN_FILENO) < 0)
+           exit(-1);
+       close(fd1[0]);
+    }
+    if (fd2[1] != STDOUT_FILENO) {
+       if (dup2(fd2[1], STDOUT_FILENO) < 0)
+           exit(-1);
+       close(fd2[1]);
+    }
+    if (fd3[1] != STDERR_FILENO) {
+       if (dup2(fd3[1], STDERR_FILENO) < 0)
+           exit(-1);
+       close(fd3[1]);
+    }
+    (void) execl("/bin/sh", "sh", "-c", cmd, (char *) NULL);
+    _exit(-1);
+    return(0); /* keep Codecenter quiet */
+}
+
+/* read the file descriptor and stuff the data into the given array for
+ * the number of bytes given. Throw the rest away.
+ */
+static int
+read_string (fd, string, len)
+int fd, len;
+char *string;
+{
+    uint i, ret;
+    char c;
+    
+    i=0;
+    do {
+       ret = read(fd, &c, 1);
+       if ( (ret > 0) && ((i+1)<len) ) {
+           string[i++] = c;
+           string[i] = '\0';
+       }
+    } while ((i<len) && (ret>0));
+    return(ret);
+}
+
+/* Read lines from fd and place them into an argv style array. Highly
+   recursive so we don't have to count lines in advance. Uses "n" as
+   the count of lines seen so far. When eof is read, the array is
+   allocated, and the recursion unravels */
+
+static char **
+read_args(n, fd)
+int n, fd;
+{
+    char buf[255], *bufp, c, **out;
+
+    bufp = buf;
+
+    while (read(fd, &c, 1) > 0) {
+       if (c != '\n') {
+           *bufp++ = c;
+           continue;
+       }
+       *bufp = '\0';
+       out = read_args(n + 1, fd);
+       out[n] = (char *) tac_malloc(strlen(buf) + 1);
+       strcpy(out[n], buf);
+       return (out);
+    }
+    /* eof */
+    out = (char **) tac_malloc(sizeof(char *) * (n + 1));
+    out[n] = NULL;
+
+    return (out);
+}
+
+
+/* Do variable interpolation on a string, then invoke it as a shell
+   command. Write an appropriate set of AV pairs to the command's
+   standard input and read its standard output into outarray. Return
+   the commands final status when it terminates */
+
+int
+call_pre_process(string, data, outargsp, outargs_cntp, error, err_len)
+char *string, *error;
+struct author_data *data;
+char ***outargsp;
+int *outargs_cntp, err_len;
+{
+    char **new_args;
+    int readfd, writefd, errorfd;
+    int status, i;
+    char *cmd = substitute(string, data);
+    int pid = my_popen(cmd, &readfd, &writefd, &errorfd);
+
+    memset(error, '\0', err_len);
+    
+    free(cmd);
+
+    if (pid < 0) {
+       close_fds(readfd, writefd, errorfd);
+       return (1);             /* deny */
+    }
+
+    for (i = 0; i < data->num_in_args; i++) {
+       if (debug & DEBUG_AUTHOR_FLAG)
+           report(LOG_DEBUG, "input %s", data->input_args[i]);
+    }
+
+    if (write_args(writefd, data->input_args, data->num_in_args)) {
+       close_fds(readfd, writefd, errorfd);
+       return (1);             /* deny */
+    }
+
+    close(writefd);
+    writefd = -1;
+
+    new_args = read_args(0, readfd);
+    *outargsp = new_args;
+
+    if (debug & DEBUG_AUTHOR_FLAG) {
+       for (i = 0; new_args[i]; i++) {
+           report(LOG_DEBUG, "output %s", new_args[i]);
+       }
+    }
+
+    read_string(errorfd, error, err_len);
+    if (error[0] != '\0') {
+       report(LOG_ERR, "Error from program (%d): \"%s\" ",
+              strlen(error), error);
+    }
+       
+    /* count the args */
+    for (i = 0; new_args[i]; i++)
+        /* NULL stmt */ ;
+
+    *outargs_cntp = i;
+
+    status = waitfor(pid);
+    close_fds(readfd, writefd, errorfd);
+    return (status);
+}
+
+/* Do variable interpolation on a string, then invoke it as a shell
+   command. Write an appropriate set of AV pairs to the command's
+   standard input and read its standard output into outarray. Return
+   the commands final status when it terminates */
+
+int
+call_post_process(string, data, outargsp, outargs_cntp)
+char *string;
+struct author_data *data;
+char ***outargsp;
+int *outargs_cntp;
+{
+    char **new_args;
+    int status;
+    int readfd, writefd, errorfd;
+    int i;
+    char *cmd = substitute(string, data);
+    int pid = my_popen(cmd, &readfd, &writefd, &errorfd);
+
+    free(cmd);
+
+    if (pid < 0) {
+       close_fds(readfd, writefd, errorfd);
+       return (1);             /* deny */
+    }
+
+    /* If the status is AUTHOR_STATUS_PASS_ADD then the current output args
+     * represent *additions* to the input args, not the full set */
+
+    if (data->status == AUTHOR_STATUS_PASS_ADD) {
+
+       for (i = 0; i < data->num_in_args; i++) {
+           if (debug & DEBUG_AUTHOR_FLAG)
+               report(LOG_DEBUG, "input %s", data->input_args[i]);
+       }
+
+       if (write_args(writefd, data->input_args, data->num_in_args)) {
+           close_fds(readfd, writefd, errorfd);
+           return (1);         /* deny */
+       }
+    }
+    for (i = 0; i < data->num_out_args; i++) {
+       if (debug & DEBUG_AUTHOR_FLAG)
+           report(LOG_DEBUG, "input %s", data->output_args[i]);
+    }
+
+    if (write_args(writefd, data->output_args, data->num_out_args)) {
+       close_fds(readfd, writefd, errorfd);
+       return (1);             /* deny */
+    }
+
+    close(writefd);
+    writefd = -1;
+
+    new_args = read_args(0, readfd);
+    *outargsp = new_args;
+
+    if (debug & DEBUG_AUTHOR_FLAG) {
+       for (i = 0; new_args[i]; i++) {
+           report(LOG_DEBUG, "output %s", new_args[i]);
+       }
+    }
+    /* count the output args */
+    for (i = 0; new_args[i]; i++)
+        /* NULL stmt */ ;
+
+    *outargs_cntp = i;
+
+    status = waitfor(pid);
+    close_fds(readfd, writefd, errorfd);
+    return (status);
+}
diff --git a/pw.c b/pw.c
new file mode 100644 (file)
index 0000000..96c761e
--- /dev/null
+++ b/pw.c
@@ -0,0 +1,137 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+/* Tacacs+ password lookup routine for those systems which don't have
+   setpwfile. Not for use on /etc/passwd files */
+
+#include "tac_plus.h"
+#include <pwd.h>
+#include <string.h>
+
+static struct passwd pw_passwd;
+
+struct passwd *
+tac_passwd_lookup(name, file)
+    char *name, *file;
+{
+    FILE *passwd_fp = NULL;
+
+    static char uname[512];
+    static char password[1024];
+    static char gecos[1024];
+    static char homedir[1024];
+    static char shell[1024];
+    char buf[1024];
+    char *s, *e;
+
+    passwd_fp = fopen(file, "r");
+
+    if (passwd_fp) {
+       if (debug & DEBUG_PASSWD_FLAG)
+           report(LOG_DEBUG, "tac_passwd_lookup: open %s %d", 
+                  file, fileno(passwd_fp));
+    } else {
+       report(LOG_ERR, "tac_passwd_lookup: cannot open file %s for reading", 
+              file);
+       return(NULL);
+    }
+
+    while (fgets(buf, sizeof(buf), passwd_fp)) {
+
+       /* uname, password, uid, gid, gecos, homedir, shell */
+
+       s = buf;
+       e = index(buf, ':');
+       if (!e)
+           break;
+
+       strncpy(uname, s, e - s);
+       uname[e - s] = '\0';
+
+       /* try next entry */
+       if (strcmp(uname, name))
+           continue;
+
+       s = e + 1;
+       e = index(s, ':');
+       if (!e) {
+           break;
+       }
+       strncpy(password, s, e - s);
+       password[e - s] = '\0';
+
+       s = e + 1;
+       e = index(s, ':');
+       if (!e) {
+           break;
+       }
+       pw_passwd.pw_uid = atoi(s);
+
+       s = e + 1;
+       e = index(s, ':');
+       pw_passwd.pw_gid = atoi(s);
+
+       s = e + 1;
+       e = index(s, ':');
+       if (!e) {
+           break;
+       }
+       strncpy(gecos, s, e - s);
+       gecos[e - s] = '\0';
+
+       s = e + 1;
+       e = index(s, ':');
+       if (!e) {
+           break;
+       }
+       strncpy(homedir, s, e - s);
+       homedir[e - s] = '\0';
+
+       s = e + 1;
+       e = index(s, '\n');
+       if (!e) {
+           break;
+       }
+       strncpy(shell, s, e - s);
+       shell[e - s] = '\0';
+
+        pw_passwd.pw_name    = uname;
+        pw_passwd.pw_passwd  = password;
+#ifndef NO_PWAGE
+        pw_passwd.pw_age     = NULL;
+        pw_passwd.pw_comment = NULL;
+#endif /* NO_PWAGE */
+        pw_passwd.pw_gecos   = gecos;
+        pw_passwd.pw_dir     = homedir;
+        pw_passwd.pw_shell   = shell;
+
+       if (debug & DEBUG_PASSWD_FLAG)
+           report(LOG_DEBUG, "tac_passwd_lookup: close %s %d", 
+                  file, fileno(passwd_fp));
+       fclose(passwd_fp);
+       return(&pw_passwd);
+    }
+
+    /* no match found */
+    if (debug & DEBUG_PASSWD_FLAG)
+           report(LOG_DEBUG, "tac_passwd_lookup: close %s %d", 
+                  file, fileno(passwd_fp));
+    fclose(passwd_fp);
+    return(NULL);
+}
diff --git a/pwlib.c b/pwlib.c
new file mode 100644 (file)
index 0000000..75f90b1
--- /dev/null
+++ b/pwlib.c
@@ -0,0 +1,503 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+#include "tac_plus.h"
+#include "expire.h"
+#include "time_limit.h"
+
+#ifdef SHADOW_PASSWORDS
+#include <shadow.h>
+#endif
+
+#ifdef USE_PAM
+int
+tac_pam_auth(char *UserName,char *Password,struct authen_data *data,char *Service);
+#endif /* USE_PAM   */
+
+/* For database verification */
+#ifdef DB
+int db_verify();
+#endif /* DB */
+
+/* For LDAP verification */
+#ifdef USE_LDAP
+#include "ldap.h"
+#endif /* LDAP */
+
+/* Generic password verification routines for des, file and cleartext
+   passwords */
+
+static int passwd_file_verify();
+
+/* Adjust data->status depending on whether a user has expired or not */
+
+void
+set_expiration_status(exp_date, data)
+char *exp_date;
+struct authen_data *data;
+{
+    int expired;
+
+    /* if the status is anything except pass, there's no point proceeding */
+    if (data->status != TAC_PLUS_AUTHEN_STATUS_PASS) {
+       return;
+    }
+
+    /* Check the expiration date, if any. If NULL, this check will return
+     * PW_OK */
+    expired = check_expiration(exp_date);
+
+    switch (expired) {
+    case PW_OK:
+       if (debug & DEBUG_PASSWD_FLAG)
+           report(LOG_DEBUG, "Password has not expired %s", 
+                  exp_date ? exp_date : "<no expiry date set>");
+
+       data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
+       return;
+
+    case PW_EXPIRING:
+       if (debug & DEBUG_PASSWD_FLAG)
+           report(LOG_DEBUG, "Password will expire soon %s", 
+                  exp_date ? exp_date : "<no expiry date set>");
+       if (data->server_msg)
+           free(data->server_msg);
+       data->server_msg = tac_strdup("Password will expire soon");
+       data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
+       return;
+
+    case PW_EXPIRED:
+       if (debug & DEBUG_PASSWD_FLAG)
+           report(LOG_DEBUG, "Password has expired %s", 
+                  exp_date ? exp_date : "<no expiry date set>");
+       if (data->server_msg)
+           free(data->server_msg);
+       data->server_msg = tac_strdup("Password has expired");
+       data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+       return;
+
+    default:
+       report(LOG_ERR, "%s: Bogus return value %d from check_expiration", 
+              session.peer, expired);
+       data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+       return;
+    }
+}
+
+/* Verify that this user/password is valid.  Works only for cleartext,
+   file and des passwords.
+   
+   Return 1 if password is valid */
+
+int
+verify(name, passwd, data, recurse)
+char *name, *passwd;
+struct authen_data *data;
+int recurse;
+{
+    char *exp_date;
+    char *timestamp;
+    char *cfg_passwd;
+    char *p;
+    
+    timestamp = (char *)cfg_get_timestamp(name, recurse); 
+    if ( timestamp != NULL ) { 
+       if( time_limit_process(timestamp) == 0  ) {
+               if ( debug & DEBUG_AUTHEN_FLAG ) 
+                       report(LOG_DEBUG,"Timestamp check failed");     
+               data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+               return (0);
+       } 
+    }
+
+    if (data->type == TAC_PLUS_AUTHEN_TYPE_PAP) {
+       cfg_passwd = cfg_get_pap_secret(name, recurse);
+    } else {
+       cfg_passwd = cfg_get_login_secret(name, recurse);
+    }
+
+    /* If there is no login or pap password for this user, see if there is 
+       a global password for her that we can use */
+
+    if (!cfg_passwd) {
+       cfg_passwd = cfg_get_global_secret(name, recurse);
+    }
+
+    /* If we still have no password for this user (or no user for that
+       matter) but the default authentication = file <file> statement
+       has been issued, attempt to use this password file */
+
+    if (!cfg_passwd) {
+       char *file = cfg_get_authen_default();
+       switch (cfg_get_authen_default_method()) {
+       case (S_file):
+
+       if (file) {
+           return (passwd_file_verify(name, passwd, data, file));
+       }
+        break;
+#ifdef DB
+       case (S_db):
+   /* ugly check for database connect string */
+   if( strstr(file, "://") ){
+           if (debug & DEBUG_PASSWD_FLAG)
+               report(LOG_DEBUG,"%s %s: DB access to %s for user %s",session.peer, session.port, file, name);
+        if (!db_verify(name, passwd, file)) {
+            data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+            return (0);
+        } else {
+            data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
+        }
+        exp_date = NULL;
+        set_expiration_status(exp_date, data);
+        return (data->status == TAC_PLUS_AUTHEN_STATUS_PASS);
+    }
+       break;
+#endif
+
+#ifdef USE_LDAP
+        case (S_ldap):
+        if (ldap_verify(name, passwd, file)==1) {
+            data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+            return (0);
+        } else {
+            data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
+        }
+        exp_date = NULL;
+        set_expiration_status(exp_date, data);
+        return (data->status == TAC_PLUS_AUTHEN_STATUS_PASS);
+        break;
+#endif /* USE_LDAP */
+
+#ifdef USE_PAM
+        case (S_pam):
+       if (debug & DEBUG_PASSWD_FLAG)
+           report(LOG_DEBUG, "PAM verify daemon %s == NAS %s", p,passwd);
+       if (tac_pam_auth(name, passwd, data,file)) {
+           if (debug & DEBUG_PASSWD_FLAG)
+               report(LOG_DEBUG, "PAM default authentication fail");
+           data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+           return(0);
+       } else {
+           data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
+
+           if (debug & DEBUG_PASSWD_FLAG)
+               report(LOG_DEBUG, " PAM default authentication pass");
+       }
+
+       exp_date = cfg_get_expires(name, recurse);
+       set_expiration_status(exp_date, data);
+       return (data->status == TAC_PLUS_AUTHEN_STATUS_PASS);
+       break;
+#endif 
+       default:
+       /* otherwise, we fail */
+       data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+       return (0);
+
+    }
+}
+
+    /* We have a configured password. Deal with it depending on its
+       type */
+
+
+    p = tac_find_substring("cleartext ", cfg_passwd);
+    if (p) {
+       if (debug & DEBUG_PASSWD_FLAG)
+           report(LOG_DEBUG, "verify daemon %s == NAS %s", p, passwd);
+
+       if (strcmp(passwd, p)) {
+           if (debug & DEBUG_PASSWD_FLAG)
+               report(LOG_DEBUG, "Password is incorrect"); 
+           data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+           return(0);
+       } else {
+           data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
+
+           if (debug & DEBUG_PASSWD_FLAG)
+               report(LOG_DEBUG, "Password is correct"); 
+       }
+
+       exp_date = cfg_get_expires(name, recurse);
+       set_expiration_status(exp_date, data);
+       return (data->status == TAC_PLUS_AUTHEN_STATUS_PASS);
+    }
+
+#ifdef USE_PAM
+    p = tac_find_substring("pam ", cfg_passwd);
+    if (p) {
+       if (debug & DEBUG_PASSWD_FLAG)
+           report(LOG_DEBUG, "PAM verify daemon %s == NAS %s", p,passwd);
+
+       if (tac_pam_auth(name, passwd, data,p)) {
+           if (debug & DEBUG_PASSWD_FLAG)
+               report(LOG_DEBUG, "PAM Password is incorrect");
+           data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+           return(0);
+       } else {
+           data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
+
+           if (debug & DEBUG_PASSWD_FLAG)
+               report(LOG_DEBUG, "PAM Password is correct");
+       }
+
+       exp_date = cfg_get_expires(name, recurse);
+       set_expiration_status(exp_date, data);
+       return (data->status == TAC_PLUS_AUTHEN_STATUS_PASS);
+    }
+
+#endif /* USE_PAM */
+
+    p = tac_find_substring("des ", cfg_passwd);
+    if (p) {
+       /* try to verify this des password */
+       if (!des_verify(passwd, p)) {
+           data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+           return (0);
+       } else {
+           data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
+       }
+
+       exp_date = cfg_get_expires(name, recurse);
+       set_expiration_status(exp_date, data);
+       return (data->status == TAC_PLUS_AUTHEN_STATUS_PASS);
+    }
+
+#ifdef DB
+    p = tac_find_substring("db ", cfg_passwd);
+    if (p) {
+        /* try to verify this password from database */
+        if (debug & DEBUG_PASSWD_FLAG)
+            report(LOG_DEBUG, "DB verify daemon %s == NAS %s", p, passwd);
+
+       if (!db_verify(name, passwd, p)) {
+            data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+
+        if (debug & DEBUG_PASSWD_FLAG)
+                report(LOG_DEBUG, "DB Password is incorrect");
+   
+        return (0);
+        } else {
+
+       if (debug & DEBUG_PASSWD_FLAG)
+                report(LOG_DEBUG, "DB Password is correct");
+            data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
+        }
+        exp_date = cfg_get_expires(name, recurse);
+        set_expiration_status(exp_date, data);
+        return (data->status == TAC_PLUS_AUTHEN_STATUS_PASS);
+    }
+#endif /* DB */
+
+    p = tac_find_substring("file ", cfg_passwd);
+    if (p) {
+       return (passwd_file_verify(name, passwd, data, p));
+    }
+    
+    /* Oops. No idea what kind of password this is. This should never
+       happen as the parser should never create such passwords. */
+
+    report(LOG_ERR, "%s: Error cannot identify password type %s for %s",
+          session.peer, 
+          cfg_passwd && cfg_passwd[0] ? cfg_passwd : "<NULL>", 
+          name ? name : "<unknown>");
+
+    data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+    return (0);
+}
+
+/* verify that this user/password is valid per /etc/passwd.
+   Return 0 if invalid. */
+static int
+etc_passwd_file_verify(user, supplied_passwd, data)
+char *user, *supplied_passwd;
+struct authen_data *data;
+{
+    struct passwd *pw;
+    char *exp_date;
+    char *cfg_passwd;
+#ifdef SHADOW_PASSWORDS
+    char buf[12];
+#endif /* SHADOW_PASSWORDS */
+
+    data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+
+    setpwent();
+    pw = getpwnam(user);
+    endpwent();
+
+    if (pw == NULL) {
+       /* no entry exists */
+       return (0);
+    }
+
+    if (*pw->pw_passwd == '\0' ||
+       supplied_passwd == NULL ||
+       *supplied_passwd == '\0') {
+       data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+       return (0);
+    }
+    cfg_passwd = pw->pw_passwd;
+    exp_date = pw->pw_shell;
+
+#ifdef SHADOW_PASSWORDS
+    if (STREQ(pw->pw_passwd, "x")) {
+       struct spwd *spwd = getspnam(user);
+
+       if (!spwd) {
+           if (debug & DEBUG_PASSWD_FLAG) {
+               report(LOG_DEBUG, "No entry for %s in shadow file", user);
+           }
+           data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+           return (0);
+       }
+       if (debug & DEBUG_PASSWD_FLAG) {
+           report(LOG_DEBUG, "Found entry for %s in shadow file", user);
+       }
+       cfg_passwd = spwd->sp_pwdp;
+
+       /* 
+        * Sigh. The Solaris shadow password file contains its own
+        * expiry date as the number of days after the epoch
+        * (January 1, 1970) when the password expires.
+        * Convert this to ascii so that the traditional tacacs
+        * password expiration routines work correctly. 
+        */
+
+       if (spwd->sp_expire > 0) {
+           long secs = spwd->sp_expire * 24 * 60 * 60;
+           char *p = ctime(&secs);
+           bcopy(p+4, buf, 7);
+           bcopy(p+20, buf+7, 4);
+           buf[11] = '\0';
+           exp_date = buf;
+       }
+    }
+#endif /* SHADOW_PASSWORDS */
+
+    /* try to verify the password */
+    if (!des_verify(supplied_passwd, cfg_passwd)) {
+       data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+       return (0);
+    } else {
+       data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
+    }
+
+    /* password ok. Check expiry field */
+    set_expiration_status(exp_date, data);
+
+    return (data->status == TAC_PLUS_AUTHEN_STATUS_PASS);
+}
+
+/* verify that this user/password is valid per a passwd(5) style
+   database. Return 0 if invalid. */
+
+static int
+passwd_file_verify(user, supplied_passwd, data, filename)
+char *user, *supplied_passwd;
+struct authen_data *data;
+char *filename;
+{
+    struct passwd *pw;
+    char *exp_date;
+    char *cfg_passwd;
+
+    data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+
+    if (filename && (STREQ(filename, "/etc/passwd")|| STREQ(filename,"/etc/shadow") )) {
+       return(etc_passwd_file_verify(user, supplied_passwd, data));
+    }
+
+
+    /* an alternate filename */
+    if (!(access(filename, R_OK) == 0)) {
+       report(LOG_ERR, "%s %s: Cannot access %s for user %s -- %s",
+              session.peer, session.port, filename, user, sys_errlist[errno]);
+       return (0);
+    }
+
+    pw = tac_passwd_lookup(user, filename);
+
+    if (pw == NULL)
+       /* no entry exists */
+       return (0);
+
+    if (*pw->pw_passwd == '\0' ||
+       supplied_passwd == NULL ||
+       *supplied_passwd == '\0') {
+       data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+       return (0);
+    }
+    cfg_passwd = pw->pw_passwd;
+    exp_date = pw->pw_shell;
+
+    /* try to verify the password */
+    if (!des_verify(supplied_passwd, cfg_passwd)) {
+       data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+       return (0);
+    } else {
+       data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
+    }
+
+    /* password ok. Check expiry field */
+    set_expiration_status(exp_date, data);
+    return (data->status == TAC_PLUS_AUTHEN_STATUS_PASS);
+}
+
+/*
+ * verify a provided password against a des encrypted one
+ * return 1 if verified, 0 otherwise.
+ */
+
+int
+des_verify(users_passwd, encrypted_passwd)
+char *users_passwd, *encrypted_passwd;
+{
+    char *ep;
+
+    if (debug & DEBUG_PASSWD_FLAG)
+       report(LOG_DEBUG, "verify %s %s", users_passwd, encrypted_passwd);
+
+    if (users_passwd == NULL ||
+       *users_passwd == '\0' ||
+       encrypted_passwd == NULL ||
+       *encrypted_passwd == '\0') {
+       if (debug & DEBUG_PASSWD_FLAG)
+           report(LOG_DEBUG, "verify returns 0");
+       return (0);
+    }
+
+    ep = (char *) crypt(users_passwd, encrypted_passwd);
+
+    if (debug & DEBUG_PASSWD_FLAG)
+       report(LOG_DEBUG, "%s encrypts to %s", users_passwd, ep);
+
+    if (strcmp(ep, encrypted_passwd) == 0) {
+       if (debug & DEBUG_PASSWD_FLAG)
+           report(LOG_DEBUG, "Password is correct");
+       return (1);
+    }
+
+    if (debug & DEBUG_PASSWD_FLAG)
+       report(LOG_DEBUG, "Password is incorrect");
+
+    return (0);
+}
diff --git a/regexp.3 b/regexp.3
new file mode 100644 (file)
index 0000000..ba0bad3
--- /dev/null
+++ b/regexp.3
@@ -0,0 +1,179 @@
+.TH REGEXP 3 local
+.DA 2 April 1986
+.SH NAME
+regcomp, regexec, regsub, regerror \- regular expression handler
+.SH SYNOPSIS
+.ft B
+.nf
+#include <regexp.h>
+
+regexp *regcomp(exp)
+char *exp;
+
+int regexec(prog, string)
+regexp *prog;
+char *string;
+
+regsub(prog, source, dest)
+regexp *prog;
+char *source;
+char *dest;
+
+regerror(msg)
+char *msg;
+.SH DESCRIPTION
+These functions implement
+.IR egrep (1)-style
+regular expressions and supporting facilities.
+.PP
+.I Regcomp
+compiles a regular expression into a structure of type
+.IR regexp ,
+and returns a pointer to it.
+The space has been allocated using
+.IR malloc (3)
+and may be released by
+.IR free .
+.PP
+.I Regexec
+matches a NUL-terminated \fIstring\fR against the compiled regular expression
+in \fIprog\fR.
+It returns 1 for success and 0 for failure, and adjusts the contents of
+\fIprog\fR's \fIstartp\fR and \fIendp\fR (see below) accordingly.
+.PP
+The members of a
+.I regexp
+structure include at least the following (not necessarily in order):
+.PP
+.RS
+char *startp[NSUBEXP];
+.br
+char *endp[NSUBEXP];
+.RE
+.PP
+where
+.I NSUBEXP
+is defined (as 10) in the header file.
+Once a successful \fIregexec\fR has been done using the \fIregexp\fR,
+each \fIstartp\fR-\fIendp\fR pair describes one substring
+within the \fIstring\fR,
+with the \fIstartp\fR pointing to the first character of the substring and
+the \fIendp\fR pointing to the first character following the substring.
+The 0th substring is the substring of \fIstring\fR that matched the whole
+regular expression.
+The others are those substrings that matched parenthesized expressions
+within the regular expression, with parenthesized expressions numbered
+in left-to-right order of their opening parentheses.
+.PP
+.I Regsub
+copies \fIsource\fR to \fIdest\fR, making substitutions according to the
+most recent \fIregexec\fR performed using \fIprog\fR.
+Each instance of `&' in \fIsource\fR is replaced by the substring
+indicated by \fIstartp\fR[\fI0\fR] and
+\fIendp\fR[\fI0\fR].
+Each instance of `\e\fIn\fR', where \fIn\fR is a digit, is replaced by
+the substring indicated by
+\fIstartp\fR[\fIn\fR] and
+\fIendp\fR[\fIn\fR].
+To get a literal `&' or `\e\fIn\fR' into \fIdest\fR, prefix it with `\e';
+to get a literal `\e' preceding `&' or `\e\fIn\fR', prefix it with
+another `\e'.
+.PP
+.I Regerror
+is called whenever an error is detected in \fIregcomp\fR, \fIregexec\fR,
+or \fIregsub\fR.
+The default \fIregerror\fR writes the string \fImsg\fR,
+with a suitable indicator of origin,
+on the standard
+error output
+and invokes \fIexit\fR(2).
+.I Regerror
+can be replaced by the user if other actions are desirable.
+.SH "REGULAR EXPRESSION SYNTAX"
+A regular expression is zero or more \fIbranches\fR, separated by `|'.
+It matches anything that matches one of the branches.
+.PP
+A branch is zero or more \fIpieces\fR, concatenated.
+It matches a match for the first, followed by a match for the second, etc.
+.PP
+A piece is an \fIatom\fR possibly followed by `*', `+', or `?'.
+An atom followed by `*' matches a sequence of 0 or more matches of the atom.
+An atom followed by `+' matches a sequence of 1 or more matches of the atom.
+An atom followed by `?' matches a match of the atom, or the null string.
+.PP
+An atom is a regular expression in parentheses (matching a match for the
+regular expression), a \fIrange\fR (see below), `.'
+(matching any single character), `^' (matching the null string at the
+beginning of the input string), `$' (matching the null string at the
+end of the input string), a `\e' followed by a single character (matching
+that character), or a single character with no other significance
+(matching that character).
+.PP
+A \fIrange\fR is a sequence of characters enclosed in `[]'.
+It normally matches any single character from the sequence.
+If the sequence begins with `^',
+it matches any single character \fInot\fR from the rest of the sequence.
+If two characters in the sequence are separated by `\-', this is shorthand
+for the full list of ASCII characters between them
+(e.g. `[0-9]' matches any decimal digit).
+To include a literal `]' in the sequence, make it the first character
+(following a possible `^').
+To include a literal `\-', make it the first or last character.
+.SH AMBIGUITY
+If a regular expression could match two different parts of the input string,
+it will match the one which begins earliest.
+If both begin in the same place        but match different lengths, or match
+the same length in different ways, life gets messier, as follows.
+.PP
+In general, the possibilities in a list of branches are considered in
+left-to-right order, the possibilities for `*', `+', and `?' are
+considered longest-first, nested constructs are considered from the
+outermost in, and concatenated constructs are considered leftmost-first.
+The match that will be chosen is the one that uses the earliest
+possibility in the first choice that has to be made.
+If there is more than one choice, the next will be made in the same manner
+(earliest possibility) subject to the decision on the first choice.
+And so forth.
+.PP
+For example, `(ab|a)b*c' could match `abc' in one of two ways.
+The first choice is between `ab' and `a'; since `ab' is earlier, and does
+lead to a successful overall match, it is chosen.
+Since the `b' is already spoken for,
+the `b*' must match its last possibility\(emthe empty string\(emsince
+it must respect the earlier choice.
+.PP
+In the particular case where no `|'s are present and there is only one
+`*', `+', or `?', the net effect is that the longest possible
+match will be chosen.
+So `ab*', presented with `xabbbby', will match `abbbb'.
+Note that if `ab*' is tried against `xabyabbbz', it
+will match `ab' just after `x', due to the begins-earliest rule.
+(In effect, the decision on where to start the match is the first choice
+to be made, hence subsequent choices must respect it even if this leads them
+to less-preferred alternatives.)
+.SH SEE ALSO
+egrep(1), expr(1)
+.SH DIAGNOSTICS
+\fIRegcomp\fR returns NULL for a failure
+(\fIregerror\fR permitting),
+where failures are syntax errors, exceeding implementation limits,
+or applying `+' or `*' to a possibly-null operand.
+.SH HISTORY
+Both code and manual page were
+written at U of T.
+They are intended to be compatible with the Bell V8 \fIregexp\fR(3),
+but are not derived from Bell code.
+.SH BUGS
+Empty branches and empty regular expressions are not portable to V8.
+.PP
+The restriction against
+applying `*' or `+' to a possibly-null operand is an artifact of the
+simplistic implementation.
+.PP
+Does not support \fIegrep\fR's newline-separated branches;
+neither does the V8 \fIregexp\fR(3), though.
+.PP
+Due to emphasis on
+compactness and simplicity,
+it's not strikingly fast.
+It does give special attention to handling simple cases quickly.
diff --git a/regexp.c b/regexp.c
new file mode 100644 (file)
index 0000000..9c0357a
--- /dev/null
+++ b/regexp.c
@@ -0,0 +1,1234 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+/*
+ * regcomp and regexec -- regsub and regerror are elsewhere
+ * @(#)regexp.c        1.3 of 18 April 87
+ *
+ *     Copyright (c) 1986 by University of Toronto.
+ *     Written by Henry Spencer.  Not derived from licensed software.
+ *
+ *     Permission is granted to anyone to use this software for any
+ *     purpose on any computer system, and to redistribute it freely,
+ *     subject to the following restrictions:
+ *
+ *     1. The author is not responsible for the consequences of use of
+ *             this software, no matter how awful, even if they arise
+ *             from defects in it.
+ *
+ *     2. The origin of this software must not be misrepresented, either
+ *             by explicit claim or by omission.
+ *
+ *     3. Altered versions must be plainly marked as such, and must not
+ *             be misrepresented as being the original software.
+ *
+ * Beware that some of this code is subtly aware of the way operator
+ * precedence is structured in regular expressions.  Serious changes in
+ * regular-expression syntax might require a total rethink.
+ */
+#include <stdio.h>
+#include "regexp.h"
+#include "regmagic.h"
+
+/*
+ * The "internal use only" fields in regexp.h are present to pass info from
+ * compile to execute that permits the execute phase to run lots faster on
+ * simple cases.  They are:
+ *
+ * regstart    char that must begin a match; '\0' if none obvious
+ * reganch     is the match anchored (at beginning-of-line only)?
+ * regmust     string (pointer into program) that match must include, or NULL
+ * regmlen     length of regmust string
+ *
+ * Regstart and reganch permit very fast decisions on suitable starting points
+ * for a match, cutting down the work a lot.  Regmust permits fast rejection
+ * of lines that cannot possibly match.  The regmust tests are costly enough
+ * that regcomp() supplies a regmust only if the r.e. contains something
+ * potentially expensive (at present, the only such thing detected is * or +
+ * at the start of the r.e., which can involve a lot of backup).  Regmlen is
+ * supplied because the test in regexec() needs it and regcomp() is computing
+ * it anyway.
+ */
+
+/*
+ * Structure for regexp "program".  This is essentially a linear encoding
+ * of a nondeterministic finite-state machine (aka syntax charts or
+ * "railroad normal form" in parsing technology).  Each node is an opcode
+ * plus a "next" pointer, possibly plus an operand.  "Next" pointers of
+ * all nodes except BRANCH implement concatenation; a "next" pointer with
+ * a BRANCH on both ends of it is connecting two alternatives.  (Here we
+ * have one of the subtle syntax dependencies:  an individual BRANCH (as
+ * opposed to a collection of them) is never concatenated with anything
+ * because of operator precedence.)  The operand of some types of node is
+ * a literal string; for others, it is a node leading into a sub-FSM.  In
+ * particular, the operand of a BRANCH node is the first node of the branch.
+ * (NB this is *not* a tree structure:  the tail of the branch connects
+ * to the thing following the set of BRANCHes.)  The opcodes are:
+ */
+
+/* definition  number  opnd?   meaning */
+#define        END     0       /* no   End of program. */
+#define        BOL     1       /* no   Match "" at beginning of line. */
+#define        EOL     2       /* no   Match "" at end of line. */
+#define        ANY     3       /* no   Match any one character. */
+#define        ANYOF   4       /* str  Match any character in this string. */
+#define        ANYBUT  5       /* str  Match any character not in this string. */
+#define        BRANCH  6       /* node Match this alternative, or the next... */
+#define        BACK    7       /* no   Match "", "next" ptr points backward. */
+#define        EXACTLY 8       /* str  Match this string. */
+#define        NOTHING 9       /* no   Match empty string. */
+#define        STAR    10      /* node Match this (simple) thing 0 or more times. */
+#define        PLUS    11      /* node Match this (simple) thing 1 or more times. */
+#define        OPEN    20      /* no   Mark this point in input as start of #n. */
+                       /*      OPEN+1 is number 1, etc. */
+#define        CLOSE   30      /* no   Analogous to OPEN. */
+
+/*
+ * Opcode notes:
+ *
+ * BRANCH      The set of branches constituting a single choice are hooked
+ *             together with their "next" pointers, since precedence prevents
+ *             anything being concatenated to any individual branch.  The
+ *             "next" pointer of the last BRANCH in a choice points to the
+ *             thing following the whole choice.  This is also where the
+ *             final "next" pointer of each individual branch points; each
+ *             branch starts with the operand node of a BRANCH node.
+ *
+ * BACK                Normal "next" pointers all implicitly point forward; BACK
+ *             exists to make loop structures possible.
+ *
+ * STAR,PLUS   '?', and complex '*' and '+', are implemented as circular
+ *             BRANCH structures using BACK.  Simple cases (one character
+ *             per match) are implemented with STAR and PLUS for speed
+ *             and to minimize recursive plunges.
+ *
+ * OPEN,CLOSE  ...are numbered at compile time.
+ */
+
+/*
+ * A node is one char of opcode followed by two chars of "next" pointer.
+ * "Next" pointers are stored as two 8-bit pieces, high order first.  The
+ * value is a positive offset from the opcode of the node containing it.
+ * An operand, if any, simply follows the node.  (Note that much of the
+ * code generation knows about this implicit relationship.)
+ *
+ * Using two bytes for the "next" pointer is vast overkill for most things,
+ * but allows patterns to get big without disasters.
+ */
+#define        OP(p)   (*(p))
+#define        NEXT(p) (((*((p)+1)&0377)<<8) + (*((p)+2)&0377))
+#define        OPERAND(p)      ((p) + 3)
+
+/*
+ * See regmagic.h for one further detail of program structure.
+ */
+
+
+/*
+ * Utility definitions.
+ */
+#ifndef CHARBITS
+#define        UCHARAT(p)      ((int)*(unsigned char *)(p))
+#else
+#define        UCHARAT(p)      ((int)*(p)&CHARBITS)
+#endif
+
+#define        FAIL(m) { regerror(m); return(NULL); }
+#define        ISMULT(c)       ((c) == '*' || (c) == '+' || (c) == '?')
+#define        META    "^$.[()|?+*\\"
+
+/*
+ * Flags to be passed up and down.
+ */
+#define        HASWIDTH        01      /* Known never to match null string. */
+#define        SIMPLE          02      /* Simple enough to be STAR/PLUS operand. */
+#define        SPSTART         04      /* Starts with * or +. */
+#define        WORST           0       /* Worst case. */
+
+/*
+ * Global work variables for regcomp().
+ */
+static char *regparse;         /* Input-scan pointer. */
+static int regnpar;            /* () count. */
+static char regdummy;
+static char *regcode;          /* Code-emit pointer; &regdummy = don't. */
+static long regsize;           /* Code size. */
+
+/*
+ * Forward declarations for regcomp()'s friends.
+ */
+#ifndef STATIC
+#define        STATIC  static
+#endif
+STATIC char *reg();
+STATIC char *regbranch();
+STATIC char *regpiece();
+STATIC char *regatom();
+STATIC char *regnode();
+STATIC char *regnext();
+STATIC void regc();
+STATIC void reginsert();
+STATIC void regtail();
+STATIC void regoptail();
+#ifdef STRCSPN
+STATIC int strcspn();
+#endif
+
+/*
+ - regcomp - compile a regular expression into internal code
+ *
+ * We can't allocate space until we know how big the compiled form will be,
+ * but we can't compile it (and thus know how big it is) until we've got a
+ * place to put the code.  So we cheat:  we compile it twice, once with code
+ * generation turned off and size counting turned on, and once "for real".
+ * This also means that we don't allocate space until we are sure that the
+ * thing really will compile successfully, and we never have to move the
+ * code and thus invalidate pointers into it.  (Note that it has to be in
+ * one piece because free() must be able to free it all.)
+ *
+ * Beware that the optimization-preparation code in here knows about some
+ * of the structure of the compiled regexp.
+ */
+regexp *
+regcomp(exp)
+char *exp;
+{
+       register regexp *r;
+       register char *scan;
+       register char *longest;
+       register int len;
+       int flags;
+       extern char *malloc();
+
+       if (exp == NULL)
+               FAIL("NULL argument");
+
+       /* First pass: determine size, legality. */
+       regparse = exp;
+       regnpar = 1;
+       regsize = 0L;
+       regcode = &regdummy;
+       regc(MAGIC);
+       if (reg(0, &flags) == NULL)
+               return(NULL);
+
+       /* Small enough for pointer-storage convention? */
+       if (regsize >= 32767L)          /* Probably could be 65535L. */
+               FAIL("regexp too big");
+
+       /* Allocate space. */
+       r = (regexp *)malloc(sizeof(regexp) + (unsigned)regsize);
+       if (r == NULL)
+               FAIL("out of space");
+
+       /* Second pass: emit code. */
+       regparse = exp;
+       regnpar = 1;
+       regcode = r->program;
+       regc(MAGIC);
+       if (reg(0, &flags) == NULL)
+               return(NULL);
+
+       /* Dig out information for optimizations. */
+       r->regstart = '\0';     /* Worst-case defaults. */
+       r->reganch = 0;
+       r->regmust = NULL;
+       r->regmlen = 0;
+       scan = r->program+1;                    /* First BRANCH. */
+       if (OP(regnext(scan)) == END) {         /* Only one top-level choice. */
+               scan = OPERAND(scan);
+
+               /* Starting-point info. */
+               if (OP(scan) == EXACTLY)
+                       r->regstart = *OPERAND(scan);
+               else if (OP(scan) == BOL)
+                       r->reganch++;
+
+               /*
+                * If there's something expensive in the r.e., find the
+                * longest literal string that must appear and make it the
+                * regmust.  Resolve ties in favor of later strings, since
+                * the regstart check works with the beginning of the r.e.
+                * and avoiding duplication strengthens checking.  Not a
+                * strong reason, but sufficient in the absence of others.
+                */
+               if (flags&SPSTART) {
+                       longest = NULL;
+                       len = 0;
+                       for (; scan != NULL; scan = regnext(scan))
+                               if (OP(scan) == EXACTLY && strlen(OPERAND(scan)) >= len) {
+                                       longest = OPERAND(scan);
+                                       len = strlen(OPERAND(scan));
+                               }
+                       r->regmust = longest;
+                       r->regmlen = len;
+               }
+       }
+
+       return(r);
+}
+
+/*
+ - reg - regular expression, i.e. main body or parenthesized thing
+ *
+ * Caller must absorb opening parenthesis.
+ *
+ * Combining parenthesis handling with the base level of regular expression
+ * is a trifle forced, but the need to tie the tails of the branches to what
+ * follows makes it hard to avoid.
+ */
+static char *
+reg(paren, flagp)
+int paren;                     /* Parenthesized? */
+int *flagp;
+{
+       register char *ret;
+       register char *br;
+       register char *ender;
+       register int parno;
+       int flags;
+
+       *flagp = HASWIDTH;      /* Tentatively. */
+
+       /* Make an OPEN node, if parenthesized. */
+       if (paren) {
+               if (regnpar >= NSUBEXP)
+                       FAIL("too many ()");
+               parno = regnpar;
+               regnpar++;
+               ret = regnode(OPEN+parno);
+       } else
+               ret = NULL;
+
+       /* Pick up the branches, linking them together. */
+       br = regbranch(&flags);
+       if (br == NULL)
+               return(NULL);
+       if (ret != NULL)
+               regtail(ret, br);       /* OPEN -> first. */
+       else
+               ret = br;
+       if (!(flags&HASWIDTH))
+               *flagp &= ~HASWIDTH;
+       *flagp |= flags&SPSTART;
+       while (*regparse == '|') {
+               regparse++;
+               br = regbranch(&flags);
+               if (br == NULL)
+                       return(NULL);
+               regtail(ret, br);       /* BRANCH -> BRANCH. */
+               if (!(flags&HASWIDTH))
+                       *flagp &= ~HASWIDTH;
+               *flagp |= flags&SPSTART;
+       }
+
+       /* Make a closing node, and hook it on the end. */
+       ender = regnode((paren) ? CLOSE+parno : END);   
+       regtail(ret, ender);
+
+       /* Hook the tails of the branches to the closing node. */
+       for (br = ret; br != NULL; br = regnext(br))
+               regoptail(br, ender);
+
+       /* Check for proper termination. */
+       if (paren && *regparse++ != ')') {
+               FAIL("unmatched ()");
+       } else if (!paren && *regparse != '\0') {
+               if (*regparse == ')') {
+                       FAIL("unmatched ()");
+               } else
+                       FAIL("junk on end");    /* "Can't happen". */
+               /* NOTREACHED */
+       }
+
+       return(ret);
+}
+
+/*
+ - regbranch - one alternative of an | operator
+ *
+ * Implements the concatenation operator.
+ */
+static char *
+regbranch(flagp)
+int *flagp;
+{
+       register char *ret;
+       register char *chain;
+       register char *latest;
+       int flags;
+
+       *flagp = WORST;         /* Tentatively. */
+
+       ret = regnode(BRANCH);
+       chain = NULL;
+       while (*regparse != '\0' && *regparse != '|' && *regparse != ')') {
+               latest = regpiece(&flags);
+               if (latest == NULL)
+                       return(NULL);
+               *flagp |= flags&HASWIDTH;
+               if (chain == NULL)      /* First piece. */
+                       *flagp |= flags&SPSTART;
+               else
+                       regtail(chain, latest);
+               chain = latest;
+       }
+       if (chain == NULL)      /* Loop ran zero times. */
+               (void) regnode(NOTHING);
+
+       return(ret);
+}
+
+/*
+ - regpiece - something followed by possible [*+?]
+ *
+ * Note that the branching code sequences used for ? and the general cases
+ * of * and + are somewhat optimized:  they use the same NOTHING node as
+ * both the endmarker for their branch list and the body of the last branch.
+ * It might seem that this node could be dispensed with entirely, but the
+ * endmarker role is not redundant.
+ */
+static char *
+regpiece(flagp)
+int *flagp;
+{
+       register char *ret;
+       register char op;
+       register char *next;
+       int flags;
+
+       ret = regatom(&flags);
+       if (ret == NULL)
+               return(NULL);
+
+       op = *regparse;
+       if (!ISMULT(op)) {
+               *flagp = flags;
+               return(ret);
+       }
+
+       if (!(flags&HASWIDTH) && op != '?')
+               FAIL("*+ operand could be empty");
+       *flagp = (op != '+') ? (WORST|SPSTART) : (WORST|HASWIDTH);
+
+       if (op == '*' && (flags&SIMPLE))
+               reginsert(STAR, ret);
+       else if (op == '*') {
+               /* Emit x* as (x&|), where & means "self". */
+               reginsert(BRANCH, ret);                 /* Either x */
+               regoptail(ret, regnode(BACK));          /* and loop */
+               regoptail(ret, ret);                    /* back */
+               regtail(ret, regnode(BRANCH));          /* or */
+               regtail(ret, regnode(NOTHING));         /* null. */
+       } else if (op == '+' && (flags&SIMPLE))
+               reginsert(PLUS, ret);
+       else if (op == '+') {
+               /* Emit x+ as x(&|), where & means "self". */
+               next = regnode(BRANCH);                 /* Either */
+               regtail(ret, next);
+               regtail(regnode(BACK), ret);            /* loop back */
+               regtail(next, regnode(BRANCH));         /* or */
+               regtail(ret, regnode(NOTHING));         /* null. */
+       } else if (op == '?') {
+               /* Emit x? as (x|) */
+               reginsert(BRANCH, ret);                 /* Either x */
+               regtail(ret, regnode(BRANCH));          /* or */
+               next = regnode(NOTHING);                /* null. */
+               regtail(ret, next);
+               regoptail(ret, next);
+       }
+       regparse++;
+       if (ISMULT(*regparse))
+               FAIL("nested *?+");
+
+       return(ret);
+}
+
+/*
+ - regatom - the lowest level
+ *
+ * Optimization:  gobbles an entire sequence of ordinary characters so that
+ * it can turn them into a single node, which is smaller to store and
+ * faster to run.  Backslashed characters are exceptions, each becoming a
+ * separate node; the code is simpler that way and it's not worth fixing.
+ */
+static char *
+regatom(flagp)
+int *flagp;
+{
+       register char *ret;
+       int flags;
+
+       *flagp = WORST;         /* Tentatively. */
+
+       switch (*regparse++) {
+       case '^':
+               ret = regnode(BOL);
+               break;
+       case '$':
+               ret = regnode(EOL);
+               break;
+       case '.':
+               ret = regnode(ANY);
+               *flagp |= HASWIDTH|SIMPLE;
+               break;
+       case '[': {
+                       register int class;
+                       register int classend;
+
+                       if (*regparse == '^') { /* Complement of range. */
+                               ret = regnode(ANYBUT);
+                               regparse++;
+                       } else
+                               ret = regnode(ANYOF);
+                       if (*regparse == ']' || *regparse == '-')
+                               regc(*regparse++);
+                       while (*regparse != '\0' && *regparse != ']') {
+                               if (*regparse == '-') {
+                                       regparse++;
+                                       if (*regparse == ']' || *regparse == '\0')
+                                               regc('-');
+                                       else {
+                                               class = UCHARAT(regparse-2)+1;
+                                               classend = UCHARAT(regparse);
+                                               if (class > classend+1)
+                                                       FAIL("invalid [] range");
+                                               for (; class <= classend; class++)
+                                                       regc(class);
+                                               regparse++;
+                                       }
+                               } else
+                                       regc(*regparse++);
+                       }
+                       regc('\0');
+                       if (*regparse != ']')
+                               FAIL("unmatched []");
+                       regparse++;
+                       *flagp |= HASWIDTH|SIMPLE;
+               }
+               break;
+       case '(':
+               ret = reg(1, &flags);
+               if (ret == NULL)
+                       return(NULL);
+               *flagp |= flags&(HASWIDTH|SPSTART);
+               break;
+       case '\0':
+       case '|':
+       case ')':
+               FAIL("internal urp");   /* Supposed to be caught earlier. */
+               break;
+       case '?':
+       case '+':
+       case '*':
+               FAIL("?+* follows nothing");
+               break;
+       case '\\':
+               if (*regparse == '\0')
+                       FAIL("trailing \\");
+               ret = regnode(EXACTLY);
+               regc(*regparse++);
+               regc('\0');
+               *flagp |= HASWIDTH|SIMPLE;
+               break;
+       default: {
+                       register int len;
+                       register char ender;
+
+                       regparse--;
+                       len = strcspn(regparse, META);
+                       if (len <= 0)
+                               FAIL("internal disaster");
+                       ender = *(regparse+len);
+                       if (len > 1 && ISMULT(ender))
+                               len--;          /* Back off clear of ?+* operand. */
+                       *flagp |= HASWIDTH;
+                       if (len == 1)
+                               *flagp |= SIMPLE;
+                       ret = regnode(EXACTLY);
+                       while (len > 0) {
+                               regc(*regparse++);
+                               len--;
+                       }
+                       regc('\0');
+               }
+               break;
+       }
+
+       return(ret);
+}
+
+/*
+ - regnode - emit a node
+ */
+static char *                  /* Location. */
+regnode(op)
+char op;
+{
+       register char *ret;
+       register char *ptr;
+
+       ret = regcode;
+       if (ret == &regdummy) {
+               regsize += 3;
+               return(ret);
+       }
+
+       ptr = ret;
+       *ptr++ = op;
+       *ptr++ = '\0';          /* Null "next" pointer. */
+       *ptr++ = '\0';
+       regcode = ptr;
+
+       return(ret);
+}
+
+/*
+ - regc - emit (if appropriate) a byte of code
+ */
+static void
+regc(b)
+char b;
+{
+       if (regcode != &regdummy)
+               *regcode++ = b;
+       else
+               regsize++;
+}
+
+/*
+ - reginsert - insert an operator in front of already-emitted operand
+ *
+ * Means relocating the operand.
+ */
+static void
+reginsert(op, opnd)
+char op;
+char *opnd;
+{
+       register char *src;
+       register char *dst;
+       register char *place;
+
+       if (regcode == &regdummy) {
+               regsize += 3;
+               return;
+       }
+
+       src = regcode;
+       regcode += 3;
+       dst = regcode;
+       while (src > opnd)
+               *--dst = *--src;
+
+       place = opnd;           /* Op node, where operand used to be. */
+       *place++ = op;
+       *place++ = '\0';
+       *place++ = '\0';
+}
+
+/*
+ - regtail - set the next-pointer at the end of a node chain
+ */
+static void
+regtail(p, val)
+char *p;
+char *val;
+{
+       register char *scan;
+       register char *temp;
+       register int offset;
+
+       if (p == &regdummy)
+               return;
+
+       /* Find last node. */
+       scan = p;
+       for (;;) {
+               temp = regnext(scan);
+               if (temp == NULL)
+                       break;
+               scan = temp;
+       }
+
+       if (OP(scan) == BACK)
+               offset = scan - val;
+       else
+               offset = val - scan;
+       *(scan+1) = (offset>>8)&0377;
+       *(scan+2) = offset&0377;
+}
+
+/*
+ - regoptail - regtail on operand of first argument; nop if operandless
+ */
+static void
+regoptail(p, val)
+char *p;
+char *val;
+{
+       /* "Operandless" and "op != BRANCH" are synonymous in practice. */
+       if (p == NULL || p == &regdummy || OP(p) != BRANCH)
+               return;
+       regtail(OPERAND(p), val);
+}
+
+/*
+ * regexec and friends
+ */
+
+/*
+ * Global work variables for regexec().
+ */
+static char *reginput;         /* String-input pointer. */
+static char *regbol;           /* Beginning of input, for ^ check. */
+static char **regstartp;       /* Pointer to startp array. */
+static char **regendp;         /* Ditto for endp. */
+
+/*
+ * Forwards.
+ */
+STATIC int regtry();
+STATIC int regmatch();
+STATIC int regrepeat();
+
+#ifdef DEBUG
+int regnarrate = 0;
+void regdump();
+STATIC char *regprop();
+#endif
+
+/*
+ - regexec - match a regexp against a string
+ */
+int
+regexec(prog, string)
+register regexp *prog;
+register char *string;
+{
+       register char *s;
+       extern char *strchr();
+
+       /* Be paranoid... */
+       if (prog == NULL || string == NULL) {
+               regerror("NULL parameter");
+               return(0);
+       }
+
+       /* Check validity of program. */
+       if (UCHARAT(prog->program) != MAGIC) {
+               regerror("corrupted program");
+               return(0);
+       }
+
+       /* If there is a "must appear" string, look for it. */
+       if (prog->regmust != NULL) {
+               s = string;
+               while ((s = strchr(s, prog->regmust[0])) != NULL) {
+                       if (strncmp(s, prog->regmust, prog->regmlen) == 0)
+                               break;  /* Found it. */
+                       s++;
+               }
+               if (s == NULL)  /* Not present. */
+                       return(0);
+       }
+
+       /* Mark beginning of line for ^ . */
+       regbol = string;
+
+       /* Simplest case:  anchored match need be tried only once. */
+       if (prog->reganch)
+               return(regtry(prog, string));
+
+       /* Messy cases:  unanchored match. */
+       s = string;
+       if (prog->regstart != '\0')
+               /* We know what char it must start with. */
+               while ((s = strchr(s, prog->regstart)) != NULL) {
+                       if (regtry(prog, s))
+                               return(1);
+                       s++;
+               }
+       else
+               /* We don't -- general case. */
+               do {
+                       if (regtry(prog, s))
+                               return(1);
+               } while (*s++ != '\0');
+
+       /* Failure. */
+       return(0);
+}
+
+/*
+ - regtry - try match at specific point
+ */
+static int                     /* 0 failure, 1 success */
+regtry(prog, string)
+regexp *prog;
+char *string;
+{
+       register int i;
+       register char **sp;
+       register char **ep;
+
+       reginput = string;
+       regstartp = prog->startp;
+       regendp = prog->endp;
+
+       sp = prog->startp;
+       ep = prog->endp;
+       for (i = NSUBEXP; i > 0; i--) {
+               *sp++ = NULL;
+               *ep++ = NULL;
+       }
+       if (regmatch(prog->program + 1)) {
+               prog->startp[0] = string;
+               prog->endp[0] = reginput;
+               return(1);
+       } else
+               return(0);
+}
+
+/*
+ - regmatch - main matching routine
+ *
+ * Conceptually the strategy is simple:  check to see whether the current
+ * node matches, call self recursively to see whether the rest matches,
+ * and then act accordingly.  In practice we make some effort to avoid
+ * recursion, in particular by going through "ordinary" nodes (that don't
+ * need to know whether the rest of the match failed) by a loop instead of
+ * by recursion.
+ */
+static int                     /* 0 failure, 1 success */
+regmatch(prog)
+char *prog;
+{
+       register char *scan;    /* Current node. */
+       char *next;             /* Next node. */
+       extern char *strchr();
+
+       scan = prog;
+#ifdef DEBUG
+       if (scan != NULL && regnarrate)
+               fprintf(stderr, "%s(\n", regprop(scan));
+#endif
+       while (scan != NULL) {
+#ifdef DEBUG
+               if (regnarrate)
+                       fprintf(stderr, "%s...\n", regprop(scan));
+#endif
+               next = regnext(scan);
+
+               switch (OP(scan)) {
+               case BOL:
+                       if (reginput != regbol)
+                               return(0);
+                       break;
+               case EOL:
+                       if (*reginput != '\0')
+                               return(0);
+                       break;
+               case ANY:
+                       if (*reginput == '\0')
+                               return(0);
+                       reginput++;
+                       break;
+               case EXACTLY: {
+                               register int len;
+                               register char *opnd;
+
+                               opnd = OPERAND(scan);
+                               /* Inline the first character, for speed. */
+                               if (*opnd != *reginput)
+                                       return(0);
+                               len = strlen(opnd);
+                               if (len > 1 && strncmp(opnd, reginput, len) != 0)
+                                       return(0);
+                               reginput += len;
+                       }
+                       break;
+               case ANYOF:
+                       if (*reginput == '\0' || strchr(OPERAND(scan), *reginput) == NULL)
+                               return(0);
+                       reginput++;
+                       break;
+               case ANYBUT:
+                       if (*reginput == '\0' || strchr(OPERAND(scan), *reginput) != NULL)
+                               return(0);
+                       reginput++;
+                       break;
+               case NOTHING:
+                       break;
+               case BACK:
+                       break;
+               case OPEN+1:
+               case OPEN+2:
+               case OPEN+3:
+               case OPEN+4:
+               case OPEN+5:
+               case OPEN+6:
+               case OPEN+7:
+               case OPEN+8:
+               case OPEN+9: {
+                               register int no;
+                               register char *save;
+
+                               no = OP(scan) - OPEN;
+                               save = reginput;
+
+                               if (regmatch(next)) {
+                                       /*
+                                        * Don't set startp if some later
+                                        * invocation of the same parentheses
+                                        * already has.
+                                        */
+                                       if (regstartp[no] == NULL)
+                                               regstartp[no] = save;
+                                       return(1);
+                               } else
+                                       return(0);
+                       }
+                       break;
+               case CLOSE+1:
+               case CLOSE+2:
+               case CLOSE+3:
+               case CLOSE+4:
+               case CLOSE+5:
+               case CLOSE+6:
+               case CLOSE+7:
+               case CLOSE+8:
+               case CLOSE+9: {
+                               register int no;
+                               register char *save;
+
+                               no = OP(scan) - CLOSE;
+                               save = reginput;
+
+                               if (regmatch(next)) {
+                                       /*
+                                        * Don't set endp if some later
+                                        * invocation of the same parentheses
+                                        * already has.
+                                        */
+                                       if (regendp[no] == NULL)
+                                               regendp[no] = save;
+                                       return(1);
+                               } else
+                                       return(0);
+                       }
+                       break;
+               case BRANCH: {
+                               register char *save;
+
+                               if (OP(next) != BRANCH)         /* No choice. */
+                                       next = OPERAND(scan);   /* Avoid recursion. */
+                               else {
+                                       do {
+                                               save = reginput;
+                                               if (regmatch(OPERAND(scan)))
+                                                       return(1);
+                                               reginput = save;
+                                               scan = regnext(scan);
+                                       } while (scan != NULL && OP(scan) == BRANCH);
+                                       return(0);
+                                       /* NOTREACHED */
+                               }
+                       }
+                       break;
+               case STAR:
+               case PLUS: {
+                               register char nextch;
+                               register int no;
+                               register char *save;
+                               register int min;
+
+                               /*
+                                * Lookahead to avoid useless match attempts
+                                * when we know what character comes next.
+                                */
+                               nextch = '\0';
+                               if (OP(next) == EXACTLY)
+                                       nextch = *OPERAND(next);
+                               min = (OP(scan) == STAR) ? 0 : 1;
+                               save = reginput;
+                               no = regrepeat(OPERAND(scan));
+                               while (no >= min) {
+                                       /* If it could work, try it. */
+                                       if (nextch == '\0' || *reginput == nextch)
+                                               if (regmatch(next))
+                                                       return(1);
+                                       /* Couldn't or didn't -- back up. */
+                                       no--;
+                                       reginput = save + no;
+                               }
+                               return(0);
+                       }
+                       break;
+               case END:
+                       return(1);      /* Success! */
+                       break;
+               default:
+                       regerror("memory corruption");
+                       return(0);
+                       break;
+               }
+
+               scan = next;
+       }
+
+       /*
+        * We get here only if there's trouble -- normally "case END" is
+        * the terminating point.
+        */
+       regerror("corrupted pointers");
+       return(0);
+}
+
+/*
+ - regrepeat - repeatedly match something simple, report how many
+ */
+static int
+regrepeat(p)
+char *p;
+{
+       register int count = 0;
+       register char *scan;
+       register char *opnd;
+       extern char *strchr();
+
+       scan = reginput;
+       opnd = OPERAND(p);
+       switch (OP(p)) {
+       case ANY:
+               count = strlen(scan);
+               scan += count;
+               break;
+       case EXACTLY:
+               while (*opnd == *scan) {
+                       count++;
+                       scan++;
+               }
+               break;
+       case ANYOF:
+               while (*scan != '\0' && strchr(opnd, *scan) != NULL) {
+                       count++;
+                       scan++;
+               }
+               break;
+       case ANYBUT:
+               while (*scan != '\0' && strchr(opnd, *scan) == NULL) {
+                       count++;
+                       scan++;
+               }
+               break;
+       default:                /* Oh dear.  Called inappropriately. */
+               regerror("internal foulup");
+               count = 0;      /* Best compromise. */
+               break;
+       }
+       reginput = scan;
+
+       return(count);
+}
+
+/*
+ - regnext - dig the "next" pointer out of a node
+ */
+static char *
+regnext(p)
+register char *p;
+{
+       register int offset;
+
+       if (p == &regdummy)
+               return(NULL);
+
+       offset = NEXT(p);
+       if (offset == 0)
+               return(NULL);
+
+       if (OP(p) == BACK)
+               return(p-offset);
+       else
+               return(p+offset);
+}
+
+#ifdef DEBUG
+
+STATIC char *regprop();
+
+/*
+ - regdump - dump a regexp onto stdout in vaguely comprehensible form
+ */
+void
+regdump(r)
+regexp *r;
+{
+       register char *s;
+       register char op = EXACTLY;     /* Arbitrary non-END op. */
+       register char *next;
+       extern char *strchr();
+
+
+       s = r->program + 1;
+       while (op != END) {     /* While that wasn't END last time... */
+               op = OP(s);
+               printf("%2d%s", s-r->program, regprop(s));      /* Where, what. */
+               next = regnext(s);
+               if (next == NULL)               /* Next ptr. */
+                       printf("(0)");
+               else 
+                       printf("(%d)", (s-r->program)+(next-s));
+               s += 3;
+               if (op == ANYOF || op == ANYBUT || op == EXACTLY) {
+                       /* Literal string, where present. */
+                       while (*s != '\0') {
+                               putchar(*s);
+                               s++;
+                       }
+                       s++;
+               }
+               putchar('\n');
+       }
+
+       /* Header fields of interest. */
+       if (r->regstart != '\0')
+               printf("start `%c' ", r->regstart);
+       if (r->reganch)
+               printf("anchored ");
+       if (r->regmust != NULL)
+               printf("must have \"%s\"", r->regmust);
+       printf("\n");
+}
+
+/*
+ - regprop - printable representation of opcode
+ */
+static char *
+regprop(op)
+char *op;
+{
+       register char *p;
+       static char buf[50];
+
+       (void) strcpy(buf, ":");
+
+       switch (OP(op)) {
+       case BOL:
+               p = "BOL";
+               break;
+       case EOL:
+               p = "EOL";
+               break;
+       case ANY:
+               p = "ANY";
+               break;
+       case ANYOF:
+               p = "ANYOF";
+               break;
+       case ANYBUT:
+               p = "ANYBUT";
+               break;
+       case BRANCH:
+               p = "BRANCH";
+               break;
+       case EXACTLY:
+               p = "EXACTLY";
+               break;
+       case NOTHING:
+               p = "NOTHING";
+               break;
+       case BACK:
+               p = "BACK";
+               break;
+       case END:
+               p = "END";
+               break;
+       case OPEN+1:
+       case OPEN+2:
+       case OPEN+3:
+       case OPEN+4:
+       case OPEN+5:
+       case OPEN+6:
+       case OPEN+7:
+       case OPEN+8:
+       case OPEN+9:
+               sprintf(buf+strlen(buf), "OPEN%d", OP(op)-OPEN);
+               p = NULL;
+               break;
+       case CLOSE+1:
+       case CLOSE+2:
+       case CLOSE+3:
+       case CLOSE+4:
+       case CLOSE+5:
+       case CLOSE+6:
+       case CLOSE+7:
+       case CLOSE+8:
+       case CLOSE+9:
+               sprintf(buf+strlen(buf), "CLOSE%d", OP(op)-CLOSE);
+               p = NULL;
+               break;
+       case STAR:
+               p = "STAR";
+               break;
+       case PLUS:
+               p = "PLUS";
+               break;
+       default:
+               regerror("corrupted opcode");
+               break;
+       }
+       if (p != NULL)
+               (void) strcat(buf, p);
+       return(buf);
+}
+#endif
+
+/*
+ * The following is provided for those people who do not have strcspn() in
+ * their C libraries.  They should get off their butts and do something
+ * about it; at least one public-domain implementation of those (highly
+ * useful) string routines has been published on Usenet.
+ */
+#ifdef STRCSPN
+/*
+ * strcspn - find length of initial segment of s1 consisting entirely
+ * of characters not from s2
+ */
+
+static int
+strcspn(s1, s2)
+char *s1;
+char *s2;
+{
+       register char *scan1;
+       register char *scan2;
+       register int count;
+
+       count = 0;
+       for (scan1 = s1; *scan1 != '\0'; scan1++) {
+               for (scan2 = s2; *scan2 != '\0';)       /* ++ moved down. */
+                       if (*scan1 == *scan2++)
+                               return(count);
+               count++;
+       }
+       return(count);
+}
+#endif
diff --git a/regexp.h b/regexp.h
new file mode 100644 (file)
index 0000000..b23d97e
--- /dev/null
+++ b/regexp.h
@@ -0,0 +1,40 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+/*
+ * Definitions etc. for regexp(3) routines.
+ *
+ * Caveat:  this is V8 regexp(3) [actually, a reimplementation thereof],
+ * not the System V one.
+ */
+#define NSUBEXP  10
+typedef struct regexp {
+       char *startp[NSUBEXP];
+       char *endp[NSUBEXP];
+       char regstart;          /* Internal use only. */
+       char reganch;           /* Internal use only. */
+       char *regmust;          /* Internal use only. */
+       int regmlen;            /* Internal use only. */
+       char program[1];        /* Unwarranted chumminess with compiler. */
+} regexp;
+
+extern regexp *regcomp();
+extern int regexec();
+extern void regsub();
+extern void regerror();
diff --git a/regmagic.h b/regmagic.h
new file mode 100644 (file)
index 0000000..0b18b0a
--- /dev/null
@@ -0,0 +1,24 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+/*
+ * The first byte of the regexp internal "program" is actually this magic
+ * number; the start node begins in the second byte.
+ */
+#define        MAGIC   0234
diff --git a/report.c b/report.c
new file mode 100644 (file)
index 0000000..a458617
--- /dev/null
+++ b/report.c
@@ -0,0 +1,260 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+#include "tac_plus.h"
+#include <stdio.h>
+
+#ifdef AIX
+#include <sys/types.h>
+#else
+#include <time.h>
+#endif
+
+#ifdef __STDC__
+#include <stdarg.h>            /* ANSI C, variable length args */
+#else
+#include <varargs.h>           /* has 'vararg' definitions */
+#endif
+
+FILE *ostream = NULL;
+
+char *logfile = LOGFILE_DEFAULT;
+
+/* report:
+ *
+ * This routine reports errors and such via stderr and syslog() if
+ * appopriate.  It just helps avoid a lot of if-else in the code.
+ *
+ * LOG_DEBUG messages are ignored unless debugging is on.
+ * All other priorities are always logged to syslog.
+ */
+
+#ifdef __STDC__
+void
+report(int priority, char *fmt,...)
+#else
+/* VARARGS2 */
+void
+report(priority, fmt, va_alist)
+int priority;
+char *fmt;
+va_dcl                         /* no terminating semi-colon */
+#endif
+{
+    char msg[255];             /* temporary string */
+    char *fp, *bufp, *charp;
+    int len, m, i, n;
+    char digits[16];
+    va_list ap;
+
+#ifdef __STDC__
+    va_start(ap, fmt);
+#else
+    va_start(ap);
+#endif
+
+    /* ensure that msg is never overwritten */
+    n = 255;
+    fp = fmt;
+    len = 0;
+    msg[n-1] = '\0';
+    bufp = msg;
+
+    while (*fp) {
+
+       if (*fp != '%') {
+           if ((len+1) >= n) {
+               break;
+           }
+           *bufp++ = *fp++;
+           len++;
+           continue;
+       }
+
+       /* seen a '%' */
+       fp++;
+
+       switch (*fp) {
+
+       case 's':
+           fp++;
+           charp = va_arg(ap, char *);
+           m = strlen(charp);
+           break;
+
+       case 'u':
+           fp++;
+           i = va_arg(ap, uint);
+           sprintf(digits, "%u", i);
+           m = strlen(digits);
+           charp = digits;
+           break;
+       case 'x':
+           fp++;
+           i = va_arg(ap, uint);
+           sprintf(digits, "%x", i);
+           m = strlen(digits);
+           charp = digits;
+           break;
+       case 'd':
+           fp++;
+           i = va_arg(ap, int);
+           sprintf(digits, "%d", i);
+           m = strlen(digits);
+           charp = digits;
+           break;
+       }
+           
+       if ((len + m + 1) >= n) {
+           break;
+       }
+
+       memcpy(bufp, charp, m);
+       bufp += m;
+       len += m;
+       continue;
+    }
+
+    msg[len] = '\0';
+
+    /* check we never overwrote the end of the buffer */
+    if (msg[n-1]) {
+       abort();
+    }
+
+    va_end(ap);
+
+
+    if (console) {
+       extern int errno;
+       
+       if (!ostream)
+           ostream = fopen("/dev/console", "w");
+
+       if (ostream) {
+           if (priority == LOG_ERR)
+               fprintf(ostream, "Error ");
+           fprintf(ostream, "%s\n", msg);
+       }
+       else 
+           syslog(LOG_ERR, "Cannot open /dev/console errno=%d", errno);
+    }
+
+    if (debug) {
+       int logfd;
+
+       logfd = open(logfile, O_CREAT | O_WRONLY | O_APPEND, 0640);
+       if (logfd >= 0) {
+           char buf[512];
+           time_t t = time(NULL);
+           char *ct = ctime(&t);
+
+           ct[24] = '\0';
+           tac_lockfd(logfile, logfd);
+           sprintf(buf, "%s [%d]: ", ct, getpid());
+           write(logfd, buf, strlen(buf));
+           if (priority == LOG_ERR)
+               write(logfd, "Error ", 6);
+           write(logfd, msg, strlen(msg));
+           write(logfd, "\n", 1);
+           close(logfd);
+       }
+    }
+
+    if (single) {
+       fprintf(stderr, "%s\n", msg);
+    }
+
+    if (priority == LOG_DEBUG)
+       return;
+
+    if (priority == LOG_ERR)
+       syslog(priority, "Error %s", msg);
+    else
+       syslog(priority, "%s", msg);
+}
+
+/* format a hex dump for syslog */
+void
+report_hex(priority, p, len)
+u_char *p;
+int len;
+{
+    char buf[256];
+    char digit[10];
+    int buflen;
+    int i;
+    
+    if (len <= 0)
+       return;
+
+    buf[0] = '\0';
+    buflen = 0;
+    for (i = 0; i < len && i < 255; i++, p++) {
+
+       sprintf(digit, "0x%x ", *p);
+       strcat(buf, digit);
+       buflen += strlen(digit);
+
+       if (buflen > 75) {
+           report(priority, "%s", buf);
+           buf[0] = '\0';
+           buflen = 0;
+       }
+    }
+
+    if (buf[0]) {
+       report(priority, "%s", buf);
+    }
+}
+
+
+/* format a non-null terminated string for syslog */
+void
+report_string(priority, p, len)
+u_char *p;
+int len;
+{
+    char buf[256];
+    char *bufp = buf;
+    int i;
+
+    if (len <= 0)
+       return;
+
+    for (i = 0; i < len && i < 255; i++) {
+       if (32 <= *p && *p <= 126) {
+           *bufp++ = *p++;
+       } else {
+           sprintf(bufp, " 0x%x ", *p);
+           bufp += strlen(bufp);
+           p++;
+       }
+    }
+    *bufp = '\0';
+    report(priority, "%s", buf);
+}
+
+void
+regerror(s)
+char *s;
+{
+    report(LOG_ERR, "in regular expression %s", s);
+}
+
diff --git a/sendauth.c b/sendauth.c
new file mode 100644 (file)
index 0000000..68e5482
--- /dev/null
@@ -0,0 +1,349 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+#include "tac_plus.h"
+#include "expire.h"
+#include "md5.h"
+
+static int do_sendauth_fn();
+static void outbound_chap();
+#ifdef MSCHAP
+static void outbound_mschap();
+#endif /* MSCHAP */
+void outbound_pap();
+
+int sendauth_fn(data)
+struct authen_data *data;
+{
+    int status;
+    char *name, *p;
+
+    name = data->NAS_id->username;
+
+    if (STREQ(name, DEFAULT_USERNAME)) {
+       /* This username is only valid for authorization */
+       data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+    } else {
+       status = do_sendauth_fn(data);
+    }
+
+    if (debug) {
+       switch (data->type) {
+       case TAC_PLUS_AUTHEN_TYPE_CHAP:
+           p = "chap";
+           break;
+
+#ifdef MSCHAP
+       case TAC_PLUS_AUTHEN_TYPE_MSCHAP:
+           p = "ms-chap";
+           break;
+#endif /* MSCHAP */
+
+       case TAC_PLUS_AUTHEN_TYPE_PAP:
+           p = "pap";
+           break;
+
+       default:
+           p = "unknown";
+           break;
+       }
+
+       report(LOG_INFO, "%s-sendauth query for '%s' %s from %s %s",
+              p,
+              name && name[0] ? name : "unknown",
+              session.peer, session.port, 
+              (data->status == TAC_PLUS_AUTHEN_STATUS_PASS) ?
+              "accepted" : "rejected");
+    }
+    return(status);
+}
+
+/*
+ * For PAP we need to supply the outgoing PAP cleartext password.
+ * from the config file. 
+ *
+ * For CHAP, we expect an id and a challenge. We will return an MD5 hash
+ * if we're successful,
+ *
+ * Return 0 if data->status is valid, otherwise 1 
+ */
+
+static int
+do_sendauth_fn(data)
+struct authen_data *data;
+{
+    char *name, *exp_date;
+
+    data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+
+    /* We must have a username */
+    if (!data->NAS_id->username[0]) {
+       /* Missing username is a gross error */
+       data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+       data->server_msg = tac_strdup("No username supplied");
+       report(LOG_ERR, "%s: No username for sendauth_fn", session.peer);
+       return (0);
+    }
+    name = data->NAS_id->username;
+
+    switch (data->type) {
+    case TAC_PLUS_AUTHEN_TYPE_CHAP:
+       outbound_chap(data);
+       break;
+
+#ifdef MSCHAP
+    case TAC_PLUS_AUTHEN_TYPE_MSCHAP:
+       outbound_mschap(data);
+       break;
+#endif /* MSCHAP */
+
+    case TAC_PLUS_AUTHEN_TYPE_PAP:
+       outbound_pap(data);
+       break;
+
+    default:
+       data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+       report(LOG_ERR, "%s %s: %s Illegal data type for sendauth_fn", 
+              session.peer, session.port, name);
+       return (0);     
+    }
+
+    exp_date = cfg_get_expires(name, TAC_PLUS_RECURSE);
+    set_expiration_status(exp_date, data);
+    return (0);
+}
+
+void
+outbound_pap(data)
+struct authen_data *data;
+{
+    char *secret, *p, *name;
+
+    name = data->NAS_id->username;
+
+    /* We must have a username */
+    if (!name) {
+       data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+       return;
+    }
+
+    /* Return her secret outbound PAP info */
+    secret = cfg_get_opap_secret(name, TAC_PLUS_RECURSE);
+    if (!secret) {
+       if (debug & DEBUG_AUTHEN_FLAG) {
+           report(LOG_ERR, "%s %s: No opap secret for %s",
+                  session.peer, session.port, name);
+       }
+       data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+       return;
+    }
+
+    p = tac_find_substring("cleartext ", secret);
+    if (!p) {
+       /* Should never happen */
+       data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+       report(LOG_ERR, "%s %s: Illegal opap secret format %s",
+              session.peer, session.port, secret);
+       return;
+    }
+
+    data->server_data = tac_strdup(p);
+    data->server_dlen = strlen(data->server_data);
+    data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
+}
+
+static void
+outbound_chap(data)
+struct authen_data *data;
+{
+    char *name, *secret, *chal, digest[MD5_LEN];
+    char *p;
+    u_char *mdp;
+    char id;
+    int chal_len, inlen;
+    MD5_CTX mdcontext;
+
+    name = data->NAS_id->username;
+
+    if (!name) {
+       report(LOG_ERR, "%s %s: no username for outbound_chap", 
+              session.peer, session.port);
+       data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+       return;
+    }
+
+    id = data->client_data[0];
+
+    chal_len = data->client_dlen - 1;
+    if (chal_len < 0) {
+       data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+       return;
+    }
+
+    if (debug & DEBUG_AUTHEN_FLAG) {
+       report(LOG_DEBUG, "%s %s: user %s, id=%d chal_len=%d",
+              session.peer, session.port, name, (int)id, chal_len);
+    }
+
+    /* Assume failure */
+    data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+
+    /* Get the secret */
+    secret = cfg_get_chap_secret(name, TAC_PLUS_RECURSE);
+
+    /* If there is no chap password for this user, see if there is 
+       a global password for her that we can use */
+    if (!secret) {
+       secret = cfg_get_global_secret(name, TAC_PLUS_RECURSE);
+    }
+
+    if (!secret) {
+       /* No secret. Fail */
+       if (debug & DEBUG_AUTHEN_FLAG) {
+           report(LOG_DEBUG, "%s %s: No chap or global secret for %s",
+                  session.peer, session.port, name);
+       }
+       data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+       return;
+    }
+
+
+    p = tac_find_substring("cleartext ", secret);
+    if (!p) {
+       /* Should never happen */
+       data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+       report(LOG_ERR, "%s %s: Illegal opap secret format %s",
+              session.peer, session.port, secret);
+       return;
+    }
+    secret = p;
+
+    /*
+     * We now have the secret, the id, and the challenge value. 
+     * Put them all together, and run them through the MD5 digest
+     * algorithm. */
+
+    inlen = sizeof(u_char) + strlen(secret) + chal_len;
+    mdp = (u_char *)tac_malloc(inlen);
+    mdp[0] = id;
+    bcopy(secret, &mdp[1], strlen(secret));
+    chal = data->client_data + 1;
+    bcopy(chal, mdp + strlen(secret) + 1, chal_len);
+    MD5Init(&mdcontext);
+    MD5Update(&mdcontext, mdp, inlen);
+    MD5Final((u_char *)digest, &mdcontext);
+    free(mdp);
+
+    /*
+     * Now return the calculated response value */
+
+    data->server_data = tac_malloc(MD5_LEN);
+    bcopy(digest, data->server_data, MD5_LEN);
+    data->server_dlen = MD5_LEN;
+
+    data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
+}
+
+#ifdef MSCHAP
+
+static void
+outbound_mschap(data)
+struct authen_data *data;
+{
+    char *name, *secret, *chal;
+    char *p;
+    char id;
+    int chal_len;
+
+    name = data->NAS_id->username;
+
+    if (!name) {
+       report(LOG_ERR, "%s %s: no username for outbound_mschap",
+              session.peer, session.port);
+       data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+       return;
+    }
+
+    id = data->client_data[0];
+
+    chal_len = data->client_dlen - 1;
+    if (data->client_dlen <= 2) {
+       data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+       return;
+    }
+
+    if (debug & DEBUG_AUTHEN_FLAG) {
+       report(LOG_DEBUG, "%s %s: user %s, id=%d chal_len=%d",
+              session.peer, session.port, name, (int)id, chal_len);
+    }
+
+    /* Assume failure */
+    data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+
+    /* Get the secret */
+    secret = cfg_get_mschap_secret(name, TAC_PLUS_RECURSE);
+
+    /* If there is no chap password for this user, see if there is 
+       a global password for her that we can use */
+    if (!secret) {
+       secret = cfg_get_global_secret(name, TAC_PLUS_RECURSE);
+    }
+
+    if (!secret) {
+       /* No secret. Fail */
+       if (debug & DEBUG_AUTHEN_FLAG) {
+           report(LOG_DEBUG, "%s %s: No ms-chap or global secret for %s",
+                  session.peer, session.port, name);
+       }
+       data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+       return;
+    }
+
+    p = tac_find_substring("cleartext ", secret);
+    if (!p) {
+       /* Should never happen */
+       data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+       report(LOG_ERR, "%s %s: Illegal ms-chap secret format %s",
+              session.peer, session.port, secret);
+       return;
+    }
+    secret = p;
+
+    /*
+     * We now have the secret, the id, and the challenge value. 
+     * Put them all together, and run them through the MD4 digest
+     * algorithm. */
+
+    chal = data->client_data + 1;
+
+    /*
+     * Now return the calculated response value */
+
+    data->server_data = tac_malloc(MSCHAP_DIGEST_LEN);
+
+    mschap_lmchallengeresponse(chal,secret,&data->server_data[0]);
+    mschap_ntchallengeresponse(chal,secret,&data->server_data[24]);
+
+    data->server_data[48] = 1; /* Mark it to use the NT response*/
+    data->server_dlen = MSCHAP_DIGEST_LEN;
+
+    data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
+}
+
+#endif /* MSCHAP */
diff --git a/sendpass.c b/sendpass.c
new file mode 100644 (file)
index 0000000..f7f0b3c
--- /dev/null
@@ -0,0 +1,171 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+#include "tac_plus.h"
+#include "expire.h"
+
+static int
+do_sendpass_fn();
+
+int sendpass_fn(data)
+struct authen_data *data;
+{
+    int status;
+    char *name = data->NAS_id->username;
+    char *port = data->NAS_id->NAS_port;
+
+    if (sendauth_only) {
+       /* sendpass is disallowed */
+       report(LOG_ERR, "%s: %s %s sendpass request rejected",
+              session.peer, session.port, name ? name : "<unknown>");
+       data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+       return(0);
+    }
+
+    if (STREQ(name, DEFAULT_USERNAME)) {
+       data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+       status = 0;
+    } else {
+       status = do_sendpass_fn(data);
+    }
+
+    if (debug)
+       report(LOG_INFO, "sendpass query for '%s' %s from %s %s",
+              name && name[0] ? name : "unknown",
+              port && port[0] ? port : "unknown", 
+              session.peer, 
+              (data->status == TAC_PLUS_AUTHEN_STATUS_PASS) ?
+              "accepted" : "rejected");
+
+    return(status);
+}
+
+/*
+ * Cleartext password information has been requested.  Look this up in
+ * the config file. Set authen_data->status.
+ *
+ * Any strings pointed to by authen_data must come from the heap. They
+ * will get freed by the caller.
+ *
+ * Return 0 if data->status is valid, otherwise 1 */
+
+static int
+do_sendpass_fn(data)
+struct authen_data *data;
+{
+    char *name;
+    char *p;
+    int expired;
+    char *exp_date;
+    char *secret;
+
+    data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+
+    /* We must have a username */
+    if (!data->NAS_id->username[0]) {
+       /* choose_authen should have already asked for a username, so this is
+        * a gross error */
+       data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+       data->server_msg = tac_strdup("No username supplied");
+       report(LOG_ERR, "%s: No username for sendpass_fn", session.peer);
+       return (0);
+    }
+    name = data->NAS_id->username;
+
+    exp_date = cfg_get_expires(name, TAC_PLUS_RECURSE);
+
+    /* The user exists. Check her expiration date, if any */
+    expired = check_expiration(exp_date);
+
+    switch (expired) {
+    case PW_EXPIRED:
+       data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+       data->server_msg = tac_strdup("Password has expired");
+       return (0);
+
+    default:
+       data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+       data->server_msg = tac_strdup("Bad return value for password expiration check");
+       report(LOG_ERR, "%s: Bogus return value %d from check_expiration",
+              session.peer, expired);
+       return (0);
+
+    case PW_OK:
+    case PW_EXPIRING:
+
+       /* The user exists, and has not expired. Return her secret info */
+       switch (data->type) {
+       case TAC_PLUS_AUTHEN_TYPE_CHAP:
+           secret = cfg_get_chap_secret(name, TAC_PLUS_RECURSE);
+           if (!secret)
+               secret = cfg_get_global_secret(name, TAC_PLUS_RECURSE);
+           break;
+
+#ifdef MSCHAP
+       case TAC_PLUS_AUTHEN_TYPE_MSCHAP:
+           secret = cfg_get_mschap_secret(name, TAC_PLUS_RECURSE);
+           if (!secret)
+               secret = cfg_get_global_secret(name, TAC_PLUS_RECURSE);
+           break;
+#endif /* MSCHAP */
+
+       case TAC_PLUS_AUTHEN_TYPE_ARAP:
+           secret = cfg_get_arap_secret(name, TAC_PLUS_RECURSE);
+           if (!secret)
+               secret = cfg_get_global_secret(name, TAC_PLUS_RECURSE);
+           break;
+
+       case TAC_PLUS_AUTHEN_TYPE_PAP:
+           secret = cfg_get_opap_secret(name, TAC_PLUS_RECURSE);
+           break;
+
+       default:
+           data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+           data->server_msg = tac_strdup("Illegal authentication type");
+           report(LOG_ERR, "%s: Illegal authentication type %d",
+                  session.peer, data->type);
+           return (0);
+       }
+
+       if (!secret) {
+           data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+           data->server_msg = tac_strdup("No secret");
+           return (0);
+       }
+
+       p = tac_find_substring("cleartext ", secret);
+       if (!p) {
+           /* Should never happen */
+           data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+           data->server_msg = tac_strdup("Illegal secret format");
+           report(LOG_ERR, "%s: Illegal secret format %s",
+                  session.peer, secret);
+           return(0);
+       }
+
+       data->server_data = tac_strdup(p);
+       data->server_dlen = strlen(data->server_data);
+       data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
+       if (expired == PW_EXPIRING) {
+           data->server_msg = tac_strdup("Secret will expire soon");
+       }
+       return (0);
+    }
+    /* never reached */
+}
diff --git a/skey_fn.c b/skey_fn.c
new file mode 100644 (file)
index 0000000..d3c9860
--- /dev/null
+++ b/skey_fn.c
@@ -0,0 +1,233 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+#ifdef SKEY
+#include "tac_plus.h"
+#include "expire.h"
+
+/* internal state variables */
+#define STATE_AUTHEN_START   0 /* no requests issued */
+#define STATE_AUTHEN_GETUSER 1 /* username has been requested */
+#define STATE_AUTHEN_GETPASS 2 /* password has been requested */
+
+#include <skey.h>
+
+struct private_data {
+    struct skey skey;
+    char password[MAX_PASSWD_LEN + 1];
+    int state;
+};
+
+/* Use s/key to verify a supplied password using state set up earlier
+when the username was supplied */
+
+static int
+skey_verify(passwd, data)
+char *passwd;
+struct authen_data *data;
+{
+    struct private_data *p = data->method_data;
+    struct skey *skeyp = &p->skey;
+
+    data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+
+    if (skeyverify(skeyp, passwd) == 0) {
+       /* S/Key authentication succeeded */
+       data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
+       if (skeyp->n < 5) {
+           data->server_msg = tac_strdup("Password will expire soon");
+           return (1);
+       }
+    }
+    return (0);
+}
+
+/*
+ * Skey tacacs login authentication function. Wants a username
+ * and a password, and tries to verify them via skey.
+ *
+ * Choose_authen will ensure that we already have a username before this
+ * gets called.
+ *
+ * We will query for a password and keep it in the method_data.
+ *
+ * Any strings returned via pointers in authen_data must come from the
+ * heap. They will get freed by the caller.
+ *
+ * Return 0 if data->status is valid, otherwise 1
+ */
+
+int
+skey_fn(data)
+struct authen_data *data;
+{
+    char *name, *passwd;
+    struct private_data *p;
+    char *prompt;
+    int pwlen;
+
+    p = (struct private_data *) data->method_data;
+
+    /* An abort has been received. Clean up and return */
+    if (data->flags & TAC_PLUS_CONTINUE_FLAG_ABORT) {
+       if (data->method_data)
+           free(data->method_data);
+       data->method_data = NULL;
+       return (1);
+    }
+    /* Initialise method_data if first time through */
+    if (!p) {
+       p = (struct private_data *) tac_malloc(sizeof(struct private_data));
+       bzero(p, sizeof(struct private_data));
+       data->method_data = p;
+       p->state = STATE_AUTHEN_START;
+    }
+
+    /* Unless we're enabling, we need a username */
+    if (data->service != TAC_PLUS_AUTHEN_SVC_ENABLE &&
+       !(char) data->NAS_id->username[0]) {
+       switch (p->state) {
+
+       case STATE_AUTHEN_GETUSER:
+           /* we have previously asked for a username but none came back.
+            * This is a gross error */
+           data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+           report(LOG_ERR, "%s: No username supplied after GETUSER",
+                  session.peer);
+           return (0);
+
+       case STATE_AUTHEN_START:
+           /* No username. Try requesting one */
+           data->status = TAC_PLUS_AUTHEN_STATUS_GETUSER;
+           if (data->service == TAC_PLUS_AUTHEN_SVC_LOGIN) {
+               prompt = "\nUser Access Verification\n\nUsername: ";
+           } else {
+               prompt = "Username: ";
+           }
+           data->server_msg = tac_strdup(prompt);
+           p->state = STATE_AUTHEN_GETUSER;
+           return (0);
+
+       default:
+           /* something awful has happened. Give up and die */
+           report(LOG_ERR, "%s: skey_fn bad state %d", 
+                  session.peer, p->state);
+           return (1);
+       }
+    }
+
+    /* we now have a username if we needed one */
+    name = data->NAS_id->username;
+
+    /* Do we have a password? */
+    passwd = p->password;
+
+    if (!passwd[0]) {
+       char skeyprompt[80];
+
+       /* no password yet. Either we need to ask for one and expect to get
+        * called again, or we asked but nothing came back, which is fatal */
+
+       switch (p->state) {
+       case STATE_AUTHEN_GETPASS:
+           /* We already asked for a password. This should be the reply */
+           if (data->client_msg) {
+               pwlen = MIN(strlen(data->client_msg), MAX_PASSWD_LEN);
+           } else {
+               pwlen = 0;
+           }
+           strncpy(passwd, data->client_msg, pwlen);
+           passwd[pwlen] = '\0';
+           break;
+
+       default:
+           /* Request a password */
+           passwd = cfg_get_login_secret(name, TAC_PLUS_RECURSE);
+           if (!passwd && !STREQ(passwd, "skey")) {
+               report(LOG_ERR, "Cannot find skey password declaration for %s",
+                      name);
+               data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+               return(1);
+           }
+
+           if (skeychallenge(&p->skey, name, skeyprompt) == 0) {
+               char buf[256];
+               sprintf(buf, "%s\nPassword: ", skeyprompt);
+               data->server_msg = tac_strdup(buf);
+               data->status = TAC_PLUS_AUTHEN_STATUS_GETPASS;
+               p->state = STATE_AUTHEN_GETPASS;
+               return (0);
+           } 
+
+           data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+           report(LOG_ERR, "Cannot generate skey prompt for %s", name);
+           return(1);
+       }
+    }
+
+    /* We have a username and password. Try validating */
+
+    /* Assume the worst */
+    data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
+
+    switch (data->service) {
+    case TAC_PLUS_AUTHEN_SVC_LOGIN:
+       skey_verify(passwd, data);
+       if (debug)
+           report(LOG_INFO, "login query for '%s' %s from %s %s",
+                  name && name[0] ? name : "unknown",
+                  data->NAS_id->NAS_port && data->NAS_id->NAS_port[0] ?
+                      data->NAS_id->NAS_port : "unknown",
+                  session.peer,
+                  (data->status == TAC_PLUS_AUTHEN_STATUS_PASS) ?
+                  "accepted" : "rejected");
+       break;
+
+    default:
+       data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+       report(LOG_ERR, "%s: Bogus service value %d from packet", 
+              session.peer, data->service);
+       break;
+    }
+
+    if (data->method_data)
+       free(data->method_data);
+    data->method_data = NULL;
+
+    switch (data->status) {
+    case TAC_PLUS_AUTHEN_STATUS_ERROR:
+    case TAC_PLUS_AUTHEN_STATUS_FAIL:
+    case TAC_PLUS_AUTHEN_STATUS_PASS:
+       return (0);
+    default:
+       report(LOG_ERR, "%s: skey_fn couldn't set recognizable status %d",
+              session.peer, data->status);
+       data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+       return (1);
+    }
+}
+#else /* SKEY */
+
+/* The following code is not needed or used. It exists solely to
+   prevent compilers from "helpfully" complaining that this source
+   file is empty, which upsets novices building the software */
+
+static int dummy = 0;
+
+#endif /* SKEY */
diff --git a/stamp-h b/stamp-h
new file mode 100644 (file)
index 0000000..9788f70
--- /dev/null
+++ b/stamp-h
@@ -0,0 +1 @@
+timestamp
diff --git a/tac_pam.c b/tac_pam.c
new file mode 100644 (file)
index 0000000..bf1b215
--- /dev/null
+++ b/tac_pam.c
@@ -0,0 +1,199 @@
+#ifdef USE_PAM
+
+/* tac_pam.auth.c
+ * A simple pam authentication  routine written by 
+ * Max Liccardo <ravel@tiscalinet.it>
+ * PAM_RUSER=username/rem_addr.
+ */
+
+ /*
+    This program was contributed by Shane Watts
+    [modifications by AGM]
+
+    You need to add the following (or equivalent) to the /etc/pam.conf file.
+    # check authorization
+    check_user   auth       required     /usr/lib/security/pam_unix_auth.so
+    check_user   account    required     /usr/lib/security/pam_unix_acct.so
+   */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <security/pam_appl.h>
+#include "tac_plus.h"
+
+typedef struct
+{
+       char *UserName;
+       char *Passwd;
+} UserCred;
+
+
+static int fconv(int num_msg, const struct pam_message **msg,
+               struct pam_response **resp,void *appdata_ptr)
+{
+       int             i;
+    UserCred   *lUserCred;
+
+
+       lUserCred  = appdata_ptr;
+
+       if(lUserCred == NULL)
+       {
+               report(LOG_ERR,"argh....maybe a SunOs 5.6 ???");
+               return(PAM_CONV_ERR);
+       }
+
+
+       *resp = (struct pam_response *) calloc(num_msg,sizeof(struct pam_response));    
+       
+       for(i=0;i<num_msg;i++)
+       {
+               switch(msg[i]->msg_style)
+               {
+                       case PAM_PROMPT_ECHO_OFF:
+                resp[i]->resp = strdup(lUserCred->Passwd);
+                break;
+                       
+                       case PAM_PROMPT_ECHO_ON:
+                resp[i]->resp = strdup(lUserCred->UserName);
+                break;         
+                       
+                       default:
+                                report(LOG_DEBUG,"conv default");
+                       break;
+               }
+               resp[i]->resp_retcode = 0;
+       }
+
+       return(PAM_SUCCESS);
+}
+
+
+
+
+int
+tac_pam_auth(char *aszUserName,char *aszPassword,struct authen_data *data,char *aszService)
+{
+       pam_handle_t    *pamh=NULL;
+       int                     retval;
+       char                    *lpszRemoteUser;                                /* Username/NAC address */
+    struct pam_conv s_conv;
+       UserCred                s_UserCred;
+
+
+       s_UserCred.UserName = aszUserName;
+    s_UserCred.Passwd  = aszPassword;
+
+       s_conv.conv = fconv;
+    s_conv.appdata_ptr = (void *) &s_UserCred;
+
+
+       if((lpszRemoteUser = calloc(strlen(aszUserName)+strlen(data->NAS_id->NAC_address)+2,sizeof(char))) == NULL)
+       {
+        report(LOG_ERR,"cannot malloc");
+               return(1);
+       }
+
+       retval = pam_start(aszService,aszUserName , &s_conv, &pamh);
+
+       if (retval != PAM_SUCCESS)
+       {
+           report(LOG_ERR, "cannot start pam-authentication"); 
+               pamh = NULL;
+               return(1);
+    }
+
+    sprintf(lpszRemoteUser,"%s:%s",aszUserName,data->NAS_id->NAC_address);
+
+    pam_set_item(pamh,PAM_RUSER,lpszRemoteUser);
+    pam_set_item(pamh,PAM_RHOST,data->NAS_id->NAS_name);
+    pam_set_item(pamh,PAM_TTY,data->NAS_id->NAS_port);
+
+       free(lpszRemoteUser);
+
+    retval = pam_authenticate(pamh,0);                                 /* is user really user? */
+
+    if(retval != PAM_SUCCESS)
+       report(LOG_ERR, "%s",pam_strerror(pamh,retval));
+    
+    if (pam_end(pamh,retval) != PAM_SUCCESS) {     /* close Linux-PAM */
+               pamh = NULL;
+               return(1);
+       }
+
+    return ( retval == PAM_SUCCESS ? 0:1 );       /* indicate success */
+}
+
+
+/* PAM authorization rotine written by
+ * Devrim SERAL <devrim@tef.gazi.edu.tr>
+*/
+
+int
+tac_pam_authorization (char *aszUserName,struct author_data *data,char *aszService)
+{
+       pam_handle_t    *pamh=NULL;
+       int                     retval;
+       char                    *lpszRemoteUser;                                /* Username/NAC address */
+       struct pam_conv s_conv;
+       UserCred                s_UserCred;
+
+
+       s_UserCred.UserName = aszUserName;
+
+       s_conv.conv = fconv;
+        s_conv.appdata_ptr = (void *) &s_UserCred;
+
+       if (aszService== NULL) 
+       {
+       report(LOG_ERR,"Service Name doesn't available So authorize him");
+                return(0);
+        }
+       
+
+       if((lpszRemoteUser = calloc(strlen(aszUserName)+strlen(data->id->NAC_address)+2,sizeof(char))) == NULL)
+       {
+        report(LOG_ERR,"cannot malloc");
+               return(1);
+       }
+
+       retval = pam_start(aszService,aszUserName , &s_conv, &pamh);
+
+       if (retval != PAM_SUCCESS)
+       {
+           report(LOG_ERR, "cannot start pam-authentication"); 
+               pamh = NULL;
+               return(1);
+    }
+
+    sprintf(lpszRemoteUser,"%s:%s",aszUserName,data->id->NAC_address);
+
+    pam_set_item(pamh,PAM_RUSER,lpszRemoteUser);
+    pam_set_item(pamh,PAM_RHOST,data->id->NAS_name);
+    pam_set_item(pamh,PAM_TTY,data->id->NAS_port);
+
+       free(lpszRemoteUser);
+
+    retval = pam_acct_mgmt(pamh, 0); /* Is user permit to gain access system */
+    
+    if(retval != PAM_SUCCESS)
+        report(LOG_ERR, "Pam Account Managment:%s",pam_strerror(pamh,retval));
+    else 
+       if (debug & DEBUG_AUTHOR_FLAG)
+        report(LOG_DEBUG, "PAM authorization allow user");    
+    
+   if (pam_end(pamh,retval) != PAM_SUCCESS) {     /* close Linux-PAM */
+               pamh = NULL;
+               return(1);
+       }
+
+    return ( retval == PAM_SUCCESS ? 0:1 );       /* indicate success */
+}
+
+
+#endif /* USE_PAM */
+
+
+
+
diff --git a/tac_plus.1 b/tac_plus.1
new file mode 100644 (file)
index 0000000..579c7ee
--- /dev/null
@@ -0,0 +1,220 @@
+.TH tac_plus 8 "10 February 1995"
+.SH NAME
+tac_plus \- tacacs plus daemon
+.SH SYNOPSIS
+.B tac_plus
+.B \-C\ <configfile>
+[
+.B \-t
+] [
+.B \-P
+] [
+.B \-g
+] [
+.B \-i
+] [
+.B \-v
+] [
+.B \-L
+] [
+.B \-p port
+] [
+.B \-d level
+]
+.SH DESCRIPTION
+tac_plus listens on tcp port
+.B
+49 
+and provides Cisco systems routers and access servers with
+authentication, authorisation and accounting services.
+.LP
+A configuration file controls the details of authentication,
+authorisation and accounting.
+.LP
+On startup, tac_plus creates the file
+.B /var/run/tac_plus.pid ,
+if possible, containing its process id.
+.LP
+.SH ARGUMENTS and OPTIONS
+.TP
+.B \-C <configfile>
+.IP
+Specify the configuration file name. A configuration file is
+.B
+always required.
+.TP 
+.B \-P
+Just parse the configuration file, echoing it to standard output while
+parsing, and then exit. Used for debugging configuration file syntax.
+.TP
+.B \-t
+Log all informational, debugging or error messages to
+.B
+/dev/console 
+in addition to logging to syslogd. Useful for debugging.
+.IP
+.B
+NOTE: 
+messages at priority LOG_DEBUG are never logged to syslog, Use the
+.B
+\-t, \-d or \-g 
+flags to see all messages produced by tac_plus.  These flags
+should not be used in normal service.
+.TP
+.B \-g
+Go into single threaded mode, only accepting and servicing a single
+connection at a time without forking and without closing file
+descriptors.  Print all messages to standard output. For debugging
+only. Don't ever try to deliver normal service this way.
+.TP
+.B \-v
+Print the current version of tac_plus to stdout and then exit.
+.TP
+.B \-L
+Lookup the hostname of the client sending requests and use if for
+logging, instead of just using its ip address.
+.TP
+.B \-p <port>
+Use the specified port number instead of the default port
+.B
+49 
+for incoming tcp connections. Note that this changes the name of the
+pid file created by the daemon, which will append the port number to
+the file name if the port is not the default one.
+.TP
+.B \-d <level>
+Switch on debugging and write debug output into
+.B
+/var/log/tac_plus.log. 
+
+See the definitions of debugging flags at the bottom of tac_plus.h for
+available flags and their meanings.  Most flags cause extra messages
+to be sent to 
+.B
+/var/log/tac_plus.log 
+and also to 
+.B
+syslog.
+.IP
+.B
+NOTE: 
+The 
+.B
+\-g 
+flag will cause these messages to also appear on stdout.  The
+.B
+\-t 
+flag will cause these messages to also be written to /dev/console.
+.IP
+The values represent bits, so they can be added together. Currently
+the following values are recognised:
+.nf
+
+Value   Meaning
+8       authorisation debugging
+16      authentication debugging
+32      password file processing debugging
+64      accounting debugging
+128     config file parsing & lookup
+256     packet transmission/reception
+512     encryption/decryption
+1024    MD5 hash algorithm debugging
+2048    very low level encryption/decryption
+
+.fi
+.TP
+.B \-i
+Run under inetd instead of running standalone. Under inetd, the config
+file is parsed every time tac_plus starts up, so this is very
+inefficient if the config file is large or there are many incoming
+connections. The standalone version only reads the config file once at
+startup.
+.IP
+If the config file is small, and you don't have very frequent incoming
+connections, and authentication is being done via passwd(5) files or
+SKEY (which are not cached), running under inetd should be tolerable,
+but still isn't recommended.
+.TP
+\-s
+.IP
+The \-s flag will cause the daemon to always reject authentication
+requests which contain a minor version number of zero (SENDPASS).  You
+can do this only if all your NASes are running an IOS version of 11.2
+or later.
+.IP
+This enhances security in the event that someone discovers your
+encryption key.  SENDPASS requests permits requestors to obtain chap,
+pap and arap passwords from your daemon, if (and only if) they know
+your encryption key.
+.LP
+.SH INVOKING TAC_PLUS
+.LP
+Tac_plus is normally invoked by root, as follows:
+.LP
+
+    # tac_plus -C <configfile>
+
+.LP
+where <configfile> is a full path to the configuration file. Tac_plus
+will background itself and start listening on port 49 for incoming tcp
+connections.
+.LP
+Tac_plus must be invoked as root to obtain privileged network socket
+49 and to read the protected configuration file which may contain
+confidential information such as encryption keys and cleartext
+passwords.
+.LP
+After the port is acquired and the config file is read, root
+privileges are no longer required.  You can arrange that tac_plus will
+change its user and groupid to more innocuous user and group (see the
+Makefile for instructions on how to do compile this) when
+appropriate.
+.LP
+.B
+NOTE:
+The new user and group still needs permission to read any
+passwd(5) files and S/KEY database if these are being used.
+.SH CONFIGURATION FILE PERMISSIONS
+.LP
+It goes without saying (though I say it here) that the configuration
+file should be unreadable and unwriteable by anyone except root, as it
+contains passwords and keys.
+.SH UPDATING THE CONFIGURATION FILE
+.LP
+If the daemon is sent a SIGUSR1, it will reinitialize itself,
+re-reading its config file from scratch. Note that if there is an
+error in the CONFIG file, the daemon will die.
+.LP
+.SH SYSLOG MESSAGES
+.LP
+tac_plus logs error messages to syslog, and informational messages to
+facility LOG_LOCAL6. Debug messages are never sent to syslog.
+.LP
+You may wish to add a line similar to the following to your
+syslog.conf file to see the informational messages logged using this
+facility.
+.nf
+
+local6.info                                    /var/adm/messages
+
+.fi
+.LP
+Note that in some versions of syslogd e.g. SunOS, this line must
+contain only tabs, not spaces, and that syslogd gives very little in
+the way of diagnostics when it encounters errors in the syslog.conf
+file.
+.fi
+.SH SEE ALSO
+.LP
+The tac_plus User's Guide.
+.SH FILES
+.TP 30
+.B /var/log/tac_plus.log
+Contains debugging output when -d is in effect.
+.TP
+.B /var/run/tac_plus.pid  or /var/run/tac_plus.pid.port
+contains the process id of the currently running daemon.  The port
+number is appended to the filename only if the port being used is not
+the default one of 49.
+.SH BUGS
+The configuration file syntax is too complex.
diff --git a/tac_plus.c b/tac_plus.c
new file mode 100644 (file)
index 0000000..cf0ffd8
--- /dev/null
@@ -0,0 +1,734 @@
+/*
+ * tac_plus.c
+ *
+ * TACACS_PLUS daemon suitable for using on Un*x systems.
+ *
+ * October 1994, Lol Grant
+ *
+ * Copyright (c) 1994-1998 by Cisco systems, Inc.
+ * Permission to use, copy, modify, and distribute this software for
+ * any purpose and without fee is hereby granted, provided that this
+ * copyright and permission notice appear on all copies of the
+ * software and supporting documentation, the name of Cisco Systems,
+ * Inc. not be used in advertising or publicity pertaining to
+ * distribution of the program without specific prior permission, and
+ * notice be given in supporting documentation that modification,
+ * copying and distribution is by permission of Cisco Systems, Inc.
+
+ * Cisco Systems, Inc. makes no representations about the suitability
+ * of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+ * IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+ * WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+#include "tac_plus.h"
+#include "sys/wait.h"
+#include "signal.h"
+
+static int standalone  = 1; /* running standalone (1) or under inetd (0) */
+static int initialised = 0; /* data structures have been allocated */
+int sendauth_only      = 0; /* don't respond to sendpass requests */
+int debug              = 0; /* debugging flags */
+int port               = 0; /* port we're listening on */
+int console            = 0; /* write all syslog messages to console */
+int parse_only         = 0; /* exit after verbose parsing */
+int single             = 0; /* single thread (for debugging) */
+int wtmpfd            = 0; /* for wtmp file logging */
+char *wtmpfile         = NULL;
+
+struct timeval started_at;
+
+struct session session;     /* session data */
+
+static char pidfilebuf[75]; /* holds current name of the pidfile */
+
+void start_session();
+
+#ifndef REAPCHILD
+static
+#ifdef VOIDSIG
+void 
+#else
+int
+#endif /* VOIDSIG */
+reapchild()
+{
+#ifdef UNIONWAIT
+    union wait status;
+#else
+    int status;
+#endif
+    int pid;
+
+    for (;;) {
+       pid = wait3(&status, WNOHANG, 0);
+       if (pid <= 0)
+           return;
+       if (debug & DEBUG_FORK_FLAG)
+           report(LOG_DEBUG, "%d reaped", pid);
+    }
+}
+#endif /* REAPCHILD */
+
+static void
+die(signum)
+int signum;
+{
+    report(LOG_INFO, "Received signal %d, shutting down", signum);
+    unlink(pidfilebuf);
+    tac_exit(0);
+}
+
+static void
+init()
+{
+    if (initialised)
+       cfg_clean_config();    
+
+    report(LOG_INFO, "Reading config");
+
+    session.acctfile = tac_strdup("/var/log/acctfile");
+    
+    if (!session.cfgfile) {
+       report(LOG_ERR, "no config file specified");
+       tac_exit(1);
+    }
+    
+    /* read the config file */
+    if (cfg_read_config(session.cfgfile)) {
+       report(LOG_ERR, "Parsing %s", session.cfgfile);
+       fprintf(stderr,"Config file not found!!\n");
+       tac_exit(1);
+    }
+
+    initialised++;
+
+    report(LOG_INFO, "Version %s Initialized %d", VERSION, initialised);
+
+}
+
+static void
+handler(signum)
+int signum;
+{
+    report(LOG_INFO, "Received signal %d", signum);
+    init();
+#ifdef REARMSIGNAL
+    signal(SIGUSR1, handler);
+    signal(SIGHUP, handler);
+#endif REARMSIGNAL
+}
+
+/*
+ * Return a socket bound to an appropriate port number/address. Exits
+ * the program on failure */
+
+get_socket()
+{
+    int s;
+    struct sockaddr_in sin;
+    struct servent *sp;
+    int on = 1;
+
+    bzero((char *) &sin, sizeof(sin));
+
+    if (port) {
+       sin.sin_port = htons(port);
+    } else {
+       sp = getservbyname("tacacs", "tcp");
+       if (sp)
+           sin.sin_port = sp->s_port;
+       else {
+           report(LOG_ERR, "Cannot find socket port");
+           tac_exit(1);
+       }
+    }
+
+    sin.sin_family = AF_INET;
+    sin.sin_addr.s_addr = htonl(INADDR_ANY);
+
+    s = socket(AF_INET, SOCK_STREAM, 0);
+
+    if (s < 0) {
+       console++;
+       report(LOG_ERR, "get_socket: socket: %s", sys_errlist[errno]);
+       tac_exit(1);
+    }
+#ifdef SO_REUSEADDR
+       if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &on,
+                      sizeof(on)) < 0)
+           perror("setsockopt - SO_REUSEADDR");
+#endif                         /* SO_REUSEADDR */
+
+    if (bind(s, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
+       console++;
+       report(LOG_ERR, "get_socket: bind %d %s",
+              ntohs(sin.sin_port),
+              sys_errlist[errno]);
+       tac_exit(1);
+    }
+    return (s);
+}
+
+static void
+open_logfile()
+{
+#ifdef LOG_LOCAL6
+    openlog("tac_plus", LOG_PID, LOG_LOCAL6);
+#else
+    openlog("tac_plus", LOG_PID);
+#endif
+    setlogmask(LOG_UPTO(LOG_DEBUG));
+}
+
+/*
+ * main
+ *
+ * We will eventually be called from inetd or via the rc scripts directly
+ * Parse arguments and act appropiately.
+ */
+
+main(argc, argv)
+int argc;
+char **argv;
+{
+    extern char *optarg;
+    int childpid;
+    int c;
+    int s;
+    FILE *fp;
+    int lookup_peer = 0;
+
+    debug = 0;                 /* no debugging */
+    standalone = 1;                    /* standalone */
+    single = 0;                        /* single threaded */
+
+    /* initialise global session data */
+    bzero(&session, sizeof(session));
+    session.peer = tac_strdup("unknown");
+
+    open_logfile();
+
+#ifdef TAC_PLUS_PORT
+    port = TAC_PLUS_PORT;
+#endif
+
+    if (argc <= 1) {
+       fprintf(stderr, "Usage: tac_plus -C <configuration file>\n");
+       fprintf(stderr, "\t[ -t ] [ -P ] [ -g ] [ -p <port> ]\n");
+       fprintf(stderr, "\t[ -d <debug level> ] [ -i ] [ -v ] [ -s ]\n");
+       fprintf(stderr, "\t[ -l logfile ]");
+#ifdef MAXSESS
+       fprintf(stderr, " [ -w whologfile ]");
+#endif
+       fprintf(stderr, "\n");
+       tac_exit(1);
+    }
+
+    while ((c = getopt(argc, argv, "td:C:ip:PgvsLl:w:u:")) != EOF)
+       switch (c) {
+       case 'L':               /* lookup peer names via DNS */
+           lookup_peer++;
+           break;
+       case 's':               /* don't respond to sendpass */
+           sendauth_only++;
+           break;
+       case 'v':               /* print version and exit */
+           version();
+           tac_exit(1);
+       case 't':
+           console++;          /* log to console too */
+           break;
+       case 'P':               /* Parse config file only */
+           parse_only++;
+           break;
+       case 'g':               /* single threaded */
+           single++;
+           break;
+       case 'p':               /* port */
+           port = atoi(optarg);
+           break;
+       case 'd':               /* debug */
+           debug = atoi(optarg);
+           break;
+       case 'C':               /* config file name */
+           session.cfgfile = tac_strdup(optarg);
+           break;
+       case 'i':               /* stand-alone */
+           standalone = 0;
+           break;
+       case 'l':               /* logfile */
+           logfile = tac_strdup(optarg);
+           break;
+#ifdef MAXSESS
+       case 'w':               /* wholog file */
+           wholog = tac_strdup(optarg);
+           break;
+#endif
+       case 'u':
+           wtmpfile = tac_strdup(optarg);
+           break;
+
+       default:
+           fprintf(stderr, "%s: bad switch %c\n", argv[0], c);
+           tac_exit(1);
+       }
+
+    if (geteuid() != 0) {
+       fprintf(stderr, "Warning, not running as uid 0\n");
+       fprintf(stderr, "Tac_plus is usually run as root\n");
+    }
+
+    parser_init();
+
+    init();
+
+    signal(SIGUSR1, handler);
+    signal(SIGHUP, handler);
+    signal(SIGTERM, die);
+    signal(SIGPIPE, SIG_IGN);
+
+    if (parse_only)
+       tac_exit(0);
+
+    if (debug)
+       report(LOG_DEBUG, "tac_plus server %s starting", VERSION);
+
+    if (!standalone) {
+       /* running under inetd */
+       struct sockaddr_in name;
+       int name_len;
+       int on = 1;
+
+       name_len = sizeof(name);
+
+       session.sock = 0;
+       if (getpeername(session.sock, (struct sockaddr *) &name, &name_len)) {
+           report(LOG_ERR, "getpeername failure %s", sys_errlist[errno]);
+       } else {
+           struct hostent *hp;
+           hp = gethostbyaddr((char *) &name.sin_addr.s_addr,
+                              sizeof(name.sin_addr.s_addr), AF_INET);
+           if (session.peer) {
+               free(session.peer);
+           }
+           session.peer = tac_strdup(hp ? hp->h_name : 
+                                 (char *) inet_ntoa(name.sin_addr));
+       }
+#ifdef FIONBIO
+       if (ioctl(session.sock, FIONBIO, &on) < 0) {
+           report(LOG_ERR, "ioctl(FIONBIO) %s", sys_errlist[errno]);
+           tac_exit(1);
+       }
+#endif
+       start_session();
+       tac_exit(0);
+    }
+
+    if (!single) {
+       /* Running standalone. Background ourselves, let go of controlling tty */
+
+#ifdef SIGTTOU
+       signal(SIGTTOU, SIG_IGN);
+#endif
+#ifdef SIGTTIN
+       signal(SIGTTIN, SIG_IGN);
+#endif
+#ifdef SIGTSTP
+       signal(SIGTSTP, SIG_IGN);
+#endif
+       
+       signal(SIGHUP, SIG_IGN);
+    
+       if ((childpid = fork()) < 0)
+           report(LOG_ERR, "Can't fork first child");
+       else if (childpid > 0)
+           exit(0);            /* parent */
+
+       if (debug)
+           report(LOG_DEBUG, "Backgrounded");
+
+#ifndef REAPCHILD
+
+#ifdef LINUX
+       if (setpgrp() == -1)
+#else /* LINUX */
+       if (setpgrp(0, getpid()) == -1)
+#endif /* LINUX */
+           report(LOG_ERR, "Can't change process group");
+       
+       c = open("/dev/tty", O_RDWR);
+       if (c >= 0) {
+           ioctl(c, TIOCNOTTY, (char *) 0);
+           (void) close(c);
+       }
+       signal(SIGCHLD, reapchild);
+
+#else /* REAPCHILD */
+
+       if (setpgrp() == 1)
+           report(LOG_ERR, "Can't change process group");
+
+       signal(SIGHUP, SIG_IGN);
+
+       if ((childpid = fork()) < 0)
+           report(LOG_ERR, "Can't fork second child");
+       else if (childpid > 0)
+           exit(0);
+    
+       if (debug & DEBUG_FORK_FLAG)
+           report(LOG_DEBUG, "Forked grandchild");
+
+       signal(SIGCHLD, SIG_IGN);
+
+#endif /* REAPCHILD */
+
+       closelog(); /* some systems require this */
+
+       for (c = 0; c < getdtablesize(); c++)
+           (void) close(c);
+
+       /* make sure we can still log to syslog now we've closed everything */
+       open_logfile();
+
+    } /* ! single threaded */
+    
+    ostream = NULL;
+    /* chdir("/"); */
+    umask(0);
+    errno = 0;
+
+    s = get_socket();
+   
+#ifndef SOMAXCONN
+#ifdef LINUX
+#define SOMAXCONN 128
+#else 
+#define SOMAXCONN 5
+#endif /* LINUX */
+#endif /* SOMAXCONN */
+
+    if (listen(s, SOMAXCONN) < 0) {
+       console++;
+       report(LOG_ERR, "listen: %s", sys_errlist[errno]);
+       tac_exit(1);
+    }
+
+    if (port == TAC_PLUS_PORT) {
+       strcpy(pidfilebuf, TACPLUS_PIDFILE);
+    } else {
+       sprintf(pidfilebuf, "%s.%d", TACPLUS_PIDFILE, port);
+    }
+
+    /* write process id to pidfile */
+    if ((fp = fopen(pidfilebuf, "w")) != NULL) {
+       fprintf(fp, "%d\n", getpid());
+       fclose(fp);
+    } else 
+       report(LOG_ERR, "Cannot write pid to %s %s", 
+              pidfilebuf, sys_errlist[errno]);
+
+#ifdef TACPLUS_GROUPID
+    if (setgid(TACPLUS_GROUPID))
+       report(LOG_ERR, "Cannot set group id to %d %s", 
+              TACPLUS_GROUPID, sys_errlist[errno]);
+#endif
+
+#ifdef TACPLUS_USERID
+    if (setuid(TACPLUS_USERID)) 
+       report(LOG_ERR, "Cannot set user id to %d %s", 
+              TACPLUS_USERID, sys_errlist[errno]);
+#endif
+
+#ifdef MAXSESS
+    maxsess_loginit();
+#endif /* MAXSESS */
+
+    report(LOG_DEBUG, "uid=%d euid=%d gid=%d egid=%d s=%d",
+          getuid(), geteuid(), getgid(), getegid(), s);
+
+    for (;;) {
+       int pid;
+       struct sockaddr_in from;
+       int from_len;
+       int newsockfd;
+       struct hostent *hp = NULL;
+
+       bzero((char *) &from, sizeof(from));
+       from_len = sizeof(from);
+
+       newsockfd = accept(s, (struct sockaddr *) &from, &from_len);
+
+       if (newsockfd < 0) {
+           if (errno == EINTR)
+               continue;
+
+           report(LOG_ERR, "accept: %s", sys_errlist[errno]);
+           continue;
+       }
+
+       if (lookup_peer) {
+           hp = gethostbyaddr((char *) &from.sin_addr.s_addr,
+                              sizeof(from.sin_addr.s_addr), AF_INET);
+       }
+
+       if (session.peer) {
+           free(session.peer);
+       }
+       session.peer = tac_strdup(hp ? hp->h_name : 
+                                 (char *) inet_ntoa(from.sin_addr));
+
+       if (debug & DEBUG_PACKET_FLAG)
+           report(LOG_DEBUG, "session request from %s sock=%d", 
+                  session.peer, newsockfd);
+
+       if (!single) {
+           pid = fork();
+
+           if (pid < 0) {
+               report(LOG_ERR, "fork error");
+               tac_exit(1);
+           }
+       } else {
+           pid = 0;
+       }
+
+       if (pid == 0) {
+           /* child */
+           if (!single)
+               close(s);
+           session.sock = newsockfd;
+           start_session();
+           shutdown(session.sock, 2);
+           close(session.sock);
+           if (!single)
+               tac_exit(0);
+       } else {
+           if (debug & DEBUG_FORK_FLAG)
+               report(LOG_DEBUG, "forked %d", pid);
+           /* parent */
+           close(newsockfd);
+       }
+    }
+}
+
+#ifdef GETDTABLESIZE
+int 
+getdtablesize()
+{
+    return(_NFILE);
+}
+#endif /* GETDTABLESIZE */
+
+/* Make sure version number is kosher. Return 0 if it is */
+int
+bad_version_check(pak)
+u_char *pak;
+{
+    HDR *hdr = (HDR *) pak;
+    
+    switch (hdr->type) {
+    case TAC_PLUS_AUTHEN:
+       /* 
+        * Let authen routines take care of more sophisticated version
+        * checking as its now a bit involved. 
+        */
+       return(0);
+
+    case TAC_PLUS_AUTHOR:
+    case TAC_PLUS_ACCT:
+       if (hdr->version != TAC_PLUS_VER_0) {
+           send_error_reply(hdr->type, "Illegal packet version");
+           return(1);
+       }
+       return(0);
+
+    default:
+       return(1);
+    }
+}
+
+/*
+ * Determine the packet type, read the rest of the packet data,
+ * decrypt it and call the appropriate service routine.
+ *
+ */
+
+void
+start_session()
+{
+    u_char *pak, *read_packet();
+    HDR *hdr;
+    void authen();
+
+    session.seq_no = 0;
+    session.aborted = 0;
+    session.version = 0;
+
+    pak = read_packet();
+    if (!pak) {
+       return;
+    }
+
+    if (debug & DEBUG_PACKET_FLAG) {
+       report(LOG_DEBUG, "validation request from %s", session.peer);
+       dump_nas_pak(pak);
+    }
+    hdr = (HDR *) pak;
+
+    session.session_id = ntohl(hdr->session_id);
+
+    /* Do some version checking */
+    if (bad_version_check(pak)) {
+       free(pak);
+       return;
+    }
+
+    switch (hdr->type) {
+    case TAC_PLUS_AUTHEN:
+       authen(pak);
+       free(pak);
+       return;
+
+    case TAC_PLUS_AUTHOR:
+       author(pak);
+       free(pak);
+       return;
+
+    case TAC_PLUS_ACCT:
+       accounting(pak);
+       return;
+
+    default:
+       /* Note: can't send error reply if type is unknown */
+       report(LOG_ERR, "Illegal type %d in received packet", hdr->type);
+       free(pak);
+       return;
+    }
+}
+
+version()
+{
+    fprintf(stdout, "tac_plus version %s\n", VERSION);
+#ifdef AIX
+    fprintf(stdout,"AIX\n");
+#endif
+#ifdef ARAP_DES
+    fprintf(stdout,"ARAP_DES\n");
+#endif
+#ifdef BSDI
+    fprintf(stdout,"BSDI\n");
+#endif
+#ifdef CONST_SYSERRLIST
+    fprintf(stdout,"CONST_SYSERRLIST\n");
+#endif
+#ifdef DEBUG
+    fprintf(stdout,"DEBUG\n");
+#endif
+#ifdef DES_DEBUG
+    fprintf(stdout,"DES_DEBUG\n");
+#endif
+#ifdef FIONBIO
+    fprintf(stdout,"FIONBIO\n");
+#endif
+#ifdef FREEBSD
+    fprintf(stdout,"FREEBSD\n");
+#endif
+#ifdef GETDTABLESIZE
+    fprintf(stdout,"GETDTABLESIZE\n");
+#endif
+#ifdef HPUX
+    fprintf(stdout,"HPUX\n");
+#endif
+#ifdef LINUX
+    fprintf(stdout,"LINUX\n");
+#endif
+#ifdef LITTLE_ENDIAN
+    fprintf(stdout,"LITTLE_ENDIAN\n");
+#endif
+#ifdef LOG_LOCAL6
+    fprintf(stdout,"LOG_LOCAL6\n");
+#endif
+#ifdef MAXSESS
+    fprintf(stdout,"MAXSESS\n");
+#endif
+#ifdef MIPS
+    fprintf(stdout,"MIPS\n");
+#endif
+#ifdef NEED_BZERO
+    fprintf(stdout,"NEED_BZERO\n");
+#endif
+#ifdef NETBSD
+    fprintf(stdout,"NETBSD\n");
+#endif
+#ifdef NO_PWAGE
+    fprintf(stdout,"NO_PWAGE\n");
+#endif
+#ifdef REAPCHILD
+    fprintf(stdout,"REAPCHILD\n");
+#endif
+#ifdef REARMSIGNAL
+    fprintf(stdout,"REARMSIGNAL\n");
+#endif
+#ifdef SHADOW_PASSWORDS
+    fprintf(stdout,"SHADOW_PASSWORDS\n");
+#endif
+#ifdef SIGTSTP
+    fprintf(stdout,"SIGTSTP\n");
+#endif
+#ifdef SIGTTIN
+    fprintf(stdout,"SIGTTIN\n");
+#endif
+#ifdef SIGTTOU
+    fprintf(stdout,"SIGTTOU\n");
+#endif
+#ifdef SKEY
+    fprintf(stdout,"SKEY\n");
+#endif
+#ifdef SOLARIS
+    fprintf(stdout,"SOLARIS\n");
+#endif
+#ifdef SO_REUSEADDR
+    fprintf(stdout,"SO_REUSEADDR\n");
+#endif
+#ifdef STDLIB_MALLOC
+    fprintf(stdout,"STDLIB_MALLOC\n");
+#endif
+#ifdef STRCSPN
+    fprintf(stdout,"STRCSPN\n");
+#endif
+#ifdef SYSLOG_IN_SYS
+    fprintf(stdout,"SYSLOG_IN_SYS\n");
+#endif
+#ifdef SYSV
+    fprintf(stdout,"SYSV\n");
+#endif
+#ifdef TACPLUS_GROUPID
+    fprintf(stdout,"TACPLUS_GROUPID\n");
+#endif
+#ifdef TAC_PLUS_PORT
+    fprintf(stdout,"TAC_PLUS_PORT\n");
+#endif
+#ifdef TACPLUS_USERID
+    fprintf(stdout,"TACPLUS_USERID\n");
+#endif
+#ifdef TRACE
+    fprintf(stdout,"TRACE\n");
+#endif
+#ifdef UNIONWAIT
+    fprintf(stdout,"UNIONWAIT\n");
+#endif
+#ifdef VOIDSIG
+    fprintf(stdout,"VOIDSIG\n");
+#endif
+#ifdef _BSD1
+    fprintf(stdout,"_BSD1\n");
+#endif
+#ifdef _BSD_INCLUDES
+    fprintf(stdout,"_BSD_INCLUDES\n");
+#endif
+#ifdef __STDC__
+    fprintf(stdout,"__STDC__\n");
+#endif
+}
diff --git a/tac_plus.cfg b/tac_plus.cfg
new file mode 100644 (file)
index 0000000..31e53f5
--- /dev/null
@@ -0,0 +1,54 @@
+# Created by Devrim SERAL(devrim@tef.gazi.edu.tr)
+# It's very simple configuration file
+# Please read user_guide and tacacs+ FAQ to more information to do more
+# complex tacacs+ configuration files.
+#
+
+key = put_you_key_here
+
+# Use /etc/passwd file to do authentication
+
+default authentication = file /etc/passwd
+
+# Now tacacs+ also use default PAM authentication
+#default authentication = pam pap
+
+#If you like to use DB authentication
+#default authentication = db "db_type://db_user:db_pass@db_hostname/db_name/db_table?name_field&pass_field
+# db_type: mysql or null
+# db_user: Database connect username
+# db_pass: Database connection password
+# db_hostname : Database hostname
+# db_name : Database name
+# db_table : authentication table name
+# name_field and pass_field: Username and password field name at the db_table
+# Accounting records log file
+
+accounting file = /var/log/tac_acc.log
+
+# Would you like to store accounting records in database..
+# db_accounting = "db_type://db_user:db_pass@db_hostname/db_name/db_table"
+# Same as above.. 
+
+#All services are alowed..
+
+user = DEFAULT {
+    service = ppp protocol = ip {}
+}
+
+# Yes we have more features like per host key 
+#host = 127.0.0.1 {
+#        key = test 
+#        type = cisco
+#}
+#user = test {
+#    name = Test User 
+#    pap = cleartext test
+#    member = staff
+#}
+#
+#group = staff {
+#    time = "Wd1800-1817|!Wd1819-2000"
+#}
+
diff --git a/tac_plus.h b/tac_plus.h
new file mode 100644 (file)
index 0000000..99d95e0
--- /dev/null
@@ -0,0 +1,751 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+/* For autoconfig */
+#include "config.h"
+
+/* 
+ * If you are defining a system from scratch, the following may be useful.
+ * Otherwise, just use the system definitions below this section.
+ */
+
+/* Define this for minor include file differences on SYSV-based systems */
+/* #define SYSV */
+
+/* Define this if your sys_errlist is defined using const */
+/* #define CONST_SYSERRLIST */
+
+/* Do you need tacacs+ versions of bzero etc. */
+/* #define NEED_BZERO */
+
+/* Define this if you have shadow passwords in /etc/passwd and
+ * /etc/shadow. Note that you usually need to be root to read
+ * /etc/shadow */
+/*#define SHADOW_PASSWORDS*/
+
+/* Define this if your malloc is defined in malloc.h instead of stdlib.h */
+/* #define STDLIB_MALLOC */
+
+/* Define this if your wait call status is a union as opposed to an int */
+/* #define UNIONWAIT */
+
+/* Define this if your signal() uses a function returning void instead 
+ * of int
+ */
+/* #define VOIDSIG */
+
+/* Define this if your password file does not contain age and comment fields. */
+/* #define NO_PWAGE */
+
+/* Define this if you need a getdtablesize routine defined */
+/* #define GETDTABLESIZE */
+
+/* Define this if your system does not reap children automatically
+ * when you ignore SIGCLD */
+/* #define REAPCHILD */
+
+/* Define this if you have DES routines you can link to for ARAP (See
+ * the user's guide for more details). 
+ */
+/* #define ARAP_DES */
+
+/* Define this if you find that your daemon quits after being sent more than
+ * one SIGUSR1. Some systems need to explicitly rearm signals after they've been
+ * used once
+ */
+/* #define REARMSIGNAL */
+
+#define VERSION "F4.0.3.alpha.v8 (Extended Tac_plus)"
+
+/*
+ * System definitions. 
+ */
+
+#ifdef NETBSD
+#define STDLIB_MALLOC
+#define NO_PWAGE
+#define CONST_SYSERRLIST
+#define VOIDSIG
+#endif
+
+#ifdef AIX
+
+/* 
+ * The only way to properly compile BSD stuff on AIX is to define a
+ * "bsdcc" compiler on your system. See /usr/lpp/bos/bsdport on your
+ * system for details. People who do NOT do this tell me that the code
+ * still compiles but that it then doesn't behave correctly e.g. child
+ * processes are not reaped correctly. Don't expect much sympathy if
+ * you do this.
+ */
+
+#define _BSD 1
+#define _BSD_INCLUDES
+#define UNIONWAIT
+#define NO_PWAGE
+#endif /* AIX */
+
+#ifdef LINUX
+#define VOIDSIG
+#define NO_PWAGE
+#define REAPCHILD
+#include <unistd.h>
+#define REARMSIGNAL
+#ifdef GLIBC
+#define CONST_SYSERRLIST
+#endif
+#endif /* LINUX */
+
+#ifdef MIPS
+#define SYSV
+#define GETDTABLESIZE
+#define REAPCHILD
+#define NEED_BZERO
+#endif /* MIPS */
+
+#ifdef SOLARIS
+#define SYSV
+#define GETDTABLESIZE
+#define REAPCHILD
+#define SHADOW_PASSWORDS
+#define NEED_BZERO
+#define REARMSIGNAL
+#endif /* SOLARIS */
+
+#ifdef HPUX
+#define SYSV
+#define GETDTABLESIZE
+#define REAPCHILD
+#define SYSLOG_IN_SYS
+#define REARMSIGNAL
+#endif /* HPUX */
+
+#ifdef FREEBSD
+#define CONST_SYSERRLIST
+#define STDLIB_MALLOC
+#define VOIDSIG
+#define NO_PWAGE
+#endif
+
+#ifdef BSDI
+#define VOIDSIG
+#define STDLIB_MALLOC
+#define NO_PWAGE
+#endif
+
+#define MD5_LEN           16
+#ifdef MSCHAP
+#define MSCHAP_DIGEST_LEN 49
+#endif /* MSCHAP */
+
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/file.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+
+#include <stdio.h>
+#include <errno.h>
+#include <pwd.h>
+#include <netdb.h>
+
+#ifdef SYSLOG_IN_SYS
+#include <syslog.h>
+#else
+#include <sys/syslog.h>
+#endif
+
+#include <utmp.h>
+
+#include <unistd.h>
+
+#ifdef SYSV
+#include <fcntl.h>
+#define index strchr
+#else /* ! SYSV */
+#include <strings.h>
+#endif /* SYSV */
+
+#ifndef TACPLUS_PIDFILE
+#define TACPLUS_PIDFILE "/var/run/tac_plus.pid"
+#endif
+
+
+/* 
+ * You probably shouldn't be changing much below this line unless you really
+ * know what you are doing.
+ */
+
+#define DOLLARSIGN '$'
+
+/*
+ * XTACACSP protocol defintions
+ */
+
+/*
+ * This structure describes an authentication method.
+ *   authen_name     contains the name of the authentication method.
+ *   authen_func     is a pointer to the authentication function.
+ *   authen_method   numeric value of authentication method
+ */
+
+#define AUTHEN_NAME_SIZE 128
+
+struct authen_type {
+    char authen_name[AUTHEN_NAME_SIZE];
+    int (*authen_func)();
+    int authen_type;
+};
+
+/*
+ * This structure describes a principal that is to be authenticated.
+ *   username        is the principals name (ASCII, null terminated)
+ *   NAS_name        is the name of the NAS where the user is
+ *   NAS_port        is the port on the NAS where the user is
+ *   NAC_address     is the remote user location.  This may be
+ *                   a remote IP address or a caller-ID or ...
+ *   priv_lvl        user's requested privilege level.
+ */
+
+struct identity {
+    char *username;
+    char *NAS_name;
+    char *NAS_port;
+    char *NAC_address;
+    int priv_lvl;
+};
+
+/*
+ * The authen_data structure is the data structure for passing
+ * information to and from the authentication function
+ * (authen_type.authen_func).
+ */
+
+struct authen_data {
+    struct identity *NAS_id;   /* user identity */
+    char *server_msg;          /* null-terminated output msg */
+
+    int server_dlen;           /* output data length */
+    char *server_data;         /* output data */
+
+    char *client_msg;          /* null-terminated input msg a user typed */
+
+    int client_dlen;           /* input data length */
+    char *client_data;         /* input data */
+
+    void *method_data;         /* opaque private method data */
+    int action;                        /* what's to be done */
+    int service;               /* calling service */
+    int status;                        /* Authen status */
+    int type;                  /* Authen type */
+    u_char flags;               /* input & output flags fields */
+};
+
+
+/* return values for  choose_authen(); */
+
+#define CHOOSE_FAILED -1     /* failed to choose an authentication function */
+#define CHOOSE_OK      0     /* successfully chose an authentication function */
+#define CHOOSE_GETUSER 1     /* need a username before choosing */
+#define CHOOSE_BADTYPE 2     /* Invalid preferred authen function specified */
+
+
+/*
+ * This structure is the data structure for passing information to
+ * and from the authorization function (do_author()).
+ */
+struct author_data {
+    struct identity *id;       /* user id */
+    int authen_method;         /* authentication method */
+
+#define AUTHEN_METH_NONE             0x01
+#define AUTHEN_METH_KRB5             0x02
+#define AUTHEN_METH_LINE             0x03
+#define AUTHEN_METH_ENABLE           0x04
+#define AUTHEN_METH_LOCAL            0x05
+#define AUTHEN_METH_TACACSPLUS       0x06
+#define AUTHEN_METH_RCMD             0x20
+
+    int authen_type;           /* authentication type see authen_type */
+    int service;               /* calling service */
+    char *msg;                 /* optional NULL-terminated return message */
+    char *admin_msg;           /* optional NULL-terminated admin message */
+    int status;                        /* return status */
+
+#define AUTHOR_STATUS_PASS_ADD       0x01
+#define AUTHOR_STATUS_PASS_REPL      0x02
+#define AUTHOR_STATUS_FAIL           0x10
+#define AUTHOR_STATUS_ERROR          0x11
+
+    int num_in_args;           /* input arg count */
+    char **input_args;         /* input arguments */
+    int num_out_args;          /* output arg cnt */
+    char **output_args;                /* output arguments */
+
+};
+
+/* An API accounting record structure */
+struct acct_rec {
+    int acct_type;             /* start, stop, update */
+
+#define ACCT_TYPE_START      1
+#define ACCT_TYPE_STOP       2
+#define ACCT_TYPE_UPDATE     3
+
+    struct identity *identity;
+    int authen_method;
+    int authen_type;
+    int authen_service;
+    char *msg;       /* output field */
+    char *admin_msg; /* output field */
+    int num_args;
+    char **args;
+};
+
+#ifndef TAC_PLUS_PORT
+#define        TAC_PLUS_PORT                   49
+#endif
+
+/* Define tac_plus name for hosts.* files */ 
+#ifdef TCPWRAPPER
+#define        TACNAME                         "tac_plus"
+#endif
+
+#define TAC_PLUS_READ_TIMEOUT          180     /* seconds */
+#define TAC_PLUS_WRITE_TIMEOUT         180     /* seconds */
+
+#define NAS_PORT_MAX_LEN                255
+
+struct session {
+    int session_id;                /* host specific unique session id */
+    int aborted;                   /* have we received an abort flag? */
+    int seq_no;                    /* seq. no. of last packet exchanged */
+    time_t last_exch;              /* time of last packet exchange */
+    int sock;                      /* socket for this connection */
+    char *key;                     /* the key */
+    int keyline;                   /* line number key was found on */
+    char *peer;                    /* name of connected peer */
+    char *cfgfile;                 /* config file name */
+    char *acctfile;                /* name of accounting file */
+    char *db_acct;                        /* name of db accounting string */
+    char port[NAS_PORT_MAX_LEN+1]; /* For error reporting */
+    u_char version;                /* version of last packet read */
+};
+
+extern struct session session;     /* the session */
+
+/* Global variables */
+
+extern int debug;                  /* debugging flag */
+extern int logging;                /* syslog logging flag */
+extern int single;                 /* do not fork (for debugging) */
+extern int console;                /* log to console */
+extern FILE *ostream;              /* for logging to console */
+extern int parse_only;             /* exit after parsing verbosely */
+extern int sendauth_only;          /* don't do sendauth */
+
+/* All tacacs+ packets have the same header format */
+
+struct tac_plus_pak_hdr {
+    u_char version;
+
+#define TAC_PLUS_MAJOR_VER_MASK 0xf0
+#define TAC_PLUS_MAJOR_VER      0xc0
+
+#define TAC_PLUS_MINOR_VER_0    0x0
+#define TAC_PLUS_VER_0  (TAC_PLUS_MAJOR_VER | TAC_PLUS_MINOR_VER_0)
+
+#define TAC_PLUS_MINOR_VER_1    0x01
+#define TAC_PLUS_VER_1  (TAC_PLUS_MAJOR_VER | TAC_PLUS_MINOR_VER_1)
+
+    u_char type;
+
+#define TAC_PLUS_AUTHEN                        1
+#define TAC_PLUS_AUTHOR                        2
+#define TAC_PLUS_ACCT                  3
+
+    u_char seq_no;             /* packet sequence number */
+    u_char encryption;         /* packet is encrypted or cleartext */
+
+#define TAC_PLUS_ENCRYPTED 0x0         /* packet is encrypted */
+#define TAC_PLUS_CLEAR     0x1         /* packet is not encrypted */
+
+    int session_id;            /* session identifier FIXME: Is this needed? */
+    int datalength;            /* length of encrypted data following this
+                                * header */
+    /* datalength bytes of encrypted data */
+};
+
+#define HASH_TAB_SIZE 157        /* user and group hash table sizes */
+
+#define TAC_PLUS_HDR_SIZE 12
+
+typedef struct tac_plus_pak_hdr HDR;
+
+/* Authentication packet NAS sends to us */ 
+
+struct authen_start {
+    u_char action;
+
+#define TAC_PLUS_AUTHEN_LOGIN    0x1
+#define TAC_PLUS_AUTHEN_CHPASS   0x2
+#define TAC_PLUS_AUTHEN_SENDPASS 0x3 /* deprecated */
+#define TAC_PLUS_AUTHEN_SENDAUTH 0x4
+
+    u_char priv_lvl;
+
+#define TAC_PLUS_PRIV_LVL_MIN 0x0
+#define TAC_PLUS_PRIV_LVL_MAX 0xf
+
+    u_char authen_type;
+
+#define TAC_PLUS_AUTHEN_TYPE_ASCII  1
+#define TAC_PLUS_AUTHEN_TYPE_PAP    2
+#define TAC_PLUS_AUTHEN_TYPE_CHAP   3
+#define TAC_PLUS_AUTHEN_TYPE_ARAP   4
+#ifdef MSCHAP
+#define TAC_PLUS_AUTHEN_TYPE_MSCHAP 5
+#endif /* MSCHAP */
+
+    u_char service;
+
+#define TAC_PLUS_AUTHEN_SVC_LOGIN  1
+#define TAC_PLUS_AUTHEN_SVC_ENABLE 2
+#define TAC_PLUS_AUTHEN_SVC_PPP    3
+#define TAC_PLUS_AUTHEN_SVC_ARAP   4
+#define TAC_PLUS_AUTHEN_SVC_PT     5
+#define TAC_PLUS_AUTHEN_SVC_RCMD   6
+#define TAC_PLUS_AUTHEN_SVC_X25    7
+#define TAC_PLUS_AUTHEN_SVC_NASI   8
+
+    u_char user_len;
+    u_char port_len;
+    u_char rem_addr_len;
+    u_char data_len;
+    /* <user_len bytes of char data> */
+    /* <port_len bytes of char data> */
+    /* <rem_addr_len bytes of u_char data> */
+    /* <data_len bytes of u_char data> */
+};
+
+#define TAC_AUTHEN_START_FIXED_FIELDS_SIZE 8
+
+/* Authentication continue packet NAS sends to us */ 
+struct authen_cont {
+    u_short user_msg_len;
+    u_short user_data_len;
+    u_char flags;
+
+#define TAC_PLUS_CONTINUE_FLAG_ABORT 0x1
+
+    /* <user_msg_len bytes of u_char data> */
+    /* <user_data_len bytes of u_char data> */
+};
+
+#define TAC_AUTHEN_CONT_FIXED_FIELDS_SIZE 5
+
+/* Authentication reply packet we send to NAS */ 
+struct authen_reply {
+    u_char status;
+
+#define TAC_PLUS_AUTHEN_STATUS_PASS     1
+#define TAC_PLUS_AUTHEN_STATUS_FAIL     2
+#define TAC_PLUS_AUTHEN_STATUS_GETDATA  3
+#define TAC_PLUS_AUTHEN_STATUS_GETUSER  4
+#define TAC_PLUS_AUTHEN_STATUS_GETPASS  5
+#define TAC_PLUS_AUTHEN_STATUS_RESTART  6
+#define TAC_PLUS_AUTHEN_STATUS_ERROR    7 
+#define TAC_PLUS_AUTHEN_STATUS_FOLLOW   0x21
+
+    u_char flags;
+
+#define TAC_PLUS_AUTHEN_FLAG_NOECHO     0x1
+
+    u_short msg_len;
+    u_short data_len;
+
+    /* <msg_len bytes of char data> */
+    /* <data_len bytes of u_char data> */
+};
+
+#define TAC_AUTHEN_REPLY_FIXED_FIELDS_SIZE 6
+
+/* An authorization request packet */
+struct author {
+    u_char authen_method;
+    u_char priv_lvl;
+    u_char authen_type;
+    u_char service;
+
+    u_char user_len;
+    u_char port_len;
+    u_char rem_addr_len;
+    u_char arg_cnt;            /* the number of args */
+
+    /* <arg_cnt u_chars containing the lengths of args 1 to arg n> */
+    /* <user_len bytes of char data> */
+    /* <port_len bytes of char data> */
+    /* <rem_addr_len bytes of u_char data> */
+    /* <char data for each arg> */
+};
+
+#define TAC_AUTHOR_REQ_FIXED_FIELDS_SIZE 8
+
+/* An authorization reply packet */
+struct author_reply {
+    u_char status;
+    u_char arg_cnt;
+    u_short msg_len;
+    u_short data_len;
+
+    /* <arg_cnt u_chars containing the lengths of arg 1 to arg n> */
+    /* <msg_len bytes of char data> */
+    /* <data_len bytes of char data> */
+    /* <char data for each arg> */
+};
+
+#define TAC_AUTHOR_REPLY_FIXED_FIELDS_SIZE 6
+
+struct acct {
+    u_char flags;
+
+#define TAC_PLUS_ACCT_FLAG_MORE     0x1
+#define TAC_PLUS_ACCT_FLAG_START    0x2
+#define TAC_PLUS_ACCT_FLAG_STOP     0x4
+#define TAC_PLUS_ACCT_FLAG_WATCHDOG 0x8
+           
+    u_char authen_method;
+    u_char priv_lvl;
+    u_char authen_type;
+    u_char authen_service;
+    u_char user_len;
+    u_char port_len;
+    u_char rem_addr_len;
+    u_char arg_cnt; /* the number of cmd args */
+    /* one u_char containing size for each arg */
+    /* <user_len bytes of char data> */
+    /* <port_len bytes of char data> */
+    /* <rem_addr_len bytes of u_char data> */
+    /* char data for args 1 ... n */
+};
+
+#define TAC_ACCT_REQ_FIXED_FIELDS_SIZE 9
+
+struct acct_reply {
+    u_short msg_len;
+    u_short data_len;
+    u_char status;
+
+#define TAC_PLUS_ACCT_STATUS_SUCCESS 0x1
+#define TAC_PLUS_ACCT_STATUS_ERROR   0x2
+#define TAC_PLUS_ACCT_STATUS_FOLLOW  0x21
+
+};
+
+#define TAC_ACCT_REPLY_FIXED_FIELDS_SIZE 5
+
+/* Odds and ends */
+#define TAC_PLUS_MAX_ITERATIONS 50
+#undef MIN
+#define MIN(a,b) ((a)<(b)?(a):(b))
+#define STREQ(a,b) (strcmp(a,b)==0)
+#define MAX_INPUT_LINE_LEN 255
+
+/* Debugging flags */
+
+#define DEBUG_PARSE_FLAG     2
+#define DEBUG_FORK_FLAG      4
+#define DEBUG_AUTHOR_FLAG    8
+#define DEBUG_AUTHEN_FLAG    16
+#define DEBUG_PASSWD_FLAG    32
+#define DEBUG_ACCT_FLAG      64
+#define DEBUG_CONFIG_FLAG    128
+#define DEBUG_PACKET_FLAG    256
+#define DEBUG_HEX_FLAG       512
+#define DEBUG_MD5_HASH_FLAG  1024
+#define DEBUG_XOR_FLAG       2048
+#define DEBUG_CLEAN_FLAG     4096
+#define DEBUG_SUBST_FLAG     8192
+#define DEBUG_PROXY_FLAG     16384
+#define DEBUG_MAXSESS_FLAG   32768
+#define DEBUG_LOCK_FLAG      65536
+
+extern char *codestring();
+extern int keycode();
+
+#define TAC_IS_USER           1
+#define TAC_PLUS_RECURSE      1
+#define TAC_PLUS_NORECURSE    0
+
+#define DEFAULT_USERNAME "DEFAULT"
+
+#include "parse.h"
+
+/* Node types */
+
+#define N_arg           50
+#define N_optarg        51
+#define N_svc_exec      52
+#define N_svc_slip      53
+#define N_svc_ppp       54
+#define N_svc_arap      55
+#define N_svc_cmd       56
+#define N_permit        57
+#define N_deny          58
+#define N_svc           59
+
+/* A parse tree node */
+struct node {
+    int type;     /* node type (arg, svc, proto) */
+    void *next;   /* pointer to next node in chain */
+    void *value;  /* node value */
+    void *value1; /* node value */
+    int dflt;     /* default value for node */
+    int line;     /* line number declared on */
+};
+
+typedef struct node NODE;
+
+union v {
+    int intval;
+    void *pval;
+};
+
+typedef union v VALUE;
+
+/* acct.c */
+extern void accounting();
+
+/* report.c */
+extern void report_string();
+extern void report_hex();
+#ifdef __STDC__
+extern void report(int priority, char *fmt,...);
+#else
+extern void report();
+#endif
+
+/* packet.c */
+extern u_char *get_authen_continue();
+extern int send_authen_reply();
+
+/* utils.c */
+extern char *tac_malloc();
+extern char *tac_strdup();
+extern char *tac_make_string();
+extern char *tac_find_substring();
+extern char *tac_realloc();
+
+/* dump.c */
+extern char *summarise_outgoing_packet_type();
+extern char *summarise_incoming_packet_type();
+
+/* author.c */
+extern void author();
+
+/* hash.c */
+extern void *hash_add_entry();
+extern void **hash_get_entries();
+extern void *hash_lookup();
+
+/* config.c */
+extern int cfg_get_intvalue();
+extern char * cfg_get_pvalue();
+extern char *cfg_get_authen_default();
+extern int cfg_get_authen_default_method();
+extern char **cfg_get_svc_attrs();
+extern NODE *cfg_get_cmd_node();
+extern NODE *cfg_get_svc_node();
+extern char *cfg_get_expires();
+extern char *cfg_get_login_secret();
+extern int cfg_get_user_nopasswd();
+extern char *cfg_get_arap_secret();
+extern char *cfg_get_chap_secret();
+#ifdef MSCHAP
+extern char *cfg_get_mschap_secret();
+#endif /* MSCHAP */
+extern char *cfg_get_pap_secret();
+extern char *cfg_get_opap_secret();
+extern char *cfg_get_global_secret();
+#ifdef USE_PAM
+extern char *cfg_get_pam_service();
+#endif / *PAM */ 
+extern void cfg_clean_config();
+extern char *cfg_nodestring();
+
+/* pw.c */
+extern struct passwd *tac_passwd_lookup();
+
+/* parse.c */
+extern void parser_init();
+
+/* pwlib.c */
+extern void set_expiration_status();
+
+/* miscellaneous */
+#ifdef CONST_SYSERRLIST
+extern const char *const sys_errlist[];
+#else
+extern char *sys_errlist[];
+#endif
+extern int errno;
+extern int sendauth_fn();
+extern int sendpass_fn();
+extern int enable_fn();
+extern int default_fn();
+extern int default_v0_fn();
+extern int skey_fn();
+#ifdef MSCHAP
+extern void mschap_lmchallengeresponse();
+extern void mschap_ntchallengeresponse();
+#endif /* MSCHAP */
+
+#ifdef MAXSESS
+
+extern void maxsess_loginit();
+extern int maxsess_check_count();
+
+/*
+ * This is a shared file used to maintain a record of who's on
+ */
+#define WHOLOG_DEFAULT "/var/log/tac_who.log"
+extern char *wholog;
+/*
+ * This is state kept per user/session
+ */
+struct peruser {
+    char username[64];         /* User name */
+    char NAS_name[32];         /* NAS user logged into */
+    char NAS_port[32];         /*  ...port on that NAS */
+    char NAC_address[32];      /*  ...IP address of NAS */
+};
+
+#endif /* MAXSESS */
+
+#ifdef USE_PAM
+extern int tac_pam_authorization();
+#endif
+
+#define LOGFILE_DEFAULT "/var/log/tac_plus.log"
+
+extern struct timeval started_at;
+extern char *logfile;
+extern char *wtmpfile;
+extern int wtmpfd;
diff --git a/tac_plus.init b/tac_plus.init
new file mode 100644 (file)
index 0000000..681786e
--- /dev/null
@@ -0,0 +1,78 @@
+#!/bin/sh
+#
+# tac_plus        This shell script takes care of starting and stopping
+#                               tac_plus (TACACS+ daemon).
+#
+# chkconfig: 235 80 20
+# description: tac_plus is TACACS+ daemon.
+# processname: tac_plus
+# config: /etc/tacacs/tac_plus.cfg
+# pidfile: /var/run/tac_plus.pid
+# debug : 0
+
+# Source function library.
+. /etc/rc.d/init.d/functions
+
+# Source networking configuration.
+. /etc/sysconfig/network
+
+# Check that networking is up.
+[ ${NETWORKING} = "no" ] && exit 0
+
+# Some config parameters
+#For config file
+tacacs_config="/etc/tacacs/tac_plus.cfg"
+#For debug option
+debug=0
+
+[ -f /usr/sbin/tac_plus ] || exit 0
+
+[ -f $tacacs_config ] || exit 0
+
+
+# See how we were called.
+case "$1" in
+  start)
+        # Start daemon.
+       if [ $debug -gt 0 ]
+        then
+        echo -n "Starting TACACS+ with debug level $debug : "
+       daemon tac_plus -C $tacacs_config -d $debug
+       else
+       echo -n "Starting TACACS+ :"
+       daemon tac_plus -C $tacacs_config
+       fi
+       echo
+        touch /var/lock/subsys/tac_plus
+        ;;
+  stop)
+        # Stop daemons.
+        echo -n "Shutting down TACACS+: "
+        killproc tac_plus
+        rm -f /var/lock/subsys/tac_plus
+        echo
+        ;;
+  status)
+       status tac_plus 
+       exit $?
+       ;;
+  restart)
+       $0 stop
+       $0 start  
+       ;;
+  
+  reload)
+       echo "TACACS+ now reloading......"
+       kill -SIGUSR1 `cat /var/run/tac_plus.pid`
+       exit $?
+       ;;
+  test)
+       echo "TACACS+ config being testing..."
+       /usr/sbin/tac_plus -P -C $tacacs_config
+       ;;
+  *)
+        echo "Usage: tac_plus {start|stop|status|restart|reload|test}"
+        exit 1
+esac
+
+exit 0
diff --git a/tac_plus.rotate b/tac_plus.rotate
new file mode 100644 (file)
index 0000000..14f972e
--- /dev/null
@@ -0,0 +1,21 @@
+# This is tac_plus logrotate config file
+# For more info please refer logrotate man page
+/var/log/tac_plus.log {
+       size 3M
+       missingok
+       errors root@localhost
+       compress
+       postrotate
+               /usr/bin/killall -HUP tac_plus 2> /dev/null || true
+       endscript
+}
+
+/var/log/tac_acc.log {
+        size 5M
+       missingok
+        errors root@localhost
+       nocompress      
+       postrotate
+                /usr/bin/killall -HUP tac_plus 2> /dev/null || true
+        endscript
+}
diff --git a/tac_plus.sql b/tac_plus.sql
new file mode 100644 (file)
index 0000000..46003af
--- /dev/null
@@ -0,0 +1,37 @@
+# This file created by Devrim SERAL<devrim@gazi.edu.tr>
+# For creating tac_plus related database and tables
+
+CREATE DATABASE tacacs;
+USE tacacs;
+
+# id table fields are:
+# name | surname | usern | tel | address 
+# name : Real name
+# surname : Surname
+# usern : Account name 
+# tel: Tel number
+# address: Address
+
+CREATE TABLE id ( name char(40) NOT NULL, surname char(40) NOT NULL,usern char(15) NOT NULL,tel char(20), address char(50) ,PRIMARY KEY(usern));
+
+# auth tables fields are:
+# usern | passwd | exp_time | time_limit | lck
+# usern : Account name
+# passwd : usern password
+# exp_time : Expire date 
+# time_limit : Time limiting
+# lck : Adminstrative lock 
+
+CREATE TABLE auth( usern CHAR(15) NOT NULL, passwd CHAR(15) NOT NULL, exp_time TIMESTAMP(8) NOT NULL ,time_limit CHAR(30) DEFAULT "*",lck ENUM("T","F") DEFAULT "F",PRIMARY KEY(usern) ); 
+
+# acct tables fields are:
+# usern | s_name | c_name | elapsed_time | bytes_in | bytes_out | fin_t
+# usern : Account name
+# s_name : Server name(RAS)
+# c_name : Client Name
+# elapsed_time : How much the user spent on router
+# bytes_in : Incoming bytes to port
+# bytes_out : Outgoing bytes from port
+# fin_t :  When the accounting is finished
+CREATE TABLE acct( usern CHAR(15) NOT NULL, s_name CHAR(30) NOT NULL, c_name CHAR(30) NOT NULL, elapsed_time INT NOT NULL, bytes_in INT DEFAULT 0, bytes_out INT  DEFAULT 0,fin_t TIMESTAMP(14) NOT NULL,INDEX acct_index(usern(10)));
diff --git a/tac_regexp.3 b/tac_regexp.3
new file mode 100644 (file)
index 0000000..ba0bad3
--- /dev/null
@@ -0,0 +1,179 @@
+.TH REGEXP 3 local
+.DA 2 April 1986
+.SH NAME
+regcomp, regexec, regsub, regerror \- regular expression handler
+.SH SYNOPSIS
+.ft B
+.nf
+#include <regexp.h>
+
+regexp *regcomp(exp)
+char *exp;
+
+int regexec(prog, string)
+regexp *prog;
+char *string;
+
+regsub(prog, source, dest)
+regexp *prog;
+char *source;
+char *dest;
+
+regerror(msg)
+char *msg;
+.SH DESCRIPTION
+These functions implement
+.IR egrep (1)-style
+regular expressions and supporting facilities.
+.PP
+.I Regcomp
+compiles a regular expression into a structure of type
+.IR regexp ,
+and returns a pointer to it.
+The space has been allocated using
+.IR malloc (3)
+and may be released by
+.IR free .
+.PP
+.I Regexec
+matches a NUL-terminated \fIstring\fR against the compiled regular expression
+in \fIprog\fR.
+It returns 1 for success and 0 for failure, and adjusts the contents of
+\fIprog\fR's \fIstartp\fR and \fIendp\fR (see below) accordingly.
+.PP
+The members of a
+.I regexp
+structure include at least the following (not necessarily in order):
+.PP
+.RS
+char *startp[NSUBEXP];
+.br
+char *endp[NSUBEXP];
+.RE
+.PP
+where
+.I NSUBEXP
+is defined (as 10) in the header file.
+Once a successful \fIregexec\fR has been done using the \fIregexp\fR,
+each \fIstartp\fR-\fIendp\fR pair describes one substring
+within the \fIstring\fR,
+with the \fIstartp\fR pointing to the first character of the substring and
+the \fIendp\fR pointing to the first character following the substring.
+The 0th substring is the substring of \fIstring\fR that matched the whole
+regular expression.
+The others are those substrings that matched parenthesized expressions
+within the regular expression, with parenthesized expressions numbered
+in left-to-right order of their opening parentheses.
+.PP
+.I Regsub
+copies \fIsource\fR to \fIdest\fR, making substitutions according to the
+most recent \fIregexec\fR performed using \fIprog\fR.
+Each instance of `&' in \fIsource\fR is replaced by the substring
+indicated by \fIstartp\fR[\fI0\fR] and
+\fIendp\fR[\fI0\fR].
+Each instance of `\e\fIn\fR', where \fIn\fR is a digit, is replaced by
+the substring indicated by
+\fIstartp\fR[\fIn\fR] and
+\fIendp\fR[\fIn\fR].
+To get a literal `&' or `\e\fIn\fR' into \fIdest\fR, prefix it with `\e';
+to get a literal `\e' preceding `&' or `\e\fIn\fR', prefix it with
+another `\e'.
+.PP
+.I Regerror
+is called whenever an error is detected in \fIregcomp\fR, \fIregexec\fR,
+or \fIregsub\fR.
+The default \fIregerror\fR writes the string \fImsg\fR,
+with a suitable indicator of origin,
+on the standard
+error output
+and invokes \fIexit\fR(2).
+.I Regerror
+can be replaced by the user if other actions are desirable.
+.SH "REGULAR EXPRESSION SYNTAX"
+A regular expression is zero or more \fIbranches\fR, separated by `|'.
+It matches anything that matches one of the branches.
+.PP
+A branch is zero or more \fIpieces\fR, concatenated.
+It matches a match for the first, followed by a match for the second, etc.
+.PP
+A piece is an \fIatom\fR possibly followed by `*', `+', or `?'.
+An atom followed by `*' matches a sequence of 0 or more matches of the atom.
+An atom followed by `+' matches a sequence of 1 or more matches of the atom.
+An atom followed by `?' matches a match of the atom, or the null string.
+.PP
+An atom is a regular expression in parentheses (matching a match for the
+regular expression), a \fIrange\fR (see below), `.'
+(matching any single character), `^' (matching the null string at the
+beginning of the input string), `$' (matching the null string at the
+end of the input string), a `\e' followed by a single character (matching
+that character), or a single character with no other significance
+(matching that character).
+.PP
+A \fIrange\fR is a sequence of characters enclosed in `[]'.
+It normally matches any single character from the sequence.
+If the sequence begins with `^',
+it matches any single character \fInot\fR from the rest of the sequence.
+If two characters in the sequence are separated by `\-', this is shorthand
+for the full list of ASCII characters between them
+(e.g. `[0-9]' matches any decimal digit).
+To include a literal `]' in the sequence, make it the first character
+(following a possible `^').
+To include a literal `\-', make it the first or last character.
+.SH AMBIGUITY
+If a regular expression could match two different parts of the input string,
+it will match the one which begins earliest.
+If both begin in the same place        but match different lengths, or match
+the same length in different ways, life gets messier, as follows.
+.PP
+In general, the possibilities in a list of branches are considered in
+left-to-right order, the possibilities for `*', `+', and `?' are
+considered longest-first, nested constructs are considered from the
+outermost in, and concatenated constructs are considered leftmost-first.
+The match that will be chosen is the one that uses the earliest
+possibility in the first choice that has to be made.
+If there is more than one choice, the next will be made in the same manner
+(earliest possibility) subject to the decision on the first choice.
+And so forth.
+.PP
+For example, `(ab|a)b*c' could match `abc' in one of two ways.
+The first choice is between `ab' and `a'; since `ab' is earlier, and does
+lead to a successful overall match, it is chosen.
+Since the `b' is already spoken for,
+the `b*' must match its last possibility\(emthe empty string\(emsince
+it must respect the earlier choice.
+.PP
+In the particular case where no `|'s are present and there is only one
+`*', `+', or `?', the net effect is that the longest possible
+match will be chosen.
+So `ab*', presented with `xabbbby', will match `abbbb'.
+Note that if `ab*' is tried against `xabyabbbz', it
+will match `ab' just after `x', due to the begins-earliest rule.
+(In effect, the decision on where to start the match is the first choice
+to be made, hence subsequent choices must respect it even if this leads them
+to less-preferred alternatives.)
+.SH SEE ALSO
+egrep(1), expr(1)
+.SH DIAGNOSTICS
+\fIRegcomp\fR returns NULL for a failure
+(\fIregerror\fR permitting),
+where failures are syntax errors, exceeding implementation limits,
+or applying `+' or `*' to a possibly-null operand.
+.SH HISTORY
+Both code and manual page were
+written at U of T.
+They are intended to be compatible with the Bell V8 \fIregexp\fR(3),
+but are not derived from Bell code.
+.SH BUGS
+Empty branches and empty regular expressions are not portable to V8.
+.PP
+The restriction against
+applying `*' or `+' to a possibly-null operand is an artifact of the
+simplistic implementation.
+.PP
+Does not support \fIegrep\fR's newline-separated branches;
+neither does the V8 \fIregexp\fR(3), though.
+.PP
+Due to emphasis on
+compactness and simplicity,
+it's not strikingly fast.
+It does give special attention to handling simple cases quickly.
diff --git a/tac_regexp.c b/tac_regexp.c
new file mode 100644 (file)
index 0000000..9c0357a
--- /dev/null
@@ -0,0 +1,1234 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+/*
+ * regcomp and regexec -- regsub and regerror are elsewhere
+ * @(#)regexp.c        1.3 of 18 April 87
+ *
+ *     Copyright (c) 1986 by University of Toronto.
+ *     Written by Henry Spencer.  Not derived from licensed software.
+ *
+ *     Permission is granted to anyone to use this software for any
+ *     purpose on any computer system, and to redistribute it freely,
+ *     subject to the following restrictions:
+ *
+ *     1. The author is not responsible for the consequences of use of
+ *             this software, no matter how awful, even if they arise
+ *             from defects in it.
+ *
+ *     2. The origin of this software must not be misrepresented, either
+ *             by explicit claim or by omission.
+ *
+ *     3. Altered versions must be plainly marked as such, and must not
+ *             be misrepresented as being the original software.
+ *
+ * Beware that some of this code is subtly aware of the way operator
+ * precedence is structured in regular expressions.  Serious changes in
+ * regular-expression syntax might require a total rethink.
+ */
+#include <stdio.h>
+#include "regexp.h"
+#include "regmagic.h"
+
+/*
+ * The "internal use only" fields in regexp.h are present to pass info from
+ * compile to execute that permits the execute phase to run lots faster on
+ * simple cases.  They are:
+ *
+ * regstart    char that must begin a match; '\0' if none obvious
+ * reganch     is the match anchored (at beginning-of-line only)?
+ * regmust     string (pointer into program) that match must include, or NULL
+ * regmlen     length of regmust string
+ *
+ * Regstart and reganch permit very fast decisions on suitable starting points
+ * for a match, cutting down the work a lot.  Regmust permits fast rejection
+ * of lines that cannot possibly match.  The regmust tests are costly enough
+ * that regcomp() supplies a regmust only if the r.e. contains something
+ * potentially expensive (at present, the only such thing detected is * or +
+ * at the start of the r.e., which can involve a lot of backup).  Regmlen is
+ * supplied because the test in regexec() needs it and regcomp() is computing
+ * it anyway.
+ */
+
+/*
+ * Structure for regexp "program".  This is essentially a linear encoding
+ * of a nondeterministic finite-state machine (aka syntax charts or
+ * "railroad normal form" in parsing technology).  Each node is an opcode
+ * plus a "next" pointer, possibly plus an operand.  "Next" pointers of
+ * all nodes except BRANCH implement concatenation; a "next" pointer with
+ * a BRANCH on both ends of it is connecting two alternatives.  (Here we
+ * have one of the subtle syntax dependencies:  an individual BRANCH (as
+ * opposed to a collection of them) is never concatenated with anything
+ * because of operator precedence.)  The operand of some types of node is
+ * a literal string; for others, it is a node leading into a sub-FSM.  In
+ * particular, the operand of a BRANCH node is the first node of the branch.
+ * (NB this is *not* a tree structure:  the tail of the branch connects
+ * to the thing following the set of BRANCHes.)  The opcodes are:
+ */
+
+/* definition  number  opnd?   meaning */
+#define        END     0       /* no   End of program. */
+#define        BOL     1       /* no   Match "" at beginning of line. */
+#define        EOL     2       /* no   Match "" at end of line. */
+#define        ANY     3       /* no   Match any one character. */
+#define        ANYOF   4       /* str  Match any character in this string. */
+#define        ANYBUT  5       /* str  Match any character not in this string. */
+#define        BRANCH  6       /* node Match this alternative, or the next... */
+#define        BACK    7       /* no   Match "", "next" ptr points backward. */
+#define        EXACTLY 8       /* str  Match this string. */
+#define        NOTHING 9       /* no   Match empty string. */
+#define        STAR    10      /* node Match this (simple) thing 0 or more times. */
+#define        PLUS    11      /* node Match this (simple) thing 1 or more times. */
+#define        OPEN    20      /* no   Mark this point in input as start of #n. */
+                       /*      OPEN+1 is number 1, etc. */
+#define        CLOSE   30      /* no   Analogous to OPEN. */
+
+/*
+ * Opcode notes:
+ *
+ * BRANCH      The set of branches constituting a single choice are hooked
+ *             together with their "next" pointers, since precedence prevents
+ *             anything being concatenated to any individual branch.  The
+ *             "next" pointer of the last BRANCH in a choice points to the
+ *             thing following the whole choice.  This is also where the
+ *             final "next" pointer of each individual branch points; each
+ *             branch starts with the operand node of a BRANCH node.
+ *
+ * BACK                Normal "next" pointers all implicitly point forward; BACK
+ *             exists to make loop structures possible.
+ *
+ * STAR,PLUS   '?', and complex '*' and '+', are implemented as circular
+ *             BRANCH structures using BACK.  Simple cases (one character
+ *             per match) are implemented with STAR and PLUS for speed
+ *             and to minimize recursive plunges.
+ *
+ * OPEN,CLOSE  ...are numbered at compile time.
+ */
+
+/*
+ * A node is one char of opcode followed by two chars of "next" pointer.
+ * "Next" pointers are stored as two 8-bit pieces, high order first.  The
+ * value is a positive offset from the opcode of the node containing it.
+ * An operand, if any, simply follows the node.  (Note that much of the
+ * code generation knows about this implicit relationship.)
+ *
+ * Using two bytes for the "next" pointer is vast overkill for most things,
+ * but allows patterns to get big without disasters.
+ */
+#define        OP(p)   (*(p))
+#define        NEXT(p) (((*((p)+1)&0377)<<8) + (*((p)+2)&0377))
+#define        OPERAND(p)      ((p) + 3)
+
+/*
+ * See regmagic.h for one further detail of program structure.
+ */
+
+
+/*
+ * Utility definitions.
+ */
+#ifndef CHARBITS
+#define        UCHARAT(p)      ((int)*(unsigned char *)(p))
+#else
+#define        UCHARAT(p)      ((int)*(p)&CHARBITS)
+#endif
+
+#define        FAIL(m) { regerror(m); return(NULL); }
+#define        ISMULT(c)       ((c) == '*' || (c) == '+' || (c) == '?')
+#define        META    "^$.[()|?+*\\"
+
+/*
+ * Flags to be passed up and down.
+ */
+#define        HASWIDTH        01      /* Known never to match null string. */
+#define        SIMPLE          02      /* Simple enough to be STAR/PLUS operand. */
+#define        SPSTART         04      /* Starts with * or +. */
+#define        WORST           0       /* Worst case. */
+
+/*
+ * Global work variables for regcomp().
+ */
+static char *regparse;         /* Input-scan pointer. */
+static int regnpar;            /* () count. */
+static char regdummy;
+static char *regcode;          /* Code-emit pointer; &regdummy = don't. */
+static long regsize;           /* Code size. */
+
+/*
+ * Forward declarations for regcomp()'s friends.
+ */
+#ifndef STATIC
+#define        STATIC  static
+#endif
+STATIC char *reg();
+STATIC char *regbranch();
+STATIC char *regpiece();
+STATIC char *regatom();
+STATIC char *regnode();
+STATIC char *regnext();
+STATIC void regc();
+STATIC void reginsert();
+STATIC void regtail();
+STATIC void regoptail();
+#ifdef STRCSPN
+STATIC int strcspn();
+#endif
+
+/*
+ - regcomp - compile a regular expression into internal code
+ *
+ * We can't allocate space until we know how big the compiled form will be,
+ * but we can't compile it (and thus know how big it is) until we've got a
+ * place to put the code.  So we cheat:  we compile it twice, once with code
+ * generation turned off and size counting turned on, and once "for real".
+ * This also means that we don't allocate space until we are sure that the
+ * thing really will compile successfully, and we never have to move the
+ * code and thus invalidate pointers into it.  (Note that it has to be in
+ * one piece because free() must be able to free it all.)
+ *
+ * Beware that the optimization-preparation code in here knows about some
+ * of the structure of the compiled regexp.
+ */
+regexp *
+regcomp(exp)
+char *exp;
+{
+       register regexp *r;
+       register char *scan;
+       register char *longest;
+       register int len;
+       int flags;
+       extern char *malloc();
+
+       if (exp == NULL)
+               FAIL("NULL argument");
+
+       /* First pass: determine size, legality. */
+       regparse = exp;
+       regnpar = 1;
+       regsize = 0L;
+       regcode = &regdummy;
+       regc(MAGIC);
+       if (reg(0, &flags) == NULL)
+               return(NULL);
+
+       /* Small enough for pointer-storage convention? */
+       if (regsize >= 32767L)          /* Probably could be 65535L. */
+               FAIL("regexp too big");
+
+       /* Allocate space. */
+       r = (regexp *)malloc(sizeof(regexp) + (unsigned)regsize);
+       if (r == NULL)
+               FAIL("out of space");
+
+       /* Second pass: emit code. */
+       regparse = exp;
+       regnpar = 1;
+       regcode = r->program;
+       regc(MAGIC);
+       if (reg(0, &flags) == NULL)
+               return(NULL);
+
+       /* Dig out information for optimizations. */
+       r->regstart = '\0';     /* Worst-case defaults. */
+       r->reganch = 0;
+       r->regmust = NULL;
+       r->regmlen = 0;
+       scan = r->program+1;                    /* First BRANCH. */
+       if (OP(regnext(scan)) == END) {         /* Only one top-level choice. */
+               scan = OPERAND(scan);
+
+               /* Starting-point info. */
+               if (OP(scan) == EXACTLY)
+                       r->regstart = *OPERAND(scan);
+               else if (OP(scan) == BOL)
+                       r->reganch++;
+
+               /*
+                * If there's something expensive in the r.e., find the
+                * longest literal string that must appear and make it the
+                * regmust.  Resolve ties in favor of later strings, since
+                * the regstart check works with the beginning of the r.e.
+                * and avoiding duplication strengthens checking.  Not a
+                * strong reason, but sufficient in the absence of others.
+                */
+               if (flags&SPSTART) {
+                       longest = NULL;
+                       len = 0;
+                       for (; scan != NULL; scan = regnext(scan))
+                               if (OP(scan) == EXACTLY && strlen(OPERAND(scan)) >= len) {
+                                       longest = OPERAND(scan);
+                                       len = strlen(OPERAND(scan));
+                               }
+                       r->regmust = longest;
+                       r->regmlen = len;
+               }
+       }
+
+       return(r);
+}
+
+/*
+ - reg - regular expression, i.e. main body or parenthesized thing
+ *
+ * Caller must absorb opening parenthesis.
+ *
+ * Combining parenthesis handling with the base level of regular expression
+ * is a trifle forced, but the need to tie the tails of the branches to what
+ * follows makes it hard to avoid.
+ */
+static char *
+reg(paren, flagp)
+int paren;                     /* Parenthesized? */
+int *flagp;
+{
+       register char *ret;
+       register char *br;
+       register char *ender;
+       register int parno;
+       int flags;
+
+       *flagp = HASWIDTH;      /* Tentatively. */
+
+       /* Make an OPEN node, if parenthesized. */
+       if (paren) {
+               if (regnpar >= NSUBEXP)
+                       FAIL("too many ()");
+               parno = regnpar;
+               regnpar++;
+               ret = regnode(OPEN+parno);
+       } else
+               ret = NULL;
+
+       /* Pick up the branches, linking them together. */
+       br = regbranch(&flags);
+       if (br == NULL)
+               return(NULL);
+       if (ret != NULL)
+               regtail(ret, br);       /* OPEN -> first. */
+       else
+               ret = br;
+       if (!(flags&HASWIDTH))
+               *flagp &= ~HASWIDTH;
+       *flagp |= flags&SPSTART;
+       while (*regparse == '|') {
+               regparse++;
+               br = regbranch(&flags);
+               if (br == NULL)
+                       return(NULL);
+               regtail(ret, br);       /* BRANCH -> BRANCH. */
+               if (!(flags&HASWIDTH))
+                       *flagp &= ~HASWIDTH;
+               *flagp |= flags&SPSTART;
+       }
+
+       /* Make a closing node, and hook it on the end. */
+       ender = regnode((paren) ? CLOSE+parno : END);   
+       regtail(ret, ender);
+
+       /* Hook the tails of the branches to the closing node. */
+       for (br = ret; br != NULL; br = regnext(br))
+               regoptail(br, ender);
+
+       /* Check for proper termination. */
+       if (paren && *regparse++ != ')') {
+               FAIL("unmatched ()");
+       } else if (!paren && *regparse != '\0') {
+               if (*regparse == ')') {
+                       FAIL("unmatched ()");
+               } else
+                       FAIL("junk on end");    /* "Can't happen". */
+               /* NOTREACHED */
+       }
+
+       return(ret);
+}
+
+/*
+ - regbranch - one alternative of an | operator
+ *
+ * Implements the concatenation operator.
+ */
+static char *
+regbranch(flagp)
+int *flagp;
+{
+       register char *ret;
+       register char *chain;
+       register char *latest;
+       int flags;
+
+       *flagp = WORST;         /* Tentatively. */
+
+       ret = regnode(BRANCH);
+       chain = NULL;
+       while (*regparse != '\0' && *regparse != '|' && *regparse != ')') {
+               latest = regpiece(&flags);
+               if (latest == NULL)
+                       return(NULL);
+               *flagp |= flags&HASWIDTH;
+               if (chain == NULL)      /* First piece. */
+                       *flagp |= flags&SPSTART;
+               else
+                       regtail(chain, latest);
+               chain = latest;
+       }
+       if (chain == NULL)      /* Loop ran zero times. */
+               (void) regnode(NOTHING);
+
+       return(ret);
+}
+
+/*
+ - regpiece - something followed by possible [*+?]
+ *
+ * Note that the branching code sequences used for ? and the general cases
+ * of * and + are somewhat optimized:  they use the same NOTHING node as
+ * both the endmarker for their branch list and the body of the last branch.
+ * It might seem that this node could be dispensed with entirely, but the
+ * endmarker role is not redundant.
+ */
+static char *
+regpiece(flagp)
+int *flagp;
+{
+       register char *ret;
+       register char op;
+       register char *next;
+       int flags;
+
+       ret = regatom(&flags);
+       if (ret == NULL)
+               return(NULL);
+
+       op = *regparse;
+       if (!ISMULT(op)) {
+               *flagp = flags;
+               return(ret);
+       }
+
+       if (!(flags&HASWIDTH) && op != '?')
+               FAIL("*+ operand could be empty");
+       *flagp = (op != '+') ? (WORST|SPSTART) : (WORST|HASWIDTH);
+
+       if (op == '*' && (flags&SIMPLE))
+               reginsert(STAR, ret);
+       else if (op == '*') {
+               /* Emit x* as (x&|), where & means "self". */
+               reginsert(BRANCH, ret);                 /* Either x */
+               regoptail(ret, regnode(BACK));          /* and loop */
+               regoptail(ret, ret);                    /* back */
+               regtail(ret, regnode(BRANCH));          /* or */
+               regtail(ret, regnode(NOTHING));         /* null. */
+       } else if (op == '+' && (flags&SIMPLE))
+               reginsert(PLUS, ret);
+       else if (op == '+') {
+               /* Emit x+ as x(&|), where & means "self". */
+               next = regnode(BRANCH);                 /* Either */
+               regtail(ret, next);
+               regtail(regnode(BACK), ret);            /* loop back */
+               regtail(next, regnode(BRANCH));         /* or */
+               regtail(ret, regnode(NOTHING));         /* null. */
+       } else if (op == '?') {
+               /* Emit x? as (x|) */
+               reginsert(BRANCH, ret);                 /* Either x */
+               regtail(ret, regnode(BRANCH));          /* or */
+               next = regnode(NOTHING);                /* null. */
+               regtail(ret, next);
+               regoptail(ret, next);
+       }
+       regparse++;
+       if (ISMULT(*regparse))
+               FAIL("nested *?+");
+
+       return(ret);
+}
+
+/*
+ - regatom - the lowest level
+ *
+ * Optimization:  gobbles an entire sequence of ordinary characters so that
+ * it can turn them into a single node, which is smaller to store and
+ * faster to run.  Backslashed characters are exceptions, each becoming a
+ * separate node; the code is simpler that way and it's not worth fixing.
+ */
+static char *
+regatom(flagp)
+int *flagp;
+{
+       register char *ret;
+       int flags;
+
+       *flagp = WORST;         /* Tentatively. */
+
+       switch (*regparse++) {
+       case '^':
+               ret = regnode(BOL);
+               break;
+       case '$':
+               ret = regnode(EOL);
+               break;
+       case '.':
+               ret = regnode(ANY);
+               *flagp |= HASWIDTH|SIMPLE;
+               break;
+       case '[': {
+                       register int class;
+                       register int classend;
+
+                       if (*regparse == '^') { /* Complement of range. */
+                               ret = regnode(ANYBUT);
+                               regparse++;
+                       } else
+                               ret = regnode(ANYOF);
+                       if (*regparse == ']' || *regparse == '-')
+                               regc(*regparse++);
+                       while (*regparse != '\0' && *regparse != ']') {
+                               if (*regparse == '-') {
+                                       regparse++;
+                                       if (*regparse == ']' || *regparse == '\0')
+                                               regc('-');
+                                       else {
+                                               class = UCHARAT(regparse-2)+1;
+                                               classend = UCHARAT(regparse);
+                                               if (class > classend+1)
+                                                       FAIL("invalid [] range");
+                                               for (; class <= classend; class++)
+                                                       regc(class);
+                                               regparse++;
+                                       }
+                               } else
+                                       regc(*regparse++);
+                       }
+                       regc('\0');
+                       if (*regparse != ']')
+                               FAIL("unmatched []");
+                       regparse++;
+                       *flagp |= HASWIDTH|SIMPLE;
+               }
+               break;
+       case '(':
+               ret = reg(1, &flags);
+               if (ret == NULL)
+                       return(NULL);
+               *flagp |= flags&(HASWIDTH|SPSTART);
+               break;
+       case '\0':
+       case '|':
+       case ')':
+               FAIL("internal urp");   /* Supposed to be caught earlier. */
+               break;
+       case '?':
+       case '+':
+       case '*':
+               FAIL("?+* follows nothing");
+               break;
+       case '\\':
+               if (*regparse == '\0')
+                       FAIL("trailing \\");
+               ret = regnode(EXACTLY);
+               regc(*regparse++);
+               regc('\0');
+               *flagp |= HASWIDTH|SIMPLE;
+               break;
+       default: {
+                       register int len;
+                       register char ender;
+
+                       regparse--;
+                       len = strcspn(regparse, META);
+                       if (len <= 0)
+                               FAIL("internal disaster");
+                       ender = *(regparse+len);
+                       if (len > 1 && ISMULT(ender))
+                               len--;          /* Back off clear of ?+* operand. */
+                       *flagp |= HASWIDTH;
+                       if (len == 1)
+                               *flagp |= SIMPLE;
+                       ret = regnode(EXACTLY);
+                       while (len > 0) {
+                               regc(*regparse++);
+                               len--;
+                       }
+                       regc('\0');
+               }
+               break;
+       }
+
+       return(ret);
+}
+
+/*
+ - regnode - emit a node
+ */
+static char *                  /* Location. */
+regnode(op)
+char op;
+{
+       register char *ret;
+       register char *ptr;
+
+       ret = regcode;
+       if (ret == &regdummy) {
+               regsize += 3;
+               return(ret);
+       }
+
+       ptr = ret;
+       *ptr++ = op;
+       *ptr++ = '\0';          /* Null "next" pointer. */
+       *ptr++ = '\0';
+       regcode = ptr;
+
+       return(ret);
+}
+
+/*
+ - regc - emit (if appropriate) a byte of code
+ */
+static void
+regc(b)
+char b;
+{
+       if (regcode != &regdummy)
+               *regcode++ = b;
+       else
+               regsize++;
+}
+
+/*
+ - reginsert - insert an operator in front of already-emitted operand
+ *
+ * Means relocating the operand.
+ */
+static void
+reginsert(op, opnd)
+char op;
+char *opnd;
+{
+       register char *src;
+       register char *dst;
+       register char *place;
+
+       if (regcode == &regdummy) {
+               regsize += 3;
+               return;
+       }
+
+       src = regcode;
+       regcode += 3;
+       dst = regcode;
+       while (src > opnd)
+               *--dst = *--src;
+
+       place = opnd;           /* Op node, where operand used to be. */
+       *place++ = op;
+       *place++ = '\0';
+       *place++ = '\0';
+}
+
+/*
+ - regtail - set the next-pointer at the end of a node chain
+ */
+static void
+regtail(p, val)
+char *p;
+char *val;
+{
+       register char *scan;
+       register char *temp;
+       register int offset;
+
+       if (p == &regdummy)
+               return;
+
+       /* Find last node. */
+       scan = p;
+       for (;;) {
+               temp = regnext(scan);
+               if (temp == NULL)
+                       break;
+               scan = temp;
+       }
+
+       if (OP(scan) == BACK)
+               offset = scan - val;
+       else
+               offset = val - scan;
+       *(scan+1) = (offset>>8)&0377;
+       *(scan+2) = offset&0377;
+}
+
+/*
+ - regoptail - regtail on operand of first argument; nop if operandless
+ */
+static void
+regoptail(p, val)
+char *p;
+char *val;
+{
+       /* "Operandless" and "op != BRANCH" are synonymous in practice. */
+       if (p == NULL || p == &regdummy || OP(p) != BRANCH)
+               return;
+       regtail(OPERAND(p), val);
+}
+
+/*
+ * regexec and friends
+ */
+
+/*
+ * Global work variables for regexec().
+ */
+static char *reginput;         /* String-input pointer. */
+static char *regbol;           /* Beginning of input, for ^ check. */
+static char **regstartp;       /* Pointer to startp array. */
+static char **regendp;         /* Ditto for endp. */
+
+/*
+ * Forwards.
+ */
+STATIC int regtry();
+STATIC int regmatch();
+STATIC int regrepeat();
+
+#ifdef DEBUG
+int regnarrate = 0;
+void regdump();
+STATIC char *regprop();
+#endif
+
+/*
+ - regexec - match a regexp against a string
+ */
+int
+regexec(prog, string)
+register regexp *prog;
+register char *string;
+{
+       register char *s;
+       extern char *strchr();
+
+       /* Be paranoid... */
+       if (prog == NULL || string == NULL) {
+               regerror("NULL parameter");
+               return(0);
+       }
+
+       /* Check validity of program. */
+       if (UCHARAT(prog->program) != MAGIC) {
+               regerror("corrupted program");
+               return(0);
+       }
+
+       /* If there is a "must appear" string, look for it. */
+       if (prog->regmust != NULL) {
+               s = string;
+               while ((s = strchr(s, prog->regmust[0])) != NULL) {
+                       if (strncmp(s, prog->regmust, prog->regmlen) == 0)
+                               break;  /* Found it. */
+                       s++;
+               }
+               if (s == NULL)  /* Not present. */
+                       return(0);
+       }
+
+       /* Mark beginning of line for ^ . */
+       regbol = string;
+
+       /* Simplest case:  anchored match need be tried only once. */
+       if (prog->reganch)
+               return(regtry(prog, string));
+
+       /* Messy cases:  unanchored match. */
+       s = string;
+       if (prog->regstart != '\0')
+               /* We know what char it must start with. */
+               while ((s = strchr(s, prog->regstart)) != NULL) {
+                       if (regtry(prog, s))
+                               return(1);
+                       s++;
+               }
+       else
+               /* We don't -- general case. */
+               do {
+                       if (regtry(prog, s))
+                               return(1);
+               } while (*s++ != '\0');
+
+       /* Failure. */
+       return(0);
+}
+
+/*
+ - regtry - try match at specific point
+ */
+static int                     /* 0 failure, 1 success */
+regtry(prog, string)
+regexp *prog;
+char *string;
+{
+       register int i;
+       register char **sp;
+       register char **ep;
+
+       reginput = string;
+       regstartp = prog->startp;
+       regendp = prog->endp;
+
+       sp = prog->startp;
+       ep = prog->endp;
+       for (i = NSUBEXP; i > 0; i--) {
+               *sp++ = NULL;
+               *ep++ = NULL;
+       }
+       if (regmatch(prog->program + 1)) {
+               prog->startp[0] = string;
+               prog->endp[0] = reginput;
+               return(1);
+       } else
+               return(0);
+}
+
+/*
+ - regmatch - main matching routine
+ *
+ * Conceptually the strategy is simple:  check to see whether the current
+ * node matches, call self recursively to see whether the rest matches,
+ * and then act accordingly.  In practice we make some effort to avoid
+ * recursion, in particular by going through "ordinary" nodes (that don't
+ * need to know whether the rest of the match failed) by a loop instead of
+ * by recursion.
+ */
+static int                     /* 0 failure, 1 success */
+regmatch(prog)
+char *prog;
+{
+       register char *scan;    /* Current node. */
+       char *next;             /* Next node. */
+       extern char *strchr();
+
+       scan = prog;
+#ifdef DEBUG
+       if (scan != NULL && regnarrate)
+               fprintf(stderr, "%s(\n", regprop(scan));
+#endif
+       while (scan != NULL) {
+#ifdef DEBUG
+               if (regnarrate)
+                       fprintf(stderr, "%s...\n", regprop(scan));
+#endif
+               next = regnext(scan);
+
+               switch (OP(scan)) {
+               case BOL:
+                       if (reginput != regbol)
+                               return(0);
+                       break;
+               case EOL:
+                       if (*reginput != '\0')
+                               return(0);
+                       break;
+               case ANY:
+                       if (*reginput == '\0')
+                               return(0);
+                       reginput++;
+                       break;
+               case EXACTLY: {
+                               register int len;
+                               register char *opnd;
+
+                               opnd = OPERAND(scan);
+                               /* Inline the first character, for speed. */
+                               if (*opnd != *reginput)
+                                       return(0);
+                               len = strlen(opnd);
+                               if (len > 1 && strncmp(opnd, reginput, len) != 0)
+                                       return(0);
+                               reginput += len;
+                       }
+                       break;
+               case ANYOF:
+                       if (*reginput == '\0' || strchr(OPERAND(scan), *reginput) == NULL)
+                               return(0);
+                       reginput++;
+                       break;
+               case ANYBUT:
+                       if (*reginput == '\0' || strchr(OPERAND(scan), *reginput) != NULL)
+                               return(0);
+                       reginput++;
+                       break;
+               case NOTHING:
+                       break;
+               case BACK:
+                       break;
+               case OPEN+1:
+               case OPEN+2:
+               case OPEN+3:
+               case OPEN+4:
+               case OPEN+5:
+               case OPEN+6:
+               case OPEN+7:
+               case OPEN+8:
+               case OPEN+9: {
+                               register int no;
+                               register char *save;
+
+                               no = OP(scan) - OPEN;
+                               save = reginput;
+
+                               if (regmatch(next)) {
+                                       /*
+                                        * Don't set startp if some later
+                                        * invocation of the same parentheses
+                                        * already has.
+                                        */
+                                       if (regstartp[no] == NULL)
+                                               regstartp[no] = save;
+                                       return(1);
+                               } else
+                                       return(0);
+                       }
+                       break;
+               case CLOSE+1:
+               case CLOSE+2:
+               case CLOSE+3:
+               case CLOSE+4:
+               case CLOSE+5:
+               case CLOSE+6:
+               case CLOSE+7:
+               case CLOSE+8:
+               case CLOSE+9: {
+                               register int no;
+                               register char *save;
+
+                               no = OP(scan) - CLOSE;
+                               save = reginput;
+
+                               if (regmatch(next)) {
+                                       /*
+                                        * Don't set endp if some later
+                                        * invocation of the same parentheses
+                                        * already has.
+                                        */
+                                       if (regendp[no] == NULL)
+                                               regendp[no] = save;
+                                       return(1);
+                               } else
+                                       return(0);
+                       }
+                       break;
+               case BRANCH: {
+                               register char *save;
+
+                               if (OP(next) != BRANCH)         /* No choice. */
+                                       next = OPERAND(scan);   /* Avoid recursion. */
+                               else {
+                                       do {
+                                               save = reginput;
+                                               if (regmatch(OPERAND(scan)))
+                                                       return(1);
+                                               reginput = save;
+                                               scan = regnext(scan);
+                                       } while (scan != NULL && OP(scan) == BRANCH);
+                                       return(0);
+                                       /* NOTREACHED */
+                               }
+                       }
+                       break;
+               case STAR:
+               case PLUS: {
+                               register char nextch;
+                               register int no;
+                               register char *save;
+                               register int min;
+
+                               /*
+                                * Lookahead to avoid useless match attempts
+                                * when we know what character comes next.
+                                */
+                               nextch = '\0';
+                               if (OP(next) == EXACTLY)
+                                       nextch = *OPERAND(next);
+                               min = (OP(scan) == STAR) ? 0 : 1;
+                               save = reginput;
+                               no = regrepeat(OPERAND(scan));
+                               while (no >= min) {
+                                       /* If it could work, try it. */
+                                       if (nextch == '\0' || *reginput == nextch)
+                                               if (regmatch(next))
+                                                       return(1);
+                                       /* Couldn't or didn't -- back up. */
+                                       no--;
+                                       reginput = save + no;
+                               }
+                               return(0);
+                       }
+                       break;
+               case END:
+                       return(1);      /* Success! */
+                       break;
+               default:
+                       regerror("memory corruption");
+                       return(0);
+                       break;
+               }
+
+               scan = next;
+       }
+
+       /*
+        * We get here only if there's trouble -- normally "case END" is
+        * the terminating point.
+        */
+       regerror("corrupted pointers");
+       return(0);
+}
+
+/*
+ - regrepeat - repeatedly match something simple, report how many
+ */
+static int
+regrepeat(p)
+char *p;
+{
+       register int count = 0;
+       register char *scan;
+       register char *opnd;
+       extern char *strchr();
+
+       scan = reginput;
+       opnd = OPERAND(p);
+       switch (OP(p)) {
+       case ANY:
+               count = strlen(scan);
+               scan += count;
+               break;
+       case EXACTLY:
+               while (*opnd == *scan) {
+                       count++;
+                       scan++;
+               }
+               break;
+       case ANYOF:
+               while (*scan != '\0' && strchr(opnd, *scan) != NULL) {
+                       count++;
+                       scan++;
+               }
+               break;
+       case ANYBUT:
+               while (*scan != '\0' && strchr(opnd, *scan) == NULL) {
+                       count++;
+                       scan++;
+               }
+               break;
+       default:                /* Oh dear.  Called inappropriately. */
+               regerror("internal foulup");
+               count = 0;      /* Best compromise. */
+               break;
+       }
+       reginput = scan;
+
+       return(count);
+}
+
+/*
+ - regnext - dig the "next" pointer out of a node
+ */
+static char *
+regnext(p)
+register char *p;
+{
+       register int offset;
+
+       if (p == &regdummy)
+               return(NULL);
+
+       offset = NEXT(p);
+       if (offset == 0)
+               return(NULL);
+
+       if (OP(p) == BACK)
+               return(p-offset);
+       else
+               return(p+offset);
+}
+
+#ifdef DEBUG
+
+STATIC char *regprop();
+
+/*
+ - regdump - dump a regexp onto stdout in vaguely comprehensible form
+ */
+void
+regdump(r)
+regexp *r;
+{
+       register char *s;
+       register char op = EXACTLY;     /* Arbitrary non-END op. */
+       register char *next;
+       extern char *strchr();
+
+
+       s = r->program + 1;
+       while (op != END) {     /* While that wasn't END last time... */
+               op = OP(s);
+               printf("%2d%s", s-r->program, regprop(s));      /* Where, what. */
+               next = regnext(s);
+               if (next == NULL)               /* Next ptr. */
+                       printf("(0)");
+               else 
+                       printf("(%d)", (s-r->program)+(next-s));
+               s += 3;
+               if (op == ANYOF || op == ANYBUT || op == EXACTLY) {
+                       /* Literal string, where present. */
+                       while (*s != '\0') {
+                               putchar(*s);
+                               s++;
+                       }
+                       s++;
+               }
+               putchar('\n');
+       }
+
+       /* Header fields of interest. */
+       if (r->regstart != '\0')
+               printf("start `%c' ", r->regstart);
+       if (r->reganch)
+               printf("anchored ");
+       if (r->regmust != NULL)
+               printf("must have \"%s\"", r->regmust);
+       printf("\n");
+}
+
+/*
+ - regprop - printable representation of opcode
+ */
+static char *
+regprop(op)
+char *op;
+{
+       register char *p;
+       static char buf[50];
+
+       (void) strcpy(buf, ":");
+
+       switch (OP(op)) {
+       case BOL:
+               p = "BOL";
+               break;
+       case EOL:
+               p = "EOL";
+               break;
+       case ANY:
+               p = "ANY";
+               break;
+       case ANYOF:
+               p = "ANYOF";
+               break;
+       case ANYBUT:
+               p = "ANYBUT";
+               break;
+       case BRANCH:
+               p = "BRANCH";
+               break;
+       case EXACTLY:
+               p = "EXACTLY";
+               break;
+       case NOTHING:
+               p = "NOTHING";
+               break;
+       case BACK:
+               p = "BACK";
+               break;
+       case END:
+               p = "END";
+               break;
+       case OPEN+1:
+       case OPEN+2:
+       case OPEN+3:
+       case OPEN+4:
+       case OPEN+5:
+       case OPEN+6:
+       case OPEN+7:
+       case OPEN+8:
+       case OPEN+9:
+               sprintf(buf+strlen(buf), "OPEN%d", OP(op)-OPEN);
+               p = NULL;
+               break;
+       case CLOSE+1:
+       case CLOSE+2:
+       case CLOSE+3:
+       case CLOSE+4:
+       case CLOSE+5:
+       case CLOSE+6:
+       case CLOSE+7:
+       case CLOSE+8:
+       case CLOSE+9:
+               sprintf(buf+strlen(buf), "CLOSE%d", OP(op)-CLOSE);
+               p = NULL;
+               break;
+       case STAR:
+               p = "STAR";
+               break;
+       case PLUS:
+               p = "PLUS";
+               break;
+       default:
+               regerror("corrupted opcode");
+               break;
+       }
+       if (p != NULL)
+               (void) strcat(buf, p);
+       return(buf);
+}
+#endif
+
+/*
+ * The following is provided for those people who do not have strcspn() in
+ * their C libraries.  They should get off their butts and do something
+ * about it; at least one public-domain implementation of those (highly
+ * useful) string routines has been published on Usenet.
+ */
+#ifdef STRCSPN
+/*
+ * strcspn - find length of initial segment of s1 consisting entirely
+ * of characters not from s2
+ */
+
+static int
+strcspn(s1, s2)
+char *s1;
+char *s2;
+{
+       register char *scan1;
+       register char *scan2;
+       register int count;
+
+       count = 0;
+       for (scan1 = s1; *scan1 != '\0'; scan1++) {
+               for (scan2 = s2; *scan2 != '\0';)       /* ++ moved down. */
+                       if (*scan1 == *scan2++)
+                               return(count);
+               count++;
+       }
+       return(count);
+}
+#endif
diff --git a/tac_regexp.h b/tac_regexp.h
new file mode 100644 (file)
index 0000000..b23d97e
--- /dev/null
@@ -0,0 +1,40 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+/*
+ * Definitions etc. for regexp(3) routines.
+ *
+ * Caveat:  this is V8 regexp(3) [actually, a reimplementation thereof],
+ * not the System V one.
+ */
+#define NSUBEXP  10
+typedef struct regexp {
+       char *startp[NSUBEXP];
+       char *endp[NSUBEXP];
+       char regstart;          /* Internal use only. */
+       char reganch;           /* Internal use only. */
+       char *regmust;          /* Internal use only. */
+       int regmlen;            /* Internal use only. */
+       char program[1];        /* Unwarranted chumminess with compiler. */
+} regexp;
+
+extern regexp *regcomp();
+extern int regexec();
+extern void regsub();
+extern void regerror();
diff --git a/tac_regmagic.h b/tac_regmagic.h
new file mode 100644 (file)
index 0000000..0b18b0a
--- /dev/null
@@ -0,0 +1,24 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+/*
+ * The first byte of the regexp internal "program" is actually this magic
+ * number; the start node begins in the second byte.
+ */
+#define        MAGIC   0234
diff --git a/tcpwrap.c b/tcpwrap.c
new file mode 100644 (file)
index 0000000..ef3d3ad
--- /dev/null
+++ b/tcpwrap.c
@@ -0,0 +1,37 @@
+/* 
+   This function  use for check access control rules from hosts.deny and
+   hosts.access file. 
+   Writen by Devrim SERAL<devrim@gazi.edu.tr>. This file protected by 
+   GNU Copyright agreement. 
+*/
+#ifdef TCPWRAPPER
+#include <tcpd.h>
+#include "tac_plus.h"
+
+int allow_severity = LOG_INFO;
+int deny_severity = LOG_WARNING;
+
+
+int
+check_from_wrap(datap)
+struct identity *datap;
+{
+    struct request_info req;
+
+    request_init(&req, RQ_DAEMON,TACNAME,RQ_CLIENT_ADDR,datap->NAS_name , NULL);
+    fromhost(&req); /* validate client host info */
+    if (!hosts_access(&req))
+      {
+        if (debug & DEBUG_AUTHEN_FLAG)
+               report(LOG_DEBUG, "Access denied for NAS=%s",datap->NAS_name);
+        send_authen_error("You are not allowed to access here");
+        refuse(&req); /* If connection is not allowed, clean up and exit. */
+        return 0;
+      } 
+    
+    if (debug & DEBUG_AUTHEN_FLAG )
+                report(LOG_DEBUG, "Access permited for NAS=%s",datap->NAS_name);
+return 1;     
+
+}
+#endif /* TCPWRAPPER */
diff --git a/time_limit.c b/time_limit.c
new file mode 100644 (file)
index 0000000..08f6c55
--- /dev/null
@@ -0,0 +1,204 @@
+/*             
+       These functions writen by Devrim SERAL <devrim@gazi.edu.tr>
+
+Applicable format is  : <day str><time str> [,|] <day str><time str> [,|] and so on  
+
+The accept parameter for day str is:
+SU = Sunday
+MO = Monday
+TU = Tuesday
+WE = Wendsday
+TH = Thursday
+FR = Friday
+SA = Saturday
+WK = For week days
+WD = For Week and
+AL = For All days
+
+And time str must be:
+Hourminute-Hourminute  
+For example it's to be -> 0000-1200 or 1600-1700 or 1600-0800
+
+License: This code 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, or (at your option) any later version.
+               
+*/     
+
+#include"time_limit.h"
+#include "tac_plus.h"
+
+int problem=0;
+
+int 
+time_limit_process(str) 
+char *str;
+{
+int ret=0;
+char *tmp_str;
+
+tmp_str=(char *)strtok(str,",|");
+while ( tmp_str != NULL) {
+       ret|=str_token_proc(tmp_str);
+       tmp_str=(char *)strtok(NULL,",");
+       }
+return (ret); 
+}
+
+int 
+str_token_proc(str)
+char *str;
+{
+int inv=0,ret;
+
+/* Pass space characters */ 
+while (isspace(*str)) str++;
+
+if (*str=='!') { 
+               inv=1;str++; 
+}
+
+ret=process(str);
+
+if (problem) {
+       if ( debug & DEBUG_AUTHEN_FLAG )
+               report(LOG_DEBUG,"Timestamp format incorrect");
+       problem=0;
+       return(0);
+} 
+
+if (inv) 
+       ret=!ret;
+return(ret);   
+}
+
+
+int
+process(str)
+char *str;
+{
+int count=0,ret=0,i,j,localtm;
+char *head,*buf,*gec;
+long sec;
+struct tm *tms;
+
+/* Pass space characters  */
+while (isspace(*str)) str++;
+
+head=str;
+
+/* Count alphanumeric char */
+while (isalpha(*str)) { 
+       count++;
+       str++;
+}
+
+if ( count==0 || count%2 ) { 
+       problem++;
+       return 0;
+}
+
+buf=(char *)malloc(count+1);
+strncpy(buf,head,count);
+gec=buf;
+str_up(buf);
+
+for(i=1;i<=(count/2);i++) {
+       for (j=0;j<NUM;j++) {
+                if(!strncmp(gec,week_days[j],2)) {
+                        ret=ret^week_day_val[j];
+                }
+        }
+       gec+=2;
+}
+
+/* We finished to use buffer so free it */
+free(buf);
+
+sec=time(0);
+tms=localtime(&sec);
+localtm=(tms->tm_hour)*60+tms->tm_min;
+ret=( week_day_val[tms->tm_wday] & ret ) && time_calc(str,localtm);
+
+if (ret>0) 
+       return (1); 
+else 
+       return(0); 
+}
+
+str_up(str)
+char *str;
+{
+  while(*str) {
+       if(islower(*str)) *str=toupper(*str);
+       str++;
+  }
+}
+
+int 
+time_calc(str,lct)
+char *str;
+int lct;
+{
+char *t1,*t2,*head;
+int say1,say2,count=0;
+
+head=str;
+
+ while (isdigit(*head) || *head=='-') {
+        count++;
+       head++; 
+ }
+
+if (*str=='\0' || count!= TPL ) {
+       problem++;      
+       return (0);
+}
+
+  t1=(char *) malloc(count);
+  strncpy(t1,str,count);       /*Put str value to t1*/
+
+  t2=(char *) strstr(t1,"-"); /* Find next time part */
+
+if (t2==NULL) {
+   free(t1);
+   problem++;
+   return(0);
+}
+       
+*t2='\0';t2++;
+       
+if ( strlen(t1)<4 || strlen(t2)<4 ) {
+       free(t1);
+       problem++;
+       return(0);
+}
+       say1=antoi(t1,2)*60+antoi(t1+2,2);
+       say2=antoi(t2,2)*60+antoi(t2+2,2);
+
+free(t1);
+
+if (say1<=say2) { 
+       if( (lct>=say1) && (lct<=say2) ) return(1); 
+}
+else {
+       if( (lct>=say1) || (lct<=say2) ) return(1); 
+}
+return(0);
+
+}
+
+int 
+antoi(str,n)
+char *str;int n;
+{
+char *buf;
+int ret;
+
+  buf=(char *) malloc(n);
+  strncpy(buf,str,n);
+  ret=atoi(buf);
+  free(buf);
+
+return(ret);
+}
diff --git a/time_limit.h b/time_limit.h
new file mode 100644 (file)
index 0000000..e8bb6fb
--- /dev/null
@@ -0,0 +1,13 @@
+#include<stdlib.h>
+#include<ctype.h>
+#include<stdio.h>
+#include<time.h>
+#include<string.h>
+#define NUM 10
+#define TPL  9 /* time part len */
+
+/*Global variables */
+static char* week_days[]={"SU","MO","TU","WE","TH","FR","SA","WK","WD","AL"};
+static long week_day_val[]={1,2,4,8,16,32,64,62,65,127};
+
+extern int time_limit_process();
diff --git a/users_guide b/users_guide
new file mode 100644 (file)
index 0000000..2626115
--- /dev/null
@@ -0,0 +1,2867 @@
+                       TAC_PLUS Developer's Kit vF4.0.2.alpha
+                       --------------------------------------
+
+Author: Lol Grant
+
+Note: this is a DEVELOPER'S KIT.  You probably shouldn't be using this
+if you don't need source code. Instead, consider using CiscoSecure,
+Cisco's supported, commercial Tacacs+ daemon.
+
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+
+Please NOTE: None of the TACACS code available here comes with any
+warranty or support, however, comments or questions may be addressed
+to Cisco systems via email at the address:
+
+               customer-service@cisco.com  (or more simply)
+                     cs@cisco.com
+
+and we will do our best to handle them, though we cannot guarantee a
+timely response, as this code is UNSUPPORTED. Be sure you've read this
+user's guide, including the frequently asked questions include in it,
+before posting.
+
+Cisco systems also maintains an extensive World Wide Web site at
+
+               http://www.cisco.com/
+
+In addition, there are two mailing lists which may be of interest to
+users of Tacacs+.
+
+The first is a mailing list run by spot.Colorado.EDU which discusses
+many things pertaining to Cisco products. It is not run by Cisco
+Systems, Inc. and is not part of Cisco's formal service request
+channels, however, many knowledgeable people, including staff members
+of Cisco Systems, Inc. voluntarily read and respond on the list.
+
+Requests to be added to or deleted from the list at spot.Colorado.EDU,
+along with other administrative issues concerning it can be sent to:
+
+        cisco-request@spot.Colorado.EDU
+
+There is also a relatively new list called TACPLUS-L, run by
+disaster.com, created for the purpose of information exchange between
+TACACS+ Users. It is intended as a supplement to the list at
+spot.Colorado.EDU, aiding TACACS+ users and prospective users in many
+issues including but not limited to technical support, bug reports and
+workarounds, configuration information, recommendations for future
+versions of TACACS+, and general talk about TACACS+ development,
+implementation, administration, etc.
+
+Please note that neither of these lists is in fact connected with
+Cisco Systems, Inc. or any of its subsidiaries. Standard etiquette
+rules apply.
+
+To subscribe to the TACPLUS-L list, send a message to
+
+       tacplus-l-request@disaster.com
+
+In the body of the letter, enter
+
+       SUBSCRIBE TACPLUS-L your Name
+
+to be automatically added, or visit their web page at
+http://www.disaster.com/tacplus/.
+
+Also, Robert Kiessling maintains a TACACS+ FAQ at
+http://www.easynet.de/tacacs-faq.
+
+Lastly, I am always interested in seeing contributed patches, so
+consider mailing any modifications you make, as context diffs (be sure
+to indicate with the version your patches are based on), to
+tacacs-patches@cisco.com. As always, no support is implied, nor any
+assurance that patches will be made available via ftp (though that is
+my intent) or incorporated into any code.
+
+Definitions and Terms
+---------------------
+
+NAS --- A Network Access Server e.g. a Cisco box, or any other
+       *client* which makes tacacs+ authentication and authorization
+       requests, or generates Tacacs+ accounting packets.
+
+Daemon -- A program which services network requests for authentication
+       and authorization, verifies identities, grants or denies
+       authorizations, and logs accounting records.
+
+passwd(5) files -- files conforming to Unix password style format, as
+       documented in section 5 of the Unix manuals.
+
+AV pairs -- strings of text in the form "attribute=value" sent between a
+       NAS and a tacacs+ daemon as part of the Tacacs+ protocol.
+
+Since a NAS is sometimes referred to as a server, and a daemon is also
+often referred to as a server, the term "server" has been avoided here
+in favor of the less ambiguous terms "NAS" and "Daemon".
+
+TACACS, XTACACS and TACACS+
+---------------------------
+
+Note that there are now at least 3 versions of authentication protocol
+that people commonly refer to as "TACACS".  
+
+The first is ordinary tacacs, which was the first one offered on Cisco
+boxes and has been in use for many years. The second is an extension
+to the first, commonly called Extended Tacacs or XTACACS, introduced
+in 1990. 
+
+The third one is TACACS+ (or T+ or tac_plus) which is what is documented
+here. TACACS+ is NOT COMPATIBLE with any previous versions of tacacs.
+
+In addition to the 3 versions of tacacs running on Cisco boxes, the
+fact that we distribute the source code to the daemon has meant that
+additional implementations of tacacs daemons have been produced by
+people who have made modifications to our source code.
+
+BUILDING TAC_PLUS
+-----------------
+Tac_plus is known to build and run on the following platforms:
+
+AIXv3.2 (using -DAIX and bsdcc. See tac_plus.h for more details).
+HP/UX A.09.01 using -DHPUX
+i86 Solaris 2.4 (SUNOS 5.4), using SUNpro SW2.0.1 & -DSOLARIS
+Sun4 Solaris 2.4 using SUNpro SC3.0.1 and -DSOLARIS
+SUNOS 4.1.2 sparc-2
+SUNOS 4.1.3
+MIPS R3K SGI IRIX 4.05F (using -DMIPS)
+BSDI BSD/386 1.1 (using -DBSDI)
+FREEBSD 2.0-RELEASE (using -DFREEBSD)
+LINUX 1.2.8 (using -DLINUX)
+
+To build tac_plus, untar the tarfile distribution into a clean
+directory e.g.
+
+       tar -xvf tac_plus.tar
+
+Edit the top of the Makefile to select the appropriate defaults
+for your system. Then type
+
+       make tac_plus
+
+The default version can authenticate using its internal database,
+s/key or passwd(5) style files. Authorization is done via the
+internal database or via calls to external programs, which the
+administrator configures.
+
+To use S/KEY, you must obtain and build the s/key library (libskey.a)
+yourself.  You can then compile in S/KEY support per the instructions
+for S/KEY in the Makefile.  I got my S/KEY code originally from
+crimelab.com but now it appears the only source is ftp.bellcore.com. I
+suggest you try a web search for s/key source code.
+
+Note: S/KEY is a trademark of Bell Communications Research (Bellcore).
+
+Should you need them, there are routines for accessing password files
+(getpwnam,setpwent,endpwent,setpwfile) in pw.c.
+
+Lastly, you may also need to add lines to your /etc/services file to
+get tacacs+ to work correctly e.g. something along the lines of:
+
+tacacs      tcp/49
+
+You'll need to consult your system manuals for exact details of how to
+do this.
+
+A NOTE ABOUT ARAP, MSCHAP AND DES
+---------------------------------
+If you have access to a DES library which implements the calls:
+
+int des_init();
+void des_setkey();
+void des_endes();
+void des_done();
+
+then you can define DES in tac_plus.h and link tac_plus with your DES
+library. This is recommended, as it will allow you to process ARAP and MSCHAP
+requests on your daemon, which is more efficient, and also more secure
+than processing them on the NAS.
+
+If you don't have access to des (which is U.S. export controlled), you
+can simply leave DES undefined in tac_plus.h. ARAP and MSCHAP
+authentication will still work, but it will be slightly less
+efficient, since the NAS will attempt to get the daemon to do the DES
+calculation before falling back to the alternative of calculating DES
+on the NAS.  It's also slightly less secure, because if someone
+discovers your encryption key, they can then download ARAP and MSCHAP
+secrets from your daemon.
+
+Note that this issue arises solely because U.S. government regulations
+currently make it difficult to export the source code for DES outside
+the US and Canada, which is why it is not included in this
+distribution.
+
+Lastly, this limitation of MSCHAP, ARAP and DES has no bearing on the
+use of des passwords for regular logins. Regular logins also use DES
+but they do it via the "crypt" system call, which is usually found in
+a library on the Unix host where you compile your daemon.
+
+There are additional restrictions on doing MSCHAP (see the FAQ later
+in this document).
+
+CONFIGURING TAC_PLUS
+---------------------
+
+Tac_plus is configured via a single configuration file. You can create
+a configuration file from scratch or, if you have passwd(5) and
+supplementary files from earlier versions of tacacs, you can convert
+these to configuration file format by running the supplied perl script
+convert.pl.
+
+CONVERTING EXISTING PASSWD(5) FILES
+-----------------------------------
+
+To convert an existing passwd(5) file e.g. one used with an older
+version of tacacs, use the convert.pl perl script as follows:
+
+convert.pl <passwd file> [-g] [<supplementary file>]
+
+1). If you have no supplementary file, simply omit it. 
+
+2). If the groupid field of your passwd file does NOT represent a
+valid acl number (e.g if it's really a unix passwd file this field is
+a group id, not an acl number), just omit the -g flag.
+
+The rest of this document assumes that you are configuring tac_plus
+from scratch.
+
+CONFIGURING TAC_PLUS FROM SCRATCH
+---------------------------------
+
+A configuration file consists of some top-level directives for setting
+defaults and for setting up the encryption key, followed by a
+declaration for each user and group you want to configure.  Within
+each user or group declaration, there are declarations for
+authenticating and authorizing that user.
+
+1). Configuring the encryption key
+
+If you want tac_plus to encrypt its packets (and you almost certainly
+*DO* want this, as there can be usernames and passwords contained in
+these packets), then you must specify an encryption key in the
+configuration file. The identical key must also be configured on any
+NAS which communicates with tac_plus.
+
+This is done using the statement
+
+key = "your key here"
+
+NOTE: You only need double quotes on the daemon if your key contains
+spaces.
+
+Confusingly, even if your key does contain spaces, you should NEVER
+use double quotes when you configure the matching key on the NAS.
+
+During debugging, it may be convenient to temporarily switch off
+encryption by not specifying any key.  Be careful to remember to 
+switch encryption back on again after you've finished debugging.
+
+The current code does not support host-specific keys (left as an
+exercise to the reader).
+
+On the NAS, you also need to configure the *same* key. Do this by
+issuing:
+
+    aaa new-model
+    tacacs-server key <your key here>
+
+COMMENTS IN CONFIGURATION FILES
+-------------------------------
+Comments can appear anywhere in the configuration file, starting with
+the # character and extending to the end of the current line. Should
+you need to disable this special meaning of the # character, e.g. if
+you have a password containing a # character, simply enclose the string
+containing it within double quotes.
+
+CONFIGURING USERS AND GROUPS
+----------------------------
+
+Each user may belong to a group (but only one group).  Each group may
+in turn belong to one other group and so on ad infinitum.
+
+Users and groups are declared as follows. Here we declare two users
+"fred" and "lily", and two groups, "admin" and "staff". 
+
+Fred is a member of group "admin", and group "admin" is in turn a
+member of group "staff". Lily is not a member of any group.
+\f
+user = lily {
+    # user lily is not a member of any group
+    # and has nothing else configured as yet
+}
+
+user = fred {
+    # fred is a member of group admin
+    member = admin
+}
+
+group = admin {
+    # group admin is a member of group staff
+    member = staff
+}
+
+group = staff {
+    # group staff is not a member of any group
+}
+
+RECURSION AND GROUPS
+--------------------
+
+In general, when the daemon looks up values e.g. passwords, it will
+look first to see if the user has her own password. If not, it looks
+to see if she belongs to a group and if so, whether the group has a
+password defined. If not, this process continues through the hierarchy
+of groups (a group can be a member of another group) until a value is
+found, or there are no more groups.
+
+This recursive process occurs for lookups of expiration dates, for
+pap, arap and chap "secrets", and also for authorization parameters (see
+later).
+
+A typical configuration technique is thus to place users into groups
+and specify as many groupwide characteristics in the group declaration
+as possible. Then, individual user declarations can be used to
+override the group settings for selected users as needed.
+
+CONFIGURING USER AUTHENTICATION
+-------------------------------
+
+User Authentication can be specified separately for PAP, ARAP, CHAP,
+and normal logins.  In addition, a user global authentication method
+can be given that will be used if a per-protocol method is not
+specified.
+
+PAP, ARAP, CHAP, and global user authentication must be given in clear
+text.
+
+The following assigns the user mary five different passwords for ARAP,
+inbound and outbound CHAP, inbound PAP, outbound PAP, and normal login
+respectively:
+
+    user = mary {
+       arap = cleartext "arap password"
+       chap = cleartext "chap password"
+       pap  = cleartext "inbound pap password"
+       opap = cleartext "outbound pap password"
+       login = des XQj4892fjk
+    }
+
+
+The following assigns the user agnes a single password for all the
+above types of login (except outbound PAP):
+
+    user = agnes {
+       global = cleartext "Agnes global password"
+    }
+
+NOTE: you cannot use a global user password for outbound PAP. This is
+because outbound PAP is implemented by sending the password from the
+daemon to the NAS. This is a security issue if the TACACS+ key is ever
+compromised.
+
+There are 3 ways to authenticate a user for login.
+
+1). You can include a DES (or cleartext) password for a user or for a
+group that s/he is a member of, viz:
+
+    user = joe {
+        member = admin
+        # this is lily's encrypted DES password. It overrides the admin
+       # group's password
+        login = des XQkR21zMB0TDU
+    }
+
+    user = fred {
+        # fred is a member of group admin. He inherits the group's password
+       # as he does not have his one of his own.
+        member = admin
+    }
+
+    group = admin {
+        # group admin has a cleartext password which all members share
+        # unless they have their own password defined
+        login = cleartext foobar
+    }
+
+If no password is needed for this user, this can be accomplished with
+the 'nopassword' option:
+
+    user = foo {
+       login = nopassword
+    }
+
+NOTE: The C program built from generate_passwd.c may be used to
+hand-generate encrypted passwords, or they may be taken from a Unix
+passwd (or shadow) file.
+
+2). Authentication using passwd(5) files.
+
+For selected users, you can perform DES authentication using existing
+passwd(5) files instead of entering the password into the
+configuration file directly (though using passwd(5) files is
+noticeably less efficient for large files).
+
+You can specify this behavior per-user, by naming a passwd(5) file in
+the password declaration (instead of giving a DES password), as
+follows:
+
+    user = fred {
+       # look in file /etc/tac_plus_passwords to authenticate this user
+       login = file /etc/tac_plus_passwords
+    }
+
+3). Authentication using s/key.
+
+If you have successfully built and linked in a suitable s/key library
+and compiled tac_plus to use s/key, you can then specify that a user
+be authenticated via s/key, as follows:
+
+    user = fred {
+      login = skey
+    }
+
+RECURSIVE PASSWORD LOOKUPS
+---------------------------
+
+As stated earlier, authentication passwords are looked up recursively:
+The daemon looks first to see if the user has her own password. If
+not, it looks to see if she belongs to a group which has a
+password. This process recurses through the hierarchy of groups (a
+group can be a member of another group) until a password is found, or
+there are no more groups.
+
+CONFIGURING DEFAULT AUTHENTICATION
+-----------------------------------
+By default, an unrecognized user will be denied authentication (NOTE:
+there is no way to authenticate someone with no username).
+
+At the top level of the configuration file, you can set the default
+authentication to use a passwd(5) file, viz:
+
+    default authentication = file /etc/passwd
+
+The effect of this statement is that if a user does not appear in the
+configuration file, the daemon will attempt to authenticate the user
+using passwords from this file i.e. /etc/passwd in this example.
+
+If you have passwd(5) files from previous versions of tacacs daemons,
+this facility allows you to authenticate using the passwd(5) from
+older versions of tacacs, while you migrate to using the new
+configuration file.
+
+CONFIGURING EXPIRY DATES
+------------------------
+An entry of the form:
+
+user = lol {
+    expires = "MMM DD YYYY"
+    password = cleartext "bite me"
+}
+
+will cause the user's passwords to become invalid, starting on the
+expiry date. The only valid date format is e.g. "Jan 1 1980". Case is
+NOT significant.
+
+A expiry warning message is sent to the user when she logs in,
+starting at 14 days before the expiration date.
+
+On expiry, the administrator must re-set the expiry date in the
+configuration file in order to grant continued access. Expiry applies
+to all password types except "file" passwords.
+
+If passwd(5) files are being used for authentication, the "expires"
+field in the configuration file is not consulted. Instead, the daemon
+looks at the the "shell" field of the password file entry for a valid
+expiry date.
+
+If Solaris shadow password files are used for authentication, the
+"expires" field in the configuration file is not consulted. The expiry
+field from the shadow password file (if it exists) is used as the
+expiration date.
+
+CONFIGURING AUTHENTICATION ON THE NAS
+-------------------------------------
+
+On the NAS, to configure login authentication on all lines (including
+vty and console lines)
+
+    aaa new-model
+    aaa authentication login default tacacs+
+
+NOTE: As soon as you issue this command, you will no longer be able to
+create new logins to your NAS without a functioning tacacs+ daemon
+appropriately configured with usernames and password, so make sure you
+have this ready. 
+
+As a safety measure while setting up, we suggest you configure an
+enable secret and make it the last resort authentication method, so
+if your tacacs+ daemon fails to respond you will be able to use the
+NAS enable password to login. To do this, configure:
+
+    enable secret foo
+    aaa authentication login default tacacs+ enable
+
+If all else fails, and you find yourself locked out of the NAS due to
+a configuration problem, the section on "recovering from lost
+passwords" on Cisco's CCO web page will help you dig your way out.
+
+CONFIGURING ENABLE PASSWORDS
+----------------------------
+
+The default privilege level for an ordinary user on the NAS is usually
+1. When a user enables, she can reset this level to a value between 0
+and 15 by using the NAS "enable" command. If she doesn't specify a
+level, the default level she enables to is 15.
+
+You can enable via tacacs+ e.g. by configuring on the NAS:
+
+       aaa authentication enable default tacacs+
+
+then whenever you attempt to enable, an authentication request is sent
+with the special username $enab<n>$ where <n> is the privilege level
+you are attempting to enable to.
+
+(Note: in order to be compatible with earlier versions of tacacs, when
+the requested enable level is 15, the daemon will also try the
+username $enable$ before trying username $enab15$).
+
+For example, with the above declaration, in order to enable on the
+NAS, you need a user declaration like this one, on the daemon:
+
+user = $enab15$ {
+    login = cleartext "the enable password for level 15"
+}
+
+Note: Be aware that this does have the side effect that you now have a
+user named $enab15$ who can then login to your NAS if she knows the
+enable password.
+
+Here is a similar declaration allowing users to enable to level 4:
+
+user = $enab4$ {
+    login = des bsoF4OivQCY8Q
+}
+\f
+CONFIGURING AUTHORIZATION
+-------------------------
+
+Authorization must be configured on both the NAS and the daemon to
+operate correctly. By default, the NAS will allow everything until you
+configure it to make authorization requests to the daemon.
+
+On the daemon, the opposite is true: The daemon will, by default, deny
+authorization of anything that isn't explicitly permitted.
+
+Authorization allows the daemon to deny commands and services
+outright, or to modify commands and services on a per-user
+basis. Authorization on the daemon is divided into two separate parts:
+commands and services.
+
+AUTHORIZING COMMANDS
+--------------------
+
+Exec commands are those commands which are typed at a Cisco exec
+prompt. When authorization is requested by the NAS, the entire command
+is sent to the tac_plus daemon for authorization.
+
+Command authorization is configured by specifying a list of
+egrep-style regular expressions to match command arguments (see the
+supplied man page, regexp.3, for a full description of regular
+expressions) and an action which is "deny" or "permit".
+
+The following configuration example permits user Fred to run the
+following commands:
+
+    telnet 131.108.13.<any number> and
+    telnet 128.<any number>.12.3 and
+    show <anything>
+
+All other commands are denied (by default).
+
+user=fred {
+
+    cmd = telnet {
+       # permit specified telnets
+       permit 131\.108\.13\.[0-9]+
+       permit 128\.[0-9]+\.12\.3
+    }
+    cmd = show {
+       # permit show commands
+       permit .*
+    }
+}
+
+NOTE: If an argument list you specify contains spaces or tabs, you
+must enclose it in double quotes.
+
+The command and arguments which the user types gets matched to the
+regular expressions you specify in the configuration file (in order of
+appearance).  The first successful match performs the associated
+action (permit or deny). If there is no match, the command is denied
+by default.
+
+Conversely, the following configuration example can be used to deny
+the command:
+
+    telnet 131.108.13.<any number>
+
+and permit all other arguments, since the last line will match any
+argument list. All other commands and services are permitted due to
+the "default service = permit" clause. 
+
+Note: the default statement must be the first in the user clause
+
+user=fred {
+    default service = permit
+    cmd = telnet {
+       # allow all fred's telnet commands except to 131.108.13.*
+       deny 131\.108\.13\.[0-9]+
+       permit .*
+    }
+}
+
+Note: Matches are not anchored, so "deny 131.108.13.[0-9]+" matches
+anywhere in the command. To anchor the match, use ^ at the beginning
+of the regular expression.
+
+Note: When a command has multiple arguments, users may enter them in
+many different permutations. It can be cumbersome to create regular
+expressions which will reliably authorize commands under these
+conditions, so administrators may wish to consider other methods of
+performing authorization e.g. by configuring NAS-local privileged
+enable levels on the NAS itself.
+
+COMMAND EXPANSION
+-----------------
+
+For command authorization, the Cisco NAS expands all commands to their
+full names e.g. when you type "config t" on the NAS, this will be
+expanded to "configuration terminal" before being sent to the daemon
+so that you don't need to list all the possible contractions of a
+command.
+
+CONFIGURING DEFAULT AUTHORIZATION
+---------------------------------
+
+There are 3 places where defaults for authorization may be
+configured. Unless specified to the contrary, the default is always to
+deny authorization.
+
+1). To override the default denial of authorization for users who are
+not explicitly listed in the configuration file, the ersatz user
+DEFAULT, if defined, can be used for authorizing such users, viz:
+
+default authentication = file /etc/passwd
+
+user = DEFAULT {
+    service = ppp protocol = ip {
+       addr-pool=foobar
+    }
+}
+
+In this example, users who do not appear elsewhere will be
+authenticated via the /etc/passwd file, and authorized by the contents
+of the user = DEFAULT entry.
+
+Note: For backward compatibility, the directive,
+
+       default authorization = permit
+
+may still be specifed at the top level of the configuration file. This
+overrides the default denial of authorization for users who are not
+explicitly listed in the configuration file, permitting all
+authorization requests for such users.
+
+2). At the user level i.e. inside the braces of a user declaration,
+the default for a user who doesn't have a service or command
+explicitly authorized is to deny that service or command.  The
+following directive will permit the service or command by default
+instead:
+
+user = lol {
+    default service = permit
+}
+
+NOTE: This directive must appear first inside the user declaration.
+
+3). At the service authorization level i.e. inside the braces of a
+service declaration, arguments in an authorization request are
+processed according to the algorithm described later. Some actions
+when authorizing services (e.g. when matching attributes are not
+found) depend on how the default is configured. The following
+declaration changes the default from deny to permit for this user and
+service.
+
+user = lol {
+    service = exec {
+        default attribute = permit
+    }
+}
+
+NOTE: This directive must appear before any others inside the service
+declaration.
+
+NOTE: for command authorization (as opposed to service authorization
+being discussed here), you specify deny .* or permit .* as the last
+line of the regular expression matches to create default behavior.
+
+AUTHORIZING EXEC STARTUP
+-------------------------
+
+If you authorize some exec commands, you implicitly agree to allow
+that user to start an exec (it doesn't make sense to permit exec
+commands if an exec can't be started to run those commands)
+
+In addition to agreeing to allow an exec to start, you can supply some
+parameters whenever an exec starts e.g. an autocommand, a dialback
+string or a connection access list (acl).
+
+In the example below, when an exec is started on the NAS, an acl of 4
+will be returned to the NAS:
+
+user=fred {
+
+    # this following line permits an exec to start and permits
+    # all commands and services by default
+
+    default service = permit
+
+    service = exec {
+       # When an exec is started, its connection access list will be 4.
+       # It also has an autocmd.
+        acl = 4
+       autocmd = "telnet foobar"
+    }
+
+    cmd = telnet {
+       # allow all fred's telnet commands except telnet to 131.108.13.*
+       deny 131\.108\.13\.[0-9]+
+       permit .*
+    }
+}
+
+NOTE: specifying an autocommand, or any other exec services, is part
+of EXEC AUTHORIZATION. For it to work, you must also configure exec
+authorization on your NAS e.g.
+
+       aaa authorization exec tacacs+
+
+
+AUTHORIZING EXEC, SLIP, PPP and ARAP SERVICES
+----------------------------------------------
+
+Authorizing exec, slip, PPP and arap services is done quite
+differently from command authorization.
+
+When authorizing these services, the NAS sends a request containing a
+number of attribute-value (AV) pairs, each having the form
+
+       attribute=value 
+
+(Note: during debugging, you may see AV pairs whose separator
+character is a "*" instead of a "=" sign. This is to signify that the
+value in a pair is optional. An "=" sign indicates a mandatory
+value. A "*" denotes an optional value).
+
+e.g. a user starting ppp/ip using an address of 131.108.12.44 would
+generate a request with the following AV pairs:
+
+       service=ppp
+       protocol=ip
+       addr*131.108.12.44
+
+You can use the NAS debugging command
+
+       debug aaa authorization
+
+to see what authorization AV pairs are being used by the NAS. Note: If
+you are not on the router console, you will also need to issue a
+'terminal monitor' command to see debug output.
+
+THE AUTHORIZATION PROCESS
+-------------------------
+
+Authorizing a single session can result in multiple requests being
+sent to the daemon.  For example, in order to authorize a dialin ppp
+user for IP, the following authorization requests will be made from
+the NAS:
+
+1). An initial authorization request to startup ppp from the exec,
+using the AV pairs service=ppp, protocol=ip, will be made (Note: this
+initial request will be omitted if you are autoselecting ppp, since
+you won't know the username yet).
+
+This request is really done to find the address for dumb PPP (or SLIP)
+clients who can't do address negotiation. Instead, they expect you to
+tell them what address to use before PPP starts up, via a text message
+e.g. "Entering PPP. Your address is 1.2.3.4". They rely on parsing
+this address from the message to know their address.
+
+2). Next, an authorization request is made from the PPP subsystem to
+see if ppp's LCP layer is authorized. LCP parameters can be set at
+this time (e.g. callback). This request contains the AV pairs
+service=ppp, protocol=lcp.
+
+3). Next an authorization request to startup ppp's IPCP layer is made
+using the AV pairs service=ppp, protocol=ipcp. Any parameters returned
+by the daemon are cached.
+
+4). Next, during PPP's address negotiation phase, each time the remote
+peer requests a specific address, if that address isn't in the cache
+obtained in step 3, a new authorization request is made to see if the
+peers requested address is allowable.  This step can be repeated
+multiple times until both sides agree on the remote peer's address or
+until the NAS (or client) decide they're never going to agree and they
+shut down PPP instead.
+
+As you can see from the above, a program which plans to handle
+authorization must be able to handle a variety of requests and respond
+appropriately.
+
+AUTHORIZATION RELIES ON AUTHENTICATION
+--------------------------------------
+
+Since we pretty much rely on having a username in authorization
+requests to decide which addresses etc. to hand out, it is important
+to know where the username for a PPP user comes from. There are
+generally 2 possible sources
+
+1). You force the user to authenticate by making her login to the exec
+and you use that login name in authorization requests. This username
+isn't propagated to PPP by default. To have this happen, you generally
+need to configure the "if-needed" method, e.g.
+
+aaa authentication login default tacacs+
+aaa authentication ppp default if-needed
+
+
+2). Alternatively, you can run an authentication protocol, PAP or CHAP
+(CHAP is much preferred), to identify the user. You don't need an
+explicit login step if you do this (so it's the only possibility if
+you are using autoselect). This authentication gets done before you
+see the first LCP authorization request of course.  Typically you
+configure this by doing: 
+
+aaa authentication ppp default tacacs+ 
+int async 1
+ppp authentication chap
+
+If you omit either of these authentication schemes, you will start to
+see authorization requests in which the username is missing.
+
+CONFIGURING SERVICE AUTHORIZATION
+---------------------------------
+
+A list of AV pairs is placed in the daemon's configuration file in
+order to authorize services.  The daemon compares each NAS AV pair to
+its configured AV pairs and either allows or denies the service. If
+the service is allowed, the daemon may add, change or delete AV pairs
+before returning them to the NAS, thereby restricting what the user is
+permitted to do.
+
+The complete algorithm by which the daemon processes its configured
+AV pairs against the list the NAS sends, is given below.
+
+The Authorization Algorithm
+---------------------------
+
+Find the user (or group) entry for this service (and protocol), then
+for each AV pair sent from the NAS:
+
+    If the AV pair from the NAS is mandatory:
+
+       a). look for an exact attribute,value match in the user's
+       mandatory list. If found, add the AV pair to the output.
+
+       b). If an exact match doesn't exist, look in the user's
+       optional list for the first attribute match. If found, add the
+       NAS AV pair to the output.
+
+       c). If no attribute match exists, deny the command if the
+       default is to deny, or,
+
+       d). If the default is permit, add the NAS AV pair to the
+       output.
+
+    If the AV pair from the NAS is optional:
+
+       e). look for an exact attribute,value match in the user's
+       mandatory list. If found, add DAEMON's AV pair to output.
+
+       f). If not found, look for the first attribute match in the
+       user's mandatory list. If found, add DAEMONS's AV pair to output.
+
+       g). If no mandatory match exists, look for an exact
+       attribute,value pair match among the daemon's optional AV
+       pairs. If found add the DAEMON's matching AV pair to the
+       output.
+
+       h). If no exact match exists, locate the first attribute match
+       among the daemon's optional AV pairs. If found add the
+       DAEMON's matching AV pair to the output.
+
+       i). If no match is found, delete the AV pair if the default is
+       deny, or
+
+       j). If the default is permit add the NAS AV pair to the output.
+    k). After all AV pairs have been processed, for each mandatory
+    DAEMON AV pair, if there is no attribute match already in the
+    output list, add the AV pair (but add only ONE AV pair for each
+    mandatory attribute).
+
+RECURSIVE AUTHORIZATION
+-----------------------
+
+Remember that authorization is also recursive over groups, in the same
+way that password lookups are recursive. Thus, if you place a user in
+a group, the daemon will look in the group for authorization
+parameters if it cannot find them in the user declaration.
+
+EXAMPLES
+--------
+
+key = "your key here"
+
+user=fred {
+    login = des mEX027bHtzTlQ
+    name = "Fred Flintstone"
+    member = administrators
+    expires = "May 23 2005"
+    arap = cleartext "Fred's arap secret"
+    chap = cleartext "Fred's chap secret"
+
+    service = exec {
+       # When Fred starts an exec, his connection access list is 5
+        acl = 5
+
+       # We require this autocmd to be done at startup
+       autocmd = "telnet foo"
+    }
+
+   # All commands except show system are denied for Fred
+    cmd = show {
+
+       # Fred can run the following show command
+
+       permit system
+       deny .*
+    }
+
+    service = ppp protocol = ip {
+       # Fred can run ip over ppp only if he uses one 
+        # of the following mandatory addresses. If he supplies no
+       # address, the first one here will be mandated
+
+       addr=131.108.12.11
+       addr=131.108.12.12
+       addr=131.108.12.13
+       addr=131.108.12.14
+
+       # Fred's mandatory input access list number is 101
+       inacl=101
+
+       # We will suggest an output access list of 102, but the NAS may
+       # choose to ignore or override it
+
+       optional outacl=102
+    }
+
+    service = slip {
+
+       # Fred can run slip. When he does, he will have to use
+       # these mandatory access lists
+
+       inacl=101
+       outacl=102
+    }
+}
+
+user = wilma {
+
+    # Wilma has no password of her own, but she's a group member so
+    # she'll use the group password if there is one. Same for her
+    # password expiry date
+
+    member = admin
+}
+
+group = admin {
+
+    # group members who don't have their own login password will be looked
+    # up in /etc/passwd
+
+    login = file /etc/passwd
+
+    # group members who have no expiry date set will use this one
+
+    expires = "Jan 1 1997"
+}
+
+
+USING PROGRAMS TO DO AUTHORIZATION
+----------------------------------
+
+There are some limitations to the authorization that can be done using
+a configuration file. The main ones are that you're constrained by the
+algorithm the daemon uses, and that the configuration is basically
+static, so if you're trying to use it to allocate dynamic things (such
+as addresses from a pool) that vary over time, you need another
+mechanism.
+
+One solution is to arrange for the daemon to call your own
+user-supplied programs to control authorization. These "callouts"
+permit almost complete control over authorization, allowing you to
+read all the fields in the authorization packet sent by the NAS
+including all its AV pairs, and to set authorization status and send a
+new set of AV pairs to the NAS in response.
+
+USING AV PAIRS FOR AUTHORIZATION
+--------------------------------
+
+During authorization, the NAS sends an authorization request packet
+containing various fields of interest and a set of AV pairs (see the
+tacacs+ protocol specification for a list of fields and pairs).
+
+Fields from the authorization packet can be supplied to the programs
+you call on their command line, by using the appropriate dollar
+variables in the configuration file (see below).
+
+AV pairs from the authorization packet are fed to the program's
+standard input, one per line. The program is expected to process the
+AV pairs and write them to its standard output, one per line. What
+happens then is determined by the exit status of the program.
+
+NOTE: AV pairs are text strings with the format
+attribute=value. Unlike the configuration file which allows spaces
+when specifying AV pairs, there should be no spaces surrounding the
+"=" sign when using the programmatic interface.
+
+CALLING SCRIPTS BEFORE AUTHORIZATION
+------------------------------------
+
+You can specify a per-user program to be called before any other
+attempt to authorize is made by using a "before" clause e.g.
+
+user = auth1 {
+    before authorization "/usr/bin/pre_authorize $user $port $address"
+}
+
+The AV pairs sent from the NAS will be supplied to this program's
+standard input, one pair per line.
+
+Fields from the initiating authorization packet which the NAS sends to
+the daemon can also be passed to the program by using dollar variables
+in the command line. A complete list of available variables is as
+follows (consult the API specification for more details).
+
+    user    -- user name
+    name    -- Nas name
+    port    -- Nas port
+    address -- Nac address (remote user location)
+    priv    -- privilege level (a digit, 0 to 15)
+    method  -- (a digit, 1 to 4)
+    type    -- (a digit, 1 to 4)
+    service -- (a digit, 1 to 7)
+    status  -- (pass, fail, error, unknown)
+
+Unrecognized variables will appear as the string "unknown". 
+
+If the program returns a status of 0, authorization is unconditionally
+permitted. No further processing is done on this request and no AV
+pairs are returned to the NAS.
+
+If the program returns a status of 1, authorization is unconditionally
+denied. No further processing is done on this request and no AV pairs
+are returned to the NAS.
+
+If the program returns a status of 2, authorization is permitted.  The
+program is expected to modify the AV pairs that it receives on its
+standard input (or to create entirely new ones) and to write them, one
+per line, to its standard output. The new AV pairs will be sent to the
+NAS with a status of AUTHOR_STATUS_PASS_REPL.  No further processing
+takes place on this request.
+
+If the program returns a status of 3, authorization is denied, but all
+attributes returned by the program via stdout are returned to the
+NAS. Also, whatever the program returns on stderr is placed into the
+server-msg field and returned to the NAS as well.
+
+Any other status value returned from the program will cause an error
+to be returned to the NAS.
+
+Note that a status of 2 is not acceptable when doing command
+authorization.
+
+CALLING PROGRAMS AFTER AUTHORIZATION
+------------------------------------
+
+You can specify a per-user program to be called after authorization
+processing has been carried out by the daemon (but before the
+authorization status and AV pairs have been transmitted to the NAS).
+
+The program can optionally modify the AV pairs being sent back to the
+NAS and change the authorization status if required.
+
+group = auth1 {
+    # call /usr/bin/post_authorize passing it the username, port
+    # and current authorization status. 
+    after authorization "/usr/bin/post_authorize $user $port $status"
+}
+
+The AV pairs resulting from the authorization algorithm that the
+daemon proposes to return to the NAS, are supplied to the program on
+standard input, one AV pair per line, so they can be modified if
+required. 
+
+Fields from the incoming authorization packet which the NAS sent to
+the daemon can also be passed to the program on its command line by
+specifying dollar variables in the command line (see previous
+section).
+
+The program is expected to process the AV pairs and write them to its
+standard output, one per line. What happens then is determined by the
+exit status of the program:
+
+If the program returns a status of 0, authorization continues as if
+the program had never been called.  Use this if e.g. you just want a
+program to send mail when an authorization occurs, without otherwise
+affecting normal authorization.
+
+If the program returns a status of 1, authorization is unconditionally
+denied. No AV pairs are returned to the NAS. No further authorization
+processing occurs on this request.
+
+If the program returns a status of 2, authorization is permitted and
+any AV pairs returned from the program on its standard output are sent
+to the NAS in place of any AV pairs that the daemon may have
+constructed.
+
+Any other value will cause an error to be returned the the NAS by the
+daemon.
+
+WARNINGS AND CAUTIONS
+---------------------
+
+Customers attempting to write authorization scripts will find the NAS
+debugging command "debug aaa authorization" invaluable.
+
+Pre and post authorization programs are invoked by handing the command
+line to the Bourne shell. On many Unix systems, if the shell doesn't
+find the specified program it returns a status of one, which denies
+authorization. However, at least one Unix system (BSDI) returns a
+status code of 2 under these circumstances, which will permit
+authorization, and probably isn't what you intended.
+
+Note also that if your program hangs, the authorization will time out
+and return an error on the NAS, and you'll tie up a process slot on
+the daemon host, eventually running out of resources. There is no
+special code to detect this in the daemon.
+
+Unless you make special arrangements, the daemon will run as root and
+hence the programs it invokes will also run as root, which is a
+security weakness. It is strongly recommended that you use absolute
+pathnames when specifying programs to execute, and that you use the
+Makefile options TAC_PLUS_USERID and TAC_PLUS_GROUPID so that the
+daemon is not running as root when calling these programs,
+
+The daemon communicates with pre and post authorization programs over
+a pair of pipes. Programs using the standard i/o library will use full
+buffering in these circumstances. This shouldn't be a problem for most
+programs, since they'll read AV pairs till they see end of file on
+input, and they'll flush all output when they exit.
+
+Note that when avpairs containing spaces are listed in the
+configuration file, you need to enclose them in double quotes so that
+they are parsed correctly. Avpairs which are returned via standard
+output do not need delimiters and so should not be enclosed in double
+quotes.
+
+CONFIGURING AUTHORIZATION ON THE NAS
+------------------------------------
+
+If authorization is not explicitly configured on the NAS, no
+authorization takes place i.e. effectively, everything is
+permitted. Note that this is the converse of what happens on the
+daemon, where anything not explicitly permitted is denied by default.
+
+To configure command authorization on the NAS, issue the following NAS
+configuration commands:
+
+    aaa authorization commands 1 tacacs+ 
+    aaa authorization commands 15 tacacs+ 
+   
+
+This will make the NAS send tacacs+ requests for all level 1 (ordinary
+user) and level 15 (privileged level) commands on all lines/interfaces.
+
+NOTE: As soon as you configure the above on your NAS, you will only be
+permitted to execute NAS commands which are permitted by your tacacs+
+daemon. So make sure you have configured, on the daemon, an
+authenticated user who is authorized to run commands, or you will be
+unable to do much on the NAS after turning on authorization. 
+
+Alternatively, or in addition, you may also want to configure the
+following:
+
+    aaa authorization commands 1 tacacs+ if-authenticated 
+
+This will use tacacs+ authorization for level 1 (user-level commands)
+but if problems arise, you can just switch off the tacacs+ server and
+authorization will then be granted to anyone who is authenticated.
+
+The following daemon configuration should be sufficient to ensure that
+you can always login as username "admin" (with a suitable password)
+and run any command as that user:
+
+user = admin {
+    default service = permit
+    login = des kppPfHq/j6gXs
+}
+
+
+ACCOUNTING
+-----------
+
+There is only one configurable accounting parameter -- the accounting
+file name. All accounting records are written, as text, to this
+filename. The filename is configured as follows at the top-level of
+the configuration file:
+
+accounting file = <filename>
+
+Since accounting requests occur (and are serviced) asynchronously, it
+is necessary to lock the accounting file so that two writers don't
+simultaneously update it.  The daemon uses the fcntl call to do this
+locking, so it is recommended that the accounting file reside on a
+local filesystem. Although fcntl locking over NFS is supported on some
+Unix implementations, it is notoriously unreliable. Even if your
+implementation is reliable, locking is likely to be extremely
+inefficient over NFS.
+
+NAS CONFIGURATION
+-----------------
+
+To get accounting records equivalent to previous versions of tacacs,
+the following is sufficient. "Stop" records contain elapsed time for
+connections and exec sessions.
+
+aaa accounting network stop-only tacacs+ 
+aaa accounting exec stop-only tacacs+
+
+
+CONFIGURING CALLBACK WITH TACACS+
+---------------------------------
+
+Note: Callback is available only in IOS 11.1 and later, and can only
+be controlled via Tacacs+ for ASYNC lines. ISDN callback can be
+configured on the NAS but cannot be controlled via AAA.
+
+Here is an example of AAA configuration (with exec and network
+accounting enabled):
+NAS configuration:
+
+aaa new-model
+tacacs-server host XX.XX.XX.XX
+tacacs-server key fookey
+aaa accounting exec wait-start tacacs+
+aaa accounting network wait-start tacacs+
+
+! Example of AAA configuration for Exec:
+aaa authentication login execcheck tacacs+ 
+aaa authorization network tacacs+
+service exec-callback
+:
+line 4
+login authentication execcheck
+
+! Example of AAA configuration for ARAP:
+aaa authentication arap arapcheck tacacs+ 
+aaa authorization network tacacs+
+arap callback
+:
+line 4
+arap authentication arapcheck
+
+! Example of AAA-specific configuration for PPP callback:
+aaa new-model
+aaa authentication ppp pppcheck tacacs+ 
+aaa authorization network tacacs+
+:
+int async 6
+ppp authentication chap pppcheck
+ppp callback accept
+
+Daemon configuration:
+
+Example of remote TACACS+ server CONFIG file entry for username `foobar':
+
+user = foobar {
+   arap = cleartext AAAA
+   login = cleartext LLLL
+   chap = cleartext CCCC
+   pap = cleartext PPPP
+   opap = cleartext OOOO
+   service = ppp protocol = lcp {
+        callback-dialstring=123456
+   }
+   service = arap {
+        callback-dialstring=2345678
+   }
+   service = exec { 
+        callback-dialstring=3456789
+        callback-line=7
+        nocallback-verify=1
+   }
+}  
+
+
+
+
+DEBUGGING CONFIGURATION FILES
+-----------------------------
+       
+When creating configuration files, it is convenient to check their
+syntax using the -P flag to tac_plus e.g.
+
+    tac_plus -P -C <config file name>
+
+will syntax check the configuration file and print any error messages
+on the terminal.
+
+DEBUGGING A RUNNING SERVER
+--------------------------
+
+There is a myriad of debugging values that can be used in conjunction
+with the -d flag to produce debugging output in /var/log/tac_plus.log.
+
+For example, starting the daemon with 
+
+       tac_plus -C CONFIG -d 16
+
+will put authentication debugging into /var/log/tac_plus.log. You can
+view this information by using the tail command.
+
+       tail -f /var/log/tac_plus.log
+
+See the man page for more information.
+
+CHANGING CONFIGURATIONS
+-----------------------
+
+To change a configuration file, you must edit the configuration file
+and then send the daemon a SIGUSR1. This will cause it to reinitialize
+itself and re-read the configuration file.
+
+On startup, tac_plus creates the file /var/run/tac_plus.pid , if possible,
+containing its process id. If you invoke the daemon so that it listens
+on a non-standard port, the file created is /var/run/tac_plus.pid.<port>
+instead, where <port> is the port number the daemon is listening on.
+
+Assuming you are listening on the default port 49, something like the
+following should work:
+
+# kill -USR1 `cat /var/run/tac_plus.pid`
+
+It's a good idea to check that the daemon is still running after
+sending it a SIGUSR1, since a syntactically incorrect configuration
+file will cause the daemon to die.
+
+NOTE: The perl script generate_passwd.pl may be used to hand-generate
+encrypted passwords, or they may be taken from a Unix passwd file.
+
+FREQUENTLY ASKED QUESTIONS
+--------------------------
+Q). Does T+ required a working DNS?
+
+A). As distributed, whenever a START packet arrive, the daemon makes a
+call to getpeername to find out the name of the requestor. Depending
+entirely on how your Unix host is set up, this may make DNS
+queries. If this is a problem, comment out the code in tac_plus.c
+which does this, and just use ip addresses instead of host names.
+
+Q). Is the "name" field used for anything
+
+A). No. It's purely for documentation. I had once thought it might be
+useful when outputting error messages, and that you might need the
+information if you converted a passwd(5) style file, but no use is
+currently made of the field.
+
+Q). Why do I get PPP authorization failures because of "no username in
+request" when I've already logged in and authenticated?
+
+A). With "aaa authentication PPP default tacacs+", the ppp
+authentication overrides the earlier login authentication.  If the ppp
+authentication fails, the username ends up blank.  Changing the config
+to "aaa authentication ppp default if-needed tacacs+" fixes this
+problem.
+
+Q). I'm sure I configured it correctly, but accounting still doesn't
+work.
+
+A). You will find that although you can configure accounting in 10.3,
+and it outputs debug messages, it doesn't send any accounting
+packets. This is because Accounting only works from 11.0 onwards.
+
+Q). Does TACACS+ use a database instead of a flat (/etc/passwd like)
+file to decrease search times, say if we are talking about a large
+database of 40,000 users?
+
+A). The TACACS+ authentication database is held internally as a hash
+table. This makes lookup times fast and fairly linear, at the expense
+of making the server use potentially large amounts of memory space.
+NOTE: If you specify that the server uses passwd(5) files for
+authentication, then you don't get this speed benefit, but you save
+space.
+
+If you're willing to write the code, it should be a relatively simple
+matter to interface the code to a database scheme e.g. unix dbm files,
+or some proprietary database package, if you wish.
+
+Q). Is there any way to avoid having clear text versions of the
+ARAP and CHAP secrets in the configuration file?
+
+CHAP and ARAP require that the server knows the cleartext password (or
+equivalently, something from which the server can generate the
+cleartext password). Note that this is part of the definition of CHAP
+and ARAP, not just the whim of some Cisco engineer who drank too much
+coffee late one night.
+
+If we encrypted the CHAP and ARAP passwords in the database, then we'd
+need to keep a key around so that the server can decrypt them when
+CHAP or ARAP needs them.  So this only ends up being a slight
+obfuscation and not much more secure than the original scheme.
+
+In extended TACACS, the CHAP and ARAP secrets were separated from the
+password file because the password file may be a system password file
+and hence world readable.  But with TACACS+'s native database, there
+is no such requirement, so we think the best solution is to
+read-protect the files.  Note that this is the same problem that a
+kerberos server has.  If your security is compromised on the kerberos
+server, then your database is wide open.  Kerberos does encrypt the
+database, but if you want your server to automatically restart, then
+you end up having to "kstash" the key in a file anyway and you're back
+to the same security problem.
+
+So storing the cleartext password on the security server is really an
+absolute requirement of the CHAP and ARAP protocols, not something
+imposed by TACACS+.
+
+We could have chosen a scheme where the NAS sends the challenge
+information to the TACACS+ daemon and the daemon uses the cleartext
+password to generate the response and returns that, but that means
+that we must include specific protocol knowledge into the protocol for
+both ARAP and CHAP and we would have to update the protocol every time
+a new authentication protocol is added.  Hence we decided to go with
+the SENDPASS mechanism.
+
+Note that the above doesn't apply to PAP. You can keep an inbound PAP
+password des-encrypted, since all you need to do with it is verify
+that the password the principal gave you is correct.
+
+Q). How is the typical login authentication sequence done?
+
+A).    NAS sends START packet to daemon
+       Daemon send GETUSER containing login prompt to NAS
+       NAS prompts user for username
+       NAS sends pkt to daemon 
+       Daemon sends GETPASS containing password prompt to the NAS
+       NAS prompts user for password
+       NAS sends pkt to daemon 
+       Daemon sends accept, reject or error to NAS
+
+
+Q). Is there a GUI for the configuration file?
+A). No. Use your favourite text editor.
+
+
+Q). What does "default service = permit" really do?
+
+A). When a request comes in to authorize exec startup, or ppp (with
+protocol lcp, ip, ipx), or slip, or arap or a specific command, the
+daemon looks for a matching declarations for the user (or groups the
+user is a member of).
+
+For exec startup, it looks for a "service=exec" OR any command
+configured.
+
+For ppp, it looks for a "service=ppp" and "protocol=(one of lcp, ip,
+ipx)".
+
+For slip there must be a "service=slip" and for arap a "service=arap"
+clause.
+
+For specific commands, there must be a matching cmd=<cmdname>.
+
+If these aren't found, authorization will fail, *unless* you say
+"default service = permit".
+
+Q). How do I make PAP work?
+
+A). Avoid using PAP if possible since it's not very secure. If you
+*must* use it, PAP passwords may be specified along with arap and chap
+passwords for each user. Note that the details of this changed in
+version 3.0 and onwards.
+
+For outbound PAP, where you are forced to send a password to the
+remote host to identify yourself, there is now a separate "opap"
+directive e.g.
+
+       opap = cleartext OOOO
+
+You use this to set the outbound PAP password. It must be a cleartext
+password.
+
+NOTE: It is very bad practice to use an outbound PAP password that is
+the same as any of your inbound passwords. For this reason, a "global"
+password does not apply to outbound PAP, only to inbound PAP,
+bidirectional CHAP and ARAP.
+
+Before 3.0, PAP logins were treated like ordinary user logins, so you
+needed to declare a user in the Daemon configuration file whose name
+was typically the remote hostname (or user), with a login password, in
+order to process the PAP request.
+
+Q). How can I deny some one from telneting from a commserver by ip
+address only. i.e. when command is 10.0.1.6 rather than telnet
+10.0.1.6.
+
+A).  The best way to restrict telnet access is by applying an outbound
+access list via the access class command (or equivalently, via the
+"acl" avpair). The NAS configuration command "access-class <n> out"
+for example applies a pre-defined standard IP access list (where n is
+a number from 1 through 99) that governs telnet access from a NAS. 
+
+E.g. the following configuration commands permit outgoing Telnet
+access from line 1 on the NAS *only* to hosts on network 192.85.55.0:
+
+  access-list 12 permit 192.85.55.0 0.0.0.255
+  line 1
+  access-class 12 out
+
+Note: you must define "access-list 12 permit 192.85.55.0 0.0.0.255" on
+the NAS. Only then can you use the acl avpair to apply it to a line
+that a user dials in on.
+
+Alternatively, you can try configuring "transport preferred none" on
+the lines in question. This will force a user to always type "telnet
+10.0.1.6" in order to telnet out from the NAS.  Then you can apply
+command authorization to this command to restrict it.
+
+Q). I have an autocommand configured in the NAS-local database and I'm
+using "aaa authentication local-override". The autocommand doesn't
+work, but the username/password does. Why?
+
+A). The "local-override" only applies to the authentication portion of
+the local database, so if you want an autocommand for this user, you
+need to also do:
+
+       aaa authorization exec local if-authenticated
+
+This will use the local DB entry if one exists, allow authenticated
+users otherwise, or fail.
+
+We don't have a "aaa authorization local-override" like we do for
+authentication. Unlike authentication, the local method for
+authorization is sort of equivalent to a local-override.
+
+Q). Can tacacs+ only be enabled on a global basis? I want to
+selectively turn it on for, e.g. only modem-connected lines. How do I
+do this?
+
+A). You turn tacacs+ ON on a global basis, but you can then change the
+behavior of individual lines to whatever you want, e.g.
+
+        aaa authentication login default tacacs+ none
+        aaa authentication login oldstyle line
+        aaa authentication login none none
+        line 1 16
+        login authentication default
+        line vty 0 4
+        login authentication oldstyle
+        line 0
+        login authentication none
+
+Note that unfortunately, you can't (yet) apply authorization
+differently to selected lines and interfaces.
+
+Q). I have leased lines running PPP, and AAA authorization is also
+configured, so the authorization on the leased lines fails.  What should
+I do?
+
+A). Since you can't (yet) configure authorization on a per-line basis,
+you have to turn on authentication on the leased lines running PPP and
+configure your T+ server so that it will authorize these lines
+correctly.
+
+A more demanding alternative is to modify the TACACS+ server source code
+to allow any authorizations coming in from the port "SerialXXX" to
+succeed.
+
+A third possibility is to not use PPP on those lines, e.g. use HDLC
+instead. HDLC doesn't require authentication or authorization.
+
+Q). What are the memory recommendations for TACACS+?
+
+A). Unless you're using passwd style files, TACACS+ holds entries in
+hash tables in memory. The overhead is modest e.g. each user entry
+occupies 72 bytes, plus space for strings like username and password
+etc. Access time should thus be pretty constant regardless of number
+of users. On a sparc 2, a config file containing 2000 users requires
+about 0.5M of swap.
+
+Q). How many users will a TACACS+ server support?  What happens when
+the maximum is exceeded?
+
+There are 2 issues. The first is that each entry in the config file
+occupies memory (swap space) so you could run out of swap space either
+starting up the daemon or when it forks to answer incoming requests
+(see above). If this happens, the daemon will drop the connection
+which should appear as an error to the NAS) and the logfile will
+contain appropriate error messages. The solution is usually to add
+more disk space for swapping.
+
+The second issue is speed: Using config files containing 75,000 user
+entries, I'm seeing about 3 authentications per second on a sparc 2
+without noticeable performance impact, though I haven't benchmarked
+this formally.
+
+So more than about 3 authentications per second on this platform will
+result in users seeing delays and having to wait for prompts.  The
+usual solution to this is to add more daemons to spread the load out.
+
+Q). How many characters may a TACACS+ Username and Password contain?
+
+A). The short answer is 31 bytes of username, with up to 254 bytes of
+password if they are cleartext (8 bytes if passwords are des
+encrypted).
+
+The long answer is that the Cisco NAS allocates a buffer of 1024
+bytes, so this is the maximum you can type in, in response to a NAS
+prompt.
+
+But the protocol spec allows a username or password length field of
+just one byte in an authentication packet, so only the first 255 of
+these characters can be sent to the daemon.
+
+Then, the API spec states that the username in the identity structure
+on the daemon is 32 bytes long, so only the first 31 bytes of username
+will be copied from the authentication packet into this structure,
+which is then null-terminated.
+
+The password, on the other hand, is copied into malloc'ed memory, so
+it can still be up to 255 characters long.
+
+Now if it's a des encrypted password, then only the first 8 bytes are
+significant, per the common unix implementations of crypt.
+
+Lastly, there is also the question of how long a username/password can
+be configured in the daemon configuration file. The answer is given by
+the value of MAX_INPUT_LINE_LEN, currently set to 255, which
+determines the length of the longest string you can enter in the
+configuration file.
+
+Q). What is the format of accounting records?
+
+Accounting records are text lines containing tab-separated fields. The
+first 6 fields are always the same. These are:
+
+timestamp, NAS name, username, port, address, record type.
+
+Following these, a variable number of fields are written, depending on
+the accounting record type. All are of the form attribute=value. There
+will always be a task_id field.
+
+Current attributes are:
+
+"unknown"
+"service"
+"start_time"
+"port"
+"elapsed_time"
+"status"
+"priv_level"
+"cmd"
+"protocol"
+"cmd-arg"
+"bytes_in"
+"bytes_out"
+"paks_in"
+"paks_out"
+"address"
+"task_id"
+"callback-dialstring"
+"nocallback-verify"
+"callback-line"
+"callback-rotary"
+
+I expect more will be added over time. 
+
+Example records are thus:
+
+Thu Jul 13 13:35:28 1995       cherub.cisco.com        chein   tty5    171.69.1.141    stop    task_id=12028   service=exec    port=5  elapsed_time=875
+Thu Jul 13 13:37:04 1995       cherub.cisco.com        lol     tty18   171.69.1.129    stop    task_id=11613   service=exec    port=18 service=exec    port=18 elapsed_time=909
+Thu Jul 13 14:09:02 1995       cherub.cisco.com        billw   tty18   171.69.1.152    start   task_id=17150   service=exec    port=18
+Thu Jul 13 14:09:02 1995       cherub.cisco.com        billw   tty18   171.69.1.152    start   task_id=17150   service=exec    port=18 service=exec    port=18
+
+
+Elapsed time is in seconds, and is the field most people are usually
+interested in.
+
+Q). How do I limit the number of sessions a user can have?
+
+A). The TACACS+ daemon can enforce how many simultaneous sessions a
+given user is allowed to have.  You must compile the daemon with the
+MAXSESS symbol defined (see the Makefile).  
+
+Maximum sessions are configured on the daemon for a user as follows:
+
+user = joeslip {
+    login = cleartext XXX
+
+    # only allow two sessions max for joeslip
+    maxsess = 2
+
+    name = "Joe SLIP User"
+    ...
+}
+
+It can also be configured under a group:
+
+group = slip_users {
+    maxsess = 2
+    ...
+}
+...
+user = fred {
+    ...
+    member = slip_users
+    ...
+}
+
+The daemon keeps a count of how many sessions a given user owns by
+monitoring START and STOP accounting records.  Thus, exec and network
+accounting must be configured for this feature to operate for exec and
+ppp users.
+
+As the restriction is enforced during the authorization phase of
+login, exec and network authorization must be configured as well, viz:
+
+aaa authentication login default tacacs+
+aaa authentication ppp default tacacs+
+aaa authorization exec tacacs+
+aaa authorization network tacacs+
+aaa accounting exec start-stop tacacs+
+aaa accounting network start-stop tacacs+
+
+Due to network outages (or other disruptions), it is possible for the
+TACACS+ daemon's record of usage to become out of sync with reality,
+so before denying access because it thinks a user is running too many
+sessions, the TACACS+ daemon will use the finger service on the NAS to
+verify how many sessions a user is running there.
+
+If the result of finger indicates that the daemon should permit
+access, access will be granted.  Note that for this check to work via
+finger, "service finger" must also be configured on the NAS.
+
+Lastly, note that because finger output truncates usernames at 10
+characters, you may encounter trouble if you have users whose names
+are not unique within those first 10 characters.
+v
+Also recall that authorization works differently on the console. So
+many people locked themselves out of their boxes after configuring
+authorization, that we stopped requiring authorization on the console
+for authenticated users. Since there's no authorization on the
+console, MAXSESS is not enforced there.
+
+Q). How can I configure time-outs on an interface via Tacacs+?
+
+A). Certain per-user/per-interface timeouts may be set by Tacacs+
+during authorization. As of 11.0, you can set an arap session timeout,
+and an exec timeout. As of 11.1 you can also set an exec idle timeout.
+
+There are currently no settable timeouts for PPP or SLIP sessions, but
+there is a workaround which applies to ASYNC PPP/SLIP idle timeouts
+started via exec sessions only: This workaround is to set an EXEC
+(idletime) timeout on an exec session which is later used to start up
+PPP or SLIP (either via a T+ autocommand or via the user explicitly
+invoking PPP or SLIP). In this case, the exec idle timeout will
+correctly terminate an idle PPP or SLIP session. Note that this
+workaround cannot be used for sessions which autoselect PPP or SLIP.
+
+An idle timeout terminates a connection when the interface is idle for
+a given period of time (this is equivalent to the "session-timeout"
+Cisco IOS configuration directive). The other timeouts are
+absolute. Of course, any timeouts set by Tacacs+ apply only to the
+current connection.
+
+
+user = lol {
+    login = cleartext foobar
+    service = exec {
+       # disconnect lol if there is no traffic for 5 minutes
+       idletime = 5
+       # disconnect lol unconditionally after one hour
+        timeout = 60
+}
+
+You also need to configure exec authorization on the NAS for the above
+timeouts, e.g.
+
+        aaa authorization exec tacacs+
+
+Note that these timeouts only work for async lines, not for ISDN
+currently.
+
+
+Note also that you cannot use the authorization "if-authenticated"
+option with these parameters, since that skips authorization if the
+user has successfully authenticated.
+
+Q). How do I send VPDN forwarding decisions to an authorization
+server?
+
+A). In 11.2 onwards, VPDN NASs can use T+ to allow an authorization
+server to make the decision to forward users.
+
+If VPDN forwarding is turned on, and the username is of the form
+user@domain, and "domain" matches a vpdn outgoing configured domain,
+then an authorization attempt is made for "domain" (see below).
+
+When making an authorization call for VPDN, a service type of "ppp"
+with a protocol type of "vpdn", with a username of "domain" will be
+made e.g. when a PPP user comes up on a line with the username
+foo@bar.com, if "vpdn enable" and "aaa authorization ...." is enabled
+on the box, then a one-time authorization of the name "bar.com" is
+attempted.
+
+If this authorization is successful, no local authentication is
+attempted on the NAS, and the connection is forwarded via VPDN
+instead.
+
+If no VPDN-specific information comes back from this authorization
+call, the login proceeds as follows:
+
+If tacacs-server directed-requests are configured (note: this is true
+by default), then IOS will strip off the domain part of a name of the
+form user@domain and use "domain" to try and select a T+ server. If
+successful, the username portion "user", without the domain, will be
+used for all subsequent authentication, authorization and accounting.
+
+If directed requests are turned off, then the entire username
+user@domain is treated as a username.
+
+vpdn specific information includes the attributes "tunnel-id",
+"source-ip" (deprecated) and "ip-addresses":
+
+tunnel-id:
+       This AV pair specifies the username that will be used to
+       authenticate the tunnel over which the individual user MID
+       will be projected.  This is analogous to the "NAS name" in the
+       "vpdn outgoing" command.
+
+ip-addresses:
+       This is a list of possible IP addresses that can be used for
+       the end-point of the tunnel. Consult the text at the end of
+       this document for more details on how to configure this
+       attribute.
+
+source-ip: (This is now deprecated. It began in release 11.2(1.4),
+           and was removed in 11.2(4.0.2)).
+       This ip address will be used as the source of all VPDN packets
+       generated as part of the VPDN tunnel (see the source-ip
+       keyword in the vpdn outgoing command).
+
+Tacacs+ syntax
+--------------
+               
+The following syntax is used on the public domain Tacacs+ server.
+
+username = domain {
+    service = ppp protocol = vpdn {
+      tunnel-id = <name for tunnel authentication>
+      ip-addresses = <addr> [<addr> ...]
+      source-ip = <ip-address>
+    }
+}
+
+In addition the T+ server can be used to store the usernames for both
+the NAS (the username specified by "tunnel-id" above) and the Home
+Gateway.  These will be used to authenticate the tunnel.
+
+Example:
+
+user = foobar.cisco.com {
+    service = ppp protocol = vpdn {
+        tunnel-id = my_nas
+        ip-addresses = "173.20.12.19 173.20.12.20"
+       source-ip = 173.5.10.1
+    }
+}
+
+user = my_nas {
+    global = cleartext egad
+}
+
+user = my_home_gateway {
+    global = cleartext wowser
+}
+
+IOS Configuration
+-----------------
+
+To enable AAA assisted VPDN forwarding on a NAS, the following minimum
+configuration is required:
+
+       vpdn enable
+       aaa new-model
+       aaa authorization network tacacs+ ...
+
+In addition, if the passwords for the home gateway and NAS are stored
+on the T+ daemon, the command
+
+       aaa authentication login tacacs+ ....
+
+should also be present.
+
+Beginning with release 11.2(1.4), the additional configuration
+
+       vpdn outgoing cisco.com ip NAS [ source-ip X.X.X.X ]
+
+can be used. This changes in 11.2(4.0.2) and becomes:
+
+       vpdn source-ip X.X.X.X
+       vpdn outgoing cisco.com ip NAS
+
+
+Q). Can someone expand on the use of the "optional" keyword.
+
+A). Most attributes are mandatory i.e. if the daemon sends them to the
+NAS, the NAS must obey them or deny the authorization. This is the
+default. It is possible to mark attributes as optional, in which case
+a NAS which cannot support the attribute is free to simply ignore it
+without causing the authorization to fail. 
+
+This was intended to be useful in cutover situations where you have
+multiple NASes running different versions of IOS, some of which
+support more attributes than others. If you make the new attributes
+optional, older NASes could ignore the optional attributes while new
+NASes could apply them. Note that this weakens your security a little,
+since you are no longer guaranteed that attributes are always applied
+on successful authorization, so it should be used judiciously.
+
+Q). Can I do do address pooling on the T+ daemon?
+
+A). Not really since nothing on the daemon tracks whether an address
+is already in use. The best way to do manage address pools is to
+define a non-overlapping pool of addresses on each NAS and return the
+name of this pool during authorization whenever a user logs in e.g.
+
+NAS:
+
+ip local pool foo 1.0.0.1 1.0.0.10
+
+Daemon:
+
+user = lol {
+    service = ppp protocol = ip {
+       addr-pool = foo
+    }
+}
+
+
+Q). What about MSCHAP?
+
+A). Version F4.X contains mschap support. Mschap is configured the
+same way as chap, only using the "mschap" keyword in place of the
+"chap" keyword.
+
+Like ARAP, MSCHAP works best when you have a des library (see the note
+at the beginning of this document), but this is optional, as the DES
+calculation can be done by the NAS instead. It also optionally
+requires a key from Microsoft which is not public domain, but this can
+also be done on the NAS too, so you can live without it.
+
+To compile the daemon with MSCHAP support, uncomment the MSCHAP line
+in the Makefile. This will add the MSCHAP code to your daemon. You can
+leave MSCHAP_DES undefined, in which case the MSCHAP DES calculation
+will be done by NAS, and no special MSCHAP key will be required.
+
+If you have a DES library and want to use it with MSCHAP (this is more
+efficient for authentication), then you can also uncomment the
+MSCHAP_DES and MSCHAP_MD4_SRC lines in the Makefile. This will ensure
+that the DES calculation done in the daemon. A key will need to be
+obtained from Microsoft and used to replace the definition of
+MSCHAP_KEY in the file mschap.h. If you're thinking by now that this
+is all too much trouble, I can't say I blame you....
+
+Q). Can I do wtmp-style logging like xtacacd used to do?
+
+A). Wtmp file logging is supported. The "-u <wtmpfilename>" command
+line flag can be used to specify a filename which will be used for
+logging wtmp style records instead of the regular T+ accounting
+records.
+
+wtmp records are generated into wtmpfilename. Since file locking is
+also used when writing to wtmpfilename, the usual provisos regarding
+NFS and locking (see accounting above) should be observed.
+
+To generate correct wtmp log records, the NAS needs to be configured
+as follows:
+
+aaa accounting exec stop-only tacacs+
+aaa accounting network stop-only tacacs+
+aaa accounting system start-stop tacacs+
+
+CANNED CONFIGURATIONS
+---------------------
+
+Here are some canned configurations for getting demos started:
+
+1). A canned configuration for login authentication only. This allows
+user fred to login with password "abcdef". If the tacacs+ server dies,
+the enable secret will be accepted as a login password instead.
+
+DAEMON:
+
+key = <some key>
+
+# repeat as necessary for each user
+user = fred {
+   login = cleartext abcdef
+}
+
+NAS:
+
+aaa new-model
+enable secret foobar
+! use tacacs+. If server dies, use the enable secret password
+aaa authentication login default tacacs+ enable
+tacacs-server host <some host ip address>
+tacacs-server key <some key>
+
+2). A canned configuration for command authorization. This will allow
+user fred to login with password abcdef and to run the privileged
+(level 15) commands 'write terminal' and 'configure'. All other
+privileged commands will be denied. 
+
+The "none" keyword in the NAS configuration line means that if the
+tacacs+ server dies, any command will be allowed.
+
+DAEMON:
+
+key = <some key>
+
+# repeat as necessary for each user
+user = fred {
+   login = cleartext abcdef
+   cmd = write  {
+        permit terminal
+    }
+   cmd = configure {
+       permit .*
+   }
+}
+
+
+NAS:
+
+aaa new-model
+! all level 15 (privileged commands). If server dies, allow everything
+aaa authorization commands 15 tacacs+ none
+tacacs-server host <some host ip address>
+tacacs-server key <some key>
+
+3). Canned configuration for network access authorization. This config
+allows "fred" to login to line 1 with password abcdef (or to and to
+run ppp using chap authentication. The chap password is "lab".
+
+DAEMON:
+
+key = <some key>
+
+# repeat as necessary for each user
+user = fred {
+   login = cleartext abcdef
+   chap = cleartext lab
+   service = ppp protocol = ip {
+       addr=1.0.0.2
+    }
+}
+
+NAS:
+
+aaa new-model
+! authenticate exec logins (if not autoselecting)
+aaa authentication login default tacacs+
+! authorize network services via tacacs+
+aaa authorization network tacacs+
+! use tacacs+ for authenticating ppp users
+aaa authentication ppp default tacacs+ 
+tacacs-server host <some host ip address>
+tacacs-server key <some key>
+interface Async1
+ip address 1.0.0.1 255.0.0.0
+async default ip address 172.21.14.55
+encapsulation ppp
+async dynamic address
+async mode interactive
+! use chap to authenticate ppp users
+ppp authentication chap
+line 1
+! need "modem inout" here and flow control if using a modem
+
+4). Canned configuration for ARAP.
+
+NAS:
+
+aaa new-model
+aaa authentication arap default guest tacacs+
+aaa authorization network tacacs+
+aaa accounting network start-stop tacacs+
+!
+appletalk routing
+arap network <number> <name>
+!
+interface Ethernet0
+ appletalk cable-range <range>
+ appletalk zone <zonename>
+!
+tacacs-server host <host>
+tacacs-server key <key>
+!
+line 1
+ location a modem
+ modem answer-timeout 0
+ modem InOut
+ autoselect arap
+ autoselect during-login
+ arap enable
+ speed <speed>
+ flowcontrol hardware
+
+Daemon:
+
+key = "some key"
+
+user = lol {
+    arap = cleartext <arap secret>
+    service = arap { }
+}
+
+Authorization AV pairs
+----------------------
+The following authorization AV pairs are supported by 10.3(3) onwards
+except where specifically noted.
+
+The following AV pairs specify which service is being authorized. They
+are typically accompanied by protocol AV pairs and other, additional
+pairs from the lists below.
+
+service=arap 
+service=shell (for exec startup, and also for command authorizations)
+service=ppp 
+service=slip
+
+service=system (not used).
+
+service=raccess
+       Used for managing reverse telnet connections e.g.
+       user = jim {
+           login = cleartext lab
+           service = raccess {
+               port#1 = nasname1/tty2
+               port#2 = nasname2/tty5
+           }
+       }
+
+       Requires IOS configuration 
+
+               aaa authorization reverse-access tacacs+
+
+       See the IOS docs for more details.
+
+protocol=lcp
+       The lower layer of PPP, always brought up before IP, IPX, etc.
+       is brought up.
+
+protocol=ip
+       Used with service=ppp and service=slip to indicate which
+       protocol layer is being authorized.
+
+
+protocol=ipx
+       Used with service=ppp to indicate which protocol layer
+       is being authorized.
+
+protocol=atalk
+       with service=ppp or service=arap
+
+protocol=vines
+       For vines over ppp.
+
+protocol=ccp
+       Authorization of CCP.  Compression Control Protocol). No other
+       av-pairs associated with this.
+
+protocol=cdp
+       Authorization of CDP (Cisco Discovery Protocol). No other
+       av-pairs associated with this.
+
+protocol=multilink
+       Authorization of multilink PPP. See 'max-links' and 'load-threshold'.
+
+protocol=unknown 
+       For undefined/unsupported conditions. Should not occur under
+       normal circumstances.
+
+cmd (EXEC) 
+       If the value of cmd is NULL e.g. the AV pair is cmd=, then
+       this is an authorization request for starting an exec.
+       
+       If cmd is non-null, this is a command authorization request,
+       It contains the name of the command being authorized
+       e.g. cmd=telnet
+
+cmd-arg (EXEC)
+       During command authorization, the name of the command is given
+       by an accompanying "cmd=" AV pair, and each command argument
+       is represented by a cmd-arg AV pair e.g. cmd-arg=archie.sura.net
+
+       NOTE: 'cmd-arg' should never appear in a configuration file.
+       It is used internally by the daemon to construct a string
+       which is then matched against the regular expressions which appear
+       in a cmd clause in the configuration file.
+       
+acl (ARAP, EXEC)
+       For ARAP this contains an access-list number.  For EXEC
+       authorization it contains an access-class number, e.g. acl=2.
+       which is applied to the line as the output access class equivalent
+       to the configuration command
+
+               line <n>
+               access-class 2 out
+
+       An outbound access-class is the best way to restrict outgoing telnet
+       connections. Note that a suitable access list (in this case,
+       numbered 2) must be predefined on the NAS.
+               
+inacl (PPP/IP/IPX)
+       This AV pair contains an IP or IPX input access list number
+       for slip or PPP e.g. inacl=2. The access list itself must be
+       pre-configured on the Cisco box. Per-user access lists do not
+       work with ISDN interfaces unless you also configure a virtual
+       interface. After 11.2(5.1)F, you can also use the name of a
+       predefined named access list, instead of a number, for the
+       value of this attribute.
+
+       Note: For IPX, inacl is only valid after 11.2(4)F.
+
+inacl#<n> (PPP/IP, PPP/IPX, 11.2(4)F)
+       This AV pair contains the definition of an input access list
+       to be installed and applied to an interface for the duration
+       of the current connection, e.g.
+
+       inacl#1="permit ip any any precedence immediate"
+       inacl#2="deny igrp 0.0.1.2 255.255.0.0 any"
+
+       Attributes are sorted numerically before they are applied.
+       For IP, standard OR extended access list syntax may be used,
+       but it is an error to mix the two within a given access-list.
+
+       For IPX, only extended access list syntax may be used.
+
+       See also:
+       sho ip access-lists
+       sho ip interface
+       sho ipx access-lists 
+       sho ipx interface
+       debug aaa author
+       debug aaa per-user
+
+outacl (PPP/IP, PPP/IPX)
+       This AV pair contains an IP or IPX output access list number
+       for SLIP. PPP/IP or PPP/IPX connections e.g. outacl=4. The
+       access list itself must be pre-configured on the Cisco
+       box. Per-user access lists do not work with ISDN interfaces
+       unless you also configure a virtual interface.  PPP/IPX is
+       supported in 11.1 onwards only. After 11.2(5.1)F, you can also
+       use the name of a predefined named access list, as well as a
+       number, for the value of this attribute.
+
+
+outacl#<n> (PPP/IP, PPP/IPX, 11.2(4)F)
+       This AV pair contains an output access list definition to be
+       installed and applied to an interface for the duration of the
+       current connection, e.g.
+
+       outacl#1="permit ip any any precedence immediate"
+       outacl#2="deny igrp 0.0.9.10 255.255.0.0 any"
+
+       Attributes are sorted numerically before they are applied.
+       For IP, standard OR extended access list syntax may be used,
+       but it is an error to mix the two within a given access-list.
+
+       For IPX, only extended access list syntax may be used.
+
+       See also:
+       sho ip access-lists
+       sho ip interface
+       sho ipx access-lists 
+       sho ipx interface
+       debug aaa author
+       debug aaa per-user
+
+addr (SLIP, PPP/IP)
+       The IP address the remote host should be assigned when a slip
+       or PPP/IP connection is made e.g. addr=1.2.3.4
+
+routing (SLIP, PPP/IP)
+       Equivalent to the /routing flag in slip and ppp commands. Can
+       have as its value the string "true" or "false".
+
+timeout (11.0 onwards, ARAP, EXEC) 
+       Sets the time until an arap or exec session disconnects
+       unconditionally (in minutes), e.g. timeout=60
+
+autocmd (EXEC)
+       During exec startup, this specifies an autocommand, like the
+       autocommand option to the username configuration command,
+       e.g. autocmd="telnet foo.com"
+
+noescape (EXEC)
+       During exec startup, this specifies "noescape", like the
+       noescape option to the username configuration command.  Can
+       have as its value the string "true" or "false",
+       e.g. noescape=true
+       
+nohangup (EXEC)
+       During exec startup, this specifies "nohangup", like the
+       nohangup option to the username configuration command.  Can
+       have as its value the string "true" or "false",
+       e.g. nohangup=true
+       
+priv-lvl (EXEC)
+       Specifies the current privilege level for command
+       authorizations, a number from zero to 15 e.g. priv_lvl=5.
+
+       Note: in 10.3 this attribute was priv_lvl i.e. 
+       it contained an underscore instead of a hyphen.
+       
+zonelist (ARAP)
+       An Appletalk zonelist for arap equivalent to the line
+       configuration command "arap zonelist" e.g. zonelist=5
+
+addr-pool (11.0 onwards, PPP/IP, SLIP)
+       This AV pair specifies the name of a local pool from which to
+       get the IP address of the remote host.
+
+       Note: addr-pool works in conjunction with local pooling.  It
+       specifies the name of a local pool (which needs to be
+       pre-configured on the NAS). Use the ip-local pool command to
+       declare local pools, e.g on the NAS:
+
+       ip address-pool local
+       ip local pool foo 1.0.0.1 1.0.0.10
+       ip local pool baz 2.0.0.1 2.0.0.20
+
+       then you can use Tacacs+ to return addr-pool=foo or
+       addr-pool=baz to indicate which address pool you want to get
+       this remote node's address from, e.g. on the daemon:
+
+       user = lol {
+           service = ppp protocol = ip {
+               addr-pool=foo
+           }
+       }
+
+route (11.1 onwards, PPP/IP, SLIP).
+       This AV pair specifies a route to be applied to an interface.
+
+       During network authorization, the "route" attribute may be
+       used to specify a per-user static route, to be installed via
+       Tacacs+.
+
+       The daemon side declaration is:
+
+       service=ppp protocol=ip {
+           route="<dst_addr> <mask> [ <gateway> ]"
+       }
+
+       This indicates a temporary static route that is to be
+       applied. "<dst_address>, <mask> and [<gateway>]" are expected
+       to be in the usual dotted-decimal notation, with meanings the
+       same as for the familiar "ip route" configuration command on a
+       NAS.
+
+       If gateway is omitted, the peer's address is taken to be the gateway.
+
+       The route is expunged once the connection terminates.
+
+route#<n> (PPP/IP/IPX, 11.2(4)F)
+       Same as the "route" attribute, except that these are valid for
+       IPX as well as IP, and they are numbered, allowing multiple
+       routes to be applied e.g.
+
+       route#1="3.0.0.0 255.0.0.0 1.2.3.4"
+       route#2="4.0.0.0 255.0.0.0"
+
+
+       or, for IPX, 
+
+       route#1="4C000000 ff000000 30.12.3.4"
+       route#2="5C000000 ff000000 30.12.3.5"
+
+       See also:
+       sho ip route
+       sho ipx route
+       debug aaa author
+       debug aaa per-user
+
+callback-rotary (11.1 onwards, valid for ARAP, EXEC, SLIP or PPP)
+       The number of a rotary group (between 0 and 100 inclusive)
+       to use for callback e.g. callback-rotary=34. Not valid for ISDN.
+
+callback-dialstring (11.1 onwards, valid for ARAP, EXEC, SLIP or PPP)
+       sets the telephone number for a callback e.g. 
+       callback-dialstring=408-555-1212. Not valid for ISDN.
+
+callback-line (11.1 onwards, valid for ARAP, EXEC, SLIP or PPP)
+       The number of a tty line to use for callback e.g.
+       callback-line=4. Not valid for ISDN.
+
+nocallback-verify (11.1 onwards, valid for ARAP, EXEC)
+       Indicates that no callback verification is required. The only
+       valid value for this parameter is the digit one i.e.
+       nocallback-verify=1. Not valid for ISDN.
+
+idletime (11.1 onwards, EXEC)
+       Sets a value, in minutes, after which an IDLE session will be
+       terminated. N.B. Does NOT work for PPP.
+
+tunnel-id (11.2 onwards, PPP/VPDN) 
+       This AV pair specifies the username that will be used to
+       authenticate the tunnel over which the individual user MID
+       will be projected.  This is analogous to the "NAS name" in the
+       "vpdn outgoing" command.
+
+ip-addresses (11.2 onwards, PPP/VPDN)
+       This is a space separated list of possible IP addresses that
+       can be used for the end-point of the tunnel.  
+
+       In 11.2(5.4)F, this attribute was extended as follows:
+
+       1) comma (',') is also consider as a delimiter
+       For example the avpair can now be written as
+
+       ip-addresses = 172.21.9.26,172.21.9.15,172.21.9.4
+       2) '/' is considered a priority delimiter. When you have a
+       number of Home Gateway routers, it is desirable to consider some
+       as the primary routers and some as backup routers.
+
+       The '/' allow you to config the routers into priority groups,
+       so that the NAS will try to forward the users to the high
+       priority routers, before forwarding to the low priority one.
+
+       For example in the following avpair:
+
+       ip-addresses = "172.21.9.26 / 172.21.9.15 / 172.21.9.4"
+       172.21.9.26 is considered to be priority 1
+       172.21.9.15 is considered to be priority 2
+       172.21.9.4  is considered to be priority 3
+       The NAS will try to forward the users to 172.21.9.26, before
+       trying 172.21.9.15.  If the NAS can't forward users to
+       172.21.9.26, it will try 172.21.9.15 next. If it fails with
+       172.21.9.15, it will then try forwarding to 172.21.9.4.
+
+source-ip (PPP/VPDN, now deprecated, only existed in releases 11.2(1.4)
+       thru 11.2(4.0.2)). This specifies a single ip address will be
+       used as the source of all VPDN packets generated as part of
+       the VPDN tunnel (see the equivalent source-ip keyword in the
+       IOS vpdn outgoing command).
+
+nas-password (PPP/VPDN, 11.2(3.4)F, 11.2(4.0.2)F)
+       During L2F tunnel authentication, nas-password specifies the password
+       for the NAS.
+
+gw-password (PPP/VPDN, 11.2(3.4)F, 11.2(4.0.2)F)
+       During L2F tunnel authentication, gw-password specifies the password
+       for the home gateway.
+
+rte-ftr-in#<n> (PPP -- IP/IPX, 11.2(4)F)
+       This AV pair specifies an input access list definition to be
+       installed and applied to routing updates on the current
+       interface, for the duration of the current connection. 
+
+       For IP, both standard and extended Cisco access list syntax is
+       recognised, but it is an error to mix the two within a given
+       access-list.
+
+       For IPX, only Cisco extended access list syntax is legal.
+
+       Attributes are sorted numerically before being applied.  For
+       IP, the first attribute must contain the name of a routing
+       process and its identifier (except for rip, where no
+       identifier is needed), e.g.
+
+       rte-fltr-in#0="router igrp 60"
+       rte-fltr-in#1="permit 0.0.3.4 255.255.0.0"
+       rte-fltr-in#2="deny any"
+
+       For IPX, no routing process is needed, e.g.
+
+       rte-fltr-in#1="deny 3C01.0000.0000.0001"
+       rte-fltr-in#2="deny 4C01.0000.0000.0002"
+
+       See also:
+
+       show ip access-lists
+       show ip protocols
+       sho ipx access-lists 
+       sho ipx interface
+       debug aaa author
+       debug aaa per-user
+       
+       Related IOS commands:
+       IP:
+               router <routing process identifier>
+               [no] distribute-list <list-name> in <interface>
+
+       IPX:
+               ipx input-network-filter <access-list-number>
+
+
+rte-ftr-out#<n> (PPP/IP, 11.2(4)F)
+       This AV pair specifies an input access list definition to be
+       installed and applied to routing updates on the current
+       interface, for the duration of the current connection. 
+
+       For IP, both standard and extended Cisco access list syntax is
+       recognised, but it is an error to mix the two within a given
+       access-list.
+
+       Attributes are sorted numerically before being applied.  The
+       first attribute must contain the name of a routing process and
+       its identifier (except for rip, where no identifier is
+       needed), e.g.
+
+       rte-fltr-out#0="router igrp 60"
+       rte-fltr-out#3="permit 0.0.5.6 255.255.0.0"
+       rte-fltr-out#4="permit any"
+
+       For IPX, no routing process is specified, e.g.
+
+       rte-fltr-out#1="deny 3C01.0000.0000.0001"
+       rte-fltr-out#2="deny 4C01.0000.0000.0002"
+
+       See also:
+
+       sho ipx access-lists 
+       sho ipx interface
+       show ip access-lists
+       show ip protocols
+       debug aaa author
+       debug aaa per-user
+       
+       Related IOS commands:
+       IP:
+               router <routing process identifier>
+               [no] distribute-list <list-name> in <interface>
+
+       IPX:
+               ipx output-network-filter <access-list-number>
+
+
+sap#<n> (11.2(4)F PPP/IPX)
+       This AV pair specifies static saps to be installed for the duration
+       of a connection e.g.
+
+        sap#1="4 CE1-LAB 1234.0000.0000.0001 451 4"
+        sap#2="5 CE3-LAB 2345.0000.0000.0001 452 5"
+
+       The syntax of static saps is the same as that used by the IOS
+       "ipx sap" command.
+
+       See also:
+       sho ipx servers
+       debug aaa author
+       debug aaa per-user
+
+       Related IOS commands:
+       [no] ipx sap ....
+
+
+route#<n> (PPP/IP/IPX, 11.2(4)F)
+
+       Same as the "route" attribute, except that these are valid for
+       IPX as well as IP, and they are numbered, allowing multiple
+       routes to be applied e.g.
+
+       route#1="3.0.0.0 255.0.0.0 1.2.3.4"
+       route#2="4.0.0.0 255.0.0.0"
+
+
+       or, for IPX, 
+
+       route#1="4C000000 ff000000 30.12.3.4"
+       route#2="5C000000 ff000000 30.12.3.5"
+
+       See also:
+       sho ip route
+       sho ipx route
+       debug aaa author
+       debug aaa per-user
+       
+
+sap-fltr-in#<n> (PPP/IPX, 11.2(4)F)
+       This AV pair specifies an input sap filter access list
+       definition to be installed and applied on the current
+       interface, for the duration of the current connection.
+
+       Only Cisco extended access list syntax is legal, e.g
+
+       sap-fltr-in#1="deny 6C01.0000.0000.0001"
+       sap-fltr-in#2="permit -1"
+
+       Attributes are sorted numerically before being applied.
+
+       sho ipx access-lists 
+       sho ipx interface
+       debug aaa author
+       debug aaa per-user
+
+       [no] ipx input-sap-filter <number>
+
+
+sap-fltr-out#<n> (PPP/IPX 11.2(4)F)
+       This AV pair specifies an output sap filter access list
+       definition to be installed and applied on the current
+       interface, for the duration of the current connection.
+
+       Only Cisco extended access list syntax is legal, e.g
+
+       sap-fltr-out#1="deny 6C01.0000.0000.0001"
+       sap-fltr-out#2="permit -1"
+
+       Attributes are sorted numerically before being applied.
+
+       sho ipx access-lists 
+       sho ipx interface
+       debug aaa author
+       debug aaa per-user
+
+       [no] ipx output-sap-filter <number>
+
+
+
+pool-def#<n> (PPP/IP, 11.2(4)F)
+       This attribute is used to define ip address pools on the NAS.
+
+       During IPCP address negotiation, if an ip pool name is
+       specified for a user (see the addr-pool attribute), a check is
+       made to see if the named pool is defined on the NAS. If it is,
+       the pool is consulted for an ip address.
+
+       If the required pool is not present on the NAS (either in the
+       local config, or as a result of a previous download
+       operation), then an authorization call to obtain it is
+       attempted, using the special username:
+
+           <nas-name>-pools
+
+       where <nas-name> is the configured name of the NAS.
+
+       Note: This username can be changed using the IOS configuration
+       directive e.g.
+
+           aaa configuration config-name nas1-pools-definition.cisco.us
+
+       The pool-def attribute is used to define ip address pools for
+       the above authorization call e.g.
+
+       user = foo {
+           login = cleartext lab
+           service = ppp protocol = ip {
+               addr-pool=bbb
+           }
+       }
+
+       user = nas1-pools {
+            service = ppp protocol = ip {
+               pool-def#1 = "aaa 1.0.0.1 1.0.0.3"
+               pool-def#2 = "bbb 2.0.0.1 2.0.0.10"
+               pool-def#3 = "ccc 3.0.0.1 3.0.0.20"
+               pool-timeout=60
+            }
+       }
+
+
+       In the example above is a configuration file fragment for defining 3
+       pools named "aaa", "bbb" and "ccc" on the NAS named "nas1".
+
+       When the user "foo" refers to the pool named "bbb", if the
+       pool "bbb" isn't defined, the NAS will attempt to download the
+       definition contained in the "nas1-pools" entry.
+
+       The other pools will also be defined at the same time (or they
+       will be ignored if they are already defined).
+
+       Since this procedure is only activated when an undefined pool
+       is referenced, one way to redefine a pool once it has been
+       downloaded is to manually delete the definition e.g. by
+       logging into the NAS, enabling, and configuring:
+
+       config t
+       no ip local pool bbb
+       ^Z
+
+       When a pool is deleted, there is no interruption in service
+       for any user who is currently using a pool address. If a pool
+       is deleted and then subsequently redefined to include a pool
+       address that was previously allocated, the new pool will pick
+       up the allocated address and track it as expected.
+
+       Since downloaded pools do not appear in the NAS configuration,
+       any downloaded pool definitions automatically disappear
+       whenever a NAS reboots. These pools are marked as "dynamic"
+       when they appear in the output of the "show ip local pools"
+       NAS command.
+
+       Since it is desirable not to have to manually delete pools to
+       redefine them, the AV pair pool-timeout=<n> can be used to
+       timeout any downloaded pool definitions. The timeout <n> is in
+       minutes.
+
+       The effect of the pool-timeout attribute is to start a timer
+       when the pool definitions are downloaded.  When the timer
+       expires, the pools are deleted. The next reference to a
+       deleted pool via will cause a re-fetch of the pool
+       definitions.  This allows pool changes to be made on the
+       daemon and propagated to the NAS in a timely manner.
+
+       See also:
+
+       sho ip local pool
+       sho ip local pool <pool-name>
+
+       IOS commands:
+
+       ip local pool <name> <start address> <end address>
+
+
+old-prompts (PPP/SLIP)
+       This attribute is integrated into the following IOS images:
+
+       11.2(7.4)P 11.2(7.4) 11.1(13.1) 11.(16.2) 11.1(13.1)AA
+       11.1(13.1)CA 11.1(13.1)IA 11.2(8.0.1)F 11.0(16.2)BT)
+
+       This attribute allows providers to make the prompts in T+
+       appear identical to those of earlier systems (tacacs and
+       xtacacs). This will allow administrators to upgrade from
+       tacacs/xtacacs to T+ transparently to users.
+       The difference between the prompts is as follows:
+       In xtacacs, when the user types "slip" or "ppp" the system
+       prompts for an address followed by a password, whereas T+
+       prompts only for an address.
+       In xtacacs, if the user types "slip host" or "ppp host", the
+       system prompts for a password. In T+, there is no prompt.
+       Using this attribute, T+ can be made to mimic the prompting
+       behaviour of xtacacs, by configuring network authorization on
+       IOS, and using the "old-prompts=true" attribute value pair for
+       slip and ppp/ip, viz:
+       user = joe {
+           global = cleartext foo
+           service = exec {
+           }
+           service = slip {
+               default attribute = permit
+               old-prompts=true
+           }
+           service = ppp protocol = ip {
+               default attribute = permit
+               old-prompts=true
+           }
+       }        
+
+       i.e. the prompts are controlled by the addition of the
+       "old-prompts=true" attribute.
+
+
+max-links (PPP/multilink - Multilink parameter; 11.3)
+       This AV pair restricts the number of multilink bundle links
+       that a user can have.
+
+       The daemon side declaration is:
+
+       service=ppp protocol=multilink {
+           max-links=<n>
+       }
+
+       The range of <n> is [1-255].
+
+       Related NAS commands:
+       int <foo>
+         [no] ppp multilink
+        int virtual-template X
+          multilink max-links <n>
+
+       show ppp multilink
+       debug multilink
+
+load-threshold (PPP/multilink - Multilink parameter; 11.3)
+       This AV pair sets the load threshold at which an additional
+       multilink link is added to the bundle (if load goes above) or
+       deleted (if load goes below).
+
+       The daemon side declaration is:
+
+       service=ppp protocol=multilink {
+           load-threshold=<n>
+       }
+
+       The range of <n> is [1-255].
+
+       Related NAS commands:
+       int <foo>
+         [no] ppp multilink
+        int virtual-template X
+          multilink load-threshold <n>
+
+       show ppp multilink
+       debug multilink
+
+
+Reserved for future use:
+
+ppp-vj-slot-compression
+link-compression
+asyncmap
+x25-addresses (PPP/VPDN)
+frame-relay (PPP/VPDN)
+
+
diff --git a/utils.c b/utils.c
new file mode 100644 (file)
index 0000000..b597e70
--- /dev/null
+++ b/utils.c
@@ -0,0 +1,305 @@
+/* 
+   Copyright (c) 1995-1998 by Cisco systems, Inc.
+
+   Permission to use, copy, modify, and distribute this software for
+   any purpose and without fee is hereby granted, provided that this
+   copyright and permission notice appear on all copies of the
+   software and supporting documentation, the name of Cisco Systems,
+   Inc. not be used in advertising or publicity pertaining to
+   distribution of the program without specific prior permission, and
+   notice be given in supporting documentation that modification,
+   copying and distribution is by permission of Cisco Systems, Inc.
+
+   Cisco Systems, Inc. makes no representations about the suitability
+   of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
+   IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+   WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+#include "tac_plus.h"
+
+#ifdef STDLIB_MALLOC
+
+#include <stdlib.h>
+
+#else /* !STDLIB_MALLOC */
+
+#include <malloc.h>
+
+#endif /* STDLIB_MALLOC */
+
+char *
+tac_malloc(size)
+int size;
+{
+    char *p;
+
+    /* some mallocs don't like requests for zero length */
+    if (size == 0) {
+       size++;
+    }
+
+    p = (char *) malloc(size);
+
+    if (p == NULL) {
+       report(LOG_ERR, "malloc %d failure", size);
+       tac_exit(1);
+    }
+    return (p);
+}
+
+char *
+tac_realloc(ptr, size)
+char *ptr;
+int size;
+{
+    char *p;
+
+    if (ptr == NULL) {
+       /* realloc(0, size) is not portable */
+       p = tac_malloc(size);
+    } else {
+       p = (char *)realloc(ptr, size);
+    }
+
+    if (p == NULL) {
+       report(LOG_ERR, "realloc %d failure", size);
+       tac_exit(1);
+    }
+    return (p);
+}
+
+tac_exit(status)
+int status;
+{
+    if (debug & DEBUG_FORK_FLAG)
+       report(LOG_DEBUG, "exit status=%d", status);
+    exit(status);
+}
+
+char *
+tac_strdup(p)
+char *p;
+{
+    char *n = strdup(p);
+
+    if (n == NULL) {
+       report(LOG_ERR, "strdup allocation failure");
+       tac_exit(1);
+    }
+    return (n);
+}
+
+char *
+tac_make_string(p, len)
+u_char *p;
+int len;
+{
+    char *string;
+    int new_len = len;
+
+    /* 
+     * Add space for a null terminator if needed. Also, no telling
+     * what various mallocs will do when asked for a length of zero.
+     */
+    if (len == 0 || p[len - 1])
+       new_len++;
+
+    string = (char *) tac_malloc(new_len);
+
+    bzero(string, new_len);
+    bcopy(p, string, len);
+    return (string);
+}
+
+/* return a pointer to the end of substring in string, or NULL. Substring 
+   must begin at start of string.
+*/
+char *
+tac_find_substring(substring, string)
+char *substring, *string;
+{
+    int len;
+
+    if (!(substring && string)) {
+       return(NULL);
+    }
+
+    len = strlen(substring);
+
+    if (len > (int) strlen(string)) {
+       return(NULL);
+    }
+       
+    if (strncmp(substring, string, len)) {
+       /* no match */
+       return(NULL);
+    }
+    return(string + len);
+}
+
+#ifdef NEED_BZERO
+int
+bzero(p, len)
+    register char *p;
+    int len;
+{
+    register int n;
+
+    if (len <= 0) {
+       return;
+    }
+    for (n=0; n < len; n++) {
+       p[n] = 0;
+    }
+}
+
+int
+bcopy(s1, s2, len)
+    register char *s1, *s2;
+    int len;
+{
+    register int n;
+
+    if ((n = len) <= 0)
+       return;
+    do
+       *s2++ = *s1++;
+    while (--n);
+}
+
+int 
+bcmp(s1,s2,n)
+    char *s1,*s2;
+    int n;
+{     
+    while (n-- > 0) {
+       if (*s1++ != *s2++) {
+           return(1);
+       }
+    }
+    return 0;
+}
+#endif /* NEED_BZERO */
+
+/* Lock a file descriptor using fcntl. Returns 1 on successfully
+   acquiring the lock. The lock disappears when we close the file.
+
+   Note that if the locked file is on an NFS-mounted partition, you
+   are at the mercy of SUN's lockd, which is probably a bad idea 
+*/
+
+int
+tac_lockfd (filename, lockfd)
+char *filename;
+int lockfd;
+{
+    int tries;
+    struct flock flock;
+    int status;
+
+    flock.l_type   = F_WRLCK;
+    flock.l_whence = SEEK_SET; /* relative to bof */
+    flock.l_start  = 0L; /* from offset zero */
+    flock.l_len    = 0L; /* lock to eof */
+
+    if (debug & DEBUG_LOCK_FLAG) {
+       syslog(LOG_ERR, "Attempting to lock %s fd %d", filename, lockfd);
+    }
+
+    for (tries = 0; tries < 10; tries++) {
+       errno = 0;
+       status = fcntl(lockfd, F_SETLK, &flock);
+       if (status == -1) {
+           if (errno == EACCES || errno == EAGAIN) {
+               sleep(1);
+               continue;
+           } else {
+               syslog(LOG_ERR, "fcntl lock error status %d on %s %d %s", 
+                      status, filename, lockfd, sys_errlist[errno]);
+               return(0);
+           }
+       }
+       /* successful lock */
+       break;
+    }
+
+    if (errno != 0) {
+       syslog(LOG_ERR, "Cannot lock %s fd %d in %d tries %s", 
+              filename, lockfd, tries+1, sys_errlist[errno]);
+
+       /* who is hogging this lock */
+       flock.l_type   = F_WRLCK;
+       flock.l_whence = SEEK_SET; /* relative to bof */
+       flock.l_start  = 0L; /* from offset zero */
+       flock.l_len    = 0L; /* lock to eof */
+#ifdef HAS_FLOCK_SYSID
+       flock.l_sysid  = 0L;
+#endif
+       flock.l_pid    = 0;
+
+       status = fcntl(lockfd, F_GETLK, &flock);
+       if ((status == -1) || (flock.l_type == F_UNLCK)) {
+           syslog(LOG_ERR, "Cannot determine %s lockholder status=%d type=%d",
+                  filename, status, flock.l_type);
+           return(0);
+       }
+
+       if (debug & DEBUG_LOCK_FLAG) {
+           syslog(LOG_ERR, "Lock on %s is being held by sys=%u pid=%d",
+                  filename, 
+#ifdef HAS_FLOCK_SYSID
+                  flock.l_sysid, 
+#else
+                  0,
+#endif
+                  flock.l_pid);
+       }
+       return(0);
+    }
+
+    if (debug & DEBUG_LOCK_FLAG) {
+       syslog(LOG_ERR, "Successfully locked %s fd %d after %d tries", 
+              filename, lockfd, tries+1);
+    }
+    return(1);
+}
+
+/* Unlock a file descriptor using fcntl. Returns 1 on successfully
+   releasing a lock. The lock dies when we close the file.
+
+   Note that if the locked file is on an NFS-mounted partition, you
+   are at the mercy of SUN's lockd, which is probably a bad idea 
+*/
+
+int
+tac_unlockfd (filename,lockfd)
+char *filename;
+int lockfd;
+{
+    struct flock flock;
+    int status;
+
+    flock.l_type   = F_WRLCK;
+    flock.l_whence = SEEK_SET; /* relative to bof */
+    flock.l_start  = 0L; /* from offset zero */
+    flock.l_len    = 0L; /* lock to eof */
+
+    if (debug & DEBUG_LOCK_FLAG) {
+       syslog(LOG_ERR, "Attempting to unlock %s fd %d", filename, lockfd);
+    }
+
+    status = fcntl(lockfd, F_UNLCK, &flock);
+    if (status == -1) {
+       syslog(LOG_ERR, "fcntl unlock error status %d on %s %d %s", 
+              status, filename, lockfd, sys_errlist[errno]);
+       return(1);
+    }
+
+    if (debug & DEBUG_LOCK_FLAG) {
+       syslog(LOG_ERR, "Successfully unlocked %s fd %d", 
+              filename, lockfd);
+    }
+    return(0);
+}