From 05e5c1c37439c5c3359ac06739039da5253a062e Mon Sep 17 00:00:00 2001 From: Jan Kratochvil Date: Sun, 5 Sep 2021 18:13:13 +0200 Subject: [PATCH] tac_plus: Cleanup the patch. --- project/tac_plus/Index.pm | 2 +- project/tac_plus/tac_plus-F4.0.3.alpha.8.gts4.diff | 15697 +++++++++++++++++++ .../tac_plus/tac_plus-F4.0.3.alpha.8.gts4.diff.gz | Bin 194171 -> 0 bytes 3 files changed, 15698 insertions(+), 1 deletion(-) create mode 100644 project/tac_plus/tac_plus-F4.0.3.alpha.8.gts4.diff delete mode 100644 project/tac_plus/tac_plus-F4.0.3.alpha.8.gts4.diff.gz diff --git a/project/tac_plus/Index.pm b/project/tac_plus/Index.pm index 5d0391b..ca1de5f 100644 --- a/project/tac_plus/Index.pm +++ b/project/tac_plus/Index.pm @@ -32,7 +32,7 @@ our @ListItem=( "platform"=>"unixuser", "priority"=>270, "icon"=>"cisco-icon.jpeg", - "download-GTS rel.4 diff for TACACS+ v4.0.3"=>"tac_plus-F4.0.3.alpha.8.gts4.diff.gz", + "download-GTS rel.4 diff for TACACS+ v4.0.3"=>"tac_plus-F4.0.3.alpha.8.gts4.diff", "download-TACACS+ v4.0.3, Devrim Seral rel.8"=>"http://www.gazi.edu.tr/tacacs/index.php?page=download", "cvs"=>"tac_plus", "link-Freshmeat"=>sub { diff --git a/project/tac_plus/tac_plus-F4.0.3.alpha.8.gts4.diff b/project/tac_plus/tac_plus-F4.0.3.alpha.8.gts4.diff new file mode 100644 index 0000000..19303bc --- /dev/null +++ b/project/tac_plus/tac_plus-F4.0.3.alpha.8.gts4.diff @@ -0,0 +1,15697 @@ +diff --git a/.exrc b/.exrc +new file mode 100644 +index 0000000..d51ef7d +--- /dev/null ++++ b/.exrc +@@ -0,0 +1,2 @@ ++set tabstop=8 ++set shiftwidth=4 +diff --git a/Makefile.am b/Makefile.am +new file mode 100644 +index 0000000..8d609b1 +--- /dev/null ++++ b/Makefile.am +@@ -0,0 +1,126 @@ ++# 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. ++ ++ ++AUTOMAKE_OPTIONS = foreign ++ ++sbin_PROGRAMS = tac_plus ++bin_PROGRAMS = generate_passwd ++man_MANS = tac_plus.1 ++noinst_MANS = tac_regexp.3 ++tacacssysconfdir = $(sysconfdir)/tacacs ++tacacssysconf_DATA = tac_plus.cfg ++EXTRA_DIST = \ ++ CHANGES \ ++ README.LDAP \ ++ README.PAM \ ++ users_guide \ ++ tac_plus.spec.in \ ++ convert.pl \ ++ tac_plus.cfg \ ++ tac_plus.init \ ++ tac_plus.rotate \ ++ tac_plus.sql \ ++ tac_plus.pam \ ++ $(tacacssysconf_DATA) \ ++ $(man_MANS) \ ++ $(noinst_MANS) ++ ++generate_passwd_SOURCES = generate_passwd.c ++ ++tac_plus_SOURCES = \ ++ acct.c acct.h \ ++ authen.c authen.h \ ++ author.c author.h \ ++ cfgfile.c cfgfile.h \ ++ cfgeval.c cfgeval.h \ ++ choose_authen.c choose_authen.h \ ++ default_fn.c default_fn.h \ ++ default_v0_fn.c default_v0_fn.h \ ++ do_acct.c do_acct.h \ ++ do_author.c do_author.h \ ++ dump.c dump.h \ ++ enable.c enable.h \ ++ encrypt.c encrypt.h \ ++ expire.c expire.h \ ++ hash.c hash.h \ ++ main.c main.h \ ++ md5.c md5.h \ ++ packet.c packet.h \ ++ parse.c parse.h \ ++ programs.c programs.h \ ++ pw.c pw.h \ ++ pwlib.c pwlib.h \ ++ report.c report.h \ ++ sendauth.c sendauth.h \ ++ sendpass.c sendpass.h \ ++ time_limit.c time_limit.h \ ++ utils.c utils.h \ ++ mschap.h \ ++ tac_regmagic.h \ ++ tac_plus.h ++ ++EXTRA_tac_plus_SOURCES = $(cond) ++tac_plus_LDFLAGS = $(conf_LDFLAGS) ++# $(use_o) has to be BEFORE $(conf_LDADD)! (for library dependencies) ++tac_plus_LDADD = $(use_o) $(conf_LDADD) ++tac_plus_DEPENDENCIES = $(use_o) ++use = @COND_USE@ ++use_o = $(filter %.o,$(use:.c=.o)) ++ ++cond_DB = db.c db.h ++cond_DB_MYSQL = db_mysql.c db_mysql.h ++cond_DB_NULL = db_null.c db_null.h ++cond_DB_PGSQL = db_pgsql.c db_pgsql.h ++cond_USE_LDAP = ldap_author.c ldap_author.h ++cond_MAXSESS = maxsess.c maxsess.h ++cond_MSCHAP = md4.c md4.h ++cond_SKEY = skey_fn.c skey_fn.h ++cond_USE_PAM = tac_pam.c tac_pam.h ++cond_TCPWRAPPER = tcpwrap.c tcpwrap.h ++cond_WITH_INCLUDED_REGEX = \ ++ tac_regexp.c tac_regexp.h ++ ++cond = \ ++ $(cond_DB) \ ++ $(cond_DB_MYSQL) \ ++ $(cond_DB_NULL) \ ++ $(cond_DB_PGSQL) \ ++ $(cond_USE_LDAP) \ ++ $(cond_MAXSESS) \ ++ $(cond_MSCHAP) \ ++ $(cond_SKEY) \ ++ $(cond_USE_PAM) \ ++ $(cond_TCPWRAPPER) \ ++ $(cond_WITH_INCLUDED_REGEX) ++ ++ ++# These rules were not migrated from Makefile.in as I don't have ++# (and I don't know) 'purify' tool: ++# ++# 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) +diff --git a/README.LDAP b/README.LDAP +new file mode 100644 +index 0000000..755b19e +--- /dev/null ++++ b/README.LDAP +@@ -0,0 +1,146 @@ ++ LDAP Authentification with Tacacs+ ++ ---------------------------------- ++ ++ ++Author : Harpes Patrick (patrick.harpes@tudor.lu) ++ Jahnen Andreas (andreas.jahnen@tudor.lu) ++Date : 16.03.2001 ++ ++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. ++ ++ ++This document aim to describe how to perform LDAP authentification for tacacs+. ++ ++ ++Requirements: ++------------- ++ ++1) tac_plus.F4.0.3-v8.alpha.tar.gz ++ This package includes the original CISCO tacacs+ package from http://www.gazi.edu.tr/tacacs/ ++2) openldap package ++ This package has been developped using the openldap libraries version 2.0.7 ++ OpenLDAP is available from www.openldap.org ++3) GCC and other GNU developpment tools (make...) ++4) A running LDAP server (test has been made using Lotus Domino LDAP server version 5.0.x and ++ OpenLDAP) ++ ++Overview: ++--------- ++ ------------ ---------------- ++ - Server - - Notes DOMINO - ++ ---------------- - running -____LDAP____- LDAP Server - ++ - CISCO Dialup -__tacacs+_____- tacacs+ - - or - ++ - Router - - - - other LDAP - ++ ---------------- ------------ - Server - ++ --------------- ++ ++The CISCO router sends tacacs+ request to the tacacs+ server. This one uses the LDAP ++server to authentificate the user. ++ ++ ++HowTo configure the CISCO router? ++--------------------------------- ++ ++There are good documentations available on how to set up the CISCO router for using ++tacacs+. This documents can be found on the tacacs+ homepage http://www.gazi.edu.tr/tacacs/ ++ ++HowTo install the tacacs+ package with LDAP support? ++---------------------------------------------------- ++ ++To enable the LDAP support on the tacacs+ package, you have to perform the following steps: ++ ++ 1. Install the Open LDAP package (version 2.0.7) (www.openldap.org) ++ Refer to the INSTALL document to build this package. ++ ++ 2. Unpack the tacacs+ package in /usr/local/src ++ # tar -zxvf tac_plus.F4.0.3-v8.alpha.tar.gz ++ ++ 3. Use the configure script to create the Makefiles ++ ++ # cd /usr/local/src/tac_plus.F5.0.0.alpha/ ++ # ./configure --with-ldap ++ ++ You can use ./configure --help to get more options ++ ++ 4. Compile the package ++ ++ # make tac_plus ++ ++ 5. Set your LD_LIBRARY_PATH to include the LDAP libraries ++ ++ # LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH; export LD_LIBRARY_PATH ++ ++ ++HowTo configure tacacs+ for using the LDAP support ++-------------------------------------------------- ++ ++To use the LDAP authentification, use the following simple tacacs+ configuration file ++ ++ key = "your tacacs key" ++ accounting file = /var/log/tac-plus/tac_plus.log ++ default authentification = ldap "ldap://" ++ user=DEFAULT { ++ service = ppp protocol = ip { ++ } ++ } ++ ++ ++For more information on the configuration file please use the complete tacacs+ documentation. ++ ++ ++How to start the tacacs+ daemon ++------------------------------- ++ ++Make sure your LD_LIBRARY_PATH includes the LDAP libraries. ++As root, start the tacacs daemon: ++ # /usr/local/src/tac_plus.F4.0.3-v8.alpha/tac_plus -C tac_plus.cfg ++ ++ ++How to configure the LDAP server ++-------------------------------- ++ ++a) Notes Domino LDAP server ++--------------------------- ++ ++You have to enable the Domino server task "LDAP" with the Administration Tool. You ++can do this with the command "laod ldap" at the server console or with the help of ++the Tools Menu of the server tab (Tools -> Task -> Start "LDAP Server"). ++ ++You can define which attributes of your Domino Directory are accessible by ++anonymous users and if it is allowed to write to your Domino Directory using LDAP in ++a Configuration document. You have to specify "Use these settings as the default ++settings for all servers" in the Basic tab of the Configuration document to display ++the LDAP options tab. There you are able to adjust the settings for a your LDAP server. ++ ++For additional information see the IBM Red Book "Getting the most from your Domino ++Directory" (11/2000), which you can downlaod from http://www.redbooks.ibm.com. ++ ++ ++b) Open LDAP ++------------ ++ ++It is also possible to use OpenLDAP for this kind of authentification. Please look at ++the documentation at http://www.openldap.org for details how to install the server. ++ ++ ++Security ++--------- ++ ++The here described tacacs+ queries are not quering any of the fields stored in your LDAP ++server. We only try to log in and this is the "test" we perform here. ++ ++Pleae note that the passwords are not send encrypted. You have to make sure that it is ++not possible to sniff them. In general is there no support from tacacs+ to support encrypted ++passwords. ++It is maybe possible to use OpenLDAP with TLS support to encrypt the passwords and use a ++secure LDAP server. This is also supported by Domino and OpenLDAP. But this is not implemented. ++ ++Good luck, ++ ++ Harpes Patrick (patrick.harpes@tudor.lu) and Jahnen Andreas (andreas.jahnen@tudor.lu) +diff --git a/README.PAM b/README.PAM +index 0c1d468..7cbd4b4 100644 +--- a/README.PAM ++++ b/README.PAM +@@ -36,5 +36,3 @@ into tac_plus.conf. + + + Max Liccardo +- +- +diff --git a/acconfig.h b/acconfig.h +new file mode 100644 +index 0000000..72e7998 +--- /dev/null ++++ b/acconfig.h +@@ -0,0 +1,97 @@ ++/* acconfig.h ++ * ++ * $Id$ ++ */ ++ ++#ifndef TAC_PLUS_CONFIG_H ++#define TAC_PLUS_CONFIG_H 1 ++ ++@TOP@ ++ ++ ++/* --maintainer-mode ++ * Sets "/etc/tacacs/tac_plus.cfg" as default config file ++ */ ++#undef MAINTAINER_MODE ++ ++/* Missing socklen_t in ++ * We don't use 'typedef' to not to yet require included here. ++ */ ++#undef socklen_t ++ ++/* 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 ++ ++/* Check for some fields in /struct passwd and /struct utmp ++ */ ++#undef HAVE_PASSWD_PW_AGE ++#undef HAVE_PASSWD_PW_COMMENT ++#undef HAVE_UTMP_UT_HOST ++ ++/* All OSes detected by configure.in: ++ */ ++#undef LINUX ++#undef GLIBC ++#undef SOLARIS ++#undef FREEBSD ++#undef HPUX ++#undef AIX ++#undef MIPS ++ ++/* --with-pam */ ++#undef USE_PAM ++/* --with-ldap */ ++#undef USE_LDAP ++/* --with-db */ ++#undef DB ++/* --with-db */ ++#undef DB_NULL ++/* --with-mysql */ ++#undef DB_MYSQL ++/* --with-pgsql */ ++#undef DB_PGSQL ++/* --enable-maxsess */ ++#undef MAXSESS ++/* --with-libwrap */ ++#undef TCPWRAPPER ++/* --with-skey */ ++#undef SKEY ++/* --with-mschap[=des] */ ++#undef MSCHAP ++#undef MSCHAP_DES ++/* --with-tac[ug]id */ ++#undef TACPLUS_USERID ++#undef TACPLUS_GROUPID ++/* --with-tacplus_pid */ ++#undef TACPLUS_PIDFILE ++/* --with-included-regex */ ++#undef WITH_INCLUDED_REGEX ++ ++ ++@BOTTOM@ ++ ++/* Keep in sync with configure.in */ ++#define _XOPEN_SOURCE 1 /* for unistd.h/crypt() */ ++#define _XOPEN_SOURCE_EXTENDED 1 /* for pwd.h/{set,end}pwent(), sys/wait.h/wait3() */ ++#define _BSD_SOURCE 1 /* for u_{char,short,int} & string.h/bcopy() */ ++#define _OSF_SOURCE 1 /* for u_{char,short,int} (on Alpha OSF1) */ ++#define __EXTENSIONS__ 1 /* for u_{char,short,int} (on Sparc Solaris) */ ++ ++#if SIZEOF_UNSIGNED_SHORT == 4 ++typedef unsigned short tac_uint32; ++#else ++#if SIZEOF_UNSIGNED_INT == 4 ++typedef unsigned int tac_uint32; ++#else ++#if SIZEOF_UNSIGNED_LONG == 4 ++typedef unsigned long tac_uint32; ++#else ++#error "Unable to find 32-bit unsigned int for TAC_UINT32 type" ++#endif /* SIZEOF_UNSIGNED_LONG */ ++#endif /* SIZEOF_UNSIGNED_INT */ ++#endif /* SIZEOF_UNSIGNED_SHORT */ ++ ++ ++#endif /* TAC_PLUS_CONFIG_H */ +diff --git a/acct.c b/acct.c +index 315064f..2145cef 100644 +--- a/acct.c ++++ b/acct.c +@@ -17,18 +17,37 @@ + FITNESS FOR A PARTICULAR PURPOSE. + */ + ++ + #include "tac_plus.h" + ++#include ++#include /* for ntohl() */ ++ ++#include "acct.h" ++#include "report.h" ++#include "packet.h" ++#include "utils.h" ++#include "do_acct.h" ++#include "main.h" ++#include "do_author.h" /* for "struct identity" */ ++#include "cfgfile.h" ++ ++#ifdef MAXSESS ++#include "maxsess.h" ++#endif ++#ifdef DB ++#include "db.h" ++#endif ++ ++ ++static void account TAC_ARGS((u_char *pak)); ++ ++ + /* + * Come here when we receive an Start Accounting packet + */ + +-void account(); +- +-/* For DB accounting */ +-#ifdef DB +-int db_acct(); +-#endif /* DB */ ++void accounting TAC_ARGS((u_char *pak)); + + void + accounting(pak) +@@ -37,8 +56,8 @@ u_char *pak; + struct acct *acct_pak; + u_char *p; + HDR *hdr; +- u_char *read_packet(); +- int i, len; ++ int i; ++ unsigned long len; + + if (debug & DEBUG_ACCT_FLAG) + report(LOG_DEBUG, "Start accounting request"); +@@ -59,7 +78,7 @@ u_char *pak; + len += p[i]; + } + +- if (len != ntohl(hdr->datalength)) { ++ if (len != (unsigned long) ntohl(hdr->datalength)) { + send_error_reply(TAC_PLUS_ACCT, NULL); + return; + } +@@ -69,7 +88,9 @@ u_char *pak; + free(pak); + } + +-void ++static void account TAC_ARGS((u_char *pak)); ++ ++static void + account(pak) + u_char *pak; + { +@@ -123,6 +144,8 @@ u_char *pak; + + identity.priv_lvl = acct_pak->priv_lvl; + ++ cfg_request_identity(&identity); ++ + rec.identity = &identity; + + /* Now process cmd args */ +diff --git a/acct.h b/acct.h +new file mode 100644 +index 0000000..0640ae6 +--- /dev/null ++++ b/acct.h +@@ -0,0 +1,12 @@ ++#ifndef ACCT_H ++#define ACCT_H 1 ++ ++#include "tac_plus.h" ++ ++#include /* for u_* */ ++ ++ ++extern void accounting TAC_ARGS((u_char *pak)); ++ ++ ++#endif /* ACCT_H */ +diff --git a/authen.c b/authen.c +index a12745e..7c873fe 100644 +--- a/authen.c ++++ b/authen.c +@@ -17,16 +17,43 @@ + FITNESS FOR A PARTICULAR PURPOSE. + */ + ++ + #include "tac_plus.h" + +-static int choose(); +-static void authenticate(); +-static void do_start(); ++#include ++#include /* for ntohl() */ ++ ++#include "authen.h" ++#include "packet.h" ++#include "report.h" ++#include "utils.h" ++#include "choose_authen.h" ++#include "do_author.h" /* for "struct identity" */ ++#include "main.h" ++#include "cfgfile.h" ++ ++#ifdef TCPWRAPPER ++#include "tcpwrap.h" ++#endif ++ ++ ++static void do_start TAC_ARGS((u_char *pak)); ++static int choose TAC_ARGS((struct authen_data *datap, struct authen_type *typep)); ++static void authenticate TAC_ARGS((struct authen_data *datap, struct authen_type *typep)); ++ ++ ++/* Configurable: ++ */ ++ ++#define TAC_PLUS_MAX_ITERATIONS 50 ++ + + /* + * Come here when we receive an authentication START packet + */ + ++void authen TAC_ARGS((u_char *pak)); ++ + void + authen(pak) + u_char *pak; +@@ -39,9 +66,9 @@ u_char *pak; + start = (struct authen_start *) (pak + TAC_PLUS_HDR_SIZE); + + if ((hdr->seq_no != 1) || +- (ntohl(hdr->datalength) != TAC_AUTHEN_START_FIXED_FIELDS_SIZE + ++ ((unsigned long) ntohl(hdr->datalength) != (unsigned long)(TAC_AUTHEN_START_FIXED_FIELDS_SIZE + + start->user_len + start->port_len + start->rem_addr_len + +- start->data_len)) { ++ start->data_len))) { + send_authen_error("Invalid AUTHEN/START packet (check keys)"); + return; + } +@@ -65,6 +92,8 @@ u_char *pak; + * attempt to authenticate. + */ + ++static void do_start TAC_ARGS((u_char *pak)); ++ + static void + do_start(pak) + u_char *pak; +@@ -109,6 +138,8 @@ u_char *pak; + + identity.priv_lvl = start->priv_lvl; + ++ cfg_request_identity(&identity); ++ + /* The authen_data structure */ + + bzero(&authen_data, sizeof(struct authen_data)); +@@ -184,6 +215,8 @@ send_authen_error("You are not allowed to access here"); + /* 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 TAC_ARGS((struct authen_data *datap, struct authen_type *typep)); ++ + static int + choose(datap, typep) + struct authen_data *datap; +@@ -293,6 +326,8 @@ struct authen_type *typep; + /* NOTREACHED */ + } + ++static void authenticate TAC_ARGS((struct authen_data *datap, struct authen_type *typep)); ++ + /* Perform authentication assuming we have successfully chosen an + authentication method */ + static void +@@ -303,7 +338,7 @@ struct authen_type *typep; + int iterations = 0; + u_char *reply, *p; + struct authen_cont *cont; +- int (*func) (); ++ int (*func) TAC_ARGS((struct authen_data *data)); + + if (debug & DEBUG_PACKET_FLAG) + report(LOG_DEBUG, "Calling authentication function"); +@@ -469,4 +504,3 @@ struct authen_type *typep; + /* NOTREACHED */ + } + } +- +diff --git a/authen.h b/authen.h +new file mode 100644 +index 0000000..d89d9cc +--- /dev/null ++++ b/authen.h +@@ -0,0 +1,12 @@ ++#ifndef AUTHEN_H ++#define AUTHEN_H 1 ++ ++#include "tac_plus.h" ++ ++#include /* for u_* */ ++ ++ ++extern void authen TAC_ARGS((u_char *pak)); ++ ++ ++#endif /* AUTHEN_H */ +diff --git a/author.c b/author.c +index a9f2277..7a05db5 100644 +--- a/author.c ++++ b/author.c +@@ -17,12 +17,27 @@ + FITNESS FOR A PARTICULAR PURPOSE. + */ + ++ + #include "tac_plus.h" + ++#include ++#include /* for ntohl() */ ++ ++#include "author.h" ++#include "report.h" ++#include "packet.h" ++#include "utils.h" ++#include "do_author.h" ++#include "main.h" ++#include "cfgfile.h" ++ ++ + /* + * Come here when we receive an authorization START packet + */ + ++void author TAC_ARGS((u_char *pak)); ++ + void + author(pak) + u_char *pak; +@@ -34,7 +49,8 @@ u_char *pak; + u_char *p; + u_char *argsizep; + char **cmd_argp; +- int i, len; ++ int i; ++ unsigned long len; + + if (debug & DEBUG_AUTHOR_FLAG) + report(LOG_DEBUG, "Start authorization request"); +@@ -58,7 +74,7 @@ u_char *pak; + len += p[i]; + } + +- if (len != ntohl(hdr->datalength)) { ++ if (len != (unsigned long) ntohl(hdr->datalength)) { + send_error_reply(TAC_PLUS_AUTHOR, NULL); + return; + } +@@ -95,6 +111,8 @@ u_char *pak; + + identity.priv_lvl = apak->priv_lvl; + ++ cfg_request_identity(&identity); ++ + /* The author_data structure */ + + author_data.id = &identity; /* user id */ +diff --git a/author.h b/author.h +new file mode 100644 +index 0000000..0e3a2ce +--- /dev/null ++++ b/author.h +@@ -0,0 +1,12 @@ ++#ifndef AUTHOR_H ++#define AUTHOR_H 1 ++ ++#include "tac_plus.h" ++ ++#include /* for u_* */ ++ ++ ++extern void author TAC_ARGS((u_char *pak)); ++ ++ ++#endif /* AUTHOR_H */ +diff --git a/autogen b/autogen +new file mode 100755 +index 0000000..83862fd +--- /dev/null ++++ b/autogen +@@ -0,0 +1,47 @@ ++#! /bin/sh ++# ++# autogen ++# ++# Copyright (C) 1999, 2000, 2001 ++# Partition Surprise Team ++# ++# $Id$ ++# ++ ++ ++# Run this to generate all the initial makefiles, etc. ++ ++set -e ++if test "x$1" = "xBASH" ;then ++ shift ++else if test "x$BASH" = "x" ;then ++ for trypath in `echo "$PATH"|tr : ' '` /usr/local/bin;do ++ if test -x "$trypath/bash";then ++ "$trypath/bash" "$0" BASH "$@" ++ exit $? ++ fi ++ done ++ echo "ERROR: Unable to find 'bash' interpreter needed for self-execution!" ++ exit 1 ++fi;fi ++ ++defaultCONFDEFS="" # --enable-debug --without-efence ++project="tac_plus" ++automake_gnu=false ++want_tarZ=false ++want_gettext=false ++want_libtool=false ++#upload="vellum.cz:WWW/sw/" ++docdir="" ++subdirs="" ++ ++CLEAN_LOCAL=" ++ .print_userprogs ++ tac_plus ++ generate_passwd ++" ++ ++ARGS_HELP_LOCAL="\ ++" ++ ++source ./autogen-body +diff --git a/autogen-body b/autogen-body +new file mode 100644 +index 0000000..6856444 +--- /dev/null ++++ b/autogen-body +@@ -0,0 +1,259 @@ ++# ++# autogen-body ++# ++# Placed into the public domain by ++# Partition Surprise Team ++# ++# $Id$ ++# ++ ++ ++# Executable code to be included from ./autogen script ++ ++# Expected variables: ++# defaultCONFDEFS, project, want_tarZ, upload, docdir, subdirs, CLEAN_LOCAL ++# function PREP_LOCAL ++ ++set -e ++t=/tmp/autogen.$$ ++autogen_failed=true ++cleaup_dir="$PWD" ++automake_reqd="" ++function cleanup ++{ ++ cd "$cleanup_dir" ++ rm -f "$t.*" ++ if $automake_gnu;then for i in $automake_reqd;do if [ '!' -s "$i" ];then rm -f "$i";fi;done;fi ++} ++EXITmsg_do=true ++trap ' ++ cleanup ++ if $autogen_failed;then ++ if $EXITmsg_do;then ++ echo -e "\n$0 failed! Try the following command to debug it: set -x" ++ EXITmsg_do=false ++ fi ++ exit 1 ++ fi ++ ' EXIT ++ ++if [ "$1" = help -o "$1" = -h -o "$1" = --help ];then cat </dev/null;then ++local func_exit=false ++ ++ "$func" "$@" ++ if $func_exit;then exit;fi ++ fi ++} ++funcdo ARGS_LOCAL "$@" ++ ++if expr match "$1" "rpm" >/dev/null;then ++ builds="/usr/src/redhat /usr/src/packages" ++ for b in $builds X;do ++ if test -d $b;then break;fi ++ done ++ if [ $b = X ];then ++ echo Build directory not reachable, searched: $builds ++ exit 1 ++ fi ++ rm -r -f /var/tmp/$project-*-root $b/BUILD/$project-* ++ specsrc="$project.spec.m4.in" ++ if [ '!' -f "$specsrc" ];then specsrc="$project.spec.in";fi ++ CONFDEFS="`awk '/^(.*)\\$/{x=x$1" ";next}{print x$0;x=""}' <$specsrc \ ++ |sed -n 's/^.*\.\/configure \(.*\)$/\1/p'`" ./autogen copy ++ make dist $project.spec ++ cp $project-*.tar.gz $b/SOURCES ++ if [ "$1" = "rpmtest" ];then SIGNIT=;else SIGNIT=--sign;fi ++ rpm -ba $SIGNIT $project.spec ++ if $want_tarZ;then make dist-tarZ;fi ++ rm $b/SOURCES/$project-*.tar.gz ++ mv $b/SRPMS/$project-*.src.rpm . ++ mv $b/RPMS/i386/$project-*.i386.rpm . ++ ls -l $project-* ++ if [ "$1" = rpmup ];then ++ echo "Uploading $[`cat $project-*|wc -c`] bytes..." ++ if [ -n "$upload" ];then ++ scp -v $project-* "$upload" ++ else ++ echo "Upload not done." ++ fi ++ fi ++ autogen_failed=false ++ exit ++fi ++ ++function subdo ++{ ++ for i in _ $subdirs;do ++ if [ -d $i ];then ++ cd "$i" ++ ./autogen $subdir_args ++ cd .. ++ fi ++ done ++} ++subdir_args="${*:-dist}" ++if [ "$subdir_args" = "copy" ];then ++ subdir_args="copy dist" ++ fi ++ ++# maintainer-clean hack is not safe, please list all files for 'rm'. ++# When the filename doesn't contain '/', it is applied to ALL directories. ++# Please note that files exactly in root dir MUST have ./ in the front ++# (to not to be considered as ALL-directories files). ++ ++CLEANFILES=" ++ *~ .#* ++ *.orig *.rej ++ core ++ Makefile Makefile.in ++ TAGS tags ID ++ .deps .libs ++ *.[oa] *.l[oa] ++ ++ ./errs* ++ ./intl ++ ./configure ./configure.scan ++ ./config.guess ./config.status ./config.sub ./config.log ./config.cache ++ ./config.h ./config.h.in ++ ./confdefs.h ./conftest* ./autoh[0-9]* ./confcache ++ ./stamp-h ./stamp-h.in ++ ./install-sh ++ ./aclocal.m4 ++ ./missing ++ ./mkinstalldirs ++ ./libtool ./ltconfig ./ltmain.sh ++ ./ChangeLog ++ ./ABOUT-NLS ./COPYING ++ ./$project-[0-9]* ./$project-devel-[0-9]* ++ ./$project.spec ./$project.m4 ./$project.spec.m4 ++ macros/macros.dep ++ po/Makefile.in.in po/POTFILES* po/cat-id-tbl.c po/cat-id-tbl.tmp po/*.gmo po/*.mo po/stamp-cat-id po/$project.pot ++ ++ $CLEAN_LOCAL ++ " ++if [ -n "$docdir" ];then ++CLEANFILES=" ++ $docdir/*.html ++ $docdir/*.info* ++ $docdir/*.txt ++ $docdir/*.tex ++ $docdir/*.sgml ++ $CLEANFILES" ++ fi ++if [ "$1" != sym ];then CLEANFILES="$CLEANFILES `find . -type l`";fi ++CLEANFILES="`echo "$CLEANFILES"|tr ' ' '\n'|sed 's,^\./\(.*/.*\)$,\1,'|sort|uniq|grep -v '^ *$'`" ++true >"$t.find" ++rm -f "$t.discard" ++echo "$CLEANFILES"|while read -r fi;do ++ if [ "$fi" != "${fi#*/}" ];then ++ echo "$fi" >>"$t.discard" ++ else echo "-o -name $fi" >>"$t.find" ++ fi;done ++for dirpatt in `(find . -type d '!' \( -name CVS $(sed 's,[]*?[],\\&,g' <"$t.find") \)|sort|uniq;cat "$t.discard")|sort|uniq -u`;do ++ for dir in $dirpatt;do if test -d $dir;then ++ (cd $dir ++ (echo "$CLEANFILES" #ALL-dir files ++ echo "$CLEANFILES"|sed -n "s,^\\$(echo $dir|sed 's,^\./,,')/,,p" #THIS-dir files ++ echo .cvsignore #MUST be last! ++ )|grep -v / >.cvsignore ++ if [ "$1" = "${1#tar}" ];then ++ rm -rf ` ++ if [ "$1" = fullclean ];then cat .cvsignore ++ else grep -v '^\.cvsignore' <.cvsignore ++ fi` ++ fi ++ ) ++ fi;done ++ done ++rm -f "$t.find" "$t.discard" ++if [ "$1" = tarprep ];then ++ autogen_failed=false ++ exit ++ fi ++if [ "$1" = tar ];then ++ subdir_args=tarprep ++ subdo ++ mydir="$(basename `pwd`)" ++ cd .. ++ tar cf - \ ++ $(for fi in `find "$mydir" -name .cvsignore`;do sed "s,^,--exclude=`dirname $fi`/," <$fi;done) \ ++ "$mydir" ++ autogen_failed=false ++ exit ++ fi ++if [ "$1" != "${1#*clean}" ];then ++ subdo ++ autogen_failed=false ++ exit 0 ++ fi ++ ++funcdo PREP_LOCAL "$@" ++subdo ++ ++if [ "$1" = copy ];then COPY=--copy;shift ++else unset COPY|cat # |cat construction is used to not fail in "set -e" state ++fi ++ ++if test -d po;then ++ touch po/POTFILES.in ++fi ++aclocal_opts="" ++if test -d macros;then ++ aclocal_opts="-I macros $aclocal_opts" ++fi ++aclocal $aclocal_opts ++if $want_gettext;then ++ gettextize $COPY ++ rm -f aclocal.m4 # We delete created aclocal.m4 as it's just bug in gettextize. It shouldn't link that file here ++ aclocal $aclocal_opts # gettextize made some changes of files which need to be reflected ++fi ++if $want_libtool;then ++ libtoolize $COPY ++fi ++autoheader ++automake_opts="" ++if $automake_gnu;then ++ automake_reqd="$automake_reqd ChangeLog README" ++ automake_opts="$automake_opts --gnu" ++ for i in $automake_reqd;do if [ '!' -f "$i" ];then touch "$i";fi;done ++fi ++automake --add-missing $COPY $automake_opts ++cleanup ++autoheader ++autoconf ++ ++if [ "$1" != dist ];then ++ # shared/static switching cannot be based on maintainer-mode in configure ++ ./configure --enable-maintainer-mode --enable-shared --disable-static $CONFDEFS ++ fi ++ ++autogen_failed=false +diff --git a/cfgeval.c b/cfgeval.c +new file mode 100644 +index 0000000..f0aed72 +--- /dev/null ++++ b/cfgeval.c +@@ -0,0 +1,1900 @@ ++/* ++ * ???(): ++ * If you belong to the group where do you belong only in the case you belong ++ * in that group, do you in fact belong to that group or don't? ++ * I've decided it is safer to decide that you DON'T belong to such group. ++ * ++ * expr_eval_notify_expr_remove(): ++ * If someone was interested in what am I like but she is no longer ++ * interested in what am I like, because she already knows what is she like, ++ * then she should tell it me, as although I still may not know what am I ++ * like then in the case she was the last who wanted to know what am I like, ++ * I should tell everyone who I didn't know what they are like and I wanted ++ * to know what they are like that I no longer want to know what they are ++ * like, because it is no longer needed to know what am I like about myself. ++ * ++ * membership_eval_immediate(): ++ * It is important to not only know, what do you want to know, but it is also ++ * important to know what do you now know but you still didn't utilize it. ++ */ ++ ++ ++#include "tac_plus.h" ++ ++#include ++ ++#include "cfgeval.h" ++#include "cfgfile.h" ++#include "main.h" ++#include "parse.h" ++#include "report.h" ++#include "hash.h" ++ ++ ++/* Configurable: ++ */ ++ ++/* Whether to do sanity checks */ ++#define SCAN_PARANOIA 1 ++ ++/* report even no-op scan up-to-date checks */ ++#define REPORT_CHECK_SCAN_VERBOSE 0 ++ ++ ++static void check_request_scan_membership TAC_ARGS((struct membership *membership, int flush)); ++static void check_eval_scan_membership TAC_ARGS((struct membership *membership, int flush)); ++static void check_eval_scan_entity TAC_ARGS((ENTITY *entity, int flush)); ++static void check_request_scan_expr TAC_ARGS((struct expr *expr, int flush)); ++static void check_eval_scan_expr TAC_ARGS((struct expr *expr, int flush)); ++ ++ ++static unsigned request_scan_seq = 0; ++static unsigned value_scan_seq = 0; ++static unsigned eval_scan_seq = 0; ++int request_scan_user_known; ++static struct tac_list eval_kicked_entity_list; ++static struct tac_list eval_destroy_entity_list; ++static struct tac_list eval_notified_expr_list; ++static struct tac_list request_virtual_membership_list; ++ ++ ++/* 'P[FA]_*' object printing section: ++ */ ++ ++static const char *eval_result_to_string TAC_ARGS((int valid, enum eval_result er)); ++ ++const char * ++eval_result_to_string(valid, er) ++int valid; ++enum eval_result er; ++{ ++ if (!valid) ++ return ("!UPDATED"); ++ ++ switch (er) { ++ case ER_TRUE: return ("ER_TRUE" ); ++ case ER_FALSE: return ("ER_FALSE" ); ++ case ER_UNKNOWN: return ("ER_UNKNOWN" ); ++ default: return ("*** INVALID ***"); ++ } ++ /* NOTREACHED */ ++} ++ ++static const char *value_scan_func_result_to_string TAC_ARGS((enum value_scan_func_result vsfr)); ++ ++const char * ++value_scan_func_result_to_string(vsfr) ++enum value_scan_func_result vsfr; ++{ ++ switch (vsfr) { ++ case VSFR_CONTINUE: return ("VSFR_CONTINUE"); ++ case VSFR_FOUND: return ("VSFR_FOUND" ); ++ case VSFR_STOP: return ("VSFR_STOP" ); ++ default: return ("*** INVALID ***"); ++ } ++ /* NOTREACHED */ ++} ++ ++/* These macros are VERY big overhead but it's primary negative effect ++ * is the size of executable. I've considered it as not much interesting, ++ * CPU overhead is hit only when DEBUG_CFGEVAL_FLAG (also not interesting). ++ */ ++ ++#define PF_VSFR "%s" ++#define PA_VSFR(vsfr) (value_scan_func_result_to_string((vsfr))) ++ ++#define PF_RESULT "%s" ++#define PA_RESULT(result) (eval_result_to_string(1 /* valid */, (result))) ++ ++#define PF_ERESULT_struct PF_RESULT ++#define PA_ERESULT_struct(entity, field) \ ++ (!(entity) ? "*NULL*(=ER_TRUE)" : \ ++ (eval_result_to_string((entity)->request_scan.seq == request_scan_seq, (entity)->request_scan.field))) ++#define PA_ERESULT_struct_NULL "*NULL*" ++ ++#define PF_ERESULT_EXPR PF_ERESULT_struct ++#define PA_ERESULT_EXPR(expr) PA_ERESULT_struct((expr), result) ++#define PF_ERESULT_ENTITY PF_ERESULT_struct ++#define PA_ERESULT_ENTITY(entity) PA_ERESULT_struct((entity), belongs) ++#define PF_ERESULT_MEMBERSHIP PF_ERESULT_struct ++#define PA_ERESULT_MEMBERSHIP(membership) (!(membership) ? PA_ERESULT_struct_NULL : PA_ERESULT_EXPR((membership)->when)) ++ ++#define PF_EXPR_ "expr@%d{%s " PF_MEMBERSHIP "}" ++#define PA_EXPR_(expr) (!(expr) ? 0 : (expr)->line), \ ++ (!(expr) ? "*NULL*" : "of"), \ ++ PA_MEMBERSHIP((!(expr) ? NULL : (expr)->membership)) ++ ++#define PF_MEMBERSHIP_ "membership{child=" PF_ENTITY ",parent=" PF_ENTITY "}" ++#define PA_MEMBERSHIP_(membership) PA_ENTITY((!(membership) ? NULL : MEMBERSHIP_TO_CHILD_ENTITY( (membership)))), \ ++ PA_ENTITY((!(membership) ? NULL : MEMBERSHIP_TO_PARENT_ENTITY((membership)))) ++ ++#define PF_ENTITY_ "{%s@%d \"%s\"}" ++#define PA_ENTITY_(entity) (!(entity) ? "*NULL* entity" : entity_type_to_string((entity)->type)), \ ++ (!(entity) ? 0 : (entity)->line), \ ++ (!(entity) ? "*NULL*" : (entity)->name) ++ ++#define PF_EXPR PF_EXPR_ "=" PF_ERESULT_EXPR ++#define PA_EXPR(expr) PA_EXPR_(expr), PA_ERESULT_EXPR(expr) ++#define PF_MEMBERSHIP PF_MEMBERSHIP_ "=" PF_ERESULT_MEMBERSHIP ++#define PA_MEMBERSHIP(membership) PA_MEMBERSHIP_(membership), PA_ERESULT_MEMBERSHIP(membership) ++#define PF_ENTITY PF_ENTITY_ "=" PF_ERESULT_ENTITY ++#define PA_ENTITY(entity) PA_ENTITY_(entity), PA_ERESULT_ENTITY(entity) ++ ++ ++/* '{unlink,free}_*()' section: ++ */ ++ ++void unlink_expr TAC_ARGS((struct expr *expr)); ++ ++/* never depend on "->parent" as it may not yet been filled! */ ++void ++unlink_expr(expr) ++struct expr *expr; ++{ ++ if (!expr) ++ return; /* prevent possible DEBUG_CLEAN_FLAG report */ ++ ++ if (debug & DEBUG_CLEAN_FLAG) { ++ if (expr->membership) ++ report(LOG_DEBUG, "unlink_expr: (of membership): " PF_EXPR, ++ PA_EXPR(expr)); ++ else ++ report(LOG_DEBUG, "unlink_expr: (standalone)"); ++ } ++ ++ while (expr) { ++ ++ /* We need to at least unlink "eval_scan->u.entity.notify_expr_node": */ ++ check_request_scan_expr(expr, 1); /* invalidate */ ++ check_eval_scan_expr(expr, 1); /* invalidate */ ++ ++ switch (expr->type) { ++ ++ case S_not: ++ unlink_expr(expr->u.not.child); ++ break; ++ ++ case S_and: ++ case S_or: ++ unlink_expr(expr->u.and_or.child_first); ++ break; ++ ++ case S_user: ++ case S_host: ++ case S_group: ++ break; ++ ++ default: ++ report(LOG_ERR, "Illegal node type %d for unlink_expr", expr->type); ++ return; ++ } ++ ++ expr = expr->next; ++ } ++} ++ ++void free_expr TAC_ARGS((struct expr *expr)); ++ ++/* given 'expr' memory WILL be freed! */ ++/* never depend on "->parent" as it may not yet been filled! */ ++void ++free_expr(expr) ++struct expr *expr; ++{ ++struct expr *expr_next; ++ ++ if (!expr) ++ return; /* prevent possible DEBUG_CLEAN_FLAG report */ ++ ++ if (debug & DEBUG_CLEAN_FLAG) ++ report(LOG_DEBUG, "free_expr: (may be unlinked)"); ++ ++ while (expr) { ++ expr_next = expr->next; ++ switch (expr->type) { ++ ++ case S_not: ++ free_expr(expr->u.not.child); ++ break; ++ ++ case S_and: ++ case S_or: ++ free_expr(expr->u.and_or.child_first); ++ break; ++ ++ case S_user: ++ case S_host: ++ case S_group: ++ if (expr->u.entity.name) ++ free((/* de-const */ char *)expr->u.entity.name); ++ /* "expr->u.entity.entity" will be freed from elsewhere */ ++ break; ++ ++ default: ++ report(LOG_ERR, "Illegal node type %d for free_expr", expr->type); ++ return; ++ } ++ ++ free(expr); ++ expr=expr_next; ++ } ++} ++ ++static void unlink_membership TAC_ARGS((struct membership *membership)); ++ ++static void ++unlink_membership(membership) ++struct membership *membership; ++{ ++ ENTITY *parent_entity; ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "unlink_membership: " PF_MEMBERSHIP, ++ PA_MEMBERSHIP(membership)); ++ ++ parent_entity = MEMBERSHIP_TO_PARENT_ENTITY(membership); ++ ++ /* 'unlink_expr()' may want a lot of existing (linked) resources */ ++ unlink_expr(membership->when); ++ ++ check_request_scan_membership(membership, 1); /* invalidate */ ++ check_eval_scan_membership(membership, 1); /* invalidate */ ++ ++#ifdef SCAN_PARANOIA ++ if (!parent_entity->to_child_membership_num) { ++ report(LOG_ERR, "INTERNAL: to_child_membership_num-- == 0 in unlink_membership"); ++ parent_entity->to_child_membership_num++; ++ } ++#endif ++ parent_entity->to_child_membership_num--; ++ ++ tac_list_node_remove(&membership->parent_node); ++ tac_list_node_remove(&membership-> child_node); ++} ++ ++/* given 'membership' memory WILL be freed! */ ++ ++static void free_membership TAC_ARGS((struct membership *membership)); ++ ++static void ++free_membership(membership) ++struct membership *membership; ++{ ++ if (debug & DEBUG_CLEAN_FLAG) ++ report(LOG_DEBUG, "free_membership: (may be unlinked)"); ++ ++ free_expr(membership->when); ++ free(membership); ++} ++ ++/* we are not allowed to free memory here, we are only 'scan_' additional 'free' */ ++ ++void scan_free_entity TAC_ARGS((ENTITY *entity)); ++ ++void ++scan_free_entity(entity) ++ENTITY *entity; ++{ ++ struct tac_list_node *parent_membership_node, *next_parent_membership_node; ++ struct membership *parent_membership; ++ struct tac_list_node *child_membership_node, *next_child_membership_node; ++ struct membership *child_membership; ++ ++ if (debug & DEBUG_CLEAN_FLAG) ++ report(LOG_DEBUG, "scan_free_entity: " PF_ENTITY, ++ PA_ENTITY(entity)); ++ ++ /* Be careful to keep '->next' ptr before we destroy the structure! */ ++ ++ for ( ++ parent_membership_node = tac_list_first_node(&entity->to_child_membership_list); ++ parent_membership_node; ++ parent_membership_node = next_parent_membership_node ++ ) { ++ parent_membership = PARENT_NODE_TO_MEMBERSHIP(parent_membership_node); ++ next_parent_membership_node = tac_list_node_next(parent_membership_node); ++ unlink_membership(parent_membership); ++ free_membership(parent_membership); ++ } ++ for ( ++ child_membership_node = tac_list_first_node(&entity->to_parent_membership_list); ++ child_membership_node; ++ child_membership_node = next_child_membership_node ++ ) { ++ child_membership = CHILD_NODE_TO_MEMBERSHIP(child_membership_node); ++ next_child_membership_node = tac_list_node_next(child_membership_node); ++ unlink_membership(child_membership); ++ free_membership(child_membership); ++ } ++} ++ ++struct expr *new_expr TAC_ARGS((int type)); ++ ++struct expr * ++new_expr(type) ++int type; ++{ ++ struct expr *expr; ++ ++ expr = (struct expr *) tac_malloc(sizeof(struct expr)); ++ expr->next = NULL; ++ expr->type = type; ++ switch (expr->type) { ++ ++ case S_not: ++ expr->u.not.child = NULL; ++ break; ++ ++ case S_and: ++ case S_or: ++ expr->u.and_or.child_first = NULL; ++ break; ++ ++ case S_user: ++ case S_host: ++ case S_group: ++ expr->u.entity.entity = NULL; ++ break; ++ ++ default: ++ report(LOG_ERR, "Illegal node type %d for new_expr", expr->type); ++ return (expr); /* it would be probably lethal to return NULL */ ++ } ++ return (expr); ++} ++ ++static int expr_sink_internal TAC_ARGS((struct expr *expr, struct membership *membership, struct expr *parent)); ++ ++static int ++expr_sink_internal(expr, membership, parent) ++struct expr *expr; ++struct membership *membership; ++struct expr *parent; ++{ ++ for (;expr; expr=expr->next) { ++ expr->membership = membership; ++ expr->parent = parent; ++ expr->request_scan.seq = request_scan_seq-1; ++ expr-> eval_scan.seq = eval_scan_seq-1; ++ switch (expr->type) { ++ ++ case S_not: ++ if (expr_sink_internal(expr->u.not.child, membership, expr /* parent */)) ++ return (1); ++ break; ++ ++ case S_and: ++ case S_or: ++ if (expr_sink_internal(expr->u.and_or.child_first, membership, expr /* parent */)) ++ return (1); ++ break; ++ ++ case S_user: ++ case S_host: ++ case S_group: ++ tac_list_node_init(&expr->eval_scan.u.entity.notify_expr_node); ++ expr->u.entity.entity = entity_lookup(expr->type, expr->u.entity.name); ++ if (!expr->u.entity.entity && expr->type == S_host) { ++ expr->u.entity.entity = new_entity(expr->type, (char *)expr->u.entity.name, expr->line); ++ if (!expr->u.entity.entity) ++ return (1); ++ expr->u.entity.name = NULL; ++ } ++ if (!expr->u.entity.entity) { ++ report(LOG_ERR, "referenced entity %s %s not found on line %d", ++ entity_type_to_string(expr->type), expr->u.entity.name, expr->line); ++ return (1); ++ } ++ /* already NULLed for not-yet-existing S_host */ ++ free((char *) expr->u.entity.name); ++ expr->u.entity.name = NULL; ++ break; ++ ++ default: ++ report(LOG_ERR, "Illegal node type %d for expr_sink", expr->type); ++ return (1); ++ } ++ } ++ return (0); ++} ++ ++static int expr_sink TAC_ARGS((struct expr *expr, struct membership *membership)); ++ ++static int ++expr_sink(expr, membership) ++struct expr *expr; ++struct membership *membership; ++{ ++ return (expr_sink_internal(expr, membership, NULL /* parent */)); ++} ++ ++static struct expr *expr_sink_register_head = NULL; ++ ++void expr_sink_register TAC_ARGS((struct expr *expr)); ++ ++void ++expr_sink_register(expr) ++struct expr *expr; ++{ ++ if (!expr) ++ return; ++ ++ expr->parent = expr_sink_register_head; ++ expr_sink_register_head = expr; ++} ++ ++int expr_sink_commit TAC_ARGS((void)); ++ ++int ++expr_sink_commit() ++{ ++ struct expr *expr; ++ ++ while ((expr = expr_sink_register_head)) { ++ expr_sink_register_head = expr->parent; ++ /* 'expr->parent' not defined for 'expr_sink()' */ ++ ++ if (expr_sink(expr, NULL /* membership */)) ++ return (1); /* failure */ ++ } ++ return (0); /* success */ ++} ++ ++struct expr *dupl_expr TAC_ARGS((const struct expr *in)); ++ ++struct expr * ++dupl_expr(in) ++const struct expr *in; ++{ ++ struct expr *expr_root = NULL; ++ struct expr **succ_exprp = &expr_root; ++ struct expr *expr; ++ ++ for (;in; in=in->next) { ++ expr = (struct expr *) tac_malloc(sizeof(struct expr)); ++ expr->line = in->line; ++ expr->next = NULL; ++ expr->type = in->type; ++ switch (in->type) { ++ ++ case S_not: ++ if (in->u.not.child && in->u.not.child->type==S_not) { ++ free(expr); ++ expr = dupl_expr(in->u.not.child->u.not.child); ++ } else ++ expr->u.not.child = dupl_expr(in->u.not.child); ++ break; ++ ++ case S_and: ++ case S_or: ++ if (!in->u.and_or.child_first) { ++ free(expr); ++ continue; ++ } else if (!in->u.and_or.child_first->next) { ++ free(expr); ++ expr = dupl_expr(in->u.and_or.child_first); ++ } else ++ expr->u.and_or.child_first = dupl_expr(in->u.and_or.child_first); ++ break; ++ ++ case S_user: ++ case S_host: ++ case S_group: ++ if (in->u.entity.name) ++ expr->u.entity.name = tac_strdup(in->u.entity.name); ++ else ++ expr->u.entity.name = NULL; ++ expr->u.entity.entity = in->u.entity.entity; ++ break; ++ ++ default: ++ report(LOG_ERR, "Illegal node type %d for dupl_expr", in->type); ++ free_expr(expr_root); ++ return (NULL); ++ } ++ ++ *succ_exprp = expr; ++ succ_exprp = &expr->next; ++ } ++ return (expr_root); ++} ++ ++ ++/* 'check_*_scan_*()' section: ++ */ ++ ++static void check_request_scan_expr TAC_ARGS((struct expr *expr, int flush)); ++ ++static void ++check_request_scan_expr(expr, flush) ++struct expr *expr; ++int flush; ++{ ++#if REPORT_CHECK_SCAN_VERBOSE ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "check_request_scan_expr: " PF_EXPR " (" PF_ERESULT_EXPR ")", ++ PA_EXPR(expr), PA_ERESULT_EXPR(expr)); ++#endif ++ ++ if (!flush && expr->request_scan.seq == request_scan_seq) ++ return; /* up to date */ ++ ++ expr->request_scan.result = ER_UNKNOWN; ++ expr->request_scan.loop_reported = 0; ++ expr->request_scan.seq = request_scan_seq; ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "check_request_scan_expr: done: " PF_EXPR, ++ PA_EXPR(expr)); ++} ++ ++static void check_eval_scan_expr TAC_ARGS((struct expr *expr, int flush)); ++ ++static void ++check_eval_scan_expr(expr, flush) ++struct expr *expr; ++int flush; ++{ ++#if REPORT_CHECK_SCAN_VERBOSE ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "check_eval_scan_expr: " PF_EXPR, ++ PA_EXPR(expr)); ++#endif ++ ++ if (!flush && expr->eval_scan.seq == eval_scan_seq) ++ return; /* up to date */ ++ check_request_scan_expr(expr, 0); ++ ++ switch (expr->type) { ++ ++ case S_user: ++ case S_host: ++ case S_group: { ++#ifdef SCAN_PARANOIA ++ if (tac_list_node_get_list(&expr->eval_scan.u.entity.notify_expr_node) == &eval_notified_expr_list) { ++ report(LOG_ERR, "INTERNAL: expr still connected to eval_notified_expr_list in check_eval_scan_expr"); ++ } else if (tac_list_node_get_list(&expr->eval_scan.u.entity.notify_expr_node)) { ++ ENTITY *notifying_entity = EXPR_ENTITY_TO_NOTIFYING_ENTITY(expr); ++ ++ if (notifying_entity != expr->u.entity.entity) ++ report(LOG_ERR, "INTERNAL: expr->notify_expr_node->list != expr->entity"); ++ if (notifying_entity->eval_scan.seq != expr->eval_scan.seq) ++ report(LOG_ERR, "INTERNAL: entity seq != expr node seq"); ++ tac_list_node_remove(&expr->eval_scan.u.entity.notify_expr_node); ++ } ++#else /* SCAN_PARANOIA */ ++ tac_list_node_init(&expr->eval_scan.u.entity.notify_expr_node); ++#endif /* SCAN_PARANOIA */ ++ } break; ++ } ++ ++ expr->eval_scan.seq = eval_scan_seq; /* used above, keep as LAST! */ ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "check_eval_scan_expr: done: " PF_EXPR, ++ PA_EXPR(expr)); ++} ++ ++static void check_request_scan_membership TAC_ARGS((struct membership *membership, int flush)); ++ ++static void ++check_request_scan_membership(membership, flush) ++struct membership *membership; ++int flush; ++{ ++#if REPORT_CHECK_SCAN_VERBOSE ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "check_request_scan_membership: " PF_MEMBERSHIP " (" PF_ERESULT_MEMBERSHIP ")", ++ PA_MEMBERSHIP(membership), PA_ERESULT_MEMBERSHIP(membership)); ++#endif ++ ++ if (!flush && membership->request_scan.seq == request_scan_seq) ++ return; /* up to date */ ++ ++#ifdef SCAN_PARANOIA ++ { ++ struct tac_list *virtual_list = tac_list_node_get_list(&membership->request_scan.virtual_membership_node); ++ ++ if (virtual_list && virtual_list != &request_virtual_membership_list) ++ report(LOG_ERR, "Illegal list in membership->virtual_membership_node.list"); ++ if (virtual_list) ++ tac_list_node_remove(&membership->request_scan.virtual_membership_node); ++ } ++#else /* SCAN_PARANOIA */ ++ tac_list_node_init(&membership->request_scan.virtual_membership_node); ++#endif /* SCAN_PARANOIA */ ++ ++ membership->request_scan.seq = request_scan_seq; /* used above, keep as LAST! */ ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "check_request_scan_membership: done: " PF_MEMBERSHIP, ++ PA_MEMBERSHIP(membership)); ++} ++ ++/* we are cross-checking (membership<->parent entity)! */ ++ ++static void check_eval_scan_membership TAC_ARGS((struct membership *membership, int flush)); ++ ++static void ++check_eval_scan_membership(membership, flush) ++struct membership *membership; ++int flush; ++{ ++#if REPORT_CHECK_SCAN_VERBOSE ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "check_eval_scan_membership: " PF_MEMBERSHIP, ++ PA_MEMBERSHIP(membership)); ++#endif ++ ++ if (!flush && membership->eval_scan.seq == eval_scan_seq) ++ return; /* up to date */ ++ check_request_scan_membership(membership, 0); ++ ++ membership->eval_scan.unsolved = 1; ++ membership->eval_scan.seq = eval_scan_seq; ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "check_eval_scan_membership: done: " PF_MEMBERSHIP, ++ PA_MEMBERSHIP(membership)); ++} ++ ++static void check_request_scan_entity TAC_ARGS((ENTITY *entity, int flush)); ++ ++static void ++check_request_scan_entity(entity, flush) ++ENTITY *entity; ++int flush; ++{ ++#if REPORT_CHECK_SCAN_VERBOSE ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "check_request_scan_entity: " PF_ENTITY " (" PF_ERESULT_ENTITY ")", ++ PA_ENTITY(entity), PA_ERESULT_ENTITY(entity)); ++#endif ++ ++ if (!flush && entity->request_scan.seq == request_scan_seq) ++ return; ++ ++ entity->request_scan.belongs = ER_UNKNOWN; ++ entity->request_scan.seq = request_scan_seq; ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "check_request_scan_entity: done: " PF_ENTITY, ++ PA_ENTITY(entity)); ++} ++ ++static void check_value_scan_entity TAC_ARGS((ENTITY *entity, int flush)); ++ ++static void ++check_value_scan_entity(entity, flush) ++ENTITY *entity; ++int flush; ++{ ++#if REPORT_CHECK_SCAN_VERBOSE ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "check_value_scan_entity: " PF_ENTITY, ++ PA_ENTITY(entity)); ++#endif ++ ++ if (!flush && entity->value_scan.seq == value_scan_seq) ++ return; ++ check_request_scan_entity(entity, 0); ++ ++ entity->value_scan.seen = 0; ++ entity->value_scan.from = NULL; ++ entity->value_scan.seq = value_scan_seq; ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "check_value_scan_entity: done: " PF_ENTITY, ++ PA_ENTITY(entity)); ++} ++ ++static void check_eval_scan_entity TAC_ARGS((ENTITY *entity, int flush)); ++ ++static void ++check_eval_scan_entity(entity, flush) ++ENTITY *entity; ++int flush; ++{ ++ struct tac_list_node *child_membership_parent_node; ++ ++#if REPORT_CHECK_SCAN_VERBOSE ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "check_eval_scan_entity: " PF_ENTITY, ++ PA_ENTITY(entity)); ++#endif ++ ++ if (!flush && entity->eval_scan.seq == eval_scan_seq) ++ return; /* up to date */ ++ check_value_scan_entity(entity, 0); ++ ++ entity->eval_scan.unsolved_to_child_membership_num = entity->to_child_membership_num; ++ ++ if ((child_membership_parent_node = tac_list_first_node(&entity->to_child_membership_list))) { ++ struct membership *child_membership = PARENT_NODE_TO_MEMBERSHIP(child_membership_parent_node); ++ ++ entity->eval_scan.unsolved_to_child_membership_first = child_membership; ++ } else ++ entity->eval_scan.unsolved_to_child_membership_first = NULL; ++ ++#ifdef SCAN_PARANOIA ++ { ++ struct tac_list_node *notify_expr_node; ++ ++ while ((notify_expr_node = tac_list_first_node(&entity->eval_scan.notify_expr_list))) { ++ struct expr *notify_expr = NOTIFY_EXPR_NODE_TO_EXPR(notify_expr_node); ++ ++ if (notify_expr->u.entity.entity != entity) ++ report(LOG_ERR, "INTERNAL: notify_expr->entity != entity"); ++ if (notify_expr->eval_scan.seq != entity->eval_scan.seq) ++ report(LOG_ERR, "INTERNAL: notify_expr seq != entity seq"); ++ tac_list_node_remove(notify_expr_node); ++ } ++ ++ if (tac_list_node_get_list(&entity->eval_scan.pending_entity_node)) ++ tac_list_node_remove(&entity->eval_scan.pending_entity_node); ++ } ++#else /* SCAN_PARANOIA */ ++ tac_list_init(&entity->eval_scan.notify_expr_list); ++ tac_list_node_init(&entity->eval_scan.pending_entity_node); ++#endif /* SCAN_PARANOIA */ ++ ++ entity->eval_scan.seq = eval_scan_seq; /* used above, keep as LAST! */ ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "check_eval_scan_entity: done: " PF_ENTITY, ++ PA_ENTITY(entity)); ++} ++ ++ ++/* invalidation managing section (for '*_scan_begin()'): ++ */ ++ ++/* this will happen once upon 'unsigned' overflow, ehm */ ++ ++#define INVALIDATE_SEQ_PTR(object,ptr) \ ++ (G_STRUCT_MEMBER(unsigned, ptr, invalidate_scan_##object##_table[what]) = (unsigned) -1) ++#define INVALIDATE_SEQ(object) \ ++ (INVALIDATE_SEQ_PTR(object,object)) ++ ++static const long invalidate_scan_expr_table[IS_MAX]={ ++ G_STRUCT_OFFSET(struct expr, request_scan.seq), ++ -1, ++ G_STRUCT_OFFSET(struct expr, eval_scan.seq)}; ++ ++static void invalidate_scan_expr TAC_ARGS((struct expr *expr,enum invalidate_scan what)); ++ ++static void ++invalidate_scan_expr(expr_single, what) ++struct expr *expr_single; ++enum invalidate_scan what; ++{ ++ struct expr *expr_parent, *expr_child; ++ ++ if (!expr_single) { ++ report(LOG_ERR, "INTERNAL: NULL input expressions not support by invalidate_scan_expr"); ++ return; ++ } ++ if (expr_single->parent) { ++ report(LOG_ERR, "INTERNAL: non-root expressions not supported by invalidate_scan_expr"); ++ return; ++ } ++ ++ /* TOP->DOWN scanner: */ ++top_down: ++ do { ++ INVALIDATE_SEQ_PTR(expr,expr_single); ++ expr_parent = expr_single; ++ ++ switch (expr_parent->type) { ++ ++ case S_not: ++ expr_child = expr_parent->u.not.child; ++ continue; ++ ++ case S_and: ++ case S_or: ++ expr_child = expr_parent->u.and_or.child_first; ++ break; ++ ++ case S_user: ++ case S_host: ++ case S_group: ++ expr_child = NULL; /* no child exists */ ++ break; ++ ++ default: ++ report(LOG_ERR, "Illegal child node type %d for invalidate_scan_expr", expr_parent->type); ++ return; ++ } ++ } while ((expr_single = expr_child)); ++ /* expr_child==NULL, we have only expr_parent: */ ++ ++ expr_child = expr_parent; ++ ++ /* we have only expr_child: */ ++ /* BOTTOM->UP scanner */ ++ do { ++ if ((expr_single = expr_child->next)) ++ goto top_down; ++ expr_parent = expr_child->parent; ++ } while ((expr_child = expr_parent)); ++} ++ ++static const long invalidate_scan_membership_table[IS_MAX]={ ++ G_STRUCT_OFFSET(struct membership, request_scan.seq), ++ -1, ++ G_STRUCT_OFFSET(struct membership, eval_scan.seq)}; ++ ++static void invalidate_scan_membership TAC_ARGS((struct membership *membership,enum invalidate_scan what)); ++ ++static void ++invalidate_scan_membership(membership, what) ++struct membership *membership; ++enum invalidate_scan what; ++{ ++ INVALIDATE_SEQ(membership); ++ ++ if (membership->when) ++ invalidate_scan_expr(membership->when, what); ++} ++ ++static const long invalidate_scan_entity_table[IS_MAX]={ ++ G_STRUCT_OFFSET(ENTITY, request_scan.seq), ++ G_STRUCT_OFFSET(ENTITY, value_scan.seq), ++ G_STRUCT_OFFSET(ENTITY, eval_scan.seq)}; ++ ++static void invalidate_scan_entity TAC_ARGS((ENTITY *entity,enum invalidate_scan what)); ++ ++static void ++invalidate_scan_entity(entity, what) ++ENTITY *entity; ++enum invalidate_scan what; ++{ ++ struct tac_list_node *child_membership_node; ++ struct membership *child_membership; ++ ++ INVALIDATE_SEQ(entity); ++ ++ if (what==IS_VALUE) /* optimalization */ ++ return; ++ ++ for ( ++ child_membership_node = tac_list_first_node(&entity->to_child_membership_list); ++ child_membership_node; ++ child_membership_node = tac_list_node_next(&child_membership->parent_node) ++ ) { ++ child_membership = PARENT_NODE_TO_MEMBERSHIP(child_membership_node); ++ invalidate_scan_membership(child_membership, what); ++ } ++} ++ ++void scan_invalidate_entities_hashtable TAC_ARGS((void **hashtable, enum invalidate_scan what)); ++ ++void ++scan_invalidate_entities_hashtable(hashtable, what) ++void **hashtable; ++enum invalidate_scan what; ++{ ++ int i; ++ ENTITY *entity; ++ ++ for (i = 0; i < HASH_TAB_SIZE; i++) ++ for (entity = (ENTITY *) hashtable[i]; entity; entity = entity->hash) ++ invalidate_scan_entity(entity, what); ++} ++ ++/* '*_scan_begin()' section: ++ */ ++ ++void request_scan_begin TAC_ARGS((void)); ++ ++void ++request_scan_begin() ++{ ++#ifdef SCAN_PARANOIA ++ static int inited = 0; ++#endif /* SCAN_PARANOIA */ ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "request_scan_begin:"); ++ ++ request_scan_user_known = 0; ++ ++ if (!++request_scan_seq) ++ scan_invalidate_entities(IS_REQUEST); ++ ++#ifdef SCAN_PARANOIA ++ if (!inited) { ++#endif /* SCAN_PARANOIA */ ++ tac_list_init(&request_virtual_membership_list); ++#ifdef SCAN_PARANOIA ++ inited = 1; ++ } else { ++ struct tac_list_node *virtual_membership_node; ++ ++ while ((virtual_membership_node = tac_list_first_node(&request_virtual_membership_list))) { ++ struct membership *virtual_membership = VIRTUAL_MEMBERSHIP_NODE_TO_MEMBERSHIP(virtual_membership_node); ++ ++ if (virtual_membership->request_scan.seq == request_scan_seq) ++ report(LOG_ERR, "INTERNAL: request_virtual_membership_list membership seq == ++request_scan_seq in request_scan_begin"); ++ tac_list_node_remove(virtual_membership_node); ++ unlink_membership(virtual_membership); ++ free_membership(virtual_membership); ++ } ++ } ++#endif /* SCAN_PARANOIA */ ++} ++ ++static void value_scan_begin TAC_ARGS((ENTITY *entity)); ++ ++static void ++value_scan_begin(entity) ++ENTITY *entity; ++{ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "value_scan_begin:"); ++ ++ if (!++value_scan_seq) ++ scan_invalidate_entities(IS_VALUE); ++ ++ check_value_scan_entity(entity, 0); /* sure as seq invalidated */ ++ /* assumed (entity->value_scan.from == NULL) */ ++} ++ ++#ifdef SCAN_PARANOIA ++ ++static void eval_scan_begin_pending_entity_node TAC_ARGS((struct tac_list_node *pending_entity_node)); ++ ++static void ++eval_scan_begin_pending_entity_node(pending_entity_node) ++struct tac_list_node *pending_entity_node; ++{ ++ ENTITY *pending_entity = PENDING_ENTITY_NODE_TO_ENTITY(pending_entity_node); ++ ++ if (pending_entity->eval_scan.seq == eval_scan_seq) ++ report(LOG_ERR, "INTERNAL: eval_{pending}_entity_list entity seq == ++eval_scan_seq in eval_scan_begin"); ++ ++ tac_list_node_remove(pending_entity_node); ++} ++ ++#endif /* SCAN_PARANOIA */ ++ ++static void eval_scan_begin TAC_ARGS((void)); ++ ++static void ++eval_scan_begin() ++{ ++#ifdef SCAN_PARANOIA ++ static int inited = 0; ++#endif /* SCAN_PARANOIA */ ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "eval_scan_begin:"); ++ ++ if (!++eval_scan_seq) ++ scan_invalidate_entities(IS_EVAL); ++ ++#ifdef SCAN_PARANOIA ++ if (!inited) { ++#endif /* SCAN_PARANOIA */ ++ tac_list_init(&eval_kicked_entity_list); ++ tac_list_init(&eval_destroy_entity_list); ++ tac_list_init(&eval_notified_expr_list); ++#ifdef SCAN_PARANOIA ++ inited = 1; ++ } else { ++ struct tac_list_node *pending_entity_node; ++ struct tac_list_node *notify_expr_node; ++ ++ while ((pending_entity_node = tac_list_first_node(&eval_kicked_entity_list))) ++ eval_scan_begin_pending_entity_node(pending_entity_node); ++ while ((pending_entity_node = tac_list_first_node(&eval_destroy_entity_list))) ++ eval_scan_begin_pending_entity_node(pending_entity_node); ++ ++ while ((notify_expr_node = tac_list_first_node(&eval_notified_expr_list))) { ++ struct expr *notify_expr = NOTIFY_EXPR_NODE_TO_EXPR(notify_expr_node); ++ ++ if (notify_expr->eval_scan.seq == eval_scan_seq) ++ report(LOG_ERR, "INTERNAL: eval_notified_expr_list expr seq == ++eval_scan_seq in eval_scan_begin"); ++ ++ tac_list_node_remove(notify_expr_node); ++ } ++ } ++#endif /* SCAN_PARANOIA */ ++} ++ ++/* 'priority=0' => addtail - used for WANTED entities ++ * 'priority=1' => addhead - used for SOLVED entities ++ * It may be better to do insert it AFTER all currently solved ++ * entities but may be not but who cares... ++ */ ++ ++static void register_kicked_entity TAC_ARGS((ENTITY *entity, int priority)); ++ ++static void register_kicked_entity(entity, priority) ++ENTITY *entity; ++int priority; ++{ ++ struct tac_list_node *pending_entity_node = &entity->eval_scan.pending_entity_node; ++ ++ check_eval_scan_entity(entity, 0); ++ ++ if (tac_list_node_get_list(pending_entity_node) == &eval_destroy_entity_list) { ++ tac_list_node_remove (pending_entity_node); ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "register_kicked_entity: REMOVED " PF_ENTITY " from eval_DESTROY_entity_list", ++ PA_ENTITY(entity)); ++ } ++ if (tac_list_node_get_list(pending_entity_node) == NULL) { ++ if (priority) ++ tac_list_addhead(&eval_kicked_entity_list, pending_entity_node); ++ else ++ tac_list_addtail(&eval_kicked_entity_list, pending_entity_node); ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "register_kicked_entity: REGISTERED " PF_ENTITY " to eval_KICKED_entity_list (priority=%s)", ++ PA_ENTITY(entity), (priority ? "YES" : "NO")); ++ } ++#ifdef SCAN_PARANOIA ++ if ((tac_list_node_get_list(pending_entity_node) != &eval_kicked_entity_list)) { ++ report(LOG_ERR, "Illegal list in entity->pending_entity_node.list"); ++ return; ++ } ++#endif ++} ++ ++/* check_eval_scan_*() is assumed both for "expr" and for "entity" ! */ ++ ++static void expr_eval_notify_expr TAC_ARGS((struct expr *expr)); ++ ++static void ++expr_eval_notify_expr(expr) ++struct expr *expr; ++{ ++ ENTITY *entity = expr->u.entity.entity; ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "expr_eval_notify_expr: REGISTERED notify " PF_EXPR " when " PF_ENTITY " is known", ++ PA_EXPR(expr), PA_ENTITY(entity)); ++ ++ if (tac_list_node_get_list(&expr->eval_scan.u.entity.notify_expr_node)) { ++#ifdef SCAN_PARANOIA ++ if (&entity->eval_scan.notify_expr_list ++ != tac_list_node_get_list(&expr->eval_scan.u.entity.notify_expr_node)) ++ report(LOG_ERR, "Another " PF_ENTITY " already registered in notify node of " PF_EXPR, ++ PA_ENTITY(EXPR_ENTITY_TO_NOTIFYING_ENTITY(expr)), PA_EXPR(expr)); ++#endif ++ return; ++ } ++ ++ tac_list_addtail(&entity->eval_scan.notify_expr_list, ++ &expr->eval_scan.u.entity.notify_expr_node); ++ ++ register_kicked_entity(entity, 0 /* priority */); ++} ++ ++/* check_eval_scan_*() is assumed for "expr" ! */ ++ ++static void expr_eval_notify_expr_remove_internal TAC_ARGS((struct expr *expr)); ++ ++static void ++expr_eval_notify_expr_remove_internal(expr) ++struct expr *expr; ++{ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "expr_eval_notify_expr_remove_internal: no longer interested in " PF_EXPR, ++ PA_EXPR(expr)); ++ ++ if (!expr) ++ return; ++ if (expr->eval_scan.seq != eval_scan_seq) ++ return; ++ if (expr->request_scan.result != ER_UNKNOWN) ++ return; ++ ++ switch (expr->type) { ++ ++ case S_not: ++ expr_eval_notify_expr_remove_internal(expr->u.not.child); ++ break; ++ ++ case S_and: ++ case S_or: { ++ struct expr *child; ++ ++ for (child=expr->u.and_or.child_first; child; child=child->next) ++ expr_eval_notify_expr_remove_internal(child); ++ } break; ++ ++ case S_user: ++ case S_host: ++ case S_group: { ++ ENTITY *entity = expr->u.entity.entity; ++ struct tac_list_node *pending_entity_node = &entity->eval_scan.pending_entity_node; ++ ++ if (!tac_list_node_get_list(&expr->eval_scan.u.entity.notify_expr_node)) ++ break; ++ ++ tac_list_node_remove(&expr->eval_scan.u.entity.notify_expr_node); ++ if (tac_list_first_node(&entity->eval_scan.notify_expr_list)) ++ break; ++ /* no one is further interested in "entity" */ ++ ++ if ((tac_list_node_get_list(pending_entity_node) == &eval_kicked_entity_list)) { ++ tac_list_node_remove (pending_entity_node); ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "expr_eval_notify_expr: REMOVED " PF_ENTITY " from eval_KICKED_entity_list", ++ PA_ENTITY(entity)); ++ } ++ if (tac_list_node_get_list(pending_entity_node) == NULL) { ++ tac_list_addtail(&eval_destroy_entity_list, pending_entity_node); ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "expr_eval_notify_expr: REGISTERED " PF_ENTITY " to eval_DESTROY_entity_list", ++ PA_ENTITY(entity)); ++ } ++#ifdef SCAN_PARANOIA ++ if (tac_list_node_get_list(pending_entity_node) != &eval_destroy_entity_list) { ++ report(LOG_ERR, "Illegal list in entity->pending_entity_node.list"); ++ return; ++ } ++#endif ++ ++ } break; ++ ++ default: ++ report(LOG_ERR, "Illegal node type %d for expr_eval_notify_expr_remove", expr->type); ++ return; ++ } ++} ++ ++static void expr_eval_notify_expr_flush_destroy_entity_list TAC_ARGS((void)); ++ ++static void expr_eval_notify_expr_flush_destroy_entity_list() ++{ ++struct tac_list_node *destroy_entity_node; ++ ++ while ((destroy_entity_node = tac_list_first_node(&eval_destroy_entity_list))) { ++ ENTITY *destroy_entity = PENDING_ENTITY_NODE_TO_ENTITY(destroy_entity_node); ++ struct tac_list_node *destroy_notify_expr_node; ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "expr_eval_notify_expr_flush_destroy_entity_list: PROCESSING " PF_ENTITY " from eval_DESTROY_entity_list", ++ PA_ENTITY(destroy_entity)); ++ ++ while ((destroy_notify_expr_node = tac_list_first_node(&destroy_entity->eval_scan.notify_expr_list))) { ++ struct expr *destroy_notify_expr = NOTIFY_EXPR_NODE_TO_EXPR(destroy_notify_expr_node); ++ ++ expr_eval_notify_expr_remove_internal(destroy_notify_expr); ++ } ++ } ++} ++ ++static void expr_eval_notify_expr_remove TAC_ARGS((struct expr *expr)); ++ ++static void ++expr_eval_notify_expr_remove(expr) ++struct expr *expr; ++{ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "expr_eval_notify_expr_remove: no longer interested in " PF_EXPR, ++ PA_EXPR(expr)); ++ ++ expr_eval_notify_expr_remove_internal(expr); ++ expr_eval_notify_expr_flush_destroy_entity_list(); ++} ++ ++/* It would be very nice to try to optimize the expression before evaluation. ++ ++ We are not interested in some CPU time complexity of the expression. ++ But we would be very happy to discard any user/host/group membership ++ dependencies (our 'variables'). Unfortunately such optimization is ++ NP problem (classification by courtesy of Daniel Kral) so it is considered ++ too expensive for us. ++ ++ TODO in future: Full NP optimization for small number of variables and/or ++ heuristic optimizations for complex expressions. ++*/ ++ ++static enum eval_result expr_eval_immediate TAC_ARGS((struct expr *expr_single)); ++ ++static enum eval_result ++expr_eval_immediate(expr_single) ++struct expr *expr_single; ++{ ++ struct expr *expr_child, *expr_parent; ++ enum eval_result result_child, result_parent = 0 /* GCC paranoia */; ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "expr_eval_immediate: " PF_EXPR, ++ PA_EXPR(expr_single)); ++ ++ if (!expr_single) ++ return (ER_TRUE); ++ ++ /* TOP->DOWN scanner: */ ++top_down: ++ while (1) { ++ enum eval_result result_single; ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "expr_eval_immediate: top_down start: " PF_EXPR, ++ PA_EXPR(expr_single)); ++ ++ check_eval_scan_expr(expr_single, 0); ++ result_single = expr_single->request_scan.result; ++ if (result_single != ER_UNKNOWN) ++ break; ++ switch (expr_single->type) { ++ ++ case S_not: ++ expr_single = expr_single->u.not.child; ++ continue; ++ ++ case S_and: ++ case S_or: ++ expr_single = expr_single->u.and_or.child_first; ++ continue; ++ ++ case S_user: ++ case S_host: ++ case S_group: { ++ ENTITY *entity = expr_single->u.entity.entity; ++ ++ check_eval_scan_entity(entity, 0); ++ ++ if (entity->request_scan.belongs == ER_UNKNOWN) ++ expr_eval_notify_expr(expr_single); ++ else ++ result_single = entity->request_scan.belongs; ++ } break; ++ ++ default: ++ report(LOG_ERR, "Illegal child node type %d for expr_eval", expr_single->type); ++ return (ER_UNKNOWN); ++ } ++ ++ expr_single->request_scan.result = result_single; ++ break; ++ } ++ ++ /* BOTTOM->UP scanner: */ ++ do { ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "expr_eval_immediate: bottom_up start: " PF_EXPR, ++ PA_EXPR(expr_single)); ++ ++ expr_parent = expr_single->parent; ++ if (!expr_parent) ++ break; ++ if (expr_parent->eval_scan.seq != eval_scan_seq) { ++ report(LOG_ERR, "INTERNAL: Parent expr node eval_scan NOT up-to-date"); ++ return (ER_UNKNOWN); ++ } ++ if (expr_parent->request_scan.seq != request_scan_seq) { ++ report(LOG_ERR, "INTERNAL: Parent expr node request_scan NOT up-to-date"); ++ return (ER_UNKNOWN); ++ } ++ if (expr_parent->request_scan.result != ER_UNKNOWN) { ++ report(LOG_WARNING, "INTERNAL-WARNING: Parent expr node already known, wasteful eval occured"); ++ return (ER_UNKNOWN); ++ } ++ ++ expr_child = expr_single; ++ result_child = expr_child->request_scan.result; ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "expr_eval_immediate: bottom_up switch: child=" PF_EXPR ",parent=" PF_EXPR, ++ PA_EXPR(expr_child), PA_EXPR(expr_parent)); ++ ++ switch (expr_parent->type) { ++ ++ case S_not: ++ switch (result_child) { ++ case ER_UNKNOWN: result_parent = ER_UNKNOWN; break; ++ case ER_FALSE: result_parent = ER_TRUE; break; ++ case ER_TRUE: result_parent = ER_FALSE; break; ++ } ++ break; ++ ++ case S_and: ++ case S_or: { ++ enum eval_result veto = (expr_parent->type==S_and ? ER_FALSE : ER_TRUE ); ++ enum eval_result consent = (expr_parent->type==S_and ? ER_TRUE : ER_FALSE); ++ ++ if (result_child == veto) ++ result_parent = veto; ++ else if (result_child == ER_UNKNOWN || result_child == consent) { ++ if (expr_child->next) { ++ expr_single = expr_child->next; ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "expr_eval_immediate: bottom_up and_or: traversed to and_or next: " PF_EXPR, ++ PA_EXPR(expr_single)); ++ goto top_down; ++ } ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "expr_eval_immediate: bottom_up and_or: full scan: " PF_EXPR, ++ PA_EXPR(expr_single)); ++ ++ /* It would be nice to pretend that all 'veto' decisions already made in the child ++ * had to set our 'result' to the correct value. But 'consent' decisions don't set ++ * us and the behaviour of auto-set from the child in 'veto' case may get changed ++ * in the future versions. ++ * So we rather don't depend on it. ++ * This overhead doesn't change altgorithmic complexity anyway. ++ */ ++ result_parent = consent; ++ for (expr_child = expr_parent->u.and_or.child_first; expr_child; expr_child = expr_child->next) ++ { ++ check_eval_scan_expr(expr_child, 0); /* shouldn't be needed */ ++ if (expr_child->request_scan.result == ER_UNKNOWN) ++ result_parent = ER_UNKNOWN; /* assumed (result_parent != veto) */ ++ else if (expr_child->request_scan.result == veto) { ++ result_parent = veto; ++ break; ++ } ++ } ++ break; ++ } ++ } break; ++ ++ default: ++ report(LOG_ERR, "Illegal parent node type %d for expr_eval", expr_parent->type); ++ return (ER_UNKNOWN); ++ } ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "expr_eval_immediate: bottom_up end: child=" PF_EXPR ",parent=" PF_EXPR, ++ PA_EXPR(expr_child), PA_EXPR(expr_parent)); ++ ++ if (result_parent != ER_UNKNOWN) { ++ expr_parent->request_scan.result = result_parent; ++ /* we no longer need any notifications from entities to solve sub-expression */ ++ expr_eval_notify_expr_remove(expr_parent); ++ } ++ ++ expr_single = expr_parent; ++ } while (0); ++ /* The whole expression has been scanned to its root, we have "expr_single" */ ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "expr_eval_immediate: done: " PF_EXPR, ++ PA_EXPR(expr_single)); ++ ++ return (expr_single->request_scan.result); ++} ++ ++static void membership_solved TAC_ARGS((struct membership *membership)); ++ ++static void ++membership_solved(membership) ++struct membership *membership; ++{ ++ ENTITY *parent_entity = MEMBERSHIP_TO_PARENT_ENTITY(membership); ++ ++ check_request_scan_entity(parent_entity, 0); ++ ++#ifdef SCAN_PARANOIA ++ if (!membership->eval_scan.unsolved) { ++ report(LOG_ERR, "INTERNAL: membership already solved in membership_solved"); ++ return; ++ } ++#endif ++ ++ membership->eval_scan.unsolved = 0; ++ ++#ifdef SCAN_PARANOIA ++ if (!parent_entity->eval_scan.unsolved_to_child_membership_num) { ++ report(LOG_ERR, "INTERNAL: unsolved_to_child_membership_num-- == 0 in membership_solved"); ++ parent_entity->eval_scan.unsolved_to_child_membership_num++; ++ } ++#endif ++ parent_entity->eval_scan.unsolved_to_child_membership_num--; ++ ++ if (!parent_entity->eval_scan.unsolved_to_child_membership_num ++ && parent_entity->request_scan.belongs == ER_UNKNOWN) { ++ parent_entity->request_scan.belongs = ER_FALSE; ++ register_kicked_entity(parent_entity, 1 /* priority */); ++ } ++} ++ ++static void membership_parent_solve TAC_ARGS((struct membership *membership, enum eval_result how)); ++ ++static void ++membership_parent_solve(membership, how) ++struct membership *membership; ++enum eval_result how; ++{ ++ enum eval_result negative = (how == ER_TRUE ? ER_FALSE : ER_TRUE); ++ ENTITY *parent_entity = MEMBERSHIP_TO_PARENT_ENTITY(membership); ++ ++ check_request_scan_entity(parent_entity, 0); ++ ++ if (parent_entity->request_scan.belongs == negative) ++ report(LOG_ERR, "INTERNAL: parent " PF_ENTITY "already negative to what says membership " PF_MEMBERSHIP "in membership_eval_immediate", ++ PA_ENTITY(parent_entity), PA_MEMBERSHIP(membership)); ++ ++ parent_entity->request_scan.belongs = how; ++ register_kicked_entity(parent_entity, 1 /* priority */); ++ ++ membership_solved(membership); ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "membership_parent_solve: " PF_MEMBERSHIP " marked parent " PF_ENTITY, ++ PA_MEMBERSHIP(membership), PA_ENTITY(parent_entity)); ++} ++ ++static void membership_eval_immediate TAC_ARGS((struct membership *membership)); ++ ++static void ++membership_eval_immediate(membership) ++struct membership *membership; ++{ ++ enum eval_result membership_valid; ++ ENTITY *child_entity, *parent_entity; ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "membership_eval_immediate: " PF_MEMBERSHIP, ++ PA_MEMBERSHIP(membership)); ++ ++ check_eval_scan_membership(membership, 0); ++ ++ if (!membership->eval_scan.unsolved) ++ return; ++ parent_entity = MEMBERSHIP_TO_PARENT_ENTITY(membership); ++ check_request_scan_entity(parent_entity, 0); ++ if (parent_entity->request_scan.belongs != ER_UNKNOWN) /* why to solve this membership? */ ++ return; ++ ++ membership_valid = expr_eval_immediate(membership->when); ++ ++ child_entity = MEMBERSHIP_TO_CHILD_ENTITY( membership); ++ check_request_scan_entity( child_entity, 0); ++ ++#if 0 /* non-valid membership doesn't YET solve the parent! */ ++ if (child_entity->request_scan.belongs == ER_FALSE || membership_valid == ER_FALSE) { ++ membership_parent_solve(membership, ER_FALSE); ++ return; ++ } ++#endif ++ ++ if (child_entity->request_scan.belongs == ER_TRUE && membership_valid == ER_TRUE ) { ++ membership_parent_solve(membership, ER_TRUE ); ++ return; ++ } ++ ++ if ( child_entity->request_scan.belongs == ER_UNKNOWN) ++ register_kicked_entity( child_entity, 0 /* priority */); ++ if (parent_entity->request_scan.belongs == ER_UNKNOWN) ++ register_kicked_entity(parent_entity, 0 /* priority */); ++ ++ if (parent_entity->request_scan.belongs != ER_UNKNOWN ++ || (child_entity->request_scan.belongs == ER_FALSE || membership_valid == ER_FALSE)) ++ membership_solved(membership); ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "membership_eval_immediate: done: " PF_MEMBERSHIP, ++ PA_MEMBERSHIP(membership)); ++} ++ ++static void entity_eval_immediate TAC_ARGS((ENTITY *entity)); ++ ++static void ++entity_eval_immediate(entity) ++ENTITY *entity; ++{ ++ struct tac_list_node *notified_expr_node; ++ struct tac_list_node *child_membership_node; ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "entity_eval_immediate: " PF_ENTITY, ++ PA_ENTITY(entity)); ++ ++ check_eval_scan_entity(entity, 0); ++ ++ if (!request_scan_user_known) { ++#ifdef SCAN_PARANOIA ++ if (entity->request_scan.belongs != ER_UNKNOWN) ++ report(LOG_ERR, "INTERNAL: belonging known while still !request_scan_user_known for " PF_ENTITY " in entity_eval_immediate", ++ PA_ENTITY(entity)); ++#endif ++ return; ++ } ++ ++ if (entity->request_scan.belongs == ER_UNKNOWN) { ++ if (entity->eval_scan.unsolved_to_child_membership_first) { ++ struct membership *order_membership = entity->eval_scan.unsolved_to_child_membership_first; ++ struct tac_list_node *next_membership_parent_node = tac_list_node_next(&order_membership->parent_node); ++ ++ membership_eval_immediate(order_membership); ++ if (next_membership_parent_node) ++ entity->eval_scan.unsolved_to_child_membership_first = PARENT_NODE_TO_MEMBERSHIP(next_membership_parent_node); ++ else ++ entity->eval_scan.unsolved_to_child_membership_first = NULL; ++ ++ register_kicked_entity(entity, 0 /* priority */); ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "entity_eval_immediate: finishing as we ordered child membership: " PF_MEMBERSHIP, ++ PA_MEMBERSHIP(order_membership)); ++ return; ++ } ++ ++ if (!entity->eval_scan.unsolved_to_child_membership_num) ++ entity->request_scan.belongs = ER_FALSE; ++ else { ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "entity_eval_immediate: finishing as unsolved child memberships still available and I'm still clueless"); ++ return; ++ } ++ } ++ /* belonging is known here */ ++ ++ /* recheck all memberships we may decide */ ++ for ( ++ child_membership_node = tac_list_first_node(&entity->to_parent_membership_list); ++ child_membership_node; ++ child_membership_node = tac_list_node_next(child_membership_node) ++ ) { ++ struct membership *child_membership = CHILD_NODE_TO_MEMBERSHIP(child_membership_node); ++ ++ membership_eval_immediate(child_membership); ++ } ++ ++ /* notify all exprs which are interested in us */ ++ while ((notified_expr_node = tac_list_first_node(&entity->eval_scan.notify_expr_list))) { ++ tac_list_node_remove(notified_expr_node); ++ tac_list_addtail(&eval_notified_expr_list, notified_expr_node); ++ } ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "entity_eval_immediate: done: " PF_ENTITY, ++ PA_ENTITY(entity)); ++} ++ ++ ++enum eval_result expr_eval TAC_ARGS((struct expr *expr)); ++ ++enum eval_result ++expr_eval(expr) ++struct expr *expr; ++{ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "expr_eval: top level order for " PF_EXPR, ++ PA_EXPR(expr)); ++ ++ if (!expr) ++ return (ER_TRUE); ++ ++ eval_scan_begin(); ++ if (expr_eval_immediate(expr) != ER_UNKNOWN) ++ return (expr->request_scan.result); ++ ++ /* all 'solved' nodes MUST be removed BEFORE '*_immediate()' has been called, ++ * otherwise we may have no longer valid node! ++ */ ++ for (;;) { ++ struct tac_list_node *notified_expr_node, *kicked_entity_node; ++ ++ /* check it rather always, checking just on notifications looks too complex. ++ */ ++ if (expr->request_scan.result != ER_UNKNOWN) { ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "expr_eval: finishing as ordered " PF_EXPR " got known", ++ PA_EXPR(expr)); ++ return (expr->request_scan.result); ++ } ++ ++#if 0 /* not needed as it is now always called after any destroy */ ++ expr_eval_notify_expr_flush_destroy_entity_list(); /* eval_destroy_entity_list */ ++#endif /* not needed */ ++ ++ if ((notified_expr_node = tac_list_first_node(&eval_notified_expr_list))) { ++ struct expr *notified_expr = NOTIFY_EXPR_NODE_TO_EXPR(notified_expr_node); ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "expr_eval: PROCESSING " PF_EXPR " from eval_NOTIFIED_expr_list", ++ PA_EXPR(notified_expr)); ++ ++ tac_list_node_remove(notified_expr_node); ++ expr_eval_immediate(notified_expr); ++ ++ if (notified_expr->membership) ++ membership_eval_immediate(notified_expr->membership); ++ ++ continue; /* shortcut */ ++ } ++ ++ if ((kicked_entity_node = tac_list_first_node(&eval_kicked_entity_list))) { ++ ENTITY *kicked_entity = PENDING_ENTITY_NODE_TO_ENTITY(kicked_entity_node); ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "expr_eval: PROCESSING " PF_ENTITY " from eval_KICKED_entity_list", ++ PA_ENTITY(kicked_entity)); ++ ++ tac_list_node_remove(kicked_entity_node); ++ entity_eval_immediate(kicked_entity); ++ continue; /* shortcut */ ++ } ++ ++ break; /* nothing done yet, all lists are empty! */ ++ } ++ ++ if (!expr->request_scan.loop_reported) { ++ report(LOG_WARNING, "Unable to resolve expression from line %d, some looping occured", expr->line); ++ expr->request_scan.loop_reported = 1; ++ } ++ return (ER_UNKNOWN); ++} ++ ++ ++void eval_force_belong_entity TAC_ARGS((ENTITY *entity)); ++ ++void eval_force_belong_entity(entity) ++ENTITY *entity; ++{ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "eval_force_belong_entity: " PF_ENTITY " (before check_scan " PF_ERESULT_ENTITY ")", ++ PA_ENTITY(entity), PA_ERESULT_ENTITY(entity)); ++ ++ check_request_scan_entity(entity, 0); ++ ++ if (entity->request_scan.belongs == ER_FALSE) ++ report(LOG_ERR, "Dangerous force of TRUE to FALSE-determined entity in eval_force_belong_entity"); ++ ++ entity->request_scan.belongs = ER_TRUE; ++} ++ ++void scan_init_entity TAC_ARGS((ENTITY *entity)); ++ ++void ++scan_init_entity(entity) ++ENTITY *entity; ++{ ++ entity->request_scan.seq = request_scan_seq-1; /* invalidate */ ++ entity-> value_scan.seq = value_scan_seq-1; /* invalidate */ ++ entity-> eval_scan.seq = eval_scan_seq-1; /* invalidate */ ++ tac_list_init(&entity->eval_scan.notify_expr_list); ++ tac_list_node_init(&entity->eval_scan.pending_entity_node); ++} ++ ++struct membership *enlist_entity_direct TAC_ARGS((ENTITY *parent, ENTITY *child, struct expr *when)); ++ ++struct membership * ++enlist_entity_direct(parent, child, when) ++ENTITY *parent; ++ENTITY *child; ++struct expr *when; ++{ ++ struct membership *membership = (struct membership *) tac_malloc(sizeof(struct membership)); ++ ++ tac_list_node_init(&membership->parent_node); ++ tac_list_node_init(&membership->child_node); ++ membership->request_scan.seq = request_scan_seq-1; ++ tac_list_node_init(&membership->request_scan.virtual_membership_node); ++ membership->eval_scan.seq = eval_scan_seq-1; ++ ++ tac_list_addtail(&parent->to_child_membership_list , &membership->parent_node); ++ parent->to_child_membership_num++; ++ tac_list_addtail(& child->to_parent_membership_list, &membership-> child_node); ++ membership->when = when; ++ if (expr_sink(membership->when, membership)) { ++ unlink_membership(membership); ++ free_membership(membership); ++ return (NULL); ++ } ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "enlist_entity_direct: done: " PF_MEMBERSHIP, ++ PA_MEMBERSHIP(membership)); ++ ++ return (membership); ++} ++ ++struct membership *virtual_enlist_entity_direct TAC_ARGS((ENTITY *parent, ENTITY *child)); ++ ++struct membership * ++virtual_enlist_entity_direct(parent, child) ++ENTITY *parent; ++ENTITY *child; ++{ ++ struct membership *membership; ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "virtual_enlist_entity_direct: the following enlist will be VIRTUAL..."); ++ ++ membership = enlist_entity_direct(parent, child, NULL /* when */); ++ if (!membership) ++ return (NULL); ++ ++ check_request_scan_membership(membership, 0); ++ tac_list_addtail(&request_virtual_membership_list, &membership->request_scan.virtual_membership_node); ++ ++ return (membership); ++} ++ ++/* returns given 'entity' or NULL if already visited */ ++ ++void (*value_scan_forward_seen_hook) TAC_ARGS((struct membership *membership)); ++ ++static ENTITY *value_scan_forward TAC_ARGS((struct membership *membership)); ++ ++static ENTITY * ++value_scan_forward(membership) ++struct membership *membership; ++{ ++ ENTITY *parent_entity; ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "value_scan_forward: from " PF_MEMBERSHIP " try forward...", ++ PA_MEMBERSHIP(membership)); ++ ++ parent_entity = MEMBERSHIP_TO_PARENT_ENTITY(membership); ++ ++ if (ER_TRUE != expr_eval(membership->when)) { ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "value_scan_forward: forward NOT successful due to failed 'when' evaluation."); ++ return (NULL); ++ } ++ check_value_scan_entity(parent_entity, 0); ++ if (parent_entity->value_scan.seen) { ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "value_scan_forward: forward NOT successful as the parent " PF_ENTITY " was already seen this value scan.", ++ PA_ENTITY(parent_entity)); ++ if (value_scan_forward_seen_hook) ++ (*value_scan_forward_seen_hook)(membership); ++ return (NULL); ++ } ++ parent_entity->value_scan.seen = 1; ++ parent_entity->value_scan.from = membership; ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "value_scan_forward: forward SUCCESSFUL to parent " PF_ENTITY, ++ PA_ENTITY(parent_entity)); ++ return (parent_entity); ++} ++ ++struct membership *value_scan_backward TAC_ARGS((ENTITY *entity)); ++ ++struct membership * ++value_scan_backward(entity) ++ENTITY *entity; ++{ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "value_scan_backward: from " PF_ENTITY " went back to " PF_MEMBERSHIP, ++ PA_ENTITY(entity), PA_MEMBERSHIP(entity->value_scan.from)); ++ ++#ifdef SCAN_PARANOIA ++ if (entity->value_scan.seq != value_scan_seq) { ++ report(LOG_ERR, "entity value_scan NOT up-to-date in value_scan_backward"); ++ return (NULL); ++ } ++#endif ++ ++ return (entity->value_scan.from); ++} ++ ++/* Scan the entity graph and return each node found. ++ 'when' conditions for graph connections are respected, ++ looping is correctly prevented. ++*/ ++ ++enum value_scan_func_result value_scan_entity TAC_ARGS((ENTITY *entity, int recurse, value_scan_func_t func, void *func_data)); ++ ++enum value_scan_func_result ++value_scan_entity(entity, recurse, func, func_data) ++ENTITY *entity; ++int recurse; ++value_scan_func_t func; ++void *func_data; ++{ ++ enum value_scan_func_result vsfr; ++ struct tac_list_node *membership_node; ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "value_scan_entity: " PF_ENTITY ", recurse=%d", ++ PA_ENTITY(entity), recurse); ++ ++ vsfr=(*func)(entity,func_data); ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "value_scan_entity: root func-> " PF_VSFR, ++ PA_VSFR(vsfr)); ++ ++ if (vsfr != VSFR_CONTINUE) { ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "value_scan_entity: finishing as root func didn't return VSFR_CONTINUE"); ++ return (vsfr); ++ } ++ if (!recurse ) { ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "value_scan_entity: finishing as recurse not ordered"); ++ return (VSFR_STOP); ++ } ++ ++ value_scan_begin(entity); ++ membership_node = tac_list_first_node(&entity->to_parent_membership_list); ++ if (!membership_node) { ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "value_scan_entity: finishing as no parent entities of root"); ++ return (VSFR_CONTINUE); /* no parent entities */ ++ } ++ ++ while (1) { ++ struct membership *membership = CHILD_NODE_TO_MEMBERSHIP(membership_node); ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "value_scan_entity: trace loop start: " PF_MEMBERSHIP, ++ PA_MEMBERSHIP(membership)); ++ ++ entity = value_scan_forward(membership); ++ if (entity) { ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "value_scan_entity: successful recurse to " PF_ENTITY, ++ PA_ENTITY(entity)); ++ ++ vsfr=(*func)(entity,func_data); ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "value_scan_entity: func(" PF_ENTITY ")-> " PF_VSFR, ++ PA_ENTITY(entity), PA_VSFR(vsfr)); ++ ++ if (vsfr == VSFR_FOUND) { ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "value_scan_entity: finishing as func returned VSFR_FOUND"); ++ return (vsfr); ++ } ++ if (vsfr == VSFR_CONTINUE) ++ membership_node = tac_list_first_node(&entity->to_parent_membership_list); ++ } ++ if (!entity || vsfr == VSFR_STOP) { ++ ENTITY *parent_entity = MEMBERSHIP_TO_PARENT_ENTITY(membership); ++ ++ entity = MEMBERSHIP_TO_CHILD_ENTITY(membership); /* for retreat from the LAST membership */ ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "value_scan_entity: unsuccessful recurse to " PF_ENTITY ", tracing back through child " PF_ENTITY, ++ PA_ENTITY(parent_entity), PA_ENTITY(entity)); ++ ++ membership_node = tac_list_node_next(&membership->child_node); ++ } ++ ++ while (!membership_node) { ++ membership = value_scan_backward(entity); ++ if (!membership) { ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "value_scan_entity: finishing as all nodes were scanned"); ++ return (VSFR_CONTINUE); /* FINISH */ ++ } ++ ++ entity = MEMBERSHIP_TO_CHILD_ENTITY(membership); /* for retreat from the LAST membership */ ++ ++ if (debug & DEBUG_CFGEVAL_FLAG) ++ report(LOG_DEBUG, "value_scan_entity: backward retreat ('next' chase) " ++ "through " PF_MEMBERSHIP " to child " PF_ENTITY, ++ PA_MEMBERSHIP(membership), PA_ENTITY(entity)); ++ ++ membership_node = tac_list_node_next(&membership->child_node); ++ } ++ } ++ /* NOTREACHED */ ++} +diff --git a/cfgeval.h b/cfgeval.h +new file mode 100644 +index 0000000..63f33cb +--- /dev/null ++++ b/cfgeval.h +@@ -0,0 +1,134 @@ ++#ifndef CFGEVAL_H ++#define CFGEVAL_H 1 ++ ++#include "tac_plus.h" ++ ++#include "utils.h" ++ ++ ++struct entity; ++typedef struct entity ENTITY; ++enum invalidate_scan { ++ IS_REQUEST = 0, ++ IS_VALUE = 1, ++ IS_EVAL = 2, ++ IS_MAX = 3 ++}; ++ ++enum eval_result { ++ ER_UNKNOWN, ++ ER_FALSE, ++ ER_TRUE ++}; ++ ++enum value_scan_func_result { ++ VSFR_CONTINUE, /* for value_scan_entity() it means 'not found' */ ++ VSFR_FOUND, /* immediately return from the whole value_scan_entity() */ ++ VSFR_STOP /* backtrack (stop) this branch, scan further; nevere returned by value_scan_entity() */ ++}; ++ ++struct membership { ++ struct tac_list_node parent_node; /* to_child_membership_list , AKA legacy "member" */ ++ struct tac_list_node child_node; /* to_parent_membership_list */ ++ ++ struct { ++ unsigned seq; /* corresponds to global request_scan_seq */ ++ struct tac_list_node virtual_membership_node; ++ } request_scan; /* cfg_request() scanning */ ++ ++ struct { ++ unsigned seq; /* corresponds to global eval_scan_seq */ ++ unsigned unsolved:1; /* we haven't yet decreased ++ * parent_entity->eval_scan.unsolved_to_child_membership_num */ ++ } eval_scan; /* expr_eval() scanning, many per value_scan */ ++ ++ struct expr *when; ++}; ++#define MEMBERSHIP_TO_PARENT_ENTITY(membership) \ ++ (&TAC_MEMBER_STRUCT(ENTITY, tac_list_node_get_list(&(membership)->parent_node), to_child_membership_list )) ++#define MEMBERSHIP_TO_CHILD_ENTITY(membership) \ ++ (&TAC_MEMBER_STRUCT(ENTITY, tac_list_node_get_list(&(membership)-> child_node), to_parent_membership_list)) ++#define PARENT_NODE_TO_MEMBERSHIP(parent_node_) \ ++ (&TAC_MEMBER_STRUCT(struct membership, (parent_node_), parent_node)) ++#define CHILD_NODE_TO_MEMBERSHIP( child_node_) \ ++ (&TAC_MEMBER_STRUCT(struct membership, ( child_node_), child_node)) ++#define UNSOLVED_CHILD_NODE_TO_MEMBERSHIP(unsolved_child_node_) \ ++ (&TAC_MEMBER_STRUCT(struct membership, (unsolved_child_node_), eval_scan.unsolved_child_node)) ++#define VIRTUAL_MEMBERSHIP_NODE_TO_MEMBERSHIP(virtual_membership_node_) \ ++ (&TAC_MEMBER_STRUCT(struct membership, (virtual_membership_node_), request_scan.virtual_membership_node)) ++ ++struct expr { ++ struct { ++ unsigned seq; /* corresponds to global eval_scan_seq */ ++ enum eval_result result; /* known result of this whole branch */ ++ unsigned loop_reported:1; /* prevent excessive logging */ ++ } request_scan; /* cfg_request() scanning */ ++ ++ struct { ++ unsigned seq; /* corresponds to global eval_scan_seq */ ++ union { ++ ++ struct { /* for S_host, S_user or S_group */ ++ /* connected to either "entity->eval_scan.notify_expr_list" ++ * or to global "eval_notified_expr_list" ++ */ ++ struct tac_list_node notify_expr_node; /* gets removed on this expr->seq!= */ ++ } entity; ++ ++ } u; ++ } eval_scan; /* expr_eval() scanning, many per value_scan */ ++ ++ struct membership *membership; /* our owner */ ++ struct expr *parent; /* NULL if we are the root expr */ ++ /* "parent" also (mis)used by expr_sink_{register,commit}() ! */ ++ struct expr *next; /* used in childs of S_and / S_or */ ++ int type; /* S_not, S_and, S_or, S_host, S_user or S_group */ ++ int line; ++ union { ++ ++ struct { ++ struct expr *child; ++ } not; ++ ++ struct { ++ struct expr *child_first; /* linked by expr->next */ ++ } and_or; ++ ++ struct { /* for S_host, S_user or S_group */ ++ const char *name; ++ ENTITY *entity; ++ } entity; ++ ++ } u; ++}; ++#define EXPR_ENTITY_TO_NOTIFYING_ENTITY(expr_) \ ++ (&TAC_MEMBER_STRUCT(ENTITY, tac_list_node_get_list(&(expr_)->eval_scan.u.entity.notify_expr_node), eval_scan.notify_expr_list)) ++#define NOTIFY_EXPR_NODE_TO_EXPR(notify_expr_node_) \ ++ (&TAC_MEMBER_STRUCT(struct expr, (notify_expr_node_), eval_scan.u.entity.notify_expr_node)) ++ ++typedef enum value_scan_func_result (*value_scan_func_t) TAC_ARGS((ENTITY *entity,void *func_data)); ++extern void (*value_scan_forward_seen_hook) TAC_ARGS((struct membership *membership)); ++ ++ ++extern int request_scan_user_known; /* have we allowed to 'solve' S_user entities at all? */ ++ ++ ++extern void unlink_expr TAC_ARGS((struct expr *expr)); ++extern void free_expr TAC_ARGS((struct expr *expr)); ++extern void scan_free_entity TAC_ARGS((ENTITY *entity)); ++extern struct expr *new_expr TAC_ARGS((int type)); ++extern enum value_scan_func_result value_scan_entity TAC_ARGS((ENTITY *entity, int recurse, value_scan_func_t func, void *func_data)); ++extern struct membership *value_scan_backward TAC_ARGS((ENTITY *entity)); ++extern enum eval_result expr_eval TAC_ARGS((struct expr *expr_single)); ++extern struct expr *dupl_expr TAC_ARGS((const struct expr *in)); ++extern void scan_init_entity TAC_ARGS((ENTITY *entity)); ++extern struct membership *enlist_entity_direct TAC_ARGS((ENTITY *parent, ENTITY *child, struct expr *when)); ++extern struct membership *virtual_enlist_entity_direct TAC_ARGS((ENTITY *parent, ENTITY *child)); ++extern void scan_invalidate_entities_hashtable TAC_ARGS((void **hashtable, enum invalidate_scan what)); ++extern void request_scan_begin TAC_ARGS((void)); ++extern void eval_force_belong_entity TAC_ARGS((ENTITY *entity)); ++extern void expr_sink_register TAC_ARGS((struct expr *expr)); ++extern int expr_sink_commit TAC_ARGS((void)); ++ ++ ++#endif /* CFGEVAL_H */ +diff --git a/cfgfile.c b/cfgfile.c +index 6eaef53..041993c 100644 +--- a/cfgfile.c ++++ b/cfgfile.c +@@ -17,38 +17,87 @@ + FITNESS FOR A PARTICULAR PURPOSE. + */ + ++ + #include "tac_plus.h" ++ + #include ++#include + #include +-#include "regexp.h" ++#include ++ ++#ifndef WITH_INCLUDED_REGEX ++#ifdef HAVE_REGEX_H ++#include ++#endif ++#endif ++ ++#include "cfgfile.h" ++#include "report.h" ++#include "utils.h" ++#include "hash.h" ++#include "parse.h" ++#include "main.h" ++#include "do_author.h" /* for "struct identity" */ ++ ++#ifdef WITH_INCLUDED_REGEX ++#include "tac_regexp.h" ++#endif ++ ++ ++static void sym_get TAC_ARGS((void)); ++static void when_expr_root_init TAC_ARGS((void)); ++static void rch TAC_ARGS((void)); ++static int parse_entity TAC_ARGS((int entity_type)); ++static NODE *parse_svcs TAC_ARGS((void)); ++static int parse_conditional_block TAC_ARGS((ENTITY *entity)); ++static NODE *parse_cmd_matches TAC_ARGS((void)); ++static NODE *parse_attrs TAC_ARGS((void)); ++static void getsym TAC_ARGS((void)); ++static enum eval_result entity_svc_default TAC_ARGS((ENTITY *entity)); ++ + + /* + := * + +- := | ++ := | + + := | +- accounting file = ++ accounting file = | + default authorization = permit | +- key = ++ key = | ++ authorization = ( first | recursive ) + + := default authentication = file + #if defined(DB) +- | db ) ++ | db + #endif + +- := permit | deny ++ := permit | deny + + := + + := + + := user = { +- [ default service = [ permit | deny ] ] ++ [ ] + * + * + } + ++ := host = { ++ [ ] ++ * ++ * ++ } ++ ++ := group = { ++ [ ] ++ * ++ * ++ } ++ ++ := default service = ( permit | deny | default ) ++ + := file | + skey | + cleartext | +@@ -80,23 +129,66 @@ + global = cleartext | + msg = + before authorization = | +- after authorization = ++ after authorization = | ++ ++ ++ := key = | ++ ++ ++ := enlist = | ++ key = | ++ ++ ++ := member = | ++ enlist = | ++ | ++ ++ ++ := { ++ * ++ } + + := | + + := cmd = { +- * ++ * + } + +- := ++ := | ++ ++ ++ := { ++ * ++ } + +- := service = ( exec | arap | slip | ppp protocol = { ++ := service = ( exec | arap | slip | ppp protocol = ) { ++# first matching is the FINAL one, no further graph scanning occurs! + [ default attribute = permit ] +- * ++ * ++ } ++ ++ := [ optional ] = | ++ ++ ++ := { ++ * + } + +- := [ optional ] = ++ := when = + ++# to avoid ambiguous precence by forbid of "or" & "and" without parentheses: ++ := | ++ not | ++ '(' ')' | ++ '(' ')' ++ ++ := | ++ or ++ ++ := | ++ and ++ ++ := ( user | host | group ) + */ + + static char sym_buf[MAX_INPUT_LINE_LEN]; /* parse buffer */ +@@ -107,102 +199,54 @@ 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 */ ++ /* ='default authorization': 0/S_permit */ ++static int algorithm_recursive = 0; /* use newer authorization alogrithm? */ ++ /* 1 if 'authorization = recursive' */ + static char *authen_default = NULL; /* top level authentication default */ +-static int authen_default_method = 0; /*For method check */ ++ /* ='default authentication' */ ++static int authen_default_method = 0; /* For method check */ ++ /* ='default authentication' symbol */ + 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; ++static void *grouptable[HASH_TAB_SIZE]; /* Table of group declarations */ ++static void *usertable[HASH_TAB_SIZE]; /* Table of user declarations */ ++static void *hosttable[HASH_TAB_SIZE]; /* Table of host declarations */ + +-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 */ + ++struct enlist_entity_item { ++ struct enlist_entity_item *next; ++ int parent_type; char *parent; ++ int child_type; char * child; /* will be created when not found (for "enlist") */ ++ struct expr *when; ++ int line; ++}; ++static struct enlist_entity_item * enlist_entity_list; ++static struct enlist_entity_item **enlist_entity_list_tailp = &enlist_entity_list; + +-static void +- sym_get(); + ++static void parse_error TAC_ARGS((char *fmt,...)) G_GNUC_PRINTF(1, 2); + + #ifdef __STDC__ ++ + #include /* ANSI C, variable length args */ + static void + parse_error(char *fmt,...) +-#else ++ ++#else /* __STDC__ */ ++ + #include /* has 'vararg' definitions */ + /* VARARGS2 */ + static void + parse_error(fmt, va_alist) + char *fmt; +- + va_dcl /* no terminating semi-colon */ +-#endif ++ ++#endif /* __STDC__ */ + { + char msg[256]; /* temporary string */ + va_list ap; +@@ -220,7 +264,9 @@ va_dcl /* no terminating semi-colon */ + tac_exit(1); + } + +-char * ++const char *cfg_nodestring TAC_ARGS((int type)); ++ ++const char * + cfg_nodestring(type) + int type; + { +@@ -250,6 +296,54 @@ cfg_nodestring(type) + } + } + ++const char *entity_type_to_string TAC_ARGS((int entity_type)); ++ ++const char * ++entity_type_to_string(entity_type) ++int entity_type; ++{ ++ switch (entity_type) { ++ case S_user: ++ return ("user"); ++ case S_host: ++ return ("host"); ++ case S_group: ++ return ("group"); ++ } ++ return (NULL); ++} ++ ++static void **entity_type_to_hashtable TAC_ARGS((int entity_type)); ++ ++static void ** ++entity_type_to_hashtable(entity_type) ++int entity_type; ++{ ++ switch (entity_type) { ++ case S_user: ++ return (usertable); ++ case S_host: ++ return (hosttable); ++ case S_group: ++ return (grouptable); ++ } ++ return (NULL); ++} ++ ++void scan_invalidate_entities TAC_ARGS((enum invalidate_scan what)); ++ ++void ++scan_invalidate_entities(what) ++enum invalidate_scan what; ++{ ++ scan_invalidate_entities_hashtable( usertable, what); ++ scan_invalidate_entities_hashtable( hosttable, what); ++ scan_invalidate_entities_hashtable(grouptable, what); ++} ++ ++ ++static void free_attrs TAC_ARGS((NODE *node)); ++ + static void + free_attrs(node) + NODE *node; +@@ -257,13 +351,17 @@ NODE *node; + NODE *next; + + while (node) { ++ unlink_expr(node->when); ++ free_expr(node->when); ++ node->when = NULL; ++ + 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); ++ (const char *) node->value); + break; + default: + report(LOG_ERR, "Illegal node type %s for free_attrs", +@@ -278,6 +376,8 @@ NODE *node; + } + } + ++static void free_cmd_matches TAC_ARGS((NODE *node)); ++ + static void + free_cmd_matches(node) + NODE *node; +@@ -288,16 +388,32 @@ NODE *node; + if (debug & DEBUG_CLEAN_FLAG) + report(LOG_DEBUG, "free_cmd_match %s %s", + cfg_nodestring(node->type), +- node->value); ++ (const char *) node->value); ++ ++ unlink_expr(node->when); ++ free_expr(node->when); ++ node->when = NULL; + + free(node->value); /* text */ +- free(node->value1); /* regexp compiled text */ ++ ++#ifdef WITH_INCLUDED_REGEX ++ ++ free(node->value1); /* tac_regexp compiled text */ ++ ++#else /* WITH_INCLUDED_REGEX */ ++ ++ regfree((regex_t *) node->value1); /* POSIX regex compiled text */ ++ ++#endif /* WITH_INCLUDED_REGEX */ ++ + next = node->next; + free(node); + node = next; + } + } + ++static void free_svcs TAC_ARGS((NODE *node)); ++ + static void + free_svcs(node) + NODE *node; +@@ -305,12 +421,16 @@ NODE *node; + NODE *next; + + while (node) { ++ unlink_expr(node->when); ++ free_expr(node->when); ++ node->when = NULL; + + switch (node->type) { + case N_svc_cmd: + if (debug & DEBUG_CLEAN_FLAG) + report(LOG_DEBUG, "free %s %s", +- cfg_nodestring(node->type), node->value); ++ cfg_nodestring(node->type), ++ (const char *) node->value); + free(node->value); /* cmd name */ + free_cmd_matches(node->value1); + next = node->next; +@@ -340,85 +460,107 @@ NODE *node; + } + } + ++static void free_enlist_entity_item TAC_ARGS((struct enlist_entity_item *item)); ++ ++static void ++free_enlist_entity_item(item) ++struct enlist_entity_item *item; ++{ ++ free(item->parent); ++ free(item->child); ++ free_expr(item->when); ++} ++ ++ ++static void free_entity TAC_ARGS((ENTITY *entity)); ++ + static void +-free_userstruct(user) +-USER *user; ++free_entity(entity) ++ENTITY *entity; + { + 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); ++ entity_type_to_string(entity->type), entity->name); ++ ++ /* function MUST be called while the whole entity is still VALID! */ ++ scan_free_entity(entity); ++ ++ if (entity->name) ++ free(entity->name); ++ if (entity->full_name) ++ free(entity->full_name); ++ if (entity->login) ++ free(entity->login); ++ if (entity->expires) ++ free(entity->expires); ++ if (entity->time) ++ free(entity->time); ++ if (entity->arap) ++ free(entity->arap); ++ if (entity->chap) ++ free(entity->chap); + #ifdef MSCHAP +- if (user->mschap) +- free(user->mschap); ++ if (entity->mschap) ++ free(entity->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); ++ if (entity->pap) ++ free(entity->pap); ++ if (entity->opap) ++ free(entity->opap); ++ if (entity->global) ++ free(entity->global); ++ if (entity->msg) ++ free(entity->msg); ++ if (entity->before_author) ++ free(entity->before_author); ++ if (entity->after_author) ++ free(entity->after_author); ++ if (entity->key) ++ free(entity->key); ++ free_svcs(entity->svcs); + } + ++static void free_hashtable TAC_ARGS((void **hashtable)); ++ + static void +-free_hoststruct(host) +-HOST *host; ++free_hashtable(hashtable) ++void **hashtable; + { +- if (debug & DEBUG_CLEAN_FLAG) +- report(LOG_DEBUG, "free %s", +- host->name); +- +- if (host->name) +- free(host->name); +- +- if (host->key) +- free(host->key); ++ int i; ++ ENTITY *entity,**entityp; + +- if (host->type) +- free(host->type); ++ for (i = 0; i < HASH_TAB_SIZE; i++) { ++ entityp = (ENTITY **) (hashtable+i); ++ while ((entity = *entityp)) { ++ *entityp = entity->hash; ++ free_entity(entity); ++ free(entity); ++ } ++ } + } + ++ + /* + * Exported routines + */ + ++void cfg_clean_config TAC_ARGS((void)); ++ + /* Free all allocated structures preparatory to re-reading the config file */ + void + cfg_clean_config() + { +- int i; +- USER *entry, *next; +- HOST *host_entry,*hn; ++ struct enlist_entity_item *enlist_entity_item; + + if (authen_default) { + free(authen_default); + authen_default = NULL; + } + ++ if (algorithm_recursive) { ++ algorithm_recursive = 0; ++ } ++ + if (authen_default_method) { + authen_default_method = 0; + } +@@ -438,43 +580,21 @@ cfg_clean_config() + 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; +- } ++ free_hashtable( usertable); ++ free_hashtable( hosttable); ++ free_hashtable(grouptable); + +- /* 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; ++ while (enlist_entity_list) { ++ enlist_entity_item = enlist_entity_list; ++ enlist_entity_list = enlist_entity_item->next; ++ free_enlist_entity_item(enlist_entity_item); ++ free(enlist_entity_item); + } ++ enlist_entity_list_tailp = &enlist_entity_list; + } + ++static int parse_permission TAC_ARGS((void)); ++ + static int + parse_permission() + { +@@ -490,6 +610,8 @@ parse_permission() + return (symbol); + } + ++static int parse TAC_ARGS((int symbol)); ++ + static int + parse(symbol) + int symbol; +@@ -505,9 +627,13 @@ int symbol; + return (0); + } + ++static int parse_opt_svc_default TAC_ARGS((void)); ++ + static int + parse_opt_svc_default() + { ++ int retval; ++ + if (sym_code != S_default) { + return (0); + } +@@ -515,14 +641,30 @@ parse_opt_svc_default() + parse(S_default); + parse(S_svc); + parse(S_separator); +- if (sym_code == S_permit) { +- parse(S_permit); +- return (S_permit); ++ ++ switch (sym_code) { ++ default: ++ parse_error("expecting 'permit', 'deny' or 'default' but found '%s' on line %d", ++ sym_buf, sym_line); ++ return (1); ++ ++ case S_default: ++ if (!algorithm_recursive) { ++ parse_error("'default service = %s' supported only if set top level 'authorization = recursive', on line %d", ++ sym_buf, sym_line); ++ return (1); ++ } ++ /* FALLTHRU */ ++ case S_permit: ++ case S_deny: ++ retval = sym_code; ++ sym_get(); ++ return (retval); + } +- parse(S_deny); +- return (S_deny); + } + ++static int parse_opt_attr_default TAC_ARGS((void)); ++ + static int + parse_opt_attr_default() + { +@@ -536,20 +678,19 @@ parse_opt_attr_default() + 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 TAC_ARGS((void)); ++ + static int + parse_decls() + { + no_user_dflt = 0; /* default if user doesn't exist */ ++ algorithm_recursive = 0; /* use backward compatible alg. by default */ ++ when_expr_root_init(); ++ enlist_entity_list_tailp = &enlist_entity_list; + + sym_code = 0; + rch(); +@@ -614,7 +755,7 @@ parse_decls() + case S_db: + #endif + #ifdef USE_LDAP +- case S_ldap; ++ case S_ldap: + #endif + #ifdef USE_PAM + case S_pam: +@@ -642,6 +783,26 @@ parse_decls() + continue; + } + ++ case S_authorization: ++ sym_get(); ++ parse(S_separator); ++ switch (sym_code) { ++ default: ++ parse_error("expecting 'first' or 'recursive' but found '%s' on line %d", ++ sym_buf, sym_line); ++ return (1); ++ ++ case S_first: ++ parse(S_first); ++ algorithm_recursive = 0; ++ continue; ++ ++ case S_recursive: ++ parse(S_recursive); ++ algorithm_recursive = 1; ++ continue; ++ } ++ + case S_key: + /* Process a key declaration. */ + sym_get(); +@@ -656,17 +817,12 @@ parse_decls() + sym_get(); + continue; + +- case S_host: +- parse_host(); +- continue; +- + case S_user: ++ case S_host: + case S_group: +- parse_user(); ++ parse_entity(sym_code); + continue; + +- /* case S_host: parse_host(); continue; */ +- + default: + parse_error("Unrecognised token %s on line %d", sym_buf, sym_line); + return (1); +@@ -674,8 +830,6 @@ parse_decls() + } + } + +-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 */ +@@ -688,147 +842,511 @@ sym_get(); parse(S_separator); if (field) { \ + } \ + field = tac_strdup(sym_buf); + +-static int +-parse_host() ++static struct expr *when_expr_root; ++#define WHEN_EXPR_ROOT_SANE() \ ++ (when_expr_root && !when_expr_root->next && when_expr_root->type==S_and) ++#define WHEN_EXPR_ROOT_EMPTY() \ ++ (WHEN_EXPR_ROOT_SANE() && !when_expr_root->u.and_or.child_first) ++ ++static struct expr *parse_expr_node TAC_ARGS((int single_item)); ++ ++static struct expr * ++parse_expr_node(single_item) ++int single_item; + { +- HOST *h; +- HOST *host = (HOST *) tac_malloc(sizeof(HOST)); +- int save_sym; +- char buf[MAX_INPUT_LINE_LEN]; ++ struct expr *expr_root = NULL; ++ struct expr **succ_exprp = &expr_root; ++ struct expr *expr; + +- bzero(host, sizeof(HOST)); ++ while (1) { ++ switch (sym_code) { + ++ case S_not: ++ expr = (struct expr *) tac_malloc(sizeof(struct expr)); ++ expr->line = sym_line; ++ *succ_exprp = expr; ++ expr->next = NULL; ++ succ_exprp = &expr->next; ++ expr->type = S_not; + sym_get(); +- parse(S_separator); +- host->name = tac_strdup(sym_buf); +- host->line = sym_line; ++ expr->u.not.child = parse_expr_node(1 /* single_item */); ++ if (!expr->u.not.child) { ++ free_expr(expr_root); ++ return (NULL); ++ } ++ break; ++ ++ case S_user: ++ case S_host: ++ case S_group: ++ expr = (struct expr *) tac_malloc(sizeof(struct expr)); ++ expr->line = sym_line; ++ *succ_exprp = expr; ++ expr->next = NULL; ++ succ_exprp = &expr->next; ++ expr->type = sym_code; ++ sym_get(); ++ expr->u.entity.name = tac_strdup(sym_buf); ++ sym_get(); ++ expr->u.entity.entity = NULL; /* not known yet */ ++ break; + +- h = hash_add_entry(hosttable, (void *) host); ++ case S_openparen: ++ sym_get(); ++ expr = parse_expr_node(0 /* single_item */); ++ *succ_exprp = expr; ++ parse(S_closeparen); + +- if (h) { +- parse_error("multiply defined %s on lines %d and %d", +- host->name, h->line, sym_line); +- return (1); ++ if (expr->next) { ++ report(LOG_ERR, "Illegal filled next field of parsed parenthesed expr"); ++ free_expr(expr_root); ++ return (NULL); + } ++ succ_exprp = &expr->next; ++ break; + +- sym_get(); +- parse(S_openbra); ++ default: ++ parse_error("expecting 'not', 'user', 'host', 'group' or '(' but found '%s' on line %d", ++ sym_buf, sym_line); ++ free_expr(expr_root); ++ return (NULL); ++ } ++ ++ if (single_item) /* used by 'not' operator with high precedence */ ++ return (expr_root); + +- 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); ++ ++ case S_and: ++ case S_or: ++ if (expr_root->type == (sym_code==S_and ? S_or : S_and)) { ++ parse_error("ambiguous use of 'and' together with 'or', parentheses required on line %d", ++ sym_line); ++ free_expr(expr_root); ++ return (NULL); ++ } ++ if (expr_root->type != sym_code) { ++ expr = (struct expr *) tac_malloc(sizeof(struct expr)); ++ expr->line = sym_line; ++ expr->next = NULL; ++ expr->type = sym_code; ++ expr->u.and_or.child_first = expr_root; ++ expr_root = expr; ++ } + sym_get(); + continue; ++ } + +- case S_closebra: +- parse(S_closebra); +- return (0); ++ return(expr_root); ++ } ++} + +- default: +- parse_error("Unrecognised keyword %s for host %s on line %d", +- sym_buf, host->name,sym_line); ++static struct expr *parse_when_decl TAC_ARGS((void)); + +- return (0); ++static struct expr * ++parse_when_decl() ++{ ++ parse(S_when); ++ if (!algorithm_recursive) { ++ parse_error("'when' conditionals supported only if set top level 'authorization = recursive', on line %d", ++ sym_line); ++ return (NULL); + } +- } /* while */ +-} /* finish parse_host */ ++ parse(S_separator); ++ return (parse_expr_node(0 /* single_item */)); ++} ++ ++static void when_expr_root_init TAC_ARGS((void)); ++ ++static void ++when_expr_root_init() ++{ ++ free_expr(when_expr_root); ++ when_expr_root = new_expr(S_and); ++} + ++static int push_parsed_when_decl TAC_ARGS((void)); + + static int +-parse_user() ++push_parsed_when_decl() + { +- USER *n; +- int isuser; +- USER *user = (USER *) tac_malloc(sizeof(USER)); +- int save_sym; +- char **fieldp; +- char buf[MAX_INPUT_LINE_LEN]; ++ struct expr *parsed_expr; + +- bzero(user, sizeof(USER)); ++ parsed_expr = parse_when_decl(); ++ if (!parsed_expr) ++ return (1); ++ if (!WHEN_EXPR_ROOT_SANE()) { ++ report(LOG_ERR, "INTERNAL: when_expr_root not valid during push_parsed_when_decl()!"); ++ free_expr(parsed_expr); ++ return (1); ++ } ++ if (parsed_expr->next) { ++ report(LOG_ERR, "INTERNAL: Illegal filled next field of parsed expr"); ++ free_expr(parsed_expr); ++ return (1); ++ } ++ parsed_expr->next = when_expr_root->u.and_or.child_first; ++ when_expr_root->u.and_or.child_first = parsed_expr; ++ when_expr_root->line = parsed_expr->line; ++ return (0); ++} + +- isuser = (sym_code == S_user); ++static int pop_when_decl TAC_ARGS((void)); + +- sym_get(); +- parse(S_separator); +- user->name = tac_strdup(sym_buf); +- user->line = sym_line; ++static int ++pop_when_decl() ++{ ++ struct expr *first_expr; + +- 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 (!WHEN_EXPR_ROOT_SANE()) { ++ report(LOG_ERR, "INTERNAL: when_expr_root not valid during pop_when_decl()!"); ++ return (1); + } +- +- if (n) { +- parse_error("multiply defined %s %s on lines %d and %d", +- isuser ? "user" : "group", +- user->name, n->line, sym_line); ++ first_expr = when_expr_root->u.and_or.child_first; ++ if (!first_expr) { ++ report(LOG_ERR, "No expr in stack and pop_when_decl() called"); + return (1); + } +- sym_get(); +- parse(S_openbra); ++ when_expr_root->u.and_or.child_first = first_expr->next; ++ first_expr->next = NULL; ++ free_expr(first_expr); ++ return (0); ++} + +- /* Is the default deny for svcs or cmds to be overridden? */ +- user->svc_dflt = parse_opt_svc_default(); ++static struct expr *copy_current_when_decl TAC_ARGS((void)); + +- while (1) { +- switch (sym_code) { +- case S_eof: +- return (0); ++static struct expr * ++copy_current_when_decl() ++{ ++ return (dupl_expr(when_expr_root)); ++} + +- case S_time: +- ASSIGN(user->time); +- sym_get(); +- continue; ++static struct expr *when_expr_dungeon; + +- case S_before: +- sym_get(); +- parse(S_authorization); +- if (user->before_author) +- free(user->before_author); +- user->before_author = tac_strdup(sym_buf); ++static void starve_when_decl TAC_ARGS((void)); ++ ++static void ++starve_when_decl() ++{ ++ if (!WHEN_EXPR_ROOT_SANE()) { ++ report(LOG_WARNING, "INTERNAL: when_expr_root not sane during starve_when_decl!"); ++ } ++ when_expr_root->next = when_expr_dungeon; ++ when_expr_dungeon = when_expr_root; ++ when_expr_root = NULL; ++ when_expr_root_init(); ++} ++ ++static int feed_when_decl TAC_ARGS((void)); ++ ++static int ++feed_when_decl() ++{ ++ if (!when_expr_dungeon) { ++ report(LOG_ERR, "INTERNAL: No expr in dungeon and feed_when_decl() called"); ++ return (1); ++ } ++ if (!WHEN_EXPR_ROOT_EMPTY()) { ++ report(LOG_WARNING, "INTERNAL: Some 'when' expression found still pushed in dungeon during feed_when_decl()!"); ++ } ++ free_expr(when_expr_root); ++ when_expr_root = when_expr_dungeon; ++ when_expr_dungeon = when_expr_dungeon->next; ++ when_expr_root->next = NULL; ++ return (0); ++} ++ ++ENTITY *entity_lookup TAC_ARGS((int type, const char *name)); ++ ++ENTITY * ++entity_lookup(type, name) ++int type; ++const char *name; ++{ ++ return (hash_lookup(entity_type_to_hashtable(type), name)); ++} ++ ++static int enlist_entity_connect TAC_ARGS((void)); ++ ++static int ++enlist_entity_connect() ++{ ++ struct enlist_entity_item *item; ++ ENTITY *parent_entity, *child_entity; ++ ++ while ((item=enlist_entity_list)) { ++ ++ parent_entity = entity_lookup(item->parent_type, item->parent); ++ if (!parent_entity) { ++ parse_error("Entity %s %s not defined, referenced as parent on line %d", ++ entity_type_to_string(item->parent_type), item->parent, item->line); ++ return (1); ++ } ++ child_entity = entity_lookup(item-> child_type, item-> child); ++ if (!child_entity) { ++ child_entity = new_entity(item->child_type, item->child, item->line); ++ if (!child_entity) ++ return (1); /* 'hash_add_entry()' conflict */ ++ item->child = NULL; /* don't free string ref'ed from 'child_entity'! */ ++ } ++ ++ if (!enlist_entity_direct(parent_entity, child_entity, item->when)) ++ return (1); /* entities not found */ ++ ++ enlist_entity_list = item->next; ++ item->when = NULL; ++ free_enlist_entity_item(item); ++ free(item); ++ } ++ enlist_entity_list_tailp = &enlist_entity_list; ++ return (0); ++} ++ ++static void enlist_entity TAC_ARGS((int parent_type, const char *parent, int child_type, const char *child)); ++ ++static void ++enlist_entity(parent_type, parent, child_type, child) ++int parent_type; ++const char *parent; ++int child_type; ++const char *child; ++{ ++ struct enlist_entity_item *item = ++ (struct enlist_entity_item *) tac_malloc(sizeof(struct enlist_entity_item)); ++ ++ item->next = NULL; ++ *enlist_entity_list_tailp = item; ++ enlist_entity_list_tailp = &item->next; ++ ++ item->parent_type = parent_type; ++ item->parent = tac_strdup(parent); ++ item-> child_type = child_type; ++ item->child = tac_strdup(child); ++ item->when = copy_current_when_decl(); ++ item->line = sym_line; ++} ++ ++static int parse_entity_spec TAC_ARGS((void)); ++ ++/* returns 0 for error, otherwise S_user, S_host or S_group; sym_buf filled */ ++static int ++parse_entity_spec() ++{ ++ int retval; ++ ++ if (sym_code != S_user ++ && sym_code != S_host ++ && sym_code != S_group ++ ) { ++ parse_error("Expecting 'user', 'host' or ' group' as entity specification, found %s on line %d", ++ sym_buf, sym_line); ++ return (0); ++ } ++ ++ retval = sym_code; + sym_get(); +- continue; + +- case S_after: ++ return (retval); ++} ++ ++static int parse_conditional_block_item TAC_ARGS((ENTITY *entity)); ++ ++static int ++parse_conditional_block_item(entity) ++ENTITY *entity; ++{ ++ switch (sym_code) { ++ case S_eof: ++ return (1); ++ ++ /* case S_closebra: not needed, handled by our caller parse_conditional_block() */ ++ ++ default: ++ parse_error("Unrecognised keyword %s for entity on line %d", ++ sym_buf, sym_line); ++ return (1); ++ ++ case S_member: + sym_get(); +- parse(S_authorization); +- if (user->after_author) +- free(user->after_author); +- user->after_author = tac_strdup(sym_buf); ++ parse(S_separator); ++ enlist_entity(S_group, sym_buf, entity->type, entity->name); + sym_get(); +- continue; ++ break; ++ ++ case S_enlist: { ++ int parsed_entity_type; ++ ++ if (entity->type != S_group) { ++ parse_error("'enlist' keyword allowed only in 'group' section on line %d", ++ sym_line); ++ return (1); ++ } ++ sym_get(); ++ parse(S_separator); ++ parsed_entity_type = parse_entity_spec(); ++ if (!parsed_entity_type) ++ return (1); ++ enlist_entity(entity->type, entity->name, parsed_entity_type, sym_buf); ++ sym_get(); ++ break; ++ } + + case S_svc: + case S_cmd: + +- if (user->svcs) { ++ if (entity->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) ++ for (p=entity->svcs; p->next; p=p->next) + /* NULL STMT */; + p->next = parse_svcs(); + } else { +- user->svcs = parse_svcs(); ++ entity->svcs = parse_svcs(); ++ } ++ break; ++ ++ case S_when: ++ if (parse_conditional_block(entity)) ++ return (1); ++ break; ++ } ++ ++ return (0); ++} ++ ++static int parse_conditional_block TAC_ARGS((ENTITY *entity)); ++ ++static int ++parse_conditional_block(entity) ++ENTITY *entity; ++{ ++ int retval = -1 /* GCC paranoia */; ++ ++ if (push_parsed_when_decl()) ++ return (1); ++ parse(S_openbra); ++ ++ while (1) { ++ if (sym_code == S_closebra) { ++ sym_get(); ++ retval = 0; /* success */ ++ break; ++ } ++ ++ if (parse_conditional_block_item(entity)) { ++ retval = 1; /* failure */ ++ break; ++ } ++ } ++ ++ if (pop_when_decl()) ++ return (1); ++ ++ return (retval); ++} ++ ++/* passed 'name' WILL be directly stored to returned ENTITY, don't touch it! */ ++ ++ENTITY *new_entity TAC_ARGS((int type, char *name, int line)); ++ ++ENTITY * ++new_entity(type, name, line) ++int type; ++char *name; ++int line; ++{ ++ ENTITY *entity = (ENTITY *) tac_malloc(sizeof(ENTITY)); ++ ENTITY *hash_conflict; ++ ++ bzero(entity, sizeof(ENTITY)); ++ tac_list_init(&entity->to_parent_membership_list); ++ tac_list_init(&entity->to_child_membership_list ); ++ entity->to_child_membership_num = 0; ++ scan_init_entity(entity); ++ ++ entity->type = type; ++ entity->name = name; ++ entity->line = line; ++ ++ hash_conflict = hash_add_entry(entity_type_to_hashtable(type), (void *) entity); ++ if (hash_conflict) { ++ parse_error("multiply defined %s %s on lines %d and %d", ++ entity_type_to_string(type), ++ entity->name, hash_conflict->line, sym_line); ++ free (entity); ++ return (NULL); ++ } ++ ++ return (entity); ++} ++ ++static int parse_entity TAC_ARGS((int entity_type)); ++ ++static int ++parse_entity(entity_type) ++int entity_type; ++{ ++ ENTITY *entity; ++ int save_sym; ++ char **fieldp = NULL /* GCC paranoia */; ++ char buf[MAX_INPUT_LINE_LEN]; ++ ++ sym_get(); ++ parse(S_separator); ++ ++ entity = new_entity(entity_type, tac_strdup(sym_buf) /* name */, sym_line /* line */); ++ if (!entity) ++ return (1); /* 'hash_add_entry()' conflict, 'tac_strdup(sym_buf)' leaked! */ ++ ++ sym_get(); ++ parse(S_openbra); ++ ++ /* Is the default deny for svcs or cmds to be overridden? */ ++ entity->svc_dflt = parse_opt_svc_default(); ++ ++ while (1) { ++ if (entity_type != S_user) ++ switch (sym_code) { ++ case S_key: ++ ASSIGN(entity->key); ++ sym_get(); ++ continue; + } ++ ++ switch (sym_code) { ++ case S_eof: ++ return (0); ++ ++ case S_time: ++ ASSIGN(entity->time); ++ sym_get(); ++ continue; ++ ++ case S_before: ++ sym_get(); ++ parse(S_authorization); ++ if (entity->before_author) ++ free(entity->before_author); ++ entity->before_author = tac_strdup(sym_buf); ++ sym_get(); ++ continue; ++ ++ case S_after: ++ sym_get(); ++ parse(S_authorization); ++ if (entity->after_author) ++ free(entity->after_author); ++ entity->after_author = tac_strdup(sym_buf); ++ sym_get(); + continue; + + case S_login: +- if (user->login) { ++ if (entity->login) { + parse_error("Duplicate value for %s %s and %s on line %d", +- codestring(sym_code), user->login, ++ codestring(sym_code), entity->login, + sym_buf, sym_line); + tac_exit(1); + } +@@ -837,15 +1355,15 @@ parse_user() + switch(sym_code) { + + case S_skey: +- user->login = tac_strdup(sym_buf); ++ entity->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; ++ entity->login = tac_strdup(nopasswd_str); ++ entity->nopasswd = 1; + break; + + case S_file: +@@ -860,7 +1378,7 @@ parse_user() + sprintf(buf, "%s ", sym_buf); + sym_get(); + strcat(buf, sym_buf); +- user->login = tac_strdup(buf); ++ entity->login = tac_strdup(buf); + break; + + default: +@@ -878,9 +1396,9 @@ parse_user() + continue; + + case S_pap: +- if (user->pap) { ++ if (entity->pap) { + parse_error("Duplicate value for %s %s and %s on line %d", +- codestring(sym_code), user->pap, ++ codestring(sym_code), entity->pap, + sym_buf, sym_line); + tac_exit(1); + } +@@ -896,11 +1414,11 @@ parse_user() + sprintf(buf, "%s ", sym_buf); + sym_get(); + strcat(buf, sym_buf); +- user->pap = tac_strdup(buf); ++ entity->pap = tac_strdup(buf); + break; + + sprintf(buf, "%s ", sym_buf); +- user->pap = tac_strdup(buf); ++ entity->pap = tac_strdup(buf); + break; + + default: +@@ -918,23 +1436,17 @@ parse_user() + continue; + + case S_name: +- ASSIGN(user->full_name); +- sym_get(); +- continue; +- +- case S_member: +- ASSIGN(user->member); ++ ASSIGN(entity->full_name); + sym_get(); + continue; + +- + case S_expires: +- ASSIGN(user->expires); ++ ASSIGN(entity->expires); + sym_get(); + continue; + + case S_message: +- ASSIGN(user->msg); ++ ASSIGN(entity->msg); + sym_get(); + continue; + +@@ -952,20 +1464,32 @@ parse_user() + parse(S_cleartext); + strcat(buf, sym_buf); + +- if (save_sym == S_arap) +- fieldp = &user->arap; +- if (save_sym == S_chap) +- fieldp = &user->chap; ++ switch (save_sym) { ++ case S_arap: ++ fieldp = &entity->arap; ++ break; ++ case S_chap: ++ fieldp = &entity->chap; ++ break; + #ifdef MSCHAP +- if (save_sym == S_mschap) +- fieldp = &user->mschap; ++ case S_mschap: ++ fieldp = &entity->mschap; ++ break; + #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; ++ case S_pap: ++ fieldp = &entity->pap; ++ break; ++ case S_opap: ++ fieldp = &entity->opap; ++ break; ++ case S_global: ++ fieldp = &entity->global; ++ break; ++ default: ++ report(LOG_ERR, "INTERNAL: fieldp not recognized (on line %d)", ++ sym_line); ++ continue; ++ } + + if (*fieldp) { + parse_error("Duplicate value for %s %s and %s on line %d", +@@ -984,7 +1508,7 @@ parse_user() + case S_maxsess: + sym_get(); + parse(S_separator); +- if (sscanf(sym_buf, "%d", &user->maxsess) != 1) { ++ if (sscanf(sym_buf, "%d", &entity->maxsess) != 1) { + parse_error("expecting integer, found '%s' on line %d", + sym_buf, sym_line); + } +@@ -997,16 +1521,14 @@ parse_user() + fprintf(stderr, + "\npassword = is obsolete. Use login = des \n"); + } +- parse_error("Unrecognised keyword %s for user on line %d", +- sym_buf, sym_line); + +- return (0); ++ if (parse_conditional_block_item(entity)) ++ return (0); /* error message already printed */ + } + } + } + +-static NODE *parse_attrs(); +-static NODE *parse_cmd_matches(); ++static NODE *parse_svcs TAC_ARGS((void)); + + static NODE * + parse_svcs() +@@ -1034,11 +1556,16 @@ parse_svcs() + + sym_get(); + parse(S_openbra); +- ++ starve_when_decl(); + result->value1 = parse_cmd_matches(); ++ parse(S_closebra); ++ if (feed_when_decl()) ++ tac_exit(1); /* no error return possibility */ ++ + result->type = N_svc_cmd; ++ result->when = copy_current_when_decl(); ++ expr_sink_register(result->when); + +- parse(S_closebra); + result->next = parse_svcs(); + return (result); + } +@@ -1075,25 +1602,52 @@ parse_svcs() + result->value1 = tac_strdup(sym_buf); + break; + } ++ + sym_get(); + parse(S_openbra); ++ starve_when_decl(); ++ + result->dflt = parse_opt_attr_default(); + result->value = parse_attrs(); ++ + parse(S_closebra); ++ feed_when_decl(); ++ ++ result->when = copy_current_when_decl(); ++ expr_sink_register(result->when); ++ + result->next = parse_svcs(); + return (result); + } + +-/* := */ ++/* := */ ++ ++static NODE *parse_cmd_matches TAC_ARGS((void)); + + static NODE * + parse_cmd_matches() + { ++ NODE *retval = NULL, **succp = &retval; + NODE *result; + +- if (sym_code != S_permit && sym_code != S_deny) { +- return (NULL); +- } ++ for (;;) { ++ switch (sym_code) { ++ default: ++ return (retval); ++ ++ case S_when: ++ if (push_parsed_when_decl()) ++ tac_exit(1); /* no error return possibility */ ++ parse(S_openbra); ++ result = parse_cmd_matches(); ++ parse(S_closebra); ++ if (pop_when_decl()) ++ tac_exit(1); /* no error return possibility */ ++ break; ++ ++ case S_permit: ++ case S_deny: ++ + result = (NODE *) tac_malloc(sizeof(NODE)); + + bzero(result, sizeof(NODE)); +@@ -1102,7 +1656,20 @@ parse_cmd_matches() + result->type = (parse_permission() == S_permit) ? N_permit : N_deny; + result->value = tac_strdup(sym_buf); + +- result->value1 = (void *) regcomp(result->value); ++#ifdef WITH_INCLUDED_REGEX ++ ++ result->value1 = (void *) tac_regcomp(result->value); ++ ++#else /* WITH_INCLUDED_REGEX */ ++ ++ result->value1 = tac_malloc(sizeof(regex_t)); ++ if (regcomp(result->value1, result->value /* regex */, REG_NOSUB /* cflags */)) { ++ free(result->value1); ++ result->value1 = NULL; ++ } ++ ++#endif /* WITH_INCLUDED_REGEX */ ++ + if (!result->value1) { + report(LOG_ERR, "in regular expression %s on line %d", + sym_buf, sym_line); +@@ -1110,30 +1677,56 @@ parse_cmd_matches() + } + sym_get(); + +- result->next = parse_cmd_matches(); ++ result->when = copy_current_when_decl(); ++ expr_sink_register(result->when); + +- return (result); ++ result->next = NULL; ++ } ++ *succp = result; ++ while (result->next) ++ result = result->next; /* skip parsed chain from parse_cmd_matches() */ ++ succp = &result->next; ++ } ++ /* NOTREACHED */ + } + ++static NODE *parse_attrs TAC_ARGS((void)); ++ + static NODE * + parse_attrs() + { ++ NODE *retval = NULL, **succp = &retval; + NODE *result; + char buf[MAX_INPUT_LINE_LEN]; +- int optional = 0; ++ int optional; + +- if (sym_code == S_closebra) { +- return (NULL); +- } ++ for (;;) { ++ optional = 0; ++ ++ switch (sym_code) { ++ case S_closebra: ++ return (retval); ++ ++ case S_when: ++ if (push_parsed_when_decl()) ++ tac_exit(1); /* no error return possibility */ ++ parse(S_openbra); ++ result = parse_attrs(); ++ parse(S_closebra); ++ if (pop_when_decl()) ++ tac_exit(1); /* no error return possibility */ ++ break; ++ ++ case S_optional: ++ optional = 1; ++ sym_get(); ++ /* FALLTHRU */ ++ default: + 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); +@@ -1144,13 +1737,22 @@ parse_attrs() + parse(S_string); + + result->value = tac_strdup(buf); +- result->next = parse_attrs(); +- return (result); ++ ++ result->when = copy_current_when_decl(); ++ expr_sink_register(result->when); ++ ++ result->next = NULL; ++ } ++ *succp = result; ++ while (result->next) ++ result = result->next; /* skip parsed chain from parse_attrs() */ ++ succp = &result->next; ++ } ++ /* NOTREACHED */ + } + + +-static void +- getsym(); ++static void sym_get TAC_ARGS((void)); + + static void + sym_get() +@@ -1163,9 +1765,11 @@ sym_get() + } + } + ++static char *sym_buf_add TAC_ARGS((int c)); ++ + static char * + sym_buf_add(c) +-char c; ++int c; /* promoted "char" type */ + { + if (sym_pos >= MAX_INPUT_LINE_LEN) { + sym_buf[MAX_INPUT_LINE_LEN-1] = '\0'; +@@ -1180,6 +1784,8 @@ char c; + return(sym_buf); + } + ++static void getsym TAC_ARGS((void)); ++ + static void + getsym() + { +@@ -1220,6 +1826,18 @@ next: + rch(); + return; + ++ case '(': ++ strcpy(sym_buf, "("); ++ sym_code = S_openparen; ++ rch(); ++ return; ++ ++ case ')': ++ strcpy(sym_buf, ")"); ++ sym_code = S_closeparen; ++ rch(); ++ return; ++ + case '#': + while ((sym_ch != '\n') && (sym_ch != EOF)) + rch(); +@@ -1304,6 +1922,8 @@ next: + } + } + ++static void rch TAC_ARGS((void)); ++ + static void + rch() + { +@@ -1318,201 +1938,208 @@ rch() + } + + +-/* For a user or group, find the value of a field. Does not recurse. */ +-VALUE +-get_value(user, field) +-USER *user; ++static VALUE get_value TAC_ARGS((ENTITY *entity, int field)); ++ ++/* Find the value of a field. Does not recurse. */ ++static VALUE ++get_value(entity, field) ++ENTITY *entity; + int field; + { + VALUE v; + ++ v.pval = NULL; /* do both just for sure... */ + v.intval = 0; + +- if (!user) { +- parse_error("get_value: illegal user"); ++ if (!entity) { ++ parse_error("get_value: illegal entity"); + return (v); + } + switch (field) { + + case S_name: +- v.pval = user->name; ++ v.pval = entity->name; + break; + + case S_login: +- v.pval = user->login; ++ v.pval = entity->login; + break; + + case S_global: +- v.pval = user->global; +- break; +- +- case S_member: +- v.pval = user->member; ++ v.pval = entity->global; + break; + + case S_expires: +- v.pval = user->expires; ++ v.pval = entity->expires; + break; + + case S_arap: +- v.pval = user->arap; ++ v.pval = entity->arap; + break; + + case S_chap: +- v.pval = user->chap; ++ v.pval = entity->chap; + break; + + #ifdef MSCHAP + case S_mschap: +- v.pval = user->mschap; ++ v.pval = entity->mschap; + break; + #endif /* MSCHAP */ + + case S_pap: +- v.pval = user->pap; ++ v.pval = entity->pap; + break; + + case S_opap: +- v.pval = user->opap; ++ v.pval = entity->opap; + break; + + case S_message: +- v.pval = user->msg; ++ v.pval = entity->msg; + break; + + case S_svc: +- v.pval = user->svcs; ++ v.pval = entity->svcs; + break; + + case S_before: +- v.pval = user->before_author; ++ v.pval = entity->before_author; + break; + + case S_after: +- v.pval = user->after_author; ++ v.pval = entity->after_author; + break; + + case S_svc_dflt: +- v.intval = user->svc_dflt; ++ v.intval = entity->svc_dflt; + break; + + #ifdef MAXSESS + case S_maxsess: +- v.intval = user->maxsess; ++ v.intval = entity->maxsess; + break; + #endif + + case S_nopasswd: +- v.intval = user->nopasswd; ++ v.intval = entity->nopasswd; + break; + + case S_time: +- v.pval = user->time; ++ v.pval = entity->time; + break; + +- default: ++ case S_key: ++ if (entity->type == S_user) { ++ report(LOG_ERR, "get_value: S_key field not supported in %s %s", ++ entity_type_to_string(entity->type), entity->name); ++ v.pval = NULL; ++ return(v); ++ } ++ v.pval = entity->key; ++ 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; ++ ++/* Internal graph scanning routines */ ++ ++static enum value_scan_func_result value_scan TAC_ARGS((int type, const char *name, int recurse, value_scan_func_t func, void *func_data)); ++ ++static enum value_scan_func_result ++value_scan(type, name, recurse, func, func_data) ++int type; ++const char *name; ++int recurse; ++value_scan_func_t func; ++void *func_data; + { +- 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; ++ ENTITY *entity; + +- case S_key: +- v.pval = host->key; +- break; ++ if (debug & DEBUG_CONFIG_FLAG) ++ report(LOG_DEBUG, "value_scan: find %s %s, recurse=%d", ++ entity_type_to_string(type), name, recurse); + +- default: +- report(LOG_ERR, "get_value: unknown field %d", field); +- break; ++ entity = entity_lookup(type, name); ++ if (!entity) { ++ if (debug & DEBUG_CONFIG_FLAG) ++ report(LOG_DEBUG, "value_scan: no %s named %s", ++ entity_type_to_string(type), name); ++ return (VSFR_CONTINUE); + } +- return (v); +-} + ++ return (value_scan_entity(entity, recurse, func, func_data)); ++} + + /* 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; ++static int circularity_check_failed; + +- if (debug & DEBUG_PARSE_FLAG) +- report(LOG_DEBUG, "circularity_check: user=%s", user->name); ++static void circularity_check_fail TAC_ARGS((struct membership *membership)); ++ ++static void ++circularity_check_fail(membership) ++struct membership *membership; ++{ ++ ENTITY *entity; ++ ++ circularity_check_failed = 1; + +- /* Initialise all groups "seen" flags to zero */ +- for (q = groups; *q; q++) { +- group = *q; +- group->flags &= ~FLAG_SEEN; ++ report(LOG_ERR, "recursively defined groups:"); ++ while (membership) { ++ entity = MEMBERSHIP_TO_CHILD_ENTITY(membership); ++ report(LOG_ERR, "%s %s", ++ entity_type_to_string(entity->type), entity->name); ++ membership = value_scan_backward(entity); + } ++} + +- entry = user; ++static enum value_scan_func_result circularity_check_func TAC_ARGS((ENTITY *entity, void *func_data)); + +- while (entry) { +- /* check groups we are a member of */ +- char *groupname = entry->member; ++static enum value_scan_func_result ++circularity_check_func(entity, func_data /* unused */) ++ENTITY *entity; ++void *func_data; ++{ ++ /* only useful to speedup case of failure */ ++ if (circularity_check_failed) ++ return (VSFR_FOUND); + +- if (debug & DEBUG_PARSE_FLAG) +- report(LOG_DEBUG, "\tmember of group %s", +- groupname ? groupname : ""); ++ return (VSFR_CONTINUE); ++} + ++static int circularity_check TAC_ARGS((void)); + +- /* if not a member of any groups, go on to next user */ +- if (!groupname) +- break; ++static int ++circularity_check() ++{ ++ ENTITY *entity; ++ ENTITY **users_base = (ENTITY **) hash_get_entries(usertable); ++ ENTITY **users; + +- 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"); ++ /* users */ ++ for (users = users_base; *users; users++) { ++ entity = *users; + +- /* 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; +- } ++ if (debug & DEBUG_PARSE_FLAG) ++ report(LOG_DEBUG, "circularity_check: user=%s", entity->name); ++ ++ circularity_check_failed = 0; ++ value_scan_forward_seen_hook = circularity_check_fail; ++ value_scan_entity(entity, TAC_PLUS_RECURSE, ++ (value_scan_func_t) circularity_check_func, NULL /* func_data-unused */); ++ value_scan_forward_seen_hook = NULL; ++ if (circularity_check_failed) ++ break; + } +- free(users); +- free(groups); +- return (0); ++ free(users_base); ++ return (circularity_check_failed); + } + + +@@ -1525,147 +2152,85 @@ circularity_check() + 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; ++static VALUE cfg_get_value_VALUE; /* private */ + +- 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); +- } ++static enum value_scan_func_result cfg_get_value_func TAC_ARGS((ENTITY *entity, int *attrp)); + ++static enum value_scan_func_result ++cfg_get_value_func(entity,attrp /* func_data */) ++ENTITY *entity; ++int *attrp; ++{ + /* found the entry. Lookup value from attr=value */ +- value = get_value(user, attr); ++ cfg_get_value_VALUE = get_value(entity, *attrp); ++ if (cfg_get_value_VALUE.pval) ++ return (VSFR_FOUND); + +- if (value.pval || !recurse) { +- return (value); +- } +- /* no value. Check containing group */ +- if (user->member) +- group = (USER *) hash_lookup(grouptable, user->member); +- else +- group = NULL; ++ return (VSFR_CONTINUE); ++} + +- while (group) { +- if (debug & DEBUG_CONFIG_FLAG) +- report(LOG_DEBUG, "cfg_get_value: recurse group = %s", +- group->name); ++static VALUE cfg_get_value TAC_ARGS((int type, const char *name, int attr, int recurse)); + +- value = get_value(group, attr); ++static VALUE ++cfg_get_value(type, name, attr, recurse) ++int type; ++const char *name; ++int attr, recurse; ++{ ++ if (debug & DEBUG_CONFIG_FLAG) ++ report(LOG_DEBUG, "cfg_get_value: type=%s name=%s attr=%s recurse=%d", ++ entity_type_to_string(type), name, codestring(attr), recurse); + +- if (value.pval) { +- return (value); +- } +- /* still nothing. Check containing group and so on */ ++ cfg_get_value_VALUE.pval = NULL; ++ value_scan(type, name, recurse, ++ (value_scan_func_t) cfg_get_value_func, &attr /* func_data */); ++ return (cfg_get_value_VALUE); ++} + +- 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 TAC_ARGS((int type, const char *name, int attr, int recurse)); + +-/* Wrappers for cfg_get_value */ + int +-cfg_get_intvalue(name, isuser, attr, recurse) +-char *name; +-int isuser, attr, recurse; ++cfg_get_intvalue(type, name, attr, recurse) ++int type; ++const char *name; ++int attr, recurse; + { +- int val = cfg_get_value(name, isuser, attr, recurse).intval; ++ int val = cfg_get_value(type, name, 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 */ ++const char *cfg_get_pvalue TAC_ARGS((int type, const char *name, int attr, int recurse)); + +- 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; ++const char * ++cfg_get_pvalue(type, name, attr, recurse) ++int type; ++const char *name; ++int attr, recurse; + { +- char *p = cfg_get_hvalue(name, attr).pval; ++ char *p = cfg_get_value(type, name, attr, recurse).pval; + + if (debug & DEBUG_CONFIG_FLAG) +- report(LOG_DEBUG, "cfg_get_phvalue: returns %s", ++ report(LOG_DEBUG, "cfg_get_pvalue: 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. */ ++/* Read the config file and do some basic sanity checking on ++ * it. Return 1 if we find any errors. ++ */ ++int cfg_read_config TAC_ARGS((const char *cfile)); + ++int + cfg_read_config(cfile) +-char *cfile; ++const char *cfile; + { + sym_line = 1; + +@@ -1679,7 +2244,17 @@ char *cfile; + return (1); + } + +- if (circularity_check()) { ++ if (0 ++ || enlist_entity_connect() ++ || expr_sink_commit() ++ /* circularity is allowed in the new fully-recursive algorithm */ ++ || (!algorithm_recursive && circularity_check()) ++ ) { ++ fclose(cf); ++ return (1); ++ } ++ if (!WHEN_EXPR_ROOT_EMPTY() || when_expr_dungeon) { ++ report(LOG_ERR, "Some 'when' expression found still pushed on stack"); + fclose(cf); + return (1); + } +@@ -1688,346 +2263,443 @@ char *cfile; + return (0); + } + +-/* return 1 if user exists, 0 otherwise */ ++/* return 1 if user exists, 0 otherwise ++ */ ++int cfg_user_exists TAC_ARGS((const char *username)); ++ + int + cfg_user_exists(username) +-char *username; ++const char *username; + { +- USER *user = (USER *) hash_lookup(usertable, username); +- +- return (user != NULL); ++ return (NULL != hash_lookup(usertable, username)); + } + + /* 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; ++ * on, and so on, recursively if recurse is non-zero ++ */ ++const char *cfg_get_expires TAC_ARGS((const char *username, int recurse)); + ++const char * ++cfg_get_expires(username, recurse) ++const char *username; ++int recurse; + { +- return (cfg_get_pvalue(username, TAC_IS_USER, S_expires, recurse)); ++ return (cfg_get_pvalue(S_user, username, 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 * ++ * on, and so on, recursively if recurse is non-zero ++ */ ++const char *cfg_get_timestamp TAC_ARGS((const char *username, int recurse)); ++ ++const char * + cfg_get_timestamp(username, recurse) +-char *username; ++const char *username; ++int recurse; + { +- return (cfg_get_pvalue(username, TAC_IS_USER, S_time, recurse)); ++ return (cfg_get_pvalue(S_user, username, 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; ++ * on, and so on, recursively if recurse is non-zero ++ */ ++const char *cfg_get_login_secret TAC_ARGS((const char *user, int recurse)); + ++const char * ++cfg_get_login_secret(user, recurse) ++const char *user; ++int recurse; + { +- return (cfg_get_pvalue(user, TAC_IS_USER, S_login, recurse)); ++ return (cfg_get_pvalue(S_user, 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 */ ++ * on, and so on, recursively if recurse is non-zero ++ */ ++int cfg_get_user_nopasswd TAC_ARGS((const char *user, int recurse)); ++ + int + cfg_get_user_nopasswd(user, recurse) +- char *user; ++const char *user; ++int recurse; + { +- return (cfg_get_intvalue(user, TAC_IS_USER, S_nopasswd, recurse)); ++ return (cfg_get_intvalue(S_user, 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; ++ * on, and so on, recursively if recurse is non-zero ++ */ ++const char *cfg_get_arap_secret TAC_ARGS((const char *user, int recurse)); + ++const char * ++cfg_get_arap_secret(user, recurse) ++const char *user; ++int recurse; + { +- return (cfg_get_pvalue(user, TAC_IS_USER, S_arap, recurse)); ++ return (cfg_get_pvalue(S_user, user, S_arap, recurse)); + } + +-char * +-cfg_get_chap_secret(user, recurse) +-char *user; ++const char *cfg_get_chap_secret TAC_ARGS((const char *user, int recurse)); + ++const char * ++cfg_get_chap_secret(user, recurse) ++const char *user; ++int recurse; + { +- return (cfg_get_pvalue(user, TAC_IS_USER, S_chap, recurse)); ++ return (cfg_get_pvalue(S_user, user, S_chap, recurse)); + } + + #ifdef MSCHAP +-char * +-cfg_get_mschap_secret(user, recurse) +-char *user; + ++const char *cfg_get_mschap_secret TAC_ARGS((const char *user, int recurse)); ++ ++const char * ++cfg_get_mschap_secret(user, recurse) ++const char *user; ++int recurse; + { +- return (cfg_get_pvalue(user, TAC_IS_USER, S_mschap, recurse)); ++ return (cfg_get_pvalue(S_user, user, S_mschap, recurse)); + } ++ + #endif /* MSCHAP */ + +-char * ++const char *cfg_get_pap_secret TAC_ARGS((const char *user, int recurse)); ++ ++const char * + cfg_get_pap_secret(user, recurse) +-char *user; ++const char *user; ++int recurse; + { +- return (cfg_get_pvalue(user, TAC_IS_USER, S_pap, recurse)); ++ return (cfg_get_pvalue(S_user, user, S_pap, recurse)); + } + +-char * ++const char *cfg_get_opap_secret TAC_ARGS((const char *user, int recurse)); ++ ++const char * + cfg_get_opap_secret(user, recurse) +-char *user; ++const char *user; ++int recurse; + { +- return (cfg_get_pvalue(user, TAC_IS_USER, S_opap, recurse)); ++ return (cfg_get_pvalue(S_user, user, S_opap, recurse)); + } + + /* return the global password for the user (or the group, etc.) */ + +-char * +-cfg_get_global_secret(user, recurse) +-char *user; ++const char *cfg_get_global_secret TAC_ARGS((const char *user, int recurse)); + ++const char * ++cfg_get_global_secret(user, recurse) ++const char *user; ++int recurse; + { +- return (cfg_get_pvalue(user, TAC_IS_USER, S_global, recurse)); ++ return (cfg_get_pvalue(S_user, 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; + ++const char *cfg_get_pam_service TAC_ARGS((const char *user, int recurse)); ++ ++const char * ++cfg_get_pam_service(user, recurse) ++const char *user; ++int recurse; + { +- char *cfg_passwd; +- char *p; ++ const char *cfg_passwd; ++ const char *p; + +-cfg_passwd = cfg_get_pap_secret(user, recurse); ++ cfg_passwd = cfg_get_pap_secret(user, recurse); + +-if (!cfg_passwd) { ++ if (!cfg_passwd) + cfg_passwd = cfg_get_global_secret(user, recurse); +-} + +-if (!cfg_passwd && !cfg_user_exists(user)) { ++ 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); ++ p = tac_find_substring("pam ", cfg_passwd); + +-if(p) { /* We find PAM services */ ++ if(p) { /* We find PAM services */ + if (debug & DEBUG_AUTHOR_FLAG) + report(LOG_DEBUG, "I get PAM sevice:%s",p); +-return (p); +-} ++ return (p); ++ } + +-if (debug & DEBUG_AUTHOR_FLAG) ++ if (debug & DEBUG_AUTHOR_FLAG) + report(LOG_DEBUG, "No any PAM Sevice"); + +-return(NULL); ++ 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 only read if the svctype 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); ++struct cfg_get_svc_node_param { ++ int svctype; ++ const char *protocol, *svcname; ++ NODE *node; ++ int retval; ++}; + +- if (!user) { +- if (debug & DEBUG_CONFIG_FLAG) +- report(LOG_DEBUG, "cfg_get_svc_node: no user named %s", username); +- return (NULL); +- } ++static enum value_scan_func_result cfg_get_svc_node_func TAC_ARGS((ENTITY *entity, struct cfg_get_svc_node_param *param)); + +- /* found the user entry. Find svc node */ +- for(svc = (NODE *) get_value(user, S_svc).pval; svc; svc = svc->next) { ++static enum value_scan_func_result ++cfg_get_svc_node_func(entity, param /* func_data */) ++ENTITY *entity; ++struct cfg_get_svc_node_param *param; ++{ ++ NODE *svc; ++ enum eval_result svc_default; + +- if (svc->type != type) ++ for (svc = (NODE *) get_value(entity, S_svc).pval; svc; svc = svc->next) { ++ if (svc->type != param->svctype) + continue; +- +- if (type == N_svc_ppp && !STREQ(svc->value1, protocol)) { ++ if (param->svctype == N_svc_ppp && param->protocol && !STREQ(svc->value1, param->protocol)) + continue; +- } +- +- if (type == N_svc && !STREQ(svc->value1, svcname)) { ++ if (param->svctype == N_svc && param->svcname && !STREQ(svc->value1, param->svcname )) ++ continue; ++ if (expr_eval(svc->when) != ER_TRUE) /* expensive */ + 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 : ""); ++ cfg_nodestring(param->svctype), ++ param->protocol ? param->protocol : "", ++ param->svcname ? param->svcname : ""); + +- return(svc); ++ param->node = svc; ++ param->retval = 1; ++ return (VSFR_FOUND); + } + +- if (!recurse) { +- if (debug & DEBUG_CONFIG_FLAG) +- report(LOG_DEBUG, "cfg_get_svc_node: returns NULL"); +- return (NULL); +- } ++ /* look at 'default service' settings */ ++ svc_default = entity_svc_default(entity); ++ switch (svc_default) { + +- /* no matching node. Check containing group */ +- if (user->member) +- group = (USER *) hash_lookup(grouptable, user->member); +- else +- group = NULL; ++ case ER_TRUE: ++ case ER_FALSE: ++ if (debug & DEBUG_AUTHOR_FLAG) ++ report(LOG_DEBUG, ++ "cfg_get_svc_node: svc=%s protocol=%s svcname=%s forced %s by default service", ++ cfg_nodestring(param->svctype), ++ param->protocol ? param->protocol : "", ++ param->svcname ? param->svcname : "", ++ (svc_default == ER_TRUE ? "permit" : "deny")); + +- while (group) { +- if (debug & DEBUG_CONFIG_FLAG) +- report(LOG_DEBUG, "cfg_get_svc_node: recurse group = %s", +- group->name); ++ param->retval = (svc_default == ER_TRUE); ++ return (VSFR_FOUND); + +- for(svc = (NODE *) get_value(group, S_svc).pval; svc; svc = svc->next) { ++ default: /* shouldn't happen */ ++ case ER_UNKNOWN: ++ return (VSFR_CONTINUE); ++ } ++ /* NOTREACHED */ ++} + +- if (svc->type != type) +- continue; ++int cfg_get_svc_node TAC_ARGS((const char *username, int svctype, const char *protocol, const char *svcname, int recurse, NODE **nodep)); + +- if (type == N_svc_ppp && !STREQ(svc->value1, protocol)) { +- continue; +- } ++int ++cfg_get_svc_node(username, svctype, protocol, svcname, recurse, nodep) ++const char *username; ++int svctype; ++const char *protocol; ++const char *svcname; ++int recurse; ++NODE **nodep; ++{ ++ struct cfg_get_svc_node_param param; ++ enum value_scan_func_result vsfr; + +- if (type == N_svc && !STREQ(svc->value1, svcname)) { +- continue; +- } ++ param.svctype = svctype; ++ param.protocol = protocol; ++ param.svcname = svcname; ++ param.node = NULL; ++ param.retval = 0; + + if (debug & DEBUG_CONFIG_FLAG) + report(LOG_DEBUG, +- "cfg_get_svc_node: found %s proto=%s svcname=%s", +- cfg_nodestring(type), ++ "cfg_get_svc_node: username=%s %s proto=%s svcname=%s rec=%d", ++ username, ++ cfg_nodestring(svctype), + protocol ? protocol : "", +- svcname ? svcname : ""); +- +- return(svc); +- } ++ svcname ? svcname : "", ++ recurse); + +- /* still nothing. Check containing group and so on */ ++ vsfr = value_scan(S_user, username, recurse, ++ (value_scan_func_t) cfg_get_svc_node_func, ¶m /* func_data */); ++ if (nodep) ++ *nodep = param.node; + +- if (group->member) +- group = (USER *) hash_lookup(grouptable, group->member); +- else +- group = NULL; +- } ++ if (vsfr == VSFR_FOUND) ++ return (param.retval); + +- 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); ++ /* The service does not exist. Do the default */ ++ return (cfg_no_user_permitted() ? 1 : 0); + } + +-/* Return a pointer to the node representing a set of command regexp ++/* Return a pointer to the node representing a set of command tac_regexp + matches for a user and command, handling recursion issues correctly */ +-NODE * +-cfg_get_cmd_node(name, cmdname, recurse) +-char *name, *cmdname; +-int recurse; + ++struct cfg_authorize_cmd_param { ++ const char *cmd; ++ const char *args; ++ enum eval_result result; ++}; ++ ++static enum value_scan_func_result cfg_authorize_cmd_func TAC_ARGS((ENTITY *entity, struct cfg_authorize_cmd_param *param)); ++ ++static enum value_scan_func_result ++cfg_authorize_cmd_func(entity, param /* func_data */) ++ENTITY *entity; ++struct cfg_authorize_cmd_param *param; + { +- 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); ++ for (svc = (NODE *) get_value(entity, S_svc).pval; svc; svc = svc->next) { ++ NODE *node; + +- /* find the user/group entry */ +- user = (USER *) hash_lookup(usertable, name); ++ if (svc->type != N_svc_cmd) ++ continue; ++ if (!STREQ(svc->value, param->cmd)) ++ continue; ++ if (expr_eval(svc->when) != ER_TRUE) /* expensive */ ++ continue; + +- if (!user) { + if (debug & DEBUG_CONFIG_FLAG) +- report(LOG_DEBUG, "cfg_get_cmd_node: no user named %s", name); +- return (NULL); ++ report(LOG_DEBUG, "cfg_authorize_cmd: found cmd %s %s node", ++ param->cmd, cfg_nodestring(svc->type)); ++ ++ /* we have 'cmd ' point, now traverse through its 'permit'/'deny' pairs: */ ++ ++ for (node = svc->value1; node; node = node->next) { ++ int match; ++ ++ if (expr_eval(node->when) != ER_TRUE) /* expensive */ ++ continue; ++ ++#ifdef WITH_INCLUDED_REGEX ++ ++ match = tac_regexec((tac_regexp *) node->value1, param->args); ++ ++#else /* WITH_INCLUDED_REGEX */ ++ ++ match = !regexec((const regex_t *) node->value1, param->args /* string */, ++ 0 /* nmatch */, NULL /* pmatch */, 0 /* eflags */); ++ ++#endif /* WITH_INCLUDED_REGEX */ ++ ++ if (debug & DEBUG_AUTHOR_FLAG) { ++ report(LOG_INFO, "line %d compare %s %s '%s' & '%s' %smatch", ++ node->line, param->cmd, ++ node->type == N_permit ? "permit" : "deny", ++ (const char *) node->value, param->args, (match ? "" : "no ")); + } +- /* 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); ++ if (!match) ++ continue; ++ ++ switch (node->type) { ++ case N_permit: ++ if (debug & DEBUG_AUTHOR_FLAG) { ++ report(LOG_DEBUG, "%s %s permitted by line %d", ++ param->cmd, param->args, node->line); + } +- svc = svc->next; ++ param->result = ER_TRUE; ++ return (VSFR_FOUND); ++ break; ++ case N_deny: ++ if (debug & DEBUG_AUTHOR_FLAG) { ++ report(LOG_DEBUG, "%s %s denied by line %d", ++ param->cmd, param->args, node->line); ++ } ++ param->result = ER_FALSE; ++ return (VSFR_FOUND); ++ break; ++ default: ++ report(LOG_ERR, "INTERNAL: illegal configuration node: %s: %s %s", ++ session.peer, param->cmd, param->args); ++ param->result = ER_UNKNOWN; /* error */ ++ return (VSFR_FOUND); ++ } ++ } ++ if (!algorithm_recursive) { /* compatibility mode: */ ++ if (debug & DEBUG_AUTHOR_FLAG) ++ report(LOG_DEBUG, "cmd %s exists, but no args match, denied (as no 'authorization = recursive' found)", ++ param->cmd); ++ param->result = ER_FALSE; /* emulate last "deny .*" */ ++ return (VSFR_FOUND); + } +- +- 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); ++ /* look at 'default service' settings */ ++ param->result = entity_svc_default(entity); ++ switch (param->result) { + +- svc = get_value(group, S_svc).pval; ++ case ER_TRUE: ++ if (debug & DEBUG_AUTHOR_FLAG) ++ report(LOG_DEBUG, "cmd %s does not exist, permitted by default", param->cmd); ++ return (VSFR_FOUND); + +- 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; +- } ++ case ER_FALSE: + +- /* still nothing. Check containing group and so on */ ++ if (debug & DEBUG_AUTHOR_FLAG) ++ report(LOG_DEBUG, "cmd %s does not exist, denied by default", param->cmd); ++ return (VSFR_FOUND); + +- if (group->member) +- group = (USER *) hash_lookup(grouptable, group->member); +- else +- group = NULL; ++ default: /* shouldn't happen */ ++ case ER_UNKNOWN: ++ return (VSFR_CONTINUE); + } ++ /* NOTREACHED */ ++} ++ ++enum eval_result cfg_authorize_cmd TAC_ARGS((const char *username, const char *cmd, const char *args)); ++ ++enum eval_result ++cfg_authorize_cmd(username, cmd, args) ++const char *username; ++const char *cmd; ++const char *args; ++{ ++ struct cfg_authorize_cmd_param param; ++ ++ param.cmd = cmd; ++ param.args = args; ++ param.result = ER_UNKNOWN; /* error */ + + if (debug & DEBUG_CONFIG_FLAG) +- report(LOG_DEBUG, "cfg_get_cmd_node: returns NULL"); ++ report(LOG_DEBUG, "cfg_authorize_cmd: name=%s cmdname=%s args=%s", ++ username, cmd, args); + +- /* no matching cmd node for this user or her containing groups */ +- return (NULL); ++ value_scan(S_user, username, TAC_PLUS_RECURSE, ++ (value_scan_func_t) cfg_authorize_cmd_func, ¶m /* func_data */); ++ ++ if (param.result != ER_UNKNOWN) ++ return (param.result); ++ ++ /* The command does not exist. Do the default */ ++ return (cfg_no_user_permitted() ? ER_TRUE : ER_FALSE); + } + + /* Return an array of character strings representing configured AV +@@ -2039,6 +2711,8 @@ int recurse; + * Lastly, indicate what default permission was configured by setting + * denyp */ + ++char **cfg_get_svc_attrs TAC_ARGS((NODE *svcnode, int *denyp)); ++ + char ** + cfg_get_svc_attrs(svcnode, denyp) + NODE *svcnode; +@@ -2063,9 +2737,14 @@ int *denyp; + + i = 0; + for (node = svcnode->value; node; node = node->next) { +- char *arg = tac_strdup(node->value); +- char *p = index(arg, '='); ++ char *arg; ++ char *p; + ++ if (expr_eval(node->when) != ER_TRUE) /* expensive */ ++ continue; /* ignore this node */ ++ ++ arg = tac_strdup(node->value); ++ p = index(arg, '='); + if (p && node->type == N_optarg) + *p = '*'; + args[i++] = arg; +@@ -2075,23 +2754,28 @@ int *denyp; + } + + +-int +-cfg_user_svc_default_is_permit(user) +-char *user; ++static enum eval_result entity_svc_default TAC_ARGS((ENTITY *entity)); + ++static enum eval_result ++entity_svc_default(entity) ++ENTITY *entity; + { +- 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); ++ switch (entity->svc_dflt) { + case S_permit: +- return (1); ++ return (ER_TRUE); ++ case S_deny: ++ return (ER_FALSE); ++ case S_default: ++ case 0: /* not specified */ ++ return (ER_UNKNOWN); ++ default: ++ report(LOG_ERR, "INTERNAL: invalid entity svc_dflt (%d)", entity->svc_dflt); ++ return (ER_UNKNOWN); + } + } + ++int cfg_no_user_permitted TAC_ARGS((void)); ++ + int + cfg_no_user_permitted() + { +@@ -2101,12 +2785,16 @@ cfg_no_user_permitted() + } + + +-char * ++const char *cfg_get_authen_default TAC_ARGS((void)); ++ ++const char * + cfg_get_authen_default() + { + return (authen_default); + } + ++int cfg_get_authen_default_method TAC_ARGS((void)); ++ + /* For describe authentication method(pam,file,db..etc) */ + int + cfg_get_authen_default_method() +@@ -2115,92 +2803,123 @@ cfg_get_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; ++/* Host entity management: ++ */ ++ ++const char *cfg_get_host_key TAC_ARGS((const char *host)); ++ ++/* For getting host key */ ++const char * ++cfg_get_host_key(host) ++const char *host; + { +- USER *user, *group; +- NODE *svc; ++ return (cfg_get_pvalue(S_host, host, S_key, algorithm_recursive /* recurse */)); ++} + +- if (debug & DEBUG_CONFIG_FLAG) +- report(LOG_DEBUG, "cfg_ppp_is_configured: username=%s rec=%d", +- username, recurse); ++static ENTITY *force_belong_entity TAC_ARGS((int type, const char *name)); ++ ++static ENTITY * ++force_belong_entity(type, name) ++int type; ++const char *name; ++{ ++ ENTITY *entity = entity_lookup(type, name); + +- /* find the user/group entry */ +- user = (USER *) hash_lookup(usertable, username); ++ if (entity) ++ eval_force_belong_entity(entity); + +- if (!user) { +- if (debug & DEBUG_CONFIG_FLAG) +- report(LOG_DEBUG, "cfg_ppp_is_configured: no user named %s", +- username); +- return (0); +- } ++ return (entity); ++} + +- /* found the user entry. Find svc node */ +- for(svc = (NODE *) get_value(user, S_svc).pval; svc; svc = svc->next) { ++/* assumed existing initialized "session.peer*" */ + +- if (svc->type != N_svc_ppp) +- continue; ++static ENTITY *request_peer_addr; ++static ENTITY *request_peer; ++static ENTITY *request_DEFAULT_group; + +- if (debug & DEBUG_CONFIG_FLAG) +- report(LOG_DEBUG, "cfg_ppp_is_configured: found svc ppp %s node", +- svc->value1); ++static void enlist_request_peer TAC_ARGS((const char *hostname, ENTITY **entityp)); + +- return(1); +- } ++static void ++enlist_request_peer(hostname, entityp) ++const char *hostname; ++ENTITY **entityp; ++{ ++ *entityp = NULL; ++ if (!hostname) ++ return; + +- if (!recurse) { +- if (debug & DEBUG_CONFIG_FLAG) +- report(LOG_DEBUG, "cfg_ppp_is_configured: returns 0"); +- return (0); +- } ++ *entityp = force_belong_entity(S_host, hostname); ++ if (*entityp && request_DEFAULT_group) ++ virtual_enlist_entity_direct(request_DEFAULT_group /* parent */, *entityp /* child */); ++} + +- /* no matching node. Check containing group */ +- if (user->member) +- group = (USER *) hash_lookup(grouptable, user->member); +- else +- group = NULL; ++/* Try to build the following scenery: ++ * ++ * host [=ER_TRUE] ++ * | ++ * +-- group ++ * ++ * host [=ER_TRUE] ++ * | ++ * +-- group ++ */ + +- while (group) { +- if (debug & DEBUG_CONFIG_FLAG) +- report(LOG_DEBUG, "cfg_ppp_is_configured: recurse group = %s", +- group->name); ++void cfg_request_scan_begin TAC_ARGS((void)); + +- for(svc = (NODE *) get_value(group, S_svc).pval; svc; svc = svc->next) { ++void ++cfg_request_scan_begin() ++{ ++ request_scan_begin(); + +- if (svc->type != N_svc_ppp) +- continue; ++ request_DEFAULT_group = entity_lookup(S_group, DEFAULT_GROUPNAME); + +- if (debug & DEBUG_CONFIG_FLAG) +- report(LOG_DEBUG, "cfg_ppp_is_configured: found svc ppp %s node", +- svc->value1); ++ if (session.peer_addr != session.peer) ++ enlist_request_peer(session.peer_addr, &request_peer_addr); ++ enlist_request_peer(session.peer, &request_peer); ++} + +- return(1); +- } ++/* Try to build the following scenery: ++ * ++ * ( user username> | user ) [=ER_TRUE] ++ * | ++ * +-- host [=ER_TRUE] ++ * | | ++ * | +- group ++ * | ++ * +-- host [=ER_TRUE] ++ * | | ++ * | +-- group ++ * | ++ * +-- group ++ */ + +- /* still nothing. Check containing group and so on */ ++void cfg_request_identity TAC_ARGS((const struct identity *identity)); + +- if (group->member) +- group = (USER *) hash_lookup(grouptable, group->member); +- else +- group = NULL; +- } ++void ++cfg_request_identity(identity) ++const struct identity *identity; ++{ ++ ENTITY *user_entity,*request_DEFAULT_group; + + if (debug & DEBUG_CONFIG_FLAG) +- report(LOG_DEBUG, "cfg_ppp_is_configured: returns 0"); ++ report(LOG_DEBUG, "cfg_request_identity: username=%s, NAS_name=%s, NAS_port=%s, NAC_address=%s, priv_lvl=%d", ++ identity->username, identity->NAS_name, identity->NAS_port, identity->NAC_address, identity->priv_lvl); + +- /* no PPP svc nodes for this user or her containing groups */ +- return (0); +-} ++ user_entity = force_belong_entity(S_user, identity->username); ++ request_DEFAULT_group = entity_lookup(S_group, DEFAULT_GROUPNAME); + +-/* For getting host key */ +-char * +-cfg_get_host_key(host) +-char *host; +-{ +- return (cfg_get_phvalue(host, S_key)); +-} ++ if (!user_entity) ++ user_entity = force_belong_entity(S_user, DEFAULT_USERNAME); ++ ++ request_scan_user_known = 1; + ++ if (!user_entity) ++ return; ++ ++ if (request_peer_addr) ++ virtual_enlist_entity_direct(request_peer_addr /* parent */, user_entity /* child */); ++ if (request_peer ) ++ virtual_enlist_entity_direct(request_peer /* parent */, user_entity /* child */); ++ if (request_DEFAULT_group) ++ virtual_enlist_entity_direct(request_DEFAULT_group /* parent */, user_entity /* child */); ++} +diff --git a/cfgfile.h b/cfgfile.h +new file mode 100644 +index 0000000..3669996 +--- /dev/null ++++ b/cfgfile.h +@@ -0,0 +1,164 @@ ++#ifndef CFGFILE_H ++#define CFGFILE_H 1 ++ ++#include "tac_plus.h" ++ ++#include "utils.h" ++#include "cfgeval.h" ++ ++ ++/* Configurable: ++ */ ++ ++#define DEFAULT_USERNAME "DEFAULT" ++#define DEFAULT_GROUPNAME "DEFAULT" ++ ++ ++#define TAC_PLUS_RECURSE 1 ++#define TAC_PLUS_NORECURSE 0 ++ ++/* 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 ++ ++typedef struct node NODE; ++ ++/* A parse tree node */ ++struct node { ++ int type; /* node type (arg, svc, proto) */ ++ NODE *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 */ ++ struct expr *when; /* conditions needed to respect this NODE */ ++}; ++ ++union v { ++ int intval; ++ void *pval; ++}; ++ ++typedef union v VALUE; ++ ++/* A user, host 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 entity { ++ char *name; /* username/groupname/hostname */ ++ void *hash; /* hash table next pointer */ ++ int line; /* line number defined on */ ++ int type; /* set to S_user, S_host or S_group */ ++ ++ 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 *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 */ ++ char *key; /* host spesific key (N/A for S_user) */ ++ int svc_dflt; /* default authorization behaviour for svc or ++ * cmd */ ++ /* =S_permit, S_deny or S_default */ ++ NODE *svcs; /* pointer to svc nodes */ ++#ifdef MAXSESS ++ int maxsess; /* Max sessions/user */ ++#endif /* MAXSESS */ ++ char *time; /* Timestamp */ ++ ++ struct tac_list to_parent_membership_list; /* ordered list of memberships to groups owning us: */ ++ struct tac_list to_child_membership_list; /* ordered list of memberships to entities in this group: */ ++ unsigned to_child_membership_num; /* # of 'to_child_membership_list' items */ ++ ++ struct { ++ unsigned seq; /* corresponds to global request_scan_seq */ ++ enum eval_result belongs; /* whether this ENTITY 'belongs' */ ++ } request_scan; /* cfg_request() scanning */ ++ ++ struct { ++ unsigned seq; /* corresponds to global value_scan_seq */ ++ unsigned seen:1; ++ struct membership *from; /* from which we got to this one or NULL */ ++ } value_scan; /* cfg_get_value() scanning, many per request_scan */ ++ ++ struct { ++ unsigned seq; /* corresponds to global eval_scan_seq */ ++ struct tac_list notify_expr_list; /* contains expr.u.waiting_expr_node */ ++ /* may be from any of: eval_{want,solved,destroy}_entity_list: */ ++ struct tac_list_node pending_entity_node; /* we are interested in this entity */ ++ /* child memberships which are not yet check_eval-ed are NOT present here, ++ * although when check_eval-entity finishes, all will be added here. ++ * List refilling driven by check_eval_scan_entity(), ++ * although each unsolved_child_node is added in check_eval_scan_membership(). ++ */ ++ unsigned unsolved_to_child_membership_num; /* when 0, we know we are ER_FALSE */ ++ struct membership *unsolved_to_child_membership_first; ++ } eval_scan; /* expr_eval() scanning, many per value_scan */ ++}; ++#define PENDING_ENTITY_NODE_TO_ENTITY(pending_entity_node_) \ ++ (&TAC_MEMBER_STRUCT(ENTITY, (pending_entity_node_), eval_scan.pending_entity_node)) ++ ++ ++struct identity; ++ ++extern const char *cfg_nodestring TAC_ARGS((int type)); ++extern void cfg_clean_config TAC_ARGS((void)); ++extern int cfg_get_intvalue TAC_ARGS((int type, const char *name, int attr, int recurse)); ++extern const char *cfg_get_pvalue TAC_ARGS((int type, const char *name, int attr, int recurse)); ++extern int cfg_read_config TAC_ARGS((const char *cfile)); ++extern int cfg_user_exists TAC_ARGS((const char *username)); ++extern const char *cfg_get_expires TAC_ARGS((const char *username, int recurse)); ++extern const char *cfg_get_timestamp TAC_ARGS((const char *username, int recurse)); ++extern const char *cfg_get_login_secret TAC_ARGS((const char *user, int recurse)); ++extern int cfg_get_user_nopasswd TAC_ARGS((const char *user, int recurse)); ++extern const char *cfg_get_arap_secret TAC_ARGS((const char *user, int recurse)); ++extern const char *cfg_get_chap_secret TAC_ARGS((const char *user, int recurse)); ++#ifdef MSCHAP ++extern const char *cfg_get_mschap_secret TAC_ARGS((const char *user, int recurse)); ++#endif /* MSCHAP */ ++extern const char *cfg_get_pap_secret TAC_ARGS((const char *user, int recurse)); ++extern const char *cfg_get_opap_secret TAC_ARGS((const char *user, int recurse)); ++extern const char *cfg_get_global_secret TAC_ARGS((const char *user, int recurse)); ++#ifdef USE_PAM ++extern const char *cfg_get_pam_service TAC_ARGS((const char *user, int recurse)); ++#endif /* PAM */ ++extern int cfg_get_svc_node TAC_ARGS((const char *username, int svctype, const char *protocol, const char *svcname, int recurse, NODE **nodep)); ++extern char **cfg_get_svc_attrs TAC_ARGS((NODE *svcnode, int *denyp)); ++extern int cfg_no_user_permitted TAC_ARGS((void)); ++extern const char *cfg_get_authen_default TAC_ARGS((void)); ++extern int cfg_get_authen_default_method TAC_ARGS((void)); ++extern const char *cfg_get_host_key TAC_ARGS((const char *host)); ++extern void cfg_request_scan_begin TAC_ARGS((void)); ++extern void cfg_request_identity TAC_ARGS((const struct identity *identity)); ++extern enum eval_result cfg_authorize_cmd TAC_ARGS((const char *username, const char *cmd, const char *args)); ++ ++/* for use by cfgeval.c: */ ++extern ENTITY *new_entity TAC_ARGS((int type, char *name, int line)); ++extern const char *entity_type_to_string TAC_ARGS((int entity_type)); ++extern void scan_invalidate_entities TAC_ARGS((enum invalidate_scan what)); ++extern ENTITY *entity_lookup TAC_ARGS((int type, const char *name)); ++ ++ ++#endif /* CFGFILE_H */ +diff --git a/choose_authen.c b/choose_authen.c +index 9329b73..d6b3062 100644 +--- a/choose_authen.c ++++ b/choose_authen.c +@@ -17,24 +17,47 @@ + FITNESS FOR A PARTICULAR PURPOSE. + */ + ++ + #include "tac_plus.h" ++ ++#include "choose_authen.h" + #include "expire.h" ++#include "enable.h" ++#include "report.h" ++#include "cfgfile.h" ++#include "default_fn.h" ++#include "default_v0_fn.h" ++#include "sendauth.h" ++#include "sendpass.h" ++#include "packet.h" ++#include "main.h" ++#include "do_author.h" /* for "struct identity" */ + +-static int choose_login(); +-static int choose_sendpass(); +-static int choose_sendauth(); ++#ifdef SKEY ++#include "skey_fn.h" ++#endif + +-int ++ ++static int choose_sendpass TAC_ARGS((struct authen_data *data, struct authen_type *type)); ++static int choose_sendauth TAC_ARGS((struct authen_data *data, struct authen_type *type)); ++static int choose_login TAC_ARGS((struct authen_data *data, struct authen_type *type)); ++ ++ ++#if 0 /* unused */ ++static int + get_minor_version() + { + return(session.version & ~TAC_PLUS_MAJOR_VER_MASK); + } ++#endif /* unused */ + + /* + * Choose an authentication function. Return CHOOSE_OK if chosen, + * CHOOSE_GETUSER if we need a username, CHOOSE_FAILED on failure + */ + ++int choose_authen TAC_ARGS((struct authen_data *data, struct authen_type *type)); ++ + int + choose_authen(data, type) + struct authen_data *data; +@@ -84,14 +107,16 @@ struct authen_type *type; + return(CHOOSE_FAILED); + } + ++static int choose_login TAC_ARGS((struct authen_data *data, struct authen_type *type)); ++ + /* 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; ++ const char *name = data->NAS_id->username; ++ const char *cfg_passwd; + + switch(type->authen_type) { + case TAC_PLUS_AUTHEN_TYPE_ASCII: +@@ -193,6 +218,8 @@ struct authen_type *type; + return(CHOOSE_FAILED); + } + ++static int choose_sendauth TAC_ARGS((struct authen_data *data, struct authen_type *type)); ++ + static int + choose_sendauth(data, type) + struct authen_data *data; +@@ -247,6 +274,8 @@ struct authen_type *type; + return(CHOOSE_FAILED); + } + ++static int choose_sendpass TAC_ARGS((struct authen_data *data, struct authen_type *type)); ++ + /* Compatibility routine for (obsolete) minor version == 0 */ + static int + choose_sendpass(data, type) +@@ -291,4 +320,3 @@ struct authen_type *type; + + return(CHOOSE_FAILED); + } +- +diff --git a/choose_authen.h b/choose_authen.h +new file mode 100644 +index 0000000..1276494 +--- /dev/null ++++ b/choose_authen.h +@@ -0,0 +1,63 @@ ++#ifndef CHOOSE_AUTHEN_H ++#define CHOOSE_AUTHEN_H 1 ++ ++#include "tac_plus.h" ++ ++#include /* for u_* */ ++ ++ ++/* ++ * 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_data; ++ ++struct authen_type { ++ char authen_name[AUTHEN_NAME_SIZE]; ++ int (*authen_func) TAC_ARGS((struct authen_data *data)); ++ int authen_type; ++}; ++ ++/* ++ * 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 */ ++ unsigned 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 */ ++ ++ ++extern int choose_authen TAC_ARGS((struct authen_data *data, struct authen_type *type)); ++ ++ ++#endif /* CHOOSE_AUTHEN_H */ +diff --git a/configure.in b/configure.in +index 6b3eb13..0b8fd72 100644 +--- a/configure.in ++++ b/configure.in +@@ -1,53 +1,101 @@ + dnl This file writen by Devrim SERAL for tac_plus daemon + + AC_INIT() ++dnl Check for Host information ++dnl AC_CANONICAL_HOST() ++AC_CANONICAL_SYSTEM() ++AM_INIT_AUTOMAKE(tac_plus, F4.0.3.alpha.8.gts4) + + 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" ++ AC_DEFINE(LINUX) ++ AC_DEFINE(GLIBC) + ;; + *solaris) +- OS="-DSOLARIS" ++ AC_DEFINE(SOLARIS) + ;; + *freebsd) +- OS="-DFREEBSD" ++ AC_DEFINE(FREEBSD) + ;; + *hpux) +- OS="-DHPUX" ++ AC_DEFINE(HPUX) + ;; + *aix) +- OS="-DAIX" ++ AC_DEFINE(AIX) ++ AC_MSG_WARN([See /usr/lpp/bos/bsdport on your system for details of how to define bsdcc]) ++ # CC="bsdcc" ++ ;; ++ *mips) ++ AC_DEFINE(MIPS) + ;; + *) + ;; + esac + ++dnl Devrim Added ++AM_CONFIG_HEADER(config.h) ++AM_MAINTAINER_MODE ++ ++if test "x$USE_MAINTAINER_MODE" = "xyes"; then ++ AC_DEFINE(MAINTAINER_MODE) ++fi ++ ++if test "x$USE_MAINTAINER_MODE" = "xyes" -a "x$GCC" = "xyes"; then ++ CFLAGS="$CFLAGS -ggdb3 -Wall -Wstrict-prototypes -pedantic -Wsign-compare" ++fi ++ ++# Set these options as otherwise some autoconf tests give different results: ++final_CFLAGS="$CFLAGS" ++CFLAGS="$CFLAGS -D_XOPEN_SOURCE=1 -D_XOPEN_SOURCE_EXTENDED=1 -D_BSD_SOURCE=1 -D_OSF_SOURCE=1 -D__EXTENSIONS__=1" ++ ++COND_USE="" ++AC_SUBST(COND_USE) ++conf_LDFLAGS="" ++AC_SUBST(conf_LDFLAGS) ++conf_LDADD="" ++AC_SUBST(conf_LDADD) ++ ++ + dnl Checks for libraries. + dnl Replace `main' with a function in -lnsl: +-AC_CHECK_LIB(nsl, main) ++AC_CHECK_LIB(nsl, main, [ conf_LDADD="-lnsl $conf_LDADD" ] ) + 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) ++AC_CHECK_LIB(og, main, [ conf_LDADD="-log $conf_LDADD" ] ) + dnl Replace `main' with a function in -lsocket: +-AC_CHECK_LIB(socket, main) ++AC_CHECK_LIB(socket, main, [ conf_LDADD="-lsocket $conf_LDADD" ] ) + dnl Check for Crypt function ++dnl Never use CONF_LDADD here as it is used also for "generate_passwd" + AC_CHECK_LIB(crypt, crypt) + AC_CHECK_LIB(c,printf) + ++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 sys/syslog.h unistd.h regex.h sys/param.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_TYPE_SIZE_T ++AC_CHECK_FUNCS(select socket strcspn strdup strtol siginterrupt) ++AC_CHECK_SIZEOF(unsigned short,2) ++AC_CHECK_SIZEOF(unsigned int,4) ++AC_CHECK_SIZEOF(unsigned long,4) + +-dnl Devrim Added +-AC_CONFIG_HEADER(config.h) + + dnl For PAM support + AC_MSG_CHECKING(for PAM support:) +@@ -55,9 +103,10 @@ 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_CHECK_LIB(dl, dlopen, [ conf_LDADD="-ldl $conf_LDADD" ] ) ++ AC_CHECK_LIB(pam, pam_start, [ conf_LDADD="-lpam $conf_LDADD" ] ) ++ AC_DEFINE(USE_PAM) ++ COND_USE="$COND_USE "'$(cond_USE_PAM)' + AC_MSG_RESULT(Pam support... yes) + else + AC_MSG_RESULT(Pam support... no) +@@ -70,10 +119,15 @@ 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" ++ dnl Replace `main' with a function in -llber: ++ AC_CHECK_LIB(lber, main, [ conf_LDADD="-llber $conf_LDADD"; liblber="-llber" ], [ liblber="" ] ) ++ dnl Replace `main' with a function in -lldap: ++ AC_CHECK_LIB(ldap, ldap_simple_bind_s, [ conf_LDADD="-lldap $conf_LDADD" ], ++ [ ++ AC_CHECK_LIB(ldap, ldap_init, [ conf_LDADD="-lldap $conf_LDADD" ],, $liblber) ++ ], $liblber ) ++ AC_DEFINE(USE_LDAP) ++ COND_USE="$COND_USE "'$(cond_USE_LDAP)' + AC_MSG_RESULT(LDAP support... yes) + else + AC_MSG_RESULT(LDAP support... no) +@@ -85,7 +139,10 @@ echo + AC_ARG_WITH(db, + [ --with-db For DB Support ],,) + if test "x$with_db" = "xyes";then +- DB="$DB -DDB -DDB_NULL" ++ AC_DEFINE(DB) ++ AC_DEFINE(DB_NULL) ++ COND_USE="$COND_USE "'$(cond_DB)' ++ COND_USE="$COND_USE "'$(cond_DB_NULL)' + AC_MSG_RESULT(DB support... yes) + else + AC_MSG_RESULT(DB support... no) +@@ -108,14 +165,15 @@ AC_ARG_WITH(mysql-prefix, + + if test "x$with_mysql" = "xyes";then + +- LDFLAGS="-L$MYSQL_PREFIX/lib/mysql $LDFLAGS" +- LDFLAGS="-I$MYSQL_PREFIX/include/mysql $LDFLAGS" ++ conf_LDFLAGS="-L$MYSQL_PREFIX/lib/mysql $conf_LDFLAGS" ++ CPPFLAGS="-I$MYSQL_PREFIX/include/mysql $CPPFLAGS" + AC_CHECK_LIB(mysqlclient, mysql_init, +- LIBS="-lmysqlclient -lm $LIBS", ++ conf_LDADD="-lmysqlclient -lm $conf_LDADD", + AC_MSG_ERROR(*** couldn't find libmysqlclient), + -lm) + +- DB="$DB -DDB_MYSQL"; ++ AC_DEFINE(DB_MYSQL) ++ COND_USE="$COND_USE "'$(cond_DB_MYSQL)' + AC_MSG_RESULT(Mysql support... yes) + else + AC_MSG_RESULT(Mysql support... no) +@@ -140,13 +198,14 @@ AC_ARG_WITH(pgsql-prefix, + + if test "x$with_pgsql" = "xyes";then + +- LDFLAGS="-L$PGSQL_PREFIX/lib/pgsql $LDFLAGS" +- LDFLAGS="-I$PGSQL_PREFIX/include/pgsql $LDFLAGS" ++ conf_LDFLAGS="-L$PGSQL_PREFIX/lib/pgsql $conf_LDFLAGS" ++ CPPFLAGS="-I$PGSQL_PREFIX/include/pgsql $CPPFLAGS" + AC_CHECK_LIB(pq,PQconnectdb , +- LIBS="-lpq $LIBS", ++ conf_LDADD="-lpq $conf_LDADD", + AC_MSG_ERROR(*** couldn't find libpq)) + +- DB="$DB -DDB_PGSQL"; ++ AC_DEFINE(DB_PGSQL) ++ COND_USE="$COND_USE "'$(cond_DB_PGSQL)' + AC_MSG_RESULT(Pgsql support... yes) + else + AC_MSG_RESULT(Pgsql support... no) +@@ -164,7 +223,8 @@ AC_ARG_WITH(tacgid, + + 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_DEFINE_UNQUOTED(TACPLUS_USERID, $with_tacuid) ++ AC_DEFINE_UNQUOTED(TACPLUS_GROUPID, $with_tacgid) + AC_MSG_RESULT(tacacs+ work with given user and group id) + fi + +@@ -173,8 +233,9 @@ AC_ARG_ENABLE(maxsess, + [ --enable-maxsess Enable maxsess feature ], + [ + if test "$enableval" = "yes";then +- DEFINES="-DMAXSESS $DEFINES"; ++ AC_DEFINE(MAXSESS) + AC_MSG_RESULT(yes) ++ COND_USE="$COND_USE "'$(cond_MAXSESS)' + else + AC_MSG_RESULT(no) + fi +@@ -184,16 +245,18 @@ fi + ]) + + 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\\\"" ++ [ pidfile="$withval" ], ++ [ pidfile="" ] + ) ++if test "x$pidfile" '!=' "x"; then ++ AC_DEFINE_UNQUOTED(TACPLUS_PIDFILE, "$pidfile/tac_plus.pid") ++fi + + dnl For libwrap check +-AC_MSG_CHECKING(whether to enable the libwrap feture) +- ++AC_MSG_CHECKING(whether to enable the libwrap feature) ++cond=false + AC_ARG_WITH(libwrap, + [ --with-libwrap[=PATH] Compile in libwrap (tcp_wrappers) support.], + [ case "$withval" in +@@ -203,48 +266,221 @@ AC_ARG_WITH(libwrap, + yes) + AC_MSG_RESULT(yes) + AC_CHECK_LIB(wrap, request_init, [ +- LIBS="-lwrap $LIBS" +- DEFINES="-DTCPWRAPPER $DEFINES"]) ++ conf_LDADD="-lwrap $conf_LDADD" ++ cond=true ++ ]) + ;; + *) + 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.) ]) ++ cond=true + ;; + esac ], + AC_MSG_RESULT(no) + ) ++if $cond; then ++ AC_DEFINE(TCPWRAPPER) ++ COND_USE="$COND_USE "'$(cond_TCPWRAPPER)' ++fi + +-dnl insert defines to Makefile +-AC_SUBST(DEFINES) +-AC_SUBST(PIDFILE) +-AC_SUBST(DB) +-AC_SUBST(OS) ++dnl For SKEY check ++AC_MSG_CHECKING(whether to use SKEY security feature) ++cond=false ++AC_ARG_WITH(skey, ++[ --with-skey[=LIBPATH] Compile with SKEY support (also use -I in CPPFLAGS var).], ++[ case "$withval" in ++ no) ++ AC_MSG_RESULT(no) ++ ;; ++ yes) ++ AC_MSG_RESULT(yes) ++ cond=true ++ ;; ++ *) ++ AC_MSG_RESULT(yes) ++ if test '!' -f "$withval";then ++ AC_MSG_ERROR([Cannot find $withval library file, you may wish to use LIBS variable instead.]) ++ fi ++ conf_LDADD="$withval $conf_LDADD" ++ cond=true ++ ;; ++ esac ], ++ AC_MSG_RESULT(no) ++) ++if $cond; then ++ AC_DEFINE(SKEY) ++ COND_USE="$COND_USE "'$(cond_SKEY)' ++fi + +-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) ++dnl For MSCHAP and also MSCHAP_DES ++AC_MSG_CHECKING(whether to compile with Microsoft CHAP) ++cond=false ++AC_ARG_WITH(mschap, ++[ --with-mschap[=des] Compile with Microsoft CHAP (optionally including MSCHAP_DES).], ++[ case "$withval" in ++ no) ++ AC_MSG_RESULT(no) ++ ;; ++ yes) ++ AC_MSG_RESULT([yes, without DES]) ++ cond=true ++ ;; ++ des) ++ AC_MSG_RESULT([yes, including DES]) ++ AC_DEFINE(MSCHAP_DES) ++ cond=true ++ ;; ++ *) ++ AC_MSG_ERROR(Unknown --with-mschap argument $withval, use: no, yes or des) ++ ;; ++ esac ], ++ AC_MSG_RESULT(no) ++) ++if $cond; then ++ AC_DEFINE(MSCHAP) ++ COND_USE="$COND_USE "'$(cond_MSCHAP)' ++fi ++ ++dnl For SunOS encryption compatibility ++dnl Never use CONF_LDADD here as it is used also for "generate_passwd" ++AC_MSG_CHECKING(whether to use SunOS encryption compatibility) ++cond=false ++AC_ARG_WITH(descrypt, ++[ --with-descrypt Be password encryption compatible with SunOS.], ++[ case "$withval" in ++ no) ++ AC_MSG_RESULT(no) ++ ;; ++ yes) ++ AC_MSG_RESULT(yes) ++ LIBS="-ldescrypt $LIBS" ++ ;; ++ *) ++ AC_MSG_RESULT(yes - $withval) ++ LIBS="$withval $LIBS" ++ ;; ++ esac ], ++ AC_MSG_RESULT(no) ++) ++ ++AC_ARG_WITH(efence, ++[ --with-efence compile with efence support (for debugging)],,[ ++ if test "x$USE_MAINTAINER_MODE" = "xyes"; then ++ with_efence=auto ++ else ++ with_efence=no + fi +- ],) +-dnl Checks for typedefs, structures, and compiler characteristics. +-AC_C_CONST +-AC_HEADER_TIME ++]) ++if test "$with_efence" != no; then ++ AC_CHECK_LIB(efence,malloc,,[ ++ if test "$with_efence" = yes; then ++ AC_MSG_ERROR(Unable to find efence library.) ++ fi ++ ]) ++fi + +-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) ++dnl Check for type in sys/socket.h ++AC_MSG_CHECKING([for parameter type of 3rd accept() arg]) ++AC_CACHE_VAL(tac_plus_cv_accept_type, [ ++ check_ok=false ++ for type in socklen_t size_t int; do ++ AC_TRY_COMPILE([ ++#include ++#include ++#if STDC_HEADERS ++#include ++#include ++#endif ++],[ ++ return 0;} ++ int accept(int s, struct sockaddr *addr, ]$type[ *addrlen) ++ { return 0; } ++ int discarded_main() { ++], ++ [check_ok=true;break],continue) ++ done ++ if $check_ok ++ then ++ tac_plus_cv_accept_type=$type ++ else ++ tac_plus_cv_accept_type=no ++ fi ++ ]) ++if test "x$tac_plus_cv_accept_type" = "xno" ++then ++ AC_DEFINE(socklen_t,int) ++ AC_MSG_RESULT([failed to detect, will try int]) ++else ++ AC_MSG_RESULT($tac_plus_cv_accept_type) ++ if test "x$tac_plus_cv_accept_type" != "xsocklen_t" ++ then ++ AC_DEFINE_UNQUOTED(socklen_t,$tac_plus_cv_accept_type) ++ fi ++fi ++ ++dnl Check for system regex (stolen from "mutt" package) ++AC_ARG_WITH(included-regex, [ --with-included-regex Use the included regex library ], ++ [tac_plus_cv_included_regex=yes], ++ [AC_CHECK_FUNCS(regcomp, tac_plus_cv_included_regex=no, tac_plus_cv_included_regex=yes)]) ++ ++if test $tac_plus_cv_included_regex = no ; then ++AC_CACHE_CHECK([whether your system's regexp library is completely broken], ++ [tac_plus_cv_included_regex_broken], ++ AC_TRY_RUN([ ++#ifdef HAVE_UNISTD_H ++#include ++#endif ++#ifdef HAVE_REGEX_H ++#include ++#endif ++main() { regex_t blah ; regmatch_t p; p.rm_eo = p.rm_eo; return regcomp(&blah, "foo.*bar", REG_NOSUB) || regexec (&blah, "foobar", 0, NULL, 0); }], ++ tac_plus_cv_included_regex_broken=no, tac_plus_cv_included_regex_broken=yes, tac_plus_cv_included_regex_broken=yes)) ++ if test $tac_plus_cv_included_regex_broken = yes ; then ++ echo "Using the included regex instead." >&AC_FD_MSG ++ tac_plus_cv_included_regex=yes ++ fi ++fi ++if test $tac_plus_cv_included_regex = yes; then ++ AC_DEFINE(WITH_INCLUDED_REGEX) ++ COND_USE="$COND_USE "'$(cond_WITH_INCLUDED_REGEX)' ++fi ++ ++dnl Check for "struct passwd.pw_{age,comment}" ++dnl Stolen from Julianne Frances Haugh's login replacement. ++AC_CACHE_CHECK(for pw_age in struct passwd, ++tac_plus_cv_struct_passwd_pw_age, AC_TRY_COMPILE([#include ], ++[ struct passwd pw; pw.pw_age = "" ], ++tac_plus_cv_struct_passwd_pw_age=yes, tac_plus_cv_struct_passwd_pw_age=no)) ++ ++if test "$tac_plus_cv_struct_passwd_pw_age" = "yes"; then ++ AC_DEFINE(HAVE_PASSWD_PW_AGE) ++fi ++AC_CACHE_CHECK(for pw_comment in struct passwd, ++tac_plus_cv_struct_passwd_pw_comment, AC_TRY_COMPILE([#include ], ++[ struct passwd pw; pw.pw_comment = "" ], ++tac_plus_cv_struct_passwd_pw_comment=yes, tac_plus_cv_struct_passwd_pw_comment=no)) ++ ++if test "$tac_plus_cv_struct_passwd_pw_comment" = "yes"; then ++ AC_DEFINE(HAVE_PASSWD_PW_COMMENT) ++fi ++AC_CACHE_CHECK(for ut_host in struct utmp, ++tac_plus_cv_struct_utmp_ut_host, AC_TRY_COMPILE([#include ], ++[ struct utmp ut; ut.ut_host = "" ], ++tac_plus_cv_struct_utmp_ut_host=yes, tac_plus_cv_struct_utmp_ut_host=no)) ++ ++if test "$tac_plus_cv_struct_utmp_ut_host" = "yes"; then ++ AC_DEFINE(HAVE_UTMP_UT_HOST) ++fi ++ ++CFLAGS="$final_CFLAGS" + +-AC_OUTPUT(Makefile,echo timestamp > stamp-h) ++AC_OUTPUT([ ++ Makefile ++ tac_plus.spec ++ ]) +diff --git a/convert.pl b/convert.pl +index eb0b5c2..83cbdcc 100755 +--- a/convert.pl ++++ b/convert.pl +@@ -141,6 +141,3 @@ exit 0 if ($acl_valid); + foreach $group (keys %groups) { + print "group = $group { }\n"; + } +- +- +- +diff --git a/db.c b/db.c +index 9cbbbe1..b5c156c 100644 +--- a/db.c ++++ b/db.c +@@ -44,18 +44,47 @@ + devrim(devrim@gazi.edu.tr) + */ + +-#if defined(DB) +-#include ++ + #include "tac_plus.h" ++ ++#ifdef DB ++ ++#include ++#include ++#include ++ ++#include "db.h" ++#include "report.h" ++#include "do_acct.h" ++#include "main.h" ++#include "do_author.h" /* for "struct identity" */ ++#include "utils.h" ++ ++ ++#ifdef DB_MYSQL ++#include "db_mysql.h" ++#endif ++#ifdef DB_NULL ++#include "db_null.h" ++#endif ++#ifdef DB_PGSQL ++#include "db_pgsql.h" ++#endif ++ ++ ++static int check_db_type TAC_ARGS((char *db_type)); ++ ++ + /* The databases recognized by this function */ + #define DEFINED_DB {"null","mysql","pgsql"} + +-char *find_attr_value(); ++int db_verify TAC_ARGS((const char *user, const char *users_passwd, const char *str_conn)); + + int + db_verify(user, users_passwd, str_conn) +-char *user, *users_passwd; /* Username and gived password */ +-char *str_conn; /* String connection to database */ ++const char *user; /* username ... */ ++const char *users_passwd; /* ... and given password */ ++const char *str_conn; /* string connection to database */ + { + char *buffer; + char *db_pref, *db_user, *db_password; +@@ -65,11 +94,7 @@ char *str_conn; /* String connection to database */ + 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); +- } ++ buffer = db_pref = (char *) tac_malloc( strlen(str_conn) + 1 ); + + strcpy( buffer, str_conn ); + +@@ -216,6 +241,8 @@ char *str_conn; /* String connection to database */ + } + + ++int db_acct TAC_ARGS((struct acct_rec *rec)); ++ + /* Db accounting routine */ + int + db_acct(rec) +@@ -227,12 +254,7 @@ struct acct_rec *rec; + 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); +- } ++ buffer = db_pref = (char *) tac_malloc( strlen(session.db_acct) + 1 ); + + strcpy( buffer, session.db_acct); + +@@ -384,8 +406,10 @@ struct acct_rec *rec; + + } + ++static int check_db_type TAC_ARGS((char *db_type)); ++ + /* For checking DB type */ +-int ++static int + check_db_type(db_type) + char *db_type; + { +@@ -400,4 +424,9 @@ for (i=0; dbp[i] ; i++ ) { + } + return ret; + } ++ ++#else /* DB */ ++ ++TAC_SOURCEFILE_EMPTY ++ + #endif /* DB */ +diff --git a/db.h b/db.h +new file mode 100644 +index 0000000..dfc0c23 +--- /dev/null ++++ b/db.h +@@ -0,0 +1,17 @@ ++#ifndef DB_H ++#define DB_H 1 ++ ++#include "tac_plus.h" ++ ++#ifdef DB ++ ++ ++struct acct_rec; ++ ++extern int db_verify TAC_ARGS((const char *user, const char *users_passwd, const char *str_conn)); ++extern int db_acct TAC_ARGS((struct acct_rec *rec)); ++ ++ ++#endif /* DB */ ++ ++#endif /* DB_H */ +diff --git a/db_mysql.c b/db_mysql.c +index 8aef6d4..4c09f48 100644 +--- a/db_mysql.c ++++ b/db_mysql.c +@@ -1,88 +1,88 @@ +-#if defined(DB_MYSQL) && defined(DB) +- + /* +-Writen by Devrim SERAL(devrim@tef.gazi.edu.tr) +-*/ ++ * Writen by Devrim SERAL(devrim@tef.gazi.edu.tr) ++ */ ++ + + #include "tac_plus.h" ++ ++#if defined(DB_MYSQL) && defined(DB) ++ + #include +-#include "mysql.h" ++#include ++#include ++ ++#include "db_mysql.h" ++#include "report.h" ++#include "pwlib.h" ++#include "main.h" ++#include "utils.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) ++static MYSQL mysqldb; ++static MYSQL_RES *res; ++static MYSQL_ROW row; + + +-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; ++int mysql_db_verify TAC_ARGS((const char *user, const char *users_passwd, const char *db_user, const char *db_password, const char *db_hostname, const char *db_name, const char *db_table, const char *dbfield_name, const char *dbfield_passwd)); + ++int mysql_db_verify(user, users_passwd, db_user, db_password, ++ db_hostname, db_name, db_table, dbfield_name, dbfield_passwd) ++const char *user; /* username ... */ ++const char *users_passwd; /* ... and given password */ ++const char *db_user; /* db's parameters */ ++const char *db_password; ++const char *db_hostname; ++const char *db_name; ++const char *db_table; ++const char *dbfield_name; ++const char *dbfield_passwd; + { +- +-char *real_passwd; +-char *mysqlcmd; +-int sql_len; ++ char *real_passwd; ++ char *mysqlcmd; ++ int sql_len; + + if (debug & DEBUG_AUTHEN_FLAG) + report(LOG_DEBUG, "MySQL: verify %s", user); + +-/* Connect database server */ ++ /* Connect database server */ + +- if ( !( mysql_connect(&mysqldb,db_hostname,db_user,db_password) ) ) +- { ++ 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 */ ++ /* Select tacacs db */ + +- if ( mysql_select_db(&mysqldb,db_name) ) +- { ++ 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 */ ++ /* Check select string length */ + +-sql_len=strlen(dbfield_passwd)+strlen(dbfield_name)+strlen(db_table)+strlen(user)+strlen(AUTHSQL); ++ sql_len = strlen(dbfield_passwd)+strlen(dbfield_name)+strlen(db_table)+strlen(user)+strlen(AUTHSQL); + +- if ( sql_len> SQLCMDL ) +- { ++ if ( sql_len> SQLCMDL ) { + if (debug & DEBUG_AUTHEN_FLAG) + report(LOG_DEBUG, "MySQL: Sql cmd exceed alowed limits"); + return(0); + } + +-/* Prepare select string */ ++ /* 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); +-} ++ mysqlcmd = (char *) tac_malloc(sql_len); + +-sprintf(mysqlcmd,AUTHSQL,dbfield_passwd,db_table,dbfield_name,user); ++ sprintf(mysqlcmd,AUTHSQL,dbfield_passwd,db_table,dbfield_name,user); + +-/* Query database */ ++ /* Query database */ + +- if (mysql_query(&mysqldb,mysqlcmd)) +- { ++ if (mysql_query(&mysqldb,mysqlcmd)) { + if (debug & DEBUG_AUTHEN_FLAG) + report(LOG_DEBUG, "MySQL: cannot query database "); + free(mysqlcmd); +@@ -91,32 +91,29 @@ sprintf(mysqlcmd,AUTHSQL,dbfield_passwd,db_table,dbfield_name,user); + + free(mysqlcmd); + +- if (!(res = mysql_store_result(&mysqldb))) +- { ++ 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 (!(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 (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); ++ real_passwd=(char *) tac_malloc(strlen(row[0])+1); + strcpy(real_passwd,row[0]); + +- if (!mysql_eof(res)) +- { ++ if (!mysql_eof(res)) { + if (debug & DEBUG_AUTHEN_FLAG) + report(LOG_DEBUG, "MySQL: Result not end!!"); + return(0); +@@ -125,7 +122,7 @@ sprintf(mysqlcmd,AUTHSQL,dbfield_passwd,db_table,dbfield_name,user); + mysql_free_result(res); + mysql_close(&mysqldb); + +-if (debug & DEBUG_AUTHEN_FLAG) ++ 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 */ +@@ -133,69 +130,65 @@ if (debug & DEBUG_AUTHEN_FLAG) + 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; ++int mysql_db_acct TAC_ARGS((const char *db_user, const char *db_password, const char *db_hostname, const char *db_name, const char *db_table, const char *s_name, const char *c_name, const char *a_username, const char *elapsed_time, const char *bytes_in, const char *bytes_out)); + ++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) ++const char *db_user; /* db's parameters */ ++const char *db_password; ++const char *db_hostname; ++const char *db_name; ++const char *db_table; ++const char *s_name; ++const char *c_name; ++const char *a_username; ++const char *elapsed_time; ++const char *bytes_in; ++const char *bytes_out; + { ++ char *mysqlcmd; ++ int sql_len; + +-char *mysqlcmd; +-int sql_len; +- +-/* Connect database server */ ++ /* Connect database server */ + +- if (!(mysql_connect(&mysqldb,db_hostname,db_user,db_password))) +- { ++ 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 */ ++ /*Select tacacs db */ + +- if (mysql_select_db(&mysqldb,db_name)) +- { ++ 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); ++ /* 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 ( 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); ++ /* Prepare select string */ ++ mysqlcmd=(char *) tac_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); ++ sprintf(mysqlcmd,ACCTSQL,db_table,a_username,s_name,c_name,elapsed_time,bytes_in,bytes_out); + +-/* Query database */ ++ /* Query database */ + +- if (mysql_query(&mysqldb,mysqlcmd)) +- { ++ if (mysql_query(&mysqldb,mysqlcmd)) { + if (debug & DEBUG_ACCT_FLAG) + report(LOG_DEBUG, "MySQL: cannot query database"); + free(mysqlcmd); +@@ -204,13 +197,18 @@ sprintf(mysqlcmd,ACCTSQL,db_table,a_username,s_name,c_name,elapsed_time,bytes_in + + free(mysqlcmd); + +-/* Check if accounting is sucess */ +- if ( mysql_affected_rows( &mysqldb ) < 0 ) +- { ++ /* 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 ++ ++#else /* defined(DB_MYSQL) && defined(DB) */ ++ ++TAC_SOURCEFILE_EMPTY ++ ++#endif /* defined(DB_MYSQL) && defined(DB) */ +diff --git a/db_mysql.h b/db_mysql.h +new file mode 100644 +index 0000000..7e806b6 +--- /dev/null ++++ b/db_mysql.h +@@ -0,0 +1,15 @@ ++#ifndef DB_MYSQL_H ++#define DB_MYSQL_H 1 ++ ++#include "tac_plus.h" ++ ++#if defined(DB_MYSQL) && defined(DB) ++ ++ ++extern int mysql_db_verify TAC_ARGS((const char *user, const char *users_passwd, const char *db_user, const char *db_password, const char *db_hostname, const char *db_name, const char *db_table, const char *dbfield_name, const char *dbfield_passwd)); ++extern int mysql_db_acct TAC_ARGS((const char *db_user, const char *db_password, const char *db_hostname, const char *db_name, const char *db_table, const char *s_name, const char *c_name, const char *a_username, const char *elapsed_time, const char *bytes_in, const char *bytes_out)); ++ ++ ++#endif /* defined(DB_MYSQL) && defined(DB) */ ++ ++#endif /* DB_MYSQL_H */ +diff --git a/db_null.c b/db_null.c +index 40252d8..9892ef0 100644 +--- a/db_null.c ++++ b/db_null.c +@@ -6,22 +6,30 @@ + ** 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) ++#if defined(DB_NULL) && defined(DB) ++ ++#include "db_null.h" ++#include "report.h" ++#include "main.h" + +-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; + ++int null_db_verify TAC_ARGS((const char *user, const char *users_passwd, const char *db_user, const char *db_password, const char *db_hostname, const char *db_table, const char *dbfield_name, const char *dbfield_passwd)); ++ ++int null_db_verify(user, users_passwd, db_user, db_password, db_hostname, ++ db_table, dbfield_name, dbfield_passwd) ++const char *user; /* username ... */ ++const char *users_passwd; /* ... and given password */ ++const char *db_user; /* db's parametr's */ ++const char *db_password; ++const char *db_hostname; ++const char *db_table; ++const char *dbfield_name; ++const char *dbfield_passwd; + { +-//report(LOG_DEBUG, "DB_NULL(%u) - ok", __LINE__); ++/* report(LOG_DEBUG, "DB_NULL(%u) - ok", __LINE__); */ + + /* Try to verify the password + Successful if username and password equal */ +@@ -36,22 +44,30 @@ char *dbfield_passwd; + + /* Null Database Accounting */ + ++int null_db_acct TAC_ARGS((const char *db_user, const char *db_password, const char *db_hostname, const char *db_name, const char *db_table, const char *s_name, const char *c_name, const char *a_username, const char *elapsed_time, const char *bytes_in, const char *bytes_out)); ++ + 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; ++const char *db_user; /* db's parametr's */ ++const char *db_password; ++const char *db_hostname; ++const char *db_name; ++const char *db_table; ++const char *s_name; ++const char *c_name; ++const char *a_username; ++const char *elapsed_time; ++const char *bytes_in; ++const 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); ++ 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 + ++#else /* defined(DB_NULL) && defined(DB) */ ++ ++TAC_SOURCEFILE_EMPTY ++ ++#endif /* defined(DB_NULL) && defined(DB) */ +diff --git a/db_null.h b/db_null.h +new file mode 100644 +index 0000000..4e82047 +--- /dev/null ++++ b/db_null.h +@@ -0,0 +1,15 @@ ++#ifndef DB_NULL_H ++#define DB_NULL_H 1 ++ ++#include "tac_plus.h" ++ ++#if defined(DB_NULL) && defined(DB) ++ ++ ++extern int null_db_verify TAC_ARGS((const char *user, const char *users_passwd, const char *db_user, const char *db_password, const char *db_hostname, const char *db_table, const char *dbfield_name, const char *dbfield_passwd)); ++extern int null_db_acct TAC_ARGS((const char *db_user, const char *db_password, const char *db_hostname, const char *db_name, const char *db_table, const char *s_name, const char *c_name, const char *a_username, const char *elapsed_time, const char *bytes_in, const char *bytes_out)); ++ ++ ++#endif /* defined(DB_NULL) && defined(DB) */ ++ ++#endif /* DB_NULL_H */ +diff --git a/db_pgsql.c b/db_pgsql.c +index 22725fe..321133c 100644 +--- a/db_pgsql.c ++++ b/db_pgsql.c +@@ -1,5 +1,3 @@ +-#if defined(DB_PGSQL) && defined(DB) +- + /* + Writen by Devrim SERAL(devrim@tef.gazi.edu.tr) + For PostgreSQL Authentication And Accounting +@@ -7,80 +5,87 @@ For PostgreSQL Authentication And Accounting + This program protected with GPL License. + */ + ++ + #include "tac_plus.h" ++ ++#if defined(DB_PGSQL) && defined(DB) ++ + #include +-#include "libpq-fe.h" ++#include ++#include ++#include ++ ++#include "db_pgsql.h" ++#include "main.h" ++#include "report.h" ++#include "utils.h" ++#include "pwlib.h" ++ ++ ++static void exit_nicely TAC_ARGS((PGconn *cn, PGresult *r)); ++ ++ + #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) ++static PGconn *conn; ++static PGresult *res; + + +-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; ++int pgsql_db_verify TAC_ARGS((const char *user, const char *users_passwd, const char *db_user, const char *db_password, const char *db_hostname, const char *db_name, const char *db_table, const char *dbfield_name, const char *dbfield_passwd)); + ++int pgsql_db_verify(user, users_passwd, db_user, db_password, ++ db_hostname, db_name, db_table, dbfield_name, dbfield_passwd) ++const char *user; /* username ... */ ++const char *users_passwd; /* ... and given password */ ++const char *db_user; /* db's parameters */ ++const char *db_password; ++const char *db_hostname; ++const char *db_name; ++const char *db_table; ++const char *dbfield_name; ++const char *dbfield_passwd; + { ++ char *real_passwd; ++ char *pgsqlcmd; ++ int sql_len; ++ int nrow; + +-char *real_passwd; +-char *pgsqlcmd; +-int sql_len; +-int nrow; +- +-if (debug & DEBUG_AUTHEN_FLAG) ++ if (debug & DEBUG_AUTHEN_FLAG) + report(LOG_DEBUG, "PGSQL: verify %s", user); + +-/* Connect database server */ ++ /* Connect database server */ + +-conn=PQsetdbLogin(db_hostname,NULL,NULL,NULL,db_name,db_user,db_password); ++ conn=PQsetdbLogin(db_hostname,NULL,NULL,NULL,db_name,db_user,db_password); + +-if ( PQstatus(conn) == CONNECTION_BAD ) +-{ ++ 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 */ ++ /* Check select string length */ + +-sql_len=strlen(dbfield_passwd)+strlen(dbfield_name)+strlen(db_table)+strlen(user)+strlen(AUTHSQL); ++ sql_len=strlen(dbfield_passwd)+strlen(dbfield_name)+strlen(db_table)+strlen(user)+strlen(AUTHSQL); + +-if ( sql_len> SQLCMDL ) +-{ ++ 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); ++ /* Prepare select string */ + +-if(pgsqlcmd==NULL) +-{ +- if (debug & DEBUG_AUTHEN_FLAG) +- report(LOG_ERR, "pgsql_db_verify: pgsqlcmd malloc error"); +- return(0); +-} ++ pgsqlcmd=(char *) tac_malloc(sql_len); + +-sprintf(pgsqlcmd,AUTHSQL,dbfield_passwd,db_table,dbfield_name,user); ++ sprintf(pgsqlcmd,AUTHSQL,dbfield_passwd,db_table,dbfield_name,user); + +-/* Query database */ +-res=PQexec(conn,pgsqlcmd); ++ /* Query database */ ++ res=PQexec(conn,pgsqlcmd); + +-if (!res || PQresultStatus(res) != PGRES_TUPLES_OK) +-{ ++ 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) ); +@@ -88,119 +93,109 @@ if (!res || PQresultStatus(res) != PGRES_TUPLES_OK) + free(pgsqlcmd); + exit_nicely(conn,res); + return(0); +-} ++ } + +-free(pgsqlcmd); ++ free(pgsqlcmd); + +-if( nrow=PQntuples(res)!=1) +-{ ++ 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 ( 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); ++ real_passwd=(char *) tac_malloc(PWLEN+1); + strncpy(real_passwd,PQgetvalue(res,0,PQfnumber(res,dbfield_passwd)),PWLEN); + real_passwd[PWLEN]='\0'; + +-exit_nicely(conn,res); ++ exit_nicely(conn,res); + +-if (debug & DEBUG_AUTHEN_FLAG) ++ 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; ++int pgsql_db_acct TAC_ARGS((const char *db_user, const char *db_password, const char *db_hostname, const char *db_name, const char *db_table, const char *s_name, const char *c_name, const char *a_username, const char *elapsed_time, const char *bytes_in, const char *bytes_out)); + ++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) ++const char *db_user; /* db's parameters */ ++const char *db_password; ++const char *db_hostname; ++const char *db_name; ++const char *db_table; ++const char *s_name; ++const char *c_name; ++const char *a_username; ++const char *elapsed_time; ++const char *bytes_in; ++const char *bytes_out; + { +- +-char *pgsqlcmd; +-int sql_len; ++ char *pgsqlcmd; ++ int sql_len; + + if (debug & DEBUG_ACCT_FLAG) + report(LOG_DEBUG, "PGSQL: Accounting for %s begin", a_username); + +-/* Connect database server */ ++ /* Connect database server */ + +-conn=PQsetdbLogin(db_hostname,NULL,NULL,NULL,db_name,db_user,db_password); ++ conn=PQsetdbLogin(db_hostname,NULL,NULL,NULL,db_name,db_user,db_password); + +-if ( PQstatus(conn) == CONNECTION_BAD ) +-{ ++ 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 */ ++ /* 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); ++ 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 ( 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); ++ /* Prepare select string */ + +-if(pgsqlcmd==NULL) +-{ +-if (debug & DEBUG_ACCT_FLAG) +- report(LOG_ERR, "pgsql_db_verify: pgsqlcmd malloc error"); +- return(0); +-} ++ pgsqlcmd=(char *) tac_malloc(sql_len); + +-sprintf(pgsqlcmd,ACCTSQL,db_table,a_username,s_name,c_name,elapsed_time,bytes_in,bytes_out); ++ sprintf(pgsqlcmd,ACCTSQL,db_table,a_username,s_name,c_name,elapsed_time,bytes_in,bytes_out); + +-/* Query database */ +-res=PQexec(conn,pgsqlcmd); ++ /* Query database */ ++ res=PQexec(conn,pgsqlcmd); + +-if (!res || PQresultStatus(res) != PGRES_COMMAND_OK ) +-{ ++ 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); ++ free(pgsqlcmd); + +-/* Flush all result and close connection */ +-exit_nicely(conn,res); ++ /* 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); +@@ -208,11 +203,20 @@ exit_nicely(conn,res); + return (1); /* Return 1 if verified, 0 otherwise. */ + } + +-int +-exit_nicely(PGconn *cn,PGresult *r) ++ ++static void exit_nicely TAC_ARGS((PGconn *cn, PGresult *r)); ++ ++static void ++exit_nicely(cn, r) ++PGconn *cn; ++PGresult *r; + { + PQclear(r); + PQfinish(cn); + } + +-#endif ++#else /* defined(DB_PGSQL) && defined(DB) */ ++ ++TAC_SOURCEFILE_EMPTY ++ ++#endif /* defined(DB_PGSQL) && defined(DB) */ +diff --git a/db_pgsql.h b/db_pgsql.h +new file mode 100644 +index 0000000..b966bfc +--- /dev/null ++++ b/db_pgsql.h +@@ -0,0 +1,15 @@ ++#ifndef DB_PGSQL_H ++#define DB_PGSQL_H 1 ++ ++#include "tac_plus.h" ++ ++#if defined(DB_PGSQL) && defined(DB) ++ ++ ++extern int pgsql_db_verify TAC_ARGS((const char *user, const char *users_passwd, const char *db_user, const char *db_password, const char *db_hostname, const char *db_name, const char *db_table, const char *dbfield_name, const char *dbfield_passwd)); ++extern int pgsql_db_acct TAC_ARGS((const char *db_user, const char *db_password, const char *db_hostname, const char *db_name, const char *db_table, const char *s_name, const char *c_name, const char *a_username, const char *elapsed_time, const char *bytes_in, const char *bytes_out)); ++ ++ ++#endif /* defined(DB_PGSQL) && defined(DB) */ ++ ++#endif /* DB_PGSQL_H */ +diff --git a/default_fn.c b/default_fn.c +index f97e9e2..d6196cf 100644 +--- a/default_fn.c ++++ b/default_fn.c +@@ -17,23 +17,50 @@ + FITNESS FOR A PARTICULAR PURPOSE. + */ + ++ + #include "tac_plus.h" ++ ++#include ++#include ++#include ++ ++#include "default_fn.h" + #include "expire.h" + #include "md5.h" ++#include "report.h" ++#include "utils.h" ++#include "cfgfile.h" ++#include "pwlib.h" ++#include "choose_authen.h" /* for "struct authen_data" */ ++#include "do_author.h" /* for "struct identity" */ ++#include "packet.h" ++#include "main.h" + + #ifdef MSCHAP + #include "md4.h" + #include "mschap.h" +- + #ifdef MSCHAP_DES + #include "arap_des.h" +-#endif ++#endif /* MSCHAP_DES */ + #endif /* MSCHAP */ + + #ifdef ARAP_DES + #include "arap_des.h" + #endif + ++ ++struct private_data; ++ ++static void chap_verify TAC_ARGS((struct authen_data *data)); ++static void arap_verify TAC_ARGS((struct authen_data *data)); ++static void pap_verify TAC_ARGS((struct authen_data *data)); ++static void tac_login TAC_ARGS((struct authen_data *data, struct private_data *p)); ++ ++#ifdef MSCHAP ++static void mschap_verify TAC_ARGS((struct authen_data *data)); ++#endif ++ ++ + /* internal state variables */ + #define STATE_AUTHEN_START 0 /* no requests issued */ + #define STATE_AUTHEN_GETUSER 1 /* username has been requested */ +@@ -44,13 +71,6 @@ struct private_data { + 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 +@@ -67,6 +87,8 @@ static void tac_login(); + * Return 0 if data->status is valid, otherwise 1 + */ + ++int default_fn TAC_ARGS((struct authen_data *data)); ++ + int + default_fn(data) + struct authen_data *data; +@@ -210,6 +232,8 @@ struct authen_data *data; + * + */ + ++static void tac_login TAC_ARGS((struct authen_data *data, struct private_data *p)); ++ + static void + tac_login(data, p) + struct authen_data *data; +@@ -278,6 +302,8 @@ struct private_data *p; + * the START packet. + */ + ++static void pap_verify TAC_ARGS((struct authen_data *data)); ++ + static void + pap_verify(data) + struct authen_data *data; +@@ -305,6 +331,8 @@ struct authen_data *data; + } + + ++static void chap_verify TAC_ARGS((struct authen_data *data)); ++ + /* Verify the challenge and id against the response by looking up the + * chap secret in the config file. Set data->status appropriately. + */ +@@ -312,8 +340,9 @@ static void + chap_verify(data) + struct authen_data *data; + { +- char *name, *secret, *chal, digest[MD5_LEN]; +- char *exp_date, *p; ++ char *name, *chal, digest[MD5_LEN]; ++ const char *secret; ++ const char *exp_date, *p; + u_char *mdp; + char id; + int chal_len, inlen; +@@ -398,13 +427,15 @@ struct authen_data *data; + } + + ++static void pw_bitshift TAC_ARGS((char *pw)); ++ + /* + * 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 ++static void + pw_bitshift(pw) + char *pw; + { +@@ -423,12 +454,14 @@ char *pw; + } + + ++static void arap_verify TAC_ARGS((struct authen_data *data)); ++ + 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; ++ const char *name, *cfg_secret, *exp_date, *p; + + if (!(char) data->NAS_id->username[0]) { + report(LOG_ERR, "%s %s: no username for arap_verify", +@@ -511,9 +544,12 @@ struct authen_data *data; + #ifdef MSCHAP + + /* Following code is added for ms-chap */ ++ ++static void mschap_desencrypt TAC_ARGS((const char *clear, unsigned char *str, unsigned char *cypher)); ++ + static void + mschap_desencrypt(clear, str, cypher) +-char *clear; ++const char *clear; + unsigned char *str; + unsigned char *cypher; + { +@@ -576,19 +612,23 @@ unsigned char *cypher; + } + + ++static void mschap_deshash TAC_ARGS((unsigned char *clear, unsigned char *cypher)); ++ + static void + mschap_deshash(clear, cypher) +-char *clear; +-char *cypher; ++unsigned char *clear; ++unsigned char *cypher; + { + mschap_desencrypt(MSCHAP_KEY, clear, cypher); + } + + ++static void mschap_lmpasswordhash TAC_ARGS((const char *password, unsigned char *passwordhash)); ++ + static void + mschap_lmpasswordhash(password, passwordhash) +-char *password; +-char *passwordhash; ++const char *password; ++unsigned char *passwordhash; + { + unsigned char upassword[15]; + int i = 0; +@@ -604,13 +644,15 @@ char *passwordhash; + } + + ++static void mschap_challengeresponse TAC_ARGS((const char *challenge, unsigned char *passwordhash, unsigned char *response)); ++ + static void + mschap_challengeresponse(challenge, passwordhash, response) +-char *challenge; +-char *passwordhash; +-char *response; ++const char *challenge; ++unsigned char *passwordhash; ++unsigned char *response; + { +- char zpasswordhash[21]; ++ unsigned char zpasswordhash[21]; + + memset(zpasswordhash, 0, 21); + memcpy(zpasswordhash, passwordhash, 16); +@@ -621,22 +663,26 @@ char *response; + } + + ++void mschap_lmchallengeresponse TAC_ARGS((const char *challenge, const char *password, unsigned char *response)); ++ + void + mschap_lmchallengeresponse(challenge, password, response) +-char *challenge; +-char *password; +-char *response; ++const char *challenge; ++const char *password; ++unsigned char *response; + { +- char passwordhash[16]; ++ unsigned char passwordhash[16]; + + mschap_lmpasswordhash(password, passwordhash); + mschap_challengeresponse(challenge, passwordhash, response); + } + + ++static int mschap_unicode_len TAC_ARGS((unsigned char *password)); ++ + static int + mschap_unicode_len(password) +-char *password; ++unsigned char *password; + { + int i; + +@@ -649,14 +695,16 @@ char *password; + } + + ++static void mschap_ntpasswordhash TAC_ARGS((const char *password, unsigned char *passwordhash)); ++ + static void + mschap_ntpasswordhash(password, passwordhash) +-char *password; +-char *passwordhash; ++const char *password; ++unsigned char *passwordhash; + { + MD4_CTX context; + int i; +- char *cp; ++ const char *cp; + unsigned char unicode_password[512]; + + memset(unicode_password, 0, 512); +@@ -676,15 +724,15 @@ char *passwordhash; + } + + ++void mschap_ntchallengeresponse TAC_ARGS((const char *challenge, const char *password, unsigned char *response)); ++ + void +-mschap_ntchallengeresponse(challenge, +- password, +- response) +-char *challenge; +-char *password; +-char *response; ++mschap_ntchallengeresponse(challenge, password, response) ++const char *challenge; ++const char *password; ++unsigned char *response; + { +- char passwordhash[16]; ++ unsigned char passwordhash[16]; + + mschap_ntpasswordhash(password, passwordhash); + mschap_challengeresponse(challenge, passwordhash, response); +@@ -694,16 +742,19 @@ char *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 TAC_ARGS((struct authen_data *data)); ++ + static void + mschap_verify(data) + struct authen_data *data; + { +- char *name, *secret, *chal, *resp; +- char *exp_date, *p; ++ const char *name, *secret, *chal, *resp; ++ const char *exp_date, *p; + char id; + int chal_len; +- char lmresponse[24]; +- char ntresponse[24]; ++ unsigned char lmresponse[24]; ++ unsigned char ntresponse[24]; + int bcmp_status; + + if (!(char) data->NAS_id->username[0]) { +diff --git a/default_fn.h b/default_fn.h +new file mode 100644 +index 0000000..20e0e0f +--- /dev/null ++++ b/default_fn.h +@@ -0,0 +1,16 @@ ++#ifndef DEFAULT_FN_H ++#define DEFAULT_FN_H 1 ++ ++#include "tac_plus.h" ++ ++ ++struct authen_data; ++ ++extern int default_fn TAC_ARGS((struct authen_data *data)); ++#ifdef MSCHAP ++extern void mschap_lmchallengeresponse TAC_ARGS((const char *challenge, const char *password, unsigned char *response)); ++extern void mschap_ntchallengeresponse TAC_ARGS((const char *challenge, const char *password, unsigned char *response)); ++#endif ++ ++ ++#endif /* DEFAULT_FN_H */ +diff --git a/default_v0_fn.c b/default_v0_fn.c +index 96e0c1d..7c4c9cb 100644 +--- a/default_v0_fn.c ++++ b/default_v0_fn.c +@@ -17,8 +17,23 @@ + FITNESS FOR A PARTICULAR PURPOSE. + */ + ++ + #include "tac_plus.h" ++ ++#include ++#include ++ ++#include "default_v0_fn.h" + #include "expire.h" ++#include "utils.h" ++#include "report.h" ++#include "pwlib.h" ++#include "choose_authen.h" /* for "struct authen_data" */ ++#include "do_author.h" /* for "struct identity" */ ++#include "packet.h" ++#include "main.h" ++#include "cfgfile.h" ++ + + /* internal state variables */ + #define STATE_AUTHEN_START 0 /* no requests issued */ +@@ -45,6 +60,8 @@ struct private_data { + * Return 0 if data->status is valid, otherwise 1 + */ + ++int default_v0_fn TAC_ARGS((struct authen_data *data)); ++ + int + default_v0_fn(data) + struct authen_data *data; +@@ -185,4 +202,3 @@ struct authen_data *data; + return (1); + } + } +- +diff --git a/default_v0_fn.h b/default_v0_fn.h +new file mode 100644 +index 0000000..1165ee4 +--- /dev/null ++++ b/default_v0_fn.h +@@ -0,0 +1,12 @@ ++#ifndef DEFAULT_V0_FN_H ++#define DEFAULT_V0_FN_H 1 ++ ++#include "tac_plus.h" ++ ++ ++struct authen_data; ++ ++extern int default_v0_fn TAC_ARGS((struct authen_data *data)); ++ ++ ++#endif /* DEFAULT_V0_FN_H */ +diff --git a/do_acct.c b/do_acct.c +index dee8eaa..287b0dc 100644 +--- a/do_acct.c ++++ b/do_acct.c +@@ -17,18 +17,46 @@ + FITNESS FOR A PARTICULAR PURPOSE. + */ + ++ + #include "tac_plus.h" + ++#include ++#include ++#include ++#include ++#include ++#ifdef HAVE_FCNTL_H ++#include ++#endif ++#include ++#include ++#ifdef HAVE_UNISTD_H ++#include ++#endif ++ ++#include "do_acct.h" ++#include "report.h" ++#include "utils.h" ++#include "main.h" ++#include "do_author.h" /* for "struct identity" */ ++ ++ ++char *wtmpfile = NULL; /* for wtmp file logging */ ++ ++ ++static int wtmpfd = 0; + static int acctfd = 0; + + /* Make a acct entry into the accounting file for accounting. + Return 1 on error */ + ++static int acct_write TAC_ARGS((char *string)); ++ + static int + acct_write(string) +- char *string; ++char *string; + { +- if (write(acctfd, string, strlen(string)) != strlen(string)) { ++ if ((unsigned long)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]); +@@ -41,11 +69,13 @@ acct_write(string) + return(0); + } + ++static int acct_write_field TAC_ARGS((char *string)); ++ + /* Write a string or "unknown" into the accounting file. + Return 1 on error */ + static int + acct_write_field(string) +- char *string; ++char *string; + { + if (string && string[0]) { + if (acct_write(string)) +@@ -57,6 +87,8 @@ acct_write_field(string) + return(0); + } + ++int do_acct TAC_ARGS((struct acct_rec *rec)); ++ + int + do_acct(rec) + struct acct_rec *rec; +@@ -131,10 +163,12 @@ struct acct_rec *rec; + return (0); + } + +-int ++static int wtmp_entry TAC_ARGS((char *line, char *name, char *host, time_t utime)); ++ ++static int + wtmp_entry (line, name, host, utime) +- char *line, *name, *host; +- time_t utime; ++char *line, *name, *host; ++time_t utime; + { + struct utmp entry; + +@@ -152,7 +186,7 @@ wtmp_entry (line, name, host, utime) + strcpy(entry.ut_name, name); + else bcopy(name, entry.ut_name, sizeof entry.ut_name); + +-#ifndef SOLARIS ++#ifdef HAVE_UTMP_UT_HOST + if (strlen(host) < sizeof entry.ut_host) + strcpy(entry.ut_host, host); + else bcopy(host, entry.ut_host, sizeof entry.ut_host); +@@ -180,16 +214,18 @@ wtmp_entry (line, name, host, utime) + close(wtmpfd); + + if (debug & DEBUG_ACCT_FLAG) { +- report(LOG_DEBUG, "wtmp: %s, %s %s %d", line, name, host, utime); ++ report(LOG_DEBUG, "wtmp: %s, %s %s %ld", line, name, host, (long)utime); + } + + return(0); + } + ++char *find_attr_value TAC_ARGS((char *attr, char **args, int cnt)); ++ + char * + find_attr_value (attr, args, cnt) +- char *attr, **args; +- int cnt; ++char *attr, **args; ++int cnt; + { + int i; + +@@ -208,9 +244,11 @@ find_attr_value (attr, args, cnt) + return(NULL); + } + ++int do_wtmp TAC_ARGS((struct acct_rec *rec)); ++ + int + do_wtmp(rec) +- struct acct_rec *rec; ++struct acct_rec *rec; + { + time_t now = time(NULL); + char *service; +diff --git a/do_acct.h b/do_acct.h +new file mode 100644 +index 0000000..5e6e025 +--- /dev/null ++++ b/do_acct.h +@@ -0,0 +1,33 @@ ++#ifndef DO_ACCT_H ++#define DO_ACCT_H 1 ++ ++#include "tac_plus.h" ++ ++ ++/* 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; ++}; ++ ++extern char *wtmpfile; /* for wtmp file logging */ ++ ++ ++extern int do_acct TAC_ARGS((struct acct_rec *rec)); ++extern char *find_attr_value TAC_ARGS((char *attr, char **args, int cnt)); ++extern int do_wtmp TAC_ARGS((struct acct_rec *rec)); ++ ++ ++#endif /* DO_ACCT_H */ +diff --git a/do_author.c b/do_author.c +index 574736f..3f23602 100644 +--- a/do_author.c ++++ b/do_author.c +@@ -17,15 +17,39 @@ + 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(); ++#include ++#include ++ ++#include "do_author.h" ++#include "tac_regexp.h" ++#include "cfgfile.h" ++#include "report.h" ++#include "utils.h" ++#include "programs.h" ++#include "main.h" ++#include "parse.h" ++#include "cfgeval.h" ++ ++#ifdef MAXSESS ++#include "maxsess.h" ++#endif ++#ifdef USE_PAM ++#include "tac_pam.h" ++#endif ++ ++ ++static int pre_authorization TAC_ARGS((const char *username, struct author_data *data)); ++static int get_nas_svc TAC_ARGS((struct author_data *data, char **cmdname, char **protocol, char **svcname)); ++static int authorize_cmd TAC_ARGS((const char *user, const char *cmd, struct author_data *data)); ++static int authorize_exec TAC_ARGS((const char *user, struct author_data *data)); ++static int authorize_svc TAC_ARGS((const char *user, int svc, const char *protocol, const char *svcname, struct author_data *data)); ++static void post_authorization TAC_ARGS((const char *username, struct author_data *data)); ++ ++ ++int do_author TAC_ARGS((struct author_data *data)); + + /* Return 0 is data->status is valid */ + int +@@ -37,7 +61,7 @@ struct author_data *data; + int svc; + char *cmd, *protocol, *svcname; + #ifdef USE_PAM +- char *pam_service= NULL; ++ const char *pam_service= NULL; + #endif + protocol = NULL; + +@@ -48,7 +72,8 @@ struct author_data *data; + + /* If this user doesn't exist in our configs, do the default */ + +- if (!cfg_user_exists(username) && !cfg_user_exists(DEFAULT_USERNAME)) { ++ if (!cfg_user_exists(username)) { ++ if (!cfg_user_exists(DEFAULT_USERNAME)) { + + if (cfg_no_user_permitted()) { + if (debug & DEBUG_AUTHOR_FLAG) +@@ -70,7 +95,8 @@ struct author_data *data; + return (0); + } + +- if (!cfg_user_exists(username) && cfg_user_exists(DEFAULT_USERNAME)) { ++ /* assumed (cfg_user_exists(DEFAULT_USERNAME)): */ ++ + if (debug & DEBUG_AUTHOR_FLAG) { + report(LOG_DEBUG, "Authorizing user '%s' instead of '%s'", + DEFAULT_USERNAME, username); +@@ -127,19 +153,20 @@ struct author_data *data; + #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 ((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 (tac_pam_authorization(data->id->username, data, pam_service)) { + if (debug & DEBUG_AUTHOR_FLAG) + report(LOG_DEBUG, "PAM Authorization Fail"); + return(0); + } +-} /* Pam_service */ ++ } /* Pam_service */ ++ + default: + break; + } +@@ -181,15 +208,17 @@ if (pam_service=cfg_get_pam_service(data->id->username,TAC_PLUS_RECURSE) ) { + A return value of 1 means no further authorization is required + */ + ++static int pre_authorization TAC_ARGS((const char *username, struct author_data *data)); ++ + static int + pre_authorization(username, data) +-char *username; ++const char *username; + struct author_data *data; + { + int status; + char **out_args; + int out_cnt, i; +- char *cmd; ++ const char *cmd; + char error_str[255]; + int error_len = 255; + +@@ -199,7 +228,7 @@ struct author_data *data; + /* If a before-authorization program exists, call it to see how to + proceed */ + +- cmd = cfg_get_pvalue(username, TAC_IS_USER, ++ cmd = cfg_get_pvalue(S_user, username, + S_before, TAC_PLUS_RECURSE); + if (!cmd) + return(0); +@@ -303,16 +332,17 @@ struct author_data *data; + change the authorization status by calling an external program. + */ + ++static void post_authorization TAC_ARGS((const char *username, struct author_data *data)); ++ + static void + post_authorization(username, data) +- char *username; +- struct author_data *data; ++const 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); ++ const char *after = cfg_get_pvalue(S_user, username, S_after, TAC_PLUS_RECURSE); + if (!after) + return; + +@@ -378,6 +408,8 @@ post_authorization(username, data) + } + + ++static char *value TAC_ARGS((char *s)); ++ + /* Return a pointer to the value part of an attr=value string */ + static char * + value(s) +@@ -393,6 +425,8 @@ char *s; + /* 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 TAC_ARGS((struct author_data *data)); ++ + static char * + assemble_args(data) + struct author_data *data; +@@ -445,30 +479,27 @@ struct author_data *data; + Otherwise, we return 1, indicating no further processing is + required for this request. */ + ++static int authorize_exec TAC_ARGS((const char *user, struct author_data *data)); ++ + static int + authorize_exec(user, data) +-char *user; ++const 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 (cfg_get_svc_node(user, N_svc_exec, NULL, NULL, TAC_PLUS_RECURSE, NULL /* nodep */)) { + if (debug & DEBUG_AUTHOR_FLAG) +- report(LOG_DEBUG, "exec is explicitly permitted by line %d", +- svc->line); ++ report(LOG_DEBUG, "exec is explicitly permitted"); + 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 (cfg_get_svc_node(user, N_svc_cmd, NULL, NULL, TAC_PLUS_RECURSE, NULL /* nodep */)) { + if (debug & DEBUG_AUTHOR_FLAG) + report(LOG_DEBUG, "exec permitted because commands are configured"); + +@@ -478,18 +509,6 @@ struct author_data *data; + 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"); + +@@ -501,16 +520,15 @@ struct author_data *data; + /* Is an exec command authorized per our database(s)? + Return 0 if status is valid */ + ++static int authorize_cmd TAC_ARGS((const char *user, const char *cmd, struct author_data *data)); ++ + static int + authorize_cmd(user, cmd, data) +-char *user, *cmd; ++const char *user; ++const char *cmd; + struct author_data *data; + { +- NODE *node; + char *args; +- int match; +- +- args = assemble_args(data); + + if (!cmd) { + data->status = AUTHOR_STATUS_ERROR; +@@ -520,93 +538,41 @@ struct author_data *data; + 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; ++ args = assemble_args(data); ++ switch (cfg_authorize_cmd(user, cmd, args)) { + +- switch (node->type) { +- case N_permit: +- if (debug & DEBUG_AUTHOR_FLAG) { +- report(LOG_DEBUG, "%s %s permitted by line %d", +- cmd, args, node->line); +- } ++ case ER_TRUE: + 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); +- } ++ ++ case ER_FALSE: + data->status = AUTHOR_STATUS_FAIL; + data->num_out_args = 0; + break; +- default: ++ ++ case ER_UNKNOWN: + 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); ++ data->admin_msg = tac_strdup("Server error illegal configuration"); + break; + } ++ + if (args) + free(args); +- args = NULL; +- return (0); +- } +- if (args) +- free(args); +- return (0); ++ return(0); + } + ++static int is_separator TAC_ARGS((int ch)); ++ + static int + is_separator(ch) +-char ch; ++int ch; /* promoted "char" type */ + { + return (ch == '=' || ch == '*'); + } + ++static int arg_ok TAC_ARGS((char *arg)); ++ + /* check an attr=value pair for well-formedness */ + static int + arg_ok(arg) +@@ -630,6 +596,8 @@ char *arg; + } + + ++static int match_attrs TAC_ARGS((char *nas_arg, char *server_arg)); ++ + /* return 1 if attrs match, 0 otherwise */ + static int + match_attrs(nas_arg, server_arg) +@@ -647,6 +615,8 @@ char *nas_arg, *server_arg; + return (0); + } + ++static int match_values TAC_ARGS((char *nas_arg, char *server_arg)); ++ + /* return 1 if values match, 0 otherwise */ + static int + match_values(nas_arg, server_arg) +@@ -671,6 +641,8 @@ char *nas_arg, *server_arg; + return(STREQ(nas_arg, server_arg)); + } + ++static int mandatory TAC_ARGS((char *arg)); ++ + /* Return 1 if arg is mandatory, 0 otherwise */ + static int + mandatory(arg) +@@ -690,6 +662,8 @@ char *arg; + return (*p == '='); + } + ++static int optional TAC_ARGS((char *arg)); ++ + static int + optional(arg) + char *arg; +@@ -699,21 +673,16 @@ char *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 */ ++ we admit (return 1) any PPP-LCP request */ ++ ++static int ppp_lcp_allowed TAC_ARGS((const char *user)); + + static int +-ppp_lcp_allowed(svc, protocol, user) +- int svc; +- char *user, *protocol; ++ppp_lcp_allowed(user) ++const char *user; + { +- /* 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)) { ++ /* It is an LCP request. Are there PPP services configured? */ ++ if (cfg_get_svc_node(user, N_svc_ppp, NULL /* protocol */, NULL /* svcname */, TAC_PLUS_RECURSE, NULL /* nodep */)) { + if (debug & DEBUG_AUTHOR_FLAG) { + report(LOG_DEBUG, + "ppp/lcp request permitted (ppp is configured for %s)", +@@ -730,14 +699,16 @@ ppp_lcp_allowed(svc, protocol, user) + return(0); + } + ++static int authorize_svc TAC_ARGS((const char *user, int svc, const char *protocol, const char *svcname, struct author_data *data)); ++ + /* 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 */ ++const char *user; ++int svc; ++const char *svcname; ++struct author_data *data; ++const char *protocol; /* valid only if svc == ppp */ + { + int max_args; + char **out_args, **outp; +@@ -747,20 +718,23 @@ authorize_svc(user, svc, protocol, svcname, data) + char **cfg_argp; + int deny_by_default; + NODE *node; ++ int service_permit; + + 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); ++ service_permit = cfg_get_svc_node(user, svc, protocol, svcname, TAC_PLUS_RECURSE, &node); + +- 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. */ ++ /* Now we may have three cases: ++ * service_permit == 1 && node != NULL ++ * service_permit == 1 && node == NULL ++ * service_permit == 0 (BTW node == NULL) ++ */ + +- if (cfg_user_svc_default_is_permit(user)) { ++ if (service_permit && !node) { ++ /* Service not found and the default is permit, we'll allow it. */ + + if (debug & DEBUG_AUTHOR_FLAG) + report(LOG_DEBUG, +@@ -775,7 +749,13 @@ authorize_svc(user, svc, protocol, svcname, data) + return(0); + } + +- if (ppp_lcp_allowed(svc, protocol, user)) { ++ if (!service_permit) { ++ /* Service not found, if this is an ++ PPP/LCP request and other ppp services are configured, ++ we'll allow it. */ ++ ++ if (svc == N_svc_ppp && protocol && STREQ(protocol, "lcp") ++ && ppp_lcp_allowed(user)) { + data->status = AUTHOR_STATUS_PASS_ADD; + data->num_out_args = 0; + data->output_args = NULL; +@@ -1200,6 +1180,8 @@ authorize_svc(user, svc, protocol, svcname, data) + name in svcname. + */ + ++static int get_nas_svc TAC_ARGS((struct author_data *data, char **cmdname, char **protocol, char **svcname)); ++ + static int + get_nas_svc(data, cmdname, protocol, svcname) + struct author_data *data; +diff --git a/do_author.h b/do_author.h +new file mode 100644 +index 0000000..08bea16 +--- /dev/null ++++ b/do_author.h +@@ -0,0 +1,64 @@ ++#ifndef DO_AUTHOR_H ++#define DO_AUTHOR_H 1 ++ ++#include "tac_plus.h" ++ ++ ++/* ++ * 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; ++}; ++ ++ ++/* ++ * 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 */ ++ ++}; ++ ++ ++extern int do_author TAC_ARGS((struct author_data *data)); ++ ++ ++#endif /* DO_AUTHOR_H */ +diff --git a/dump.c b/dump.c +index ae58e5e..511085b 100644 +--- a/dump.c ++++ b/dump.c +@@ -17,8 +17,21 @@ + FITNESS FOR A PARTICULAR PURPOSE. + */ + ++ + #include "tac_plus.h" + ++#include /* for ntohl() */ ++ ++#include "dump.h" ++#include "report.h" ++#include "utils.h" ++#include "packet.h" ++#include "do_author.h" ++#include "main.h" ++ ++ ++char *summarise_outgoing_packet_type TAC_ARGS((u_char *pak)); ++ + /* Routines for dumping packets to stderr */ + char * + summarise_outgoing_packet_type(pak) +@@ -90,7 +103,9 @@ u_char *pak; + return (p); + } + +-void ++static void dump_header TAC_ARGS((u_char *pak)); ++ ++static void + dump_header(pak) + u_char *pak; + { +@@ -117,7 +132,10 @@ u_char *pak; + } + + ++void dump_nas_pak TAC_ARGS((u_char *pak)); ++ + /* Dump packets originated by a NAS */ ++void + dump_nas_pak(pak) + u_char *pak; + { +@@ -376,6 +394,9 @@ u_char *pak; + + /* Dump packets originated by Tacacsd */ + ++void dump_tacacs_pak TAC_ARGS((u_char *pak)); ++ ++void + dump_tacacs_pak(pak) + u_char *pak; + { +@@ -482,6 +503,8 @@ u_char *pak; + report(LOG_DEBUG, "End packet"); + } + ++char *summarise_incoming_packet_type TAC_ARGS((u_char *pak)); ++ + /* summarise packet types for logging routines. */ + char * + summarise_incoming_packet_type(pak) +diff --git a/dump.h b/dump.h +new file mode 100644 +index 0000000..bddce30 +--- /dev/null ++++ b/dump.h +@@ -0,0 +1,15 @@ ++#ifndef DUMP_H ++#define DUMP_H 1 ++ ++#include "tac_plus.h" ++ ++#include /* for u_* */ ++ ++ ++extern char *summarise_outgoing_packet_type TAC_ARGS((u_char *pak)); ++extern void dump_nas_pak TAC_ARGS((u_char *pak)); ++extern void dump_tacacs_pak TAC_ARGS((u_char *pak)); ++extern char *summarise_incoming_packet_type TAC_ARGS((u_char *pak)); ++ ++ ++#endif /* DUMP_H */ +diff --git a/enable.c b/enable.c +index 63a17eb..3e56c92 100644 +--- a/enable.c ++++ b/enable.c +@@ -17,8 +17,23 @@ + FITNESS FOR A PARTICULAR PURPOSE. + */ + ++ + #include "tac_plus.h" ++ ++#include ++#include ++ ++#include "enable.h" + #include "expire.h" ++#include "utils.h" ++#include "report.h" ++#include "pwlib.h" ++#include "choose_authen.h" /* for "struct authen_data" */ ++#include "do_author.h" /* for "struct identity" */ ++#include "packet.h" ++#include "main.h" ++#include "cfgfile.h" ++ + + /* internal state variables */ + #define STATE_AUTHEN_START 0 /* no requests issued */ +@@ -30,6 +45,8 @@ struct private_data { + int state; + }; + ++static void enable TAC_ARGS((char *passwd, struct authen_data *data)); ++ + static void + enable(passwd, data) + char *passwd; +@@ -84,6 +101,8 @@ struct authen_data *data; + * Return 0 if data->status is valid, otherwise 1 + */ + ++int enable_fn TAC_ARGS((struct authen_data *data)); ++ + int + enable_fn(data) + struct authen_data *data; +diff --git a/enable.h b/enable.h +new file mode 100644 +index 0000000..10a4e8a +--- /dev/null ++++ b/enable.h +@@ -0,0 +1,12 @@ ++#ifndef ENABLE_H ++#define ENABLE_H 1 ++ ++#include "tac_plus.h" ++ ++ ++struct authen_data; ++ ++extern int enable_fn TAC_ARGS((struct authen_data *data)); ++ ++ ++#endif /* ENABLE_H */ +diff --git a/encrypt.c b/encrypt.c +index 60ba5e9..2840916 100644 +--- a/encrypt.c ++++ b/encrypt.c +@@ -17,8 +17,19 @@ + FITNESS FOR A PARTICULAR PURPOSE. + */ + ++ + #include "tac_plus.h" ++ ++#include ++#include /* for ntohl() */ ++ ++#include "encrypt.h" + #include "md5.h" ++#include "utils.h" ++#include "report.h" ++#include "packet.h" ++#include "main.h" ++ + + /* + * create_md5_hash(): create an md5 hash of the "session_id", "the user's +@@ -36,21 +47,25 @@ + * + */ + +-void ++static void create_md5_hash TAC_ARGS((int session_id, const char *key, unsigned version, unsigned seq_no, u_char *prev_hash, u_char *hash)); ++ ++static 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; ++const char *key; ++unsigned version; /* promoted "u_char" type */ ++unsigned seq_no; /* promoted "u_char" type */ + u_char *prev_hash; + u_char *hash; + { + u_char *md_stream, *mdp; + int md_len; + MD5_CTX mdcontext; ++ u_char version_uchar = version; ++ u_char seq_no_uchar = seq_no; + +- md_len = sizeof(session_id) + strlen(key) + sizeof(version) + +- sizeof(seq_no); ++ md_len = sizeof(session_id) + strlen(key) + sizeof(version_uchar) + ++ sizeof(seq_no_uchar); + + if (prev_hash) { + md_len += MD5_LEN; +@@ -62,11 +77,11 @@ u_char *hash; + bcopy(key, mdp, strlen(key)); + mdp += strlen(key); + +- bcopy(&version, mdp, sizeof(version)); +- mdp += sizeof(version); ++ bcopy(&version_uchar, mdp, sizeof(version_uchar)); ++ mdp += sizeof(version_uchar); + +- bcopy(&seq_no, mdp, sizeof(seq_no)); +- mdp += sizeof(seq_no); ++ bcopy(&seq_no_uchar, mdp, sizeof(seq_no_uchar)); ++ mdp += sizeof(seq_no_uchar); + + if (prev_hash) { + bcopy(prev_hash, mdp, MD5_LEN); +@@ -90,10 +105,13 @@ u_char *hash; + * Return 0 on success, -1 on failure. + */ + ++int md5_xor TAC_ARGS((HDR *hdr, u_char *data, const char *key)); ++ ++int + md5_xor(hdr, data, key) + HDR *hdr; + u_char *data; +-char *key; ++const char *key; + { + int i, j; + u_char hash[MD5_LEN]; /* the md5 hash */ +diff --git a/encrypt.h b/encrypt.h +new file mode 100644 +index 0000000..9c7ae53 +--- /dev/null ++++ b/encrypt.h +@@ -0,0 +1,14 @@ ++#ifndef ENCRYPT_H ++#define ENCRYPT_H 1 ++ ++#include "tac_plus.h" ++ ++#include /* for u_* */ ++ ++#include "packet.h" /* for "HDR" */ ++ ++ ++extern int md5_xor TAC_ARGS((HDR *hdr, u_char *data, const char *key)); ++ ++ ++#endif /* ENCRYPT_H */ +diff --git a/expire.c b/expire.c +index 1ef745c..3aed354 100644 +--- a/expire.c ++++ b/expire.c +@@ -17,9 +17,17 @@ + FITNESS FOR A PARTICULAR PURPOSE. + */ + ++ + #include "tac_plus.h" ++ ++#include ++#include ++#include ++#include ++ + #include "expire.h" + ++ + /* + * check a date for expiry. If the field specifies + * a shell return PW_OK +@@ -37,9 +45,11 @@ static char *monthname[] = {"JAN", "FEB", "MAR", "APR", "MAY", "JUN", + static long days_ere_month[] = {0, 31, 59, 90, 120, 151, + 181, 212, 243, 273, 304, 334}; + ++int check_expiration TAC_ARGS((const char *date)); ++ + int + check_expiration(date) +-char *date; ++const char *date; + { + long day, month, year, leaps, now, expiration, warning; + char monthstr[10]; +@@ -52,11 +62,11 @@ char *date; + return (PW_OK); + + /* Parse date string. Fail it upon error. */ +- if (sscanf(date, "%s %d %d", monthstr, &day, &year) != 3) ++ if (sscanf(date, "%s %ld %ld", monthstr, &day, &year) != 3) + return (PW_EXPIRED); + + for(i=0; i < 3; i++) { +- monthstr[i] = toupper(monthstr[i]); ++ monthstr[i] = toupper((int) monthstr[i]); + } + + /* Compute the expiration date in days. */ +diff --git a/expire.h b/expire.h +index c7df48d..6e7fc5e 100644 +--- a/expire.h ++++ b/expire.h +@@ -1,3 +1,8 @@ ++#ifndef EXPIRE_H ++#define EXPIRE_H 1 ++ ++#include "tac_plus.h" ++ + /* + Copyright (c) 1995-1998 by Cisco systems, Inc. + +@@ -17,10 +22,15 @@ + 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(); ++ ++extern int check_expiration TAC_ARGS((const char *date)); ++ ++ ++#endif /* EXPIRE_H */ +diff --git a/generate_passwd.c b/generate_passwd.c +index 509e48a..a21098d 100644 +--- a/generate_passwd.c ++++ b/generate_passwd.c +@@ -23,12 +23,41 @@ + Usage: a.out [salt] + */ + +-#define NULL 0 + ++#ifdef HAVE_CONFIG_H ++#include "config.h" ++#endif ++ ++ ++#ifdef HAVE_UNISTD_H ++#include ++#endif ++#include ++#include ++#include ++#include ++ ++ ++#ifndef NULL ++#define NULL ((void *) 0) ++#endif ++ ++/* Stolen from pbmplus.h of netpbm: */ ++ ++#if __STDC__ ++#define TAC_ARGS(alist) alist ++#else /*__STDC__*/ ++#define TAC_ARGS(alist) () ++#endif /*__STDC__*/ ++ ++ ++int main TAC_ARGS((int argc, char **argv)); ++ ++int + main(argc, argv) ++int argc; + char **argv; + { +- char *crypt(); + char pass[25], *salt, buf[24]; + char *result; + int n; +@@ -42,10 +71,10 @@ char **argv; + + write(1, prompt, strlen(prompt)); + n = read(0, pass, sizeof(pass)); +- pass[n-1] = NULL; ++ pass[n-1] = 0; + + if (!salt) { +- int i, r, r1, r2; ++ int i, r, r1 = 0 /* GCC paranoia */, r2 = 0 /* GCC paranoia */; + + srand(time(0)); + +@@ -82,9 +111,6 @@ char **argv; + + write(1, result, strlen(result)); + write(1, "\n", 1); +-} +- +- +- +- + ++ return (EXIT_SUCCESS); ++} +diff --git a/hash.c b/hash.c +index 443d42b..2db807b 100644 +--- a/hash.c ++++ b/hash.c +@@ -17,19 +17,24 @@ + FITNESS FOR A PARTICULAR PURPOSE. + */ + ++ + #include "tac_plus.h" + ++#include "hash.h" ++#include "utils.h" ++ ++ + struct entry { + char *name; + void *hash; + }; + +-typedef struct entry ENTRY; ++static int calculate_hash TAC_ARGS((const char *name)); + + /* Calculate hash value from a string */ + static int + calculate_hash(name) +-char *name; ++const char *name; + { + int i; + int len = strlen(name); +@@ -43,12 +48,14 @@ char *name; + return (hashval); + } + ++void *hash_lookup TAC_ARGS((void **hashtab, const char *name)); ++ + /* Lookup a name in a hash table. Return its node if it exists, NULL + otherwise */ + void * + hash_lookup(hashtab, name) + void **hashtab; +-char *name; ++const char *name; + { + ENTRY *entry; + int hashval = calculate_hash(name); +@@ -64,6 +71,8 @@ char *name; + return (NULL); + } + ++void *hash_add_entry TAC_ARGS((void **hashtab, ENTRY *newentry)); ++ + /* Add a node to a hash table. Return node if it exists, NULL + otherwise */ + void * +@@ -86,6 +95,8 @@ ENTRY *newentry; + } + + ++void **hash_get_entries TAC_ARGS((void **hashtab)); ++ + /* Return an array of pointers to all the entries in a hash table */ + void ** + hash_get_entries(hashtab) +diff --git a/hash.h b/hash.h +new file mode 100644 +index 0000000..a808f05 +--- /dev/null ++++ b/hash.h +@@ -0,0 +1,17 @@ ++#ifndef HASH_H ++#define HASH_H 1 ++ ++#include "tac_plus.h" ++ ++ ++#define HASH_TAB_SIZE 157 /* user and group hash table sizes */ ++ ++typedef struct entry ENTRY; ++ ++ ++extern void *hash_lookup TAC_ARGS((void **hashtab, const char *name)); ++extern void *hash_add_entry TAC_ARGS((void **hashtab, ENTRY *newentry)); ++extern void **hash_get_entries TAC_ARGS((void **hashtab)); ++ ++ ++#endif /* HASH_H */ +diff --git a/ldap_author.c b/ldap_author.c +index ca1a8ef..c788e82 100644 +--- a/ldap_author.c ++++ b/ldap_author.c +@@ -36,21 +36,30 @@ Port name isn't required.. I would like to change format with : + */ + + +-#if defined(USE_LDAP) ++#include "tac_plus.h" ++ ++#ifdef USE_LDAP ++ + #include + #include ++#include + #include + #include + #include + +-#include "tac_plus.h" +-#include "ldap.h" ++#include "ldap_author.h" ++#include "main.h" ++#include "utils.h" ++#include "report.h" ++ + ++int ldap_verify TAC_ARGS((const char *user, const char *users_passwd, const char *str_conn)); + + int + ldap_verify(user, users_passwd, str_conn) +-char *user, *users_passwd; /* Username and gived password */ +-char *str_conn; /* String connection to database */ ++const char *user; /* username ... */ ++const char *users_passwd; /* ... and given password */ ++const char *str_conn; /* string connection to database */ + { + char *buf; + char *ldapServer; +@@ -62,11 +71,7 @@ char *str_conn; /* String connection to database */ + /* 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); +- } ++ buf = (char *) tac_malloc(strlen(str_conn)+1); + + strcpy(buf,str_conn); + ldapServer=strstr(buf, "://"); +@@ -99,7 +104,7 @@ char *str_conn; /* String connection to database */ + return 1; + } + +- err=ldap_simple_bind_s(ld, user, users_passwd); ++ err=ldap_simple_bind_s(ld, (/* de-const */ char *) user, (/* de-const */ char *) users_passwd); + + if(err != LDAP_SUCCESS) + { +@@ -116,4 +121,9 @@ char *str_conn; /* String connection to database */ + return 0; + } + } +-#endif /* LDAP */ ++ ++#else /* USE_LDAP */ ++ ++TAC_SOURCEFILE_EMPTY ++ ++#endif /* USE_LDAP */ +diff --git a/ldap_author.h b/ldap_author.h +index 6479426..ecd71dd 100644 +--- a/ldap_author.h ++++ b/ldap_author.h +@@ -1 +1,14 @@ +-int ldap_verify(); ++#ifndef LDAP_AUTHOR_H ++#define LDAP_AUTHOR_H 1 ++ ++#include "tac_plus.h" ++ ++#ifdef USE_LDAP ++ ++ ++extern int ldap_verify TAC_ARGS((const char *user, const char *users_passwd, const char *str_conn)); ++ ++ ++#endif /* USE_LDAP */ ++ ++#endif /* LDAP_AUTHOR_H */ +diff --git a/main.c b/main.c +index cf0ffd8..c356b75 100644 +--- a/main.c ++++ b/main.c +@@ -14,7 +14,7 @@ + * 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, +@@ -22,37 +22,87 @@ + * FITNESS FOR A PARTICULAR PURPOSE. + */ + ++ + #include "tac_plus.h" +-#include "sys/wait.h" +-#include "signal.h" ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#ifdef HAVE_SYS_TIME_H ++#include ++#endif ++#ifdef HAVE_UNISTD_H ++#include ++#endif ++#ifdef HAVE_SYSLOG_H ++#include ++#endif ++#ifdef HAVE_SYS_SYSLOG_H ++#include ++#endif ++#ifdef HAVE_FCNTL_H ++#include ++#endif ++#ifdef HAVE_SYS_IOCTL_H ++#include ++#endif ++ ++#include "main.h" ++#include "report.h" ++#include "utils.h" ++#include "cfgfile.h" ++#include "packet.h" ++#include "dump.h" ++#include "author.h" ++#include "acct.h" ++#include "authen.h" ++#include "do_acct.h" ++#include "parse.h" ++ ++#ifdef MAXSESS ++#include "maxsess.h" ++#endif ++ ++ ++static void version TAC_ARGS((void)); ++static void start_session TAC_ARGS((void)); ++ ++ ++/* Configurable: ++ */ ++ ++#ifndef TAC_PLUS_PORT ++#define TAC_PLUS_PORT 49 ++#endif ++ + + 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 */ ++static 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() ++static RETSIGTYPE reapchild TAC_ARGS((int signo)); ++ ++static RETSIGTYPE ++reapchild(signo) ++int signo; + { + #ifdef UNIONWAIT + union wait status; +@@ -61,6 +111,8 @@ reapchild() + #endif + int pid; + ++ signal(SIGCHLD, reapchild); ++ + for (;;) { + pid = wait3(&status, WNOHANG, 0); + if (pid <= 0) +@@ -71,6 +123,8 @@ reapchild() + } + #endif /* REAPCHILD */ + ++static void die TAC_ARGS((int signum)); ++ + static void + die(signum) + int signum; +@@ -80,6 +134,8 @@ int signum; + tac_exit(0); + } + ++static void init TAC_ARGS((void)); ++ + static void + init() + { +@@ -104,26 +160,59 @@ init() + + initialised++; + +- report(LOG_INFO, "Version %s Initialized %d", VERSION, initialised); ++ report(LOG_INFO, "Version %s%s Initialized %d", VERSION, VERSION_TAIL, initialised); + + } + +-static void ++/* 'handler()' will be called during initialization to setup signal handler, ++ * keep it in mind when modifying it! ++ */ ++ ++static int handler_occured = 0; ++ ++static RETSIGTYPE handler TAC_ARGS((int signum)); ++ ++static RETSIGTYPE + handler(signum) + int signum; + { +- report(LOG_INFO, "Received signal %d", signum); +- init(); +-#ifdef REARMSIGNAL ++ /* never execute any non-trivial (=system-call) commands here ++ * as it may immediately abort the whole 'handler()' (SYSV signal). ++ * We hope that SA_RESTART is NOT set for our signals ++ */ ++ handler_occured = 1; ++ /* It is never wrong to reinstall 'handler' just to be safe */ + signal(SIGUSR1, handler); +- signal(SIGHUP, handler); +-#endif REARMSIGNAL ++ signal(SIGHUP , handler); ++ ++ /* DON'T interrupt! */ ++#ifdef HAVE_SIGINTERRUPT ++ siginterrupt(SIGUSR1, 0 /* flag */); ++ siginterrupt(SIGHUP , 0 /* flag */); ++#endif ++} ++ ++static void check_handler_occured TAC_ARGS((void)); ++ ++static RETSIGTYPE ++check_handler_occured() ++{ ++ ++ if (!handler_occured) ++ return; ++ ++ handler_occured = 0; ++ report(LOG_INFO, "Signal detected, reloading configuration"); ++ init(); + } + + /* + * Return a socket bound to an appropriate port number/address. Exits + * the program on failure */ + ++static int get_socket TAC_ARGS((void)); ++ ++static int + get_socket() + { + int s; +@@ -171,6 +260,8 @@ get_socket() + return (s); + } + ++static void open_logfile TAC_ARGS((void)); ++ + static void + open_logfile() + { +@@ -182,6 +273,29 @@ open_logfile() + setlogmask(LOG_UPTO(LOG_DEBUG)); + } + ++static void prep_session_peer TAC_ARGS((const struct sockaddr_in *from)); ++ ++static void ++prep_session_peer(from) ++const struct sockaddr_in *from; ++{ ++ struct hostent *hp; ++ ++ if (session.peer_addr && session.peer_addr != session.peer) ++ free(session.peer_addr); ++ if (session.peer) ++ free(session.peer); ++ ++ session.peer_addr = tac_strdup( (char *) inet_ntoa(from->sin_addr) ); ++ ++ hp = gethostbyaddr((char *) &from->sin_addr.s_addr, sizeof(from->sin_addr.s_addr), AF_INET); ++ ++ if (hp) ++ session.peer = tac_strdup(hp->h_name); ++ else ++ session.peer = session.peer_addr; ++} ++ + /* + * main + * +@@ -189,6 +303,9 @@ open_logfile() + * Parse arguments and act appropiately. + */ + ++int main TAC_ARGS((int argc, char **argv)); ++ ++int + main(argc, argv) + int argc; + char **argv; +@@ -214,6 +331,10 @@ char **argv; + port = TAC_PLUS_PORT; + #endif + ++#ifdef MAINTAINER_MODE ++ session.cfgfile = "/etc/tacacs/tac_plus.cfg"; ++#endif ++ + if (argc <= 1) { + fprintf(stderr, "Usage: tac_plus -C \n"); + fprintf(stderr, "\t[ -t ] [ -P ] [ -g ] [ -p ]\n"); +@@ -284,8 +405,9 @@ char **argv; + + init(); + +- signal(SIGUSR1, handler); +- signal(SIGHUP, handler); ++ handler(-1 /* signum */); /* connect to the signals */ ++ handler_occured = 0; /* post-fix cludge */ ++ + signal(SIGTERM, die); + signal(SIGPIPE, SIG_IGN); + +@@ -293,31 +415,27 @@ char **argv; + tac_exit(0); + + if (debug) +- report(LOG_DEBUG, "tac_plus server %s starting", VERSION); ++ report(LOG_DEBUG, "tac_plus server %s%s starting", VERSION, VERSION_TAIL); + + if (!standalone) { + /* running under inetd */ + struct sockaddr_in name; +- int name_len; +- int on = 1; ++ socklen_t name_len; ++#ifdef FIONBIO ++ int fionbio_on = 1; ++#endif + + 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)); +- } ++ prep_session_peer(NULL); ++ } else ++ prep_session_peer(&name); ++ ++ session.sock = 0; + #ifdef FIONBIO +- if (ioctl(session.sock, FIONBIO, &on) < 0) { ++ if (ioctl(session.sock, FIONBIO, &fionbio_on) < 0) { + report(LOG_ERR, "ioctl(FIONBIO) %s", sys_errlist[errno]); + tac_exit(1); + } +@@ -351,23 +469,29 @@ char **argv; + + #ifndef REAPCHILD + +-#ifdef LINUX ++#ifdef SETPGRP_VOID + if (setpgrp() == -1) +-#else /* LINUX */ ++#else /* SETPGRP_VOID */ + if (setpgrp(0, getpid()) == -1) +-#endif /* LINUX */ ++#endif /* SETPGRP_VOID */ + report(LOG_ERR, "Can't change process group"); + ++#ifdef TIOCNOTTY + c = open("/dev/tty", O_RDWR); + if (c >= 0) { + ioctl(c, TIOCNOTTY, (char *) 0); + (void) close(c); + } ++#endif + signal(SIGCHLD, reapchild); + + #else /* REAPCHILD */ + ++#ifdef SETPGRP_VOID + if (setpgrp() == 1) ++#else /* SETPGRP_VOID */ ++ if (setpgrp(0, getpid()) == 1) ++#endif /* SETPGRP_VOID */ + report(LOG_ERR, "Can't change process group"); + + signal(SIGHUP, SIG_IGN); +@@ -423,7 +547,7 @@ char **argv; + + /* write process id to pidfile */ + if ((fp = fopen(pidfilebuf, "w")) != NULL) { +- fprintf(fp, "%d\n", getpid()); ++ fprintf(fp, "%d\n", (int) getpid()); + fclose(fp); + } else + report(LOG_ERR, "Cannot write pid to %s %s", +@@ -446,38 +570,45 @@ char **argv; + #endif /* MAXSESS */ + + report(LOG_DEBUG, "uid=%d euid=%d gid=%d egid=%d s=%d", +- getuid(), geteuid(), getgid(), getegid(), s); ++ (int) getuid(), (int) geteuid(), (int) getgid(), (int) getegid(), s); + + for (;;) { + int pid; + struct sockaddr_in from; +- int from_len; ++ socklen_t from_len; + int newsockfd; +- struct hostent *hp = NULL; ++ ++ check_handler_occured(); + + bzero((char *) &from, sizeof(from)); + from_len = sizeof(from); + ++ /* PERMIT interrupt of accept()! */ ++#ifdef HAVE_SIGINTERRUPT ++ siginterrupt(SIGUSR1, 1 /* flag */); ++ siginterrupt(SIGHUP , 1 /* flag */); ++#endif ++ ++ errno = 0; + newsockfd = accept(s, (struct sockaddr *) &from, &from_len); + ++ /* DON'T interrupt! */ ++#ifdef HAVE_SIGINTERRUPT ++ siginterrupt(SIGUSR1, 0 /* flag */); ++ siginterrupt(SIGHUP , 0 /* flag */); ++#endif ++ ++ check_handler_occured(); ++ + if (newsockfd < 0) { +- if (errno == EINTR) ++ /* sometimes we may get even 'errno==0' when 'handler()' signal occured */ ++ if (errno == EINTR || errno == 0) + 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)); ++ prep_session_peer(&from); + + if (debug & DEBUG_PACKET_FLAG) + report(LOG_DEBUG, "session request from %s sock=%d", +@@ -521,8 +652,10 @@ getdtablesize() + } + #endif /* GETDTABLESIZE */ + ++static int bad_version_check TAC_ARGS((u_char *pak)); ++ + /* Make sure version number is kosher. Return 0 if it is */ +-int ++static int + bad_version_check(pak) + u_char *pak; + { +@@ -555,17 +688,21 @@ u_char *pak; + * + */ + +-void ++static void start_session TAC_ARGS((void)); ++ ++static void + start_session() + { +- u_char *pak, *read_packet(); ++ u_char *pak; + HDR *hdr; +- void authen(); + + session.seq_no = 0; + session.aborted = 0; + session.version = 0; + ++ /* Now we are starting our new 'request' cycle */ ++ cfg_request_scan_begin(); ++ + pak = read_packet(); + if (!pak) { + return; +@@ -608,9 +745,12 @@ start_session() + } + } + ++static void version TAC_ARGS((void)); ++ ++static void + version() + { +- fprintf(stdout, "tac_plus version %s\n", VERSION); ++ fprintf(stdout, "tac_plus version %s%s\n", VERSION, VERSION_TAIL); + #ifdef AIX + fprintf(stdout,"AIX\n"); + #endif +@@ -644,9 +784,6 @@ version() + #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 +@@ -662,15 +799,9 @@ version() + #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 +@@ -692,14 +823,8 @@ version() + #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"); ++#ifdef HAVE_STRCSPN ++ fprintf(stdout,"HAVE_STRCSPN\n"); + #endif + #ifdef SYSV + fprintf(stdout,"SYSV\n"); +@@ -713,15 +838,9 @@ version() + #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 +diff --git a/main.h b/main.h +new file mode 100644 +index 0000000..79c643a +--- /dev/null ++++ b/main.h +@@ -0,0 +1,59 @@ ++#ifndef MAIN_H ++#define MAIN_H 1 ++ ++#include "tac_plus.h" ++ ++#include /* for u_* */ ++ ++ ++#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 *peer_addr; /* numerical name of connected peer */ ++ /* it MAY (peer==peer_addr)! */ ++ 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 int debug; /* debugging flag */ ++extern int single; /* do not fork (for debugging) */ ++extern int console; /* log to console */ ++extern int parse_only; /* exit after parsing verbosely */ ++extern int sendauth_only; /* don't do sendauth */ ++ ++/* 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_CFGEVAL_FLAG 16384 ++#define DEBUG_MAXSESS_FLAG 32768 ++#define DEBUG_LOCK_FLAG 65536 ++ ++ ++extern struct session session; /* the session */ ++extern int main TAC_ARGS((int argc, char **argv)); ++ ++ ++#endif /* MAIN_H */ +diff --git a/maxsess.c b/maxsess.c +index 83e0815..aa81a9a 100644 +--- a/maxsess.c ++++ b/maxsess.c +@@ -17,9 +17,62 @@ + FITNESS FOR A PARTICULAR PURPOSE. + */ + ++ + #include "tac_plus.h" + + #ifdef MAXSESS ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#ifdef HAVE_FCNTL_H ++#include ++#endif ++#include ++#include ++#ifdef HAVE_SYS_TIME_H ++#include ++#endif ++#ifdef HAVE_UNISTD_H ++#include ++#endif ++#include ++ ++#include "maxsess.h" ++#include "report.h" ++#include "utils.h" ++#include "cfgfile.h" ++#include "main.h" ++#include "do_acct.h" ++#include "parse.h" ++ ++ ++void maxsess_loginit TAC_ARGS((void)); ++ ++ ++/* Configurable: ++ */ ++ ++/* This is a shared file used to maintain a record of who's on ++ */ ++#define WHOLOG_DEFAULT "/var/log/tac_who.log" ++ ++ ++/* ++ * 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 */ ++}; ++ ++ + char *wholog = WHOLOG_DEFAULT; + /* + * initialize wholog file for tracking of user logins/logouts from +@@ -41,6 +94,8 @@ maxsess_loginit() + } + } + ++static char *portname TAC_ARGS((char *oldport)); ++ + /* + * Given a port description, return it in a canonical format. + * +@@ -67,6 +122,8 @@ char *oldport; + return (p); + } + ++static void write_record TAC_ARGS((char *name, FILE *fp, void *buf, int size, long int offset)); ++ + /* + * Seek to offset and write a buffer into the file pointed to by fp + */ +@@ -79,7 +136,7 @@ void *buf; + char *name; + { + if (fseek(fp, offset, SEEK_SET) < 0) { +- report(LOG_ERR, "%s fd=%d Cannot seek to %d %s", ++ report(LOG_ERR, "%s fd=%d Cannot seek to %ld %s", + name, fileno(fp), offset, sys_errlist[errno]); + } + if (fwrite(buf, size, 1, fp) != 1) { +@@ -88,6 +145,8 @@ char *name; + } + } + ++static void process_stop_record TAC_ARGS((struct identity *idp)); ++ + static void + process_stop_record(idp) + struct identity *idp; +@@ -133,6 +192,8 @@ struct identity *idp; + fclose(fp); + } + ++static void process_start_record TAC_ARGS((struct identity *idp)); ++ + static void + process_start_record(idp) + struct identity *idp; +@@ -215,10 +276,13 @@ struct identity *idp; + } + + ++void loguser TAC_ARGS((struct acct_rec *rec)); ++ + /* + * Given a start or a stop accounting record, update the file of + * records which tracks who's logged on and where. + */ ++void + loguser(rec) + struct acct_rec *rec; + { +@@ -260,10 +324,12 @@ struct acct_rec *rec; + * Return -1 on error, eof or timeout. Otherwise return number of + * bytes read. */ + +-int ++static int timed_read TAC_ARGS((int fd, void *ptr, int nbytes, int timeout)); ++ ++static int + timed_read(fd, ptr, nbytes, timeout) + int fd; +-u_char *ptr; ++void *ptr; + int nbytes; + int timeout; + { +@@ -348,6 +414,8 @@ int timeout; + * with a maximum possible width of 10. + */ + ++static int ckfinger TAC_ARGS((char *user, char *nas, struct identity *idp)); ++ + static int + ckfinger(user, nas, idp) + char *user, *nas; +@@ -372,7 +440,7 @@ struct identity *idp; + + /* Get IP addr for the NAS */ + inaddr = inet_addr(nas); +- if (inaddr != -1) { ++ if (inaddr != (u_long)-1) { + /* A dotted decimal address */ + bcopy(&inaddr, &sin.sin_addr, sizeof(inaddr)); + sin.sin_family = AF_INET; +@@ -492,6 +560,8 @@ struct identity *idp; + return (count); + } + ++static int countusers_by_finger TAC_ARGS((struct identity *idp)); ++ + /* + * Verify how many sessions a user has according to the wholog file. + * Use finger to contact each NAS that wholog says has this user +@@ -565,6 +635,8 @@ struct identity *idp; + return (nsess); + } + ++static int countuser TAC_ARGS((struct identity *idp)); ++ + /* + * Estimate how many sessions a named user currently owns by looking in + * the wholog file. +@@ -604,6 +676,8 @@ struct identity *idp; + return (nsess); + } + ++static int is_async TAC_ARGS((char *portname)); ++ + /* + * is_async() + * Tell if the named NAS port is an async-like device. +@@ -622,6 +696,8 @@ char *portname; + return (0); + } + ++int maxsess_check_count TAC_ARGS((char *user, struct author_data *data)); ++ + /* + * See if this user can have more sessions. + */ +@@ -636,7 +712,7 @@ struct author_data *data; + /* No max session configured--don't check */ + id = data->id; + +- maxsess = cfg_get_intvalue(user, TAC_IS_USER, S_maxsess, TAC_PLUS_RECURSE); ++ maxsess = cfg_get_intvalue(S_user, 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", +@@ -680,13 +756,6 @@ struct author_data *data; + + #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; ++TAC_SOURCEFILE_EMPTY + + #endif /* MAXSESS */ +diff --git a/maxsess.h b/maxsess.h +new file mode 100644 +index 0000000..c96d2b8 +--- /dev/null ++++ b/maxsess.h +@@ -0,0 +1,25 @@ ++#ifndef MAXSESS_H ++#define MAXSESS_H 1 ++ ++#include "tac_plus.h" ++ ++#ifdef MAXSESS ++ ++#include "do_author.h" ++ ++ ++/* This is a shared file used to maintain a record of who's on ++ */ ++extern char *wholog; ++ ++ ++struct acct_rec; ++ ++extern void maxsess_loginit TAC_ARGS((void)); ++extern void loguser TAC_ARGS((struct acct_rec *rec)); ++extern int maxsess_check_count TAC_ARGS((char *user, struct author_data *data)); ++ ++ ++#endif /* MAXSESS */ ++ ++#endif /* MAXSESS_H */ +diff --git a/md4.c b/md4.c +index d99c84c..5ac377d 100644 +--- a/md4.c ++++ b/md4.c +@@ -43,19 +43,27 @@ + */ + + ++#include "tac_plus.h" ++ ++#ifdef MSCHAP ++ + #include ++ + #include "md4.h" + /* + #include "master.h" + #include + */ + ++ + typedef unsigned char *POINTER; + typedef unsigned short int UINT2; + typedef unsigned long int UINT4; + +-#define PROTO_LIST(list) () +-#define const ++static void MD4Transform TAC_ARGS((UINT4 state[4], const unsigned char block[64])); ++static void Encode TAC_ARGS((unsigned char *output, UINT4 *input, unsigned int len)); ++static void Decode TAC_ARGS((UINT4 *output, const unsigned char *input, unsigned int len)); ++ + + /* Constants for MD4Transform routine. + */ +@@ -72,12 +80,6 @@ typedef unsigned long int UINT4; + #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, +@@ -111,6 +113,9 @@ static unsigned char PADDING[64] = { + + /* MD4 initialization. Begins an MD4 operation, writing a new context. + */ ++ ++void MD4Init TAC_ARGS((MD4_CTX *context)); ++ + void MD4Init (context) + MD4_CTX *context; /* context */ + { +@@ -128,6 +133,9 @@ MD4_CTX *context; /* context */ + operation, processing another message block, and updating the + context. + */ ++ ++void MD4Update TAC_ARGS((MD4_CTX *context, const unsigned char *input, unsigned int inputLen)); ++ + void MD4Update (context, input, inputLen) + MD4_CTX *context; /* context */ + const unsigned char *input; /* input block */ +@@ -168,6 +176,9 @@ unsigned int inputLen; /* length of input block */ + /* MD4 finalization. Ends an MD4 message-digest operation, writing the + the message digest and zeroizing the context. + */ ++ ++void MD4Final TAC_ARGS((unsigned char digest[16], MD4_CTX *context)); ++ + void MD4Final (digest, context) + unsigned char digest[16]; /* message digest */ + MD4_CTX *context; /* context */ +@@ -196,6 +207,9 @@ MD4_CTX *context; /* context */ + + /* MD4 basic transformation. Transforms state based on block. + */ ++ ++static void MD4Transform TAC_ARGS((UINT4 state[4], const unsigned char block[64])); ++ + static void MD4Transform (state, block) + UINT4 state[4]; + const unsigned char block[64]; +@@ -271,6 +285,9 @@ const unsigned char block[64]; + /* Encodes input (UINT4) into output (unsigned char). Assumes len is + a multiple of 4. + */ ++ ++static void Encode TAC_ARGS((unsigned char *output, UINT4 *input, unsigned int len)); ++ + static void Encode (output, input, len) + unsigned char *output; + UINT4 *input; +@@ -289,8 +306,10 @@ unsigned int len; + /* Decodes input (unsigned char) into output (UINT4). Assumes len is + a multiple of 4. + */ +-static void Decode (output, input, len) + ++static void Decode TAC_ARGS((UINT4 *output, const unsigned char *input, unsigned int len)); ++ ++static void Decode (output, input, len) + UINT4 *output; + const unsigned char *input; + unsigned int len; +@@ -301,3 +320,9 @@ unsigned int len; + output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) | + (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24); + } ++ ++#else /* MSCHAP */ ++ ++TAC_SOURCEFILE_EMPTY ++ ++#endif /* MSCHAP */ +diff --git a/md4.h b/md4.h +index d821229..38d0a81 100644 +--- a/md4.h ++++ b/md4.h +@@ -1,3 +1,10 @@ ++#ifndef MD4_H ++#define MD4_H 1 ++ ++#include "tac_plus.h" ++ ++#ifdef MSCHAP ++ + /* + Copyright (c) 1995-1998 by Cisco systems, Inc. + +@@ -42,8 +49,6 @@ + documentation and/or software. + */ + +-#ifndef _MD4_H_ +-#define _MD4_H_ + /* MD4 context. */ + typedef struct MD4Context { + unsigned long int state[4]; /* state (ABCD) */ +@@ -51,11 +56,12 @@ typedef struct MD4Context { + unsigned char buffer[64]; /* input buffer */ + } MD4_CTX; + +-void MD4Init(); +-void MD4Update(); +-void MD4Final(); +-char * MD4End(); +-char * MD4File(); +-char * MD4Data(); + +-#endif /* _MD4_H_ */ ++extern void MD4Init TAC_ARGS((MD4_CTX *context)); ++extern void MD4Update TAC_ARGS((MD4_CTX *context, const unsigned char *input, unsigned int inputLen)); ++extern void MD4Final TAC_ARGS((unsigned char digest[16], MD4_CTX *context)); ++ ++ ++#endif /* MSCHAP */ ++ ++#endif /* MD4_H */ +diff --git a/md5.c b/md5.c +index 06225b0..32ca404 100644 +--- a/md5.c ++++ b/md5.c +@@ -47,12 +47,20 @@ + * to contain all the information that RFC 1321's global.h contains. + */ + ++ ++#include "tac_plus.h" ++ + #include "md5.h" + ++ ++static void MD5Transform TAC_ARGS((UINT4 *state, unsigned char *block)); ++static void Encode TAC_ARGS((unsigned char *output, UINT4 *input, unsigned int len)); ++static void Decode TAC_ARGS((UINT4 *output, unsigned char *input, unsigned int len)); ++ ++ + /* Constants for MD5Transform routine. + */ + +- + #define S11 7 + #define S12 12 + #define S13 17 +@@ -70,24 +78,6 @@ + #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, +@@ -129,6 +119,8 @@ Rotation is separate from addition to prevent recomputation. + (a) += (b); \ + } + ++void MD5Init TAC_ARGS((MD5_CTX *context)); ++ + /* MD5 initialization. Begins an MD5 operation, writing a new context. + */ + void +@@ -143,6 +135,8 @@ MD5_CTX *context; /* context */ + context->state[3] = 0x10325476; + } + ++void MD5Update TAC_ARGS((MD5_CTX *context, unsigned char *input, unsigned int inputLen)); ++ + /* MD5 block update operation. Continues an MD5 message-digest + operation, processing another message block, and updating the + context. +@@ -168,8 +162,7 @@ unsigned int inputLen; /* length of input block */ + + /* Transform as many times as possible. */ + if (inputLen >= partLen) { +- MD5_memcpy +- ((POINTER) & context->buffer[index], (POINTER) input, partLen); ++ memcpy((POINTER) & context->buffer[index], (POINTER) input, partLen); + MD5Transform(context->state, context->buffer); + + for (i = partLen; i + 63 < inputLen; i += 64) +@@ -180,11 +173,12 @@ unsigned int inputLen; /* length of input block */ + i = 0; + + /* Buffer remaining input */ +- MD5_memcpy +- ((POINTER) & context->buffer[index], (POINTER) & input[i], ++ memcpy((POINTER) & context->buffer[index], (POINTER) & input[i], + inputLen - i); + } + ++void MD5Final TAC_ARGS((unsigned char *digest, MD5_CTX *context)); ++ + /* MD5 finalization. Ends an MD5 message-digest operation, writing the + the message digest and zeroizing the context. + */ +@@ -211,9 +205,11 @@ MD5_CTX *context; /* context */ + Encode(digest, context->state, 16); + + /* Zeroize sensitive information. */ +- MD5_memset((POINTER) context, 0, sizeof(*context)); ++ memset((POINTER) context, 0, sizeof(*context)); + } + ++static void MD5Transform TAC_ARGS((UINT4 *state, unsigned char *block)); ++ + /* MD5 basic transformation. Transforms state based on block. + */ + static void +@@ -303,9 +299,11 @@ unsigned char block[64]; + state[3] += d; + + /* Zeroize sensitive information. */ +- MD5_memset((POINTER) x, 0, sizeof(x)); ++ memset((POINTER) x, 0, sizeof(x)); + } + ++static void Encode TAC_ARGS((unsigned char *output, UINT4 *input, unsigned int len)); ++ + /* Encodes input (UINT4) into output (unsigned char). Assumes len is + a multiple of 4. + */ +@@ -325,6 +323,8 @@ unsigned int len; + } + } + ++static void Decode TAC_ARGS((UINT4 *output, unsigned char *input, unsigned int len)); ++ + /* Decodes input (unsigned char) into output (UINT4). Assumes len is + a multiple of 4. + */ +@@ -340,36 +340,3 @@ unsigned int len; + 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 +index 097156e..90f9021 100644 +--- a/md5.h ++++ b/md5.h +@@ -1,3 +1,8 @@ ++#ifndef MD5_H ++#define MD5_H 1 ++ ++#include "tac_plus.h" ++ + /* + Copyright (c) 1995-1998 by Cisco systems, Inc. + +@@ -44,8 +49,6 @@ + * documentation and/or software. + */ + +-#ifndef _MD5_H +-#define _MD5_H + + /* delineate the cisco changes to the RSA supplied module */ + #define CISCO_MD5_MODS +@@ -53,16 +56,11 @@ + #if defined(CISCO_MD5_MODS) + + /* typedef a 32-bit type */ +-typedef unsigned long int UINT4; ++typedef tac_uint32 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. */ +@@ -72,10 +70,10 @@ typedef struct { + 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 *)); ++ ++extern void MD5Init TAC_ARGS((MD5_CTX *context)); ++extern void MD5Update TAC_ARGS((MD5_CTX *context, unsigned char *input, unsigned int inputLen)); ++extern void MD5Final TAC_ARGS((unsigned char *digest, MD5_CTX *context)); + + +-#endif /* _MD5_H */ ++#endif /* MD5_H */ +diff --git a/mschap.h b/mschap.h +index 192d9d2..d98b84b 100644 +--- a/mschap.h ++++ b/mschap.h +@@ -1,3 +1,8 @@ ++#ifndef MSCHAP_H ++#define MSCHAP_H 1 ++ ++#include "tac_plus.h" ++ + /* + Copyright (c) 1995-1998 by Cisco systems, Inc. + +@@ -17,4 +22,8 @@ + FITNESS FOR A PARTICULAR PURPOSE. + */ + ++ + #define MSCHAP_KEY "Contact Microsoft for the MSCHAP key" ++ ++ ++#endif /* MSCHAP_H */ +diff --git a/packet.c b/packet.c +index f3f7023..2e003d1 100644 +--- a/packet.c ++++ b/packet.c +@@ -17,14 +17,51 @@ + FITNESS FOR A PARTICULAR PURPOSE. + */ + ++ + #include "tac_plus.h" + ++#include ++#include /* for ntohl() */ ++#include ++#include ++#ifdef HAVE_SYS_TIME_H ++#include ++#endif ++#include ++#ifdef HAVE_UNISTD_H ++#include ++#endif ++ ++#include "packet.h" ++#include "utils.h" ++#include "report.h" ++#include "dump.h" ++#include "cfgfile.h" ++#include "encrypt.h" ++#include "main.h" ++#include "do_author.h" ++ ++ ++static int write_packet TAC_ARGS((u_char *pak)); ++ ++ ++/* Configurable: ++ */ ++ ++#define TAC_PLUS_READ_TIMEOUT 180 /* seconds */ ++#define TAC_PLUS_WRITE_TIMEOUT 180 /* seconds */ ++ ++ + /* Everything to do with reading and writing packets */ + ++void send_acct_reply TAC_ARGS((unsigned status, const char *msg, const char *data)); ++ + /* send an accounting response packet */ ++void + send_acct_reply(status, msg, data) +- u_char status; +- char *msg, *data; ++unsigned status; /* promoted "u_char" type */ ++const char *msg; ++const char *data; + { + u_char *pak, *p; + HDR *hdr; +@@ -73,13 +110,16 @@ send_acct_reply(status, msg, data) + free(pak); + } + ++void send_author_reply TAC_ARGS((unsigned status, const char *msg, const char *data, int arg_cnt, /* const */ char **args)); ++ + /* send an authorization reply packet */ ++void + send_author_reply(status, msg, data, arg_cnt, args) +-u_char status; +-char *msg; +-char *data; ++unsigned status; /* promoted "u_char" type */ ++const char *msg; ++const char *data; + int arg_cnt; +-char **args; ++/* const */ char **args; + { + u_char *pak, *p; + HDR *hdr; +@@ -159,8 +199,11 @@ char **args; + /* Send an authentication reply packet indicating an error has + occurred. msg is a null terminated character string */ + ++void send_authen_error TAC_ARGS((const char *msg)); ++ ++void + send_authen_error(msg) +-char *msg; ++const char *msg; + { + char buf[255]; + +@@ -176,13 +219,16 @@ char *msg; + + /* create and send an authentication reply packet from tacacs+ to a NAS */ + ++void send_authen_reply TAC_ARGS((int status, const char *msg, unsigned msg_len, const unsigned char *data, unsigned data_len, unsigned flags)); ++ ++void + 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; ++const char *msg; ++unsigned msg_len; /* promoted "u_short" type */ ++const unsigned char *data; ++unsigned data_len; /* promoted "u_short" type */ ++unsigned flags; /* promoted "u_char" type */ + { + u_char *pak, *p; + HDR *hdr; +@@ -228,12 +274,14 @@ u_char flags; + } + + ++u_char *get_authen_continue TAC_ARGS((void)); ++ + /* read an authentication GETDATA packet from a NAS. Return 0 on failure */ + u_char * + get_authen_continue() + { + HDR *hdr; +- u_char *pak, *read_packet(); ++ u_char *pak; + struct authen_cont *cont; + char msg[255]; + +@@ -255,10 +303,10 @@ get_authen_continue() + 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 + ++ if ((unsigned long)(TAC_AUTHEN_CONT_FIXED_FIELDS_SIZE + + cont->user_msg_len + +- cont->user_data_len != +- ntohl(hdr->datalength)) { ++ cont->user_data_len) != ++ (unsigned long) ntohl(hdr->datalength)) { + char *m = "Illegally sized authentication cont packet"; + report(LOG_ERR, "%s: %s", session.peer, m); + send_authen_error(m); +@@ -278,7 +326,9 @@ get_authen_continue() + * Return -1 on error, eof or timeout. Otherwise return number of + * bytes read. */ + +-int ++static int sockread TAC_ARGS((int fd, u_char *ptr, int nbytes, int timeout)); ++ ++static int + sockread(fd, ptr, nbytes, timeout) + int fd; + u_char *ptr; +@@ -354,7 +404,9 @@ int timeout; + * Return -1 on error, eof or timeout. Otherwise return number of + * bytes written. */ + +-int ++static int sockwrite TAC_ARGS((int fd, u_char *ptr, int bytes, int timeout)); ++ ++static int + sockwrite(fd, ptr, bytes, timeout) + int fd; + u_char *ptr; +@@ -416,16 +468,32 @@ int timeout; + return (bytes - remaining); + } + ++static const char *get_session_key TAC_ARGS((void)); ++ ++static const char * ++get_session_key() ++{ ++ const char *retval = NULL; ++ ++ if ((retval = cfg_get_host_key(session.peer_addr))) ++ return (retval); ++ if (session.peer_addr != session.peer ++ && (retval = cfg_get_host_key(session.peer ))) ++ return (retval); ++ return (session.key); ++} ++ + /* read a packet from the wire, and decrypt it. Increment the global + seq_no return NULL on failure */ + ++u_char *read_packet TAC_ARGS((void)); ++ + 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"); +@@ -451,7 +519,7 @@ read_packet() + len < TAC_PLUS_HDR_SIZE || len > 0x10000) { + report(LOG_ERR, + "%s: Illegal data size: %lu\n", +- session.peer, ntohl(hdr.datalength)); ++ session.peer, (unsigned long) ntohl(hdr.datalength)); + return(NULL); + } + pkt = (u_char *) tac_malloc(len); +@@ -463,9 +531,9 @@ read_packet() + data = pkt + TAC_PLUS_HDR_SIZE; + + /* read the rest of the packet data */ +- if (sockread(session.sock, data, ntohl(hdr.datalength), ++ if ((unsigned long)sockread(session.sock, data, ntohl(hdr.datalength), + TAC_PLUS_READ_TIMEOUT) != +- ntohl(hdr.datalength)) { ++ (unsigned long) ntohl(hdr.datalength)) { + report(LOG_ERR, "%s: start_session: bad socket read", session.peer); + return (NULL); + } +@@ -480,10 +548,7 @@ read_packet() + } + + /* decrypt the data portion */ +- if ( !(tkey=(char *)cfg_get_host_key(session.peer)) ) +- tkey = session.key; +- +- if (md5_xor((HDR *)pkt, data, tkey)) { ++ if (md5_xor((HDR *)pkt, data, get_session_key())) { + report(LOG_ERR, "%s: start_session error decrypting data", + session.peer); + return (NULL); +@@ -498,14 +563,16 @@ read_packet() + return (pkt); + } + ++static int write_packet TAC_ARGS((u_char *pak)); ++ + /* write a packet to the wire, encrypting it */ ++static int + 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); + +@@ -513,10 +580,7 @@ u_char *pak; + 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)) { ++ if (md5_xor((HDR *)pak, data, get_session_key())) { + report(LOG_ERR, "%s: write_packet: error encrypting data", session.peer); + return (-1); + } +@@ -528,6 +592,9 @@ u_char *pak; + return (0); + } + ++void send_error_reply TAC_ARGS((int type, char *msg)); ++ ++void + send_error_reply(type, msg) + int type; + char *msg; +diff --git a/packet.h b/packet.h +new file mode 100644 +index 0000000..83a4d9c +--- /dev/null ++++ b/packet.h +@@ -0,0 +1,217 @@ ++#ifndef PACKET_H ++#define PACKET_H 1 ++ ++#include "tac_plus.h" ++ ++#include /* for u_* */ ++ ++ ++/* 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 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; ++ /* */ ++ /* */ ++ /* */ ++ /* */ ++}; ++ ++#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 ++ ++ /* */ ++ /* */ ++}; ++ ++#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; ++ ++ /* */ ++ /* */ ++}; ++ ++#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 */ ++ ++ /* */ ++ /* */ ++ /* */ ++ /* */ ++ /* */ ++}; ++ ++#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; ++ ++ /* */ ++ /* */ ++ /* */ ++ /* */ ++}; ++ ++#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 */ ++ /* */ ++ /* */ ++ /* */ ++ /* 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 ++ ++ ++extern void send_acct_reply TAC_ARGS((unsigned status, const char *msg, const char *data)); ++extern void send_author_reply TAC_ARGS((unsigned status, const char *msg, const char *data, int arg_cnt, /* const */ char **args)); ++extern void send_authen_error TAC_ARGS((const char *msg)); ++extern void send_authen_reply TAC_ARGS((int status, const char *msg, unsigned msg_len, const unsigned char *data, unsigned data_len, unsigned flags)); ++extern u_char *get_authen_continue TAC_ARGS((void)); ++extern u_char *read_packet TAC_ARGS((void)); ++extern void send_error_reply TAC_ARGS((int type, char *msg)); ++ ++ ++#endif /* PACKET_H */ +diff --git a/parse.c b/parse.c +index 6ac6908..b9f3360 100644 +--- a/parse.c ++++ b/parse.c +@@ -19,8 +19,15 @@ + + /* Keywords of the configuration language */ + ++ + #include "tac_plus.h" + ++#include "parse.h" ++#include "utils.h" ++#include "report.h" ++#include "hash.h" ++ ++ + static void *wordtable[HASH_TAB_SIZE]; /* Table of keyword declarations */ + + struct keyword { +@@ -31,6 +38,8 @@ struct keyword { + + typedef struct keyword KEYWORD; + ++static void declare TAC_ARGS((char *name, int value)); ++ + static void + declare(name, value) + char *name; +@@ -53,6 +62,8 @@ declare(name, value) + + /* Declare keywords of the "configuration language". */ + ++void parser_init TAC_ARGS((void)); ++ + void + parser_init() + { +@@ -85,7 +96,6 @@ parser_init() + 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); +@@ -115,12 +125,23 @@ parser_init() + declare("service", S_svc); + declare("user", S_user); + declare("time", S_time); ++ declare("and", S_and); ++ declare("closeparen", S_closeparen); ++ declare("enlist", S_enlist); ++ declare("first", S_first); ++ declare("not", S_not); ++ declare("openparen", S_openparen); ++ declare("or", S_or); ++ declare("recursive", S_recursive); ++ declare("when", S_when); + } + ++int keycode TAC_ARGS((const char *keyword)); ++ + /* Return a keyword code if a keyword is recognized. 0 otherwise */ + int + keycode(keyword) +-char *keyword; ++const char *keyword; + { + KEYWORD *k = hash_lookup(wordtable, keyword); + +@@ -129,7 +150,9 @@ char *keyword; + return (S_unknown); + } + +-char * ++const char *codestring TAC_ARGS((int type)); ++ ++const char * + codestring(type) + int type; + { +@@ -156,8 +179,6 @@ int type; + return ("group"); + case S_host: + return ("host"); +- case S_type: +- return ("type"); + case S_file: + return ("file"); + case S_skey: +@@ -250,5 +271,23 @@ int type; + return("lcp"); + case S_time: + return("time"); ++ case S_and: ++ return("and"); ++ case S_closeparen: ++ return(")"); ++ case S_enlist: ++ return("enlist"); ++ case S_first: ++ return("first"); ++ case S_not: ++ return("not"); ++ case S_openparen: ++ return("("); ++ case S_or: ++ return("or"); ++ case S_recursive: ++ return("recursive"); ++ case S_when: ++ return("when"); + } + } +diff --git a/parse.h b/parse.h +index e5be7f7..34d72ee 100644 +--- a/parse.h ++++ b/parse.h +@@ -1,3 +1,8 @@ ++#ifndef PARSE_H ++#define PARSE_H 1 ++ ++#include "tac_plus.h" ++ + /* + Copyright (c) 1995-1998 by Cisco systems, Inc. + +@@ -17,8 +22,6 @@ + FITNESS FOR A PARTICULAR PURPOSE. + */ + +-/* Dummy password, if nopasswd is specified */ +-extern char *nopasswd_str; + + /* Keywords & values */ + +@@ -82,9 +85,24 @@ extern char *nopasswd_str; + #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 ++#define S_and 49 ++#define S_closeparen 50 ++#define S_enlist 51 ++#define S_first 52 ++#define S_not 53 ++#define S_openparen 54 ++#define S_or 55 ++#define S_recursive 56 ++#define S_when 57 ++ ++ ++extern void parser_init TAC_ARGS((void)); ++extern int keycode TAC_ARGS((const char *keyword)); ++extern const char *codestring TAC_ARGS((int type)); ++ + ++#endif /* PARSE_H */ +diff --git a/programs.c b/programs.c +index bce0178..ac42b23 100644 +--- a/programs.c ++++ b/programs.c +@@ -19,10 +19,29 @@ + + /* Routines to fork children and communicate with them via pipes */ + ++ + #include "tac_plus.h" +-#include "sys/wait.h" ++ ++#include ++#include ++#include ++#ifdef HAVE_UNISTD_H + #include +-#include "signal.h" ++#endif ++#include ++#ifdef HAVE_SYSLOG_H ++#include ++#endif ++#ifdef HAVE_SYS_SYSLOG_H ++#include ++#endif ++ ++#include "programs.h" ++#include "utils.h" ++#include "report.h" ++#include "do_author.h" /* for "struct author_data" */ ++#include "main.h" ++ + + /* Support for dollar variables. Look in the authorization data and + return strings representing values found there. If not found, return +@@ -38,6 +57,8 @@ type -- (1 to 4) + service -- (1 to 7) + status -- (pass, fail, error, unknown) */ + ++static char *lookup TAC_ARGS((char *sym, struct author_data *data)); ++ + static char * + lookup(sym, data) + char *sym; +@@ -96,12 +117,14 @@ struct author_data *data; + values for the various $ variables by looking in the authorization + data */ + ++static char *substitute TAC_ARGS((const char *string, struct author_data *data)); ++ + static char * + substitute(string, data) +-char *string; ++const char *string; + struct author_data *data; + { +- char *cp; ++ const char *cp; + char out[MAX_INPUT_LINE_LEN], *outp; + char sym[MAX_INPUT_LINE_LEN], *symp; + char *value, *valuep; +@@ -129,7 +152,7 @@ struct author_data *data; + + } else { + /* copy symbol into sym */ +- while (*cp && isalpha(*cp)) ++ while (*cp && isalpha((int) *cp)) + *symp++ = *cp++; + } + +@@ -160,6 +183,8 @@ struct author_data *data; + /* Wait for a (child) pid to terminate. Return its status. Probably + horribly implementation dependent. */ + ++static int waitfor TAC_ARGS((int pid)); ++ + static int + waitfor(pid) + int pid; +@@ -189,6 +214,8 @@ int pid; + return (WEXITSTATUS(status)); + } + ++static int write_args TAC_ARGS((int fd, char **args, int arg_cnt)); ++ + /* Write an argv array of strings to fd, adding a newline to each one */ + static int + write_args(fd, args, arg_cnt) +@@ -211,6 +238,8 @@ char **args; + return (0); + } + ++static void close_fds TAC_ARGS((int fd1, int fd2, int fd3)); ++ + /* Close the three given file-descruptors */ + static void + close_fds(fd1, fd2, fd3) +@@ -230,6 +259,8 @@ close_fds(fd1, fd2, 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 TAC_ARGS((char *cmd, int *readfdp, int *writefdp, int *errorfdp)); ++ + static int + my_popen(cmd, readfdp, writefdp, errorfdp) + char *cmd; +@@ -297,6 +328,8 @@ int *readfdp, *writefdp, *errorfdp; + return(0); /* keep Codecenter quiet */ + } + ++static int read_string TAC_ARGS((int fd, char *string, int len)); ++ + /* read the file descriptor and stuff the data into the given array for + * the number of bytes given. Throw the rest away. + */ +@@ -305,7 +338,7 @@ read_string (fd, string, len) + int fd, len; + char *string; + { +- uint i, ret; ++ int i, ret; + char c; + + i=0; +@@ -324,6 +357,8 @@ char *string; + the count of lines seen so far. When eof is read, the array is + allocated, and the recursion unravels */ + ++static char **read_args TAC_ARGS((int n, int fd)); ++ + static char ** + read_args(n, fd) + int n, fd; +@@ -356,12 +391,16 @@ int n, fd; + standard input and read its standard output into outarray. Return + the commands final status when it terminates */ + ++int call_pre_process TAC_ARGS((const char *string, struct author_data *data, char ***outargsp, int *outargs_cntp, char *error, int err_len)); ++ + int + call_pre_process(string, data, outargsp, outargs_cntp, error, err_len) +-char *string, *error; ++const char *string; + struct author_data *data; + char ***outargsp; +-int *outargs_cntp, err_len; ++int *outargs_cntp; ++char *error; ++int err_len; + { + char **new_args; + int readfd, writefd, errorfd; +@@ -402,8 +441,8 @@ int *outargs_cntp, err_len; + + read_string(errorfd, error, err_len); + if (error[0] != '\0') { +- report(LOG_ERR, "Error from program (%d): \"%s\" ", +- strlen(error), error); ++ report(LOG_ERR, "Error from program (%u): \"%s\" ", ++ (unsigned) strlen(error), error); + } + + /* count the args */ +@@ -422,9 +461,11 @@ int *outargs_cntp, err_len; + standard input and read its standard output into outarray. Return + the commands final status when it terminates */ + ++int call_post_process TAC_ARGS((const char *string, struct author_data *data, char ***outargsp, int *outargs_cntp)); ++ + int + call_post_process(string, data, outargsp, outargs_cntp) +-char *string; ++const char *string; + struct author_data *data; + char ***outargsp; + int *outargs_cntp; +diff --git a/programs.h b/programs.h +new file mode 100644 +index 0000000..31ebef3 +--- /dev/null ++++ b/programs.h +@@ -0,0 +1,13 @@ ++#ifndef PROGRAMS_H ++#define PROGRAMS_H 1 ++ ++#include "tac_plus.h" ++ ++ ++struct author_data; ++ ++extern int call_pre_process TAC_ARGS((const char *string, struct author_data *data, char ***outargsp, int *outargs_cntp, char *error, int err_len)); ++extern int call_post_process TAC_ARGS((const char *string, struct author_data *data, char ***outargsp, int *outargs_cntp)); ++ ++ ++#endif /* PROGRAMS_H */ +diff --git a/pw.c b/pw.c +index 96c761e..a749262 100644 +--- a/pw.c ++++ b/pw.c +@@ -20,15 +20,26 @@ + /* Tacacs+ password lookup routine for those systems which don't have + setpwfile. Not for use on /etc/passwd files */ + ++ + #include "tac_plus.h" ++ ++#include + #include + #include + ++#include "pw.h" ++#include "report.h" ++#include "main.h" ++ ++ + static struct passwd pw_passwd; + ++struct passwd *tac_passwd_lookup TAC_ARGS((const char *name, const char *file)); ++ + struct passwd * + tac_passwd_lookup(name, file) +- char *name, *file; ++const char *name; ++const char *file; + { + FILE *passwd_fp = NULL; + +@@ -113,10 +124,12 @@ tac_passwd_lookup(name, file) + + pw_passwd.pw_name = uname; + pw_passwd.pw_passwd = password; +-#ifndef NO_PWAGE ++#ifdef HAVE_PASSWD_PW_AGE + pw_passwd.pw_age = NULL; ++#endif ++#ifdef HAVE_PASSWD_PW_COMMENT + pw_passwd.pw_comment = NULL; +-#endif /* NO_PWAGE */ ++#endif + pw_passwd.pw_gecos = gecos; + pw_passwd.pw_dir = homedir; + pw_passwd.pw_shell = shell; +diff --git a/pw.h b/pw.h +new file mode 100644 +index 0000000..44dc425 +--- /dev/null ++++ b/pw.h +@@ -0,0 +1,10 @@ ++#ifndef PW_H ++#define PW_H 1 ++ ++#include "tac_plus.h" ++ ++ ++extern struct passwd *tac_passwd_lookup TAC_ARGS((const char *name, const char *file)); ++ ++ ++#endif /* PW_H */ +diff --git a/pwlib.c b/pwlib.c +index 75f90b1..70a468e 100644 +--- a/pwlib.c ++++ b/pwlib.c +@@ -17,39 +17,61 @@ + FITNESS FOR A PARTICULAR PURPOSE. + */ + ++ + #include "tac_plus.h" +-#include "expire.h" +-#include "time_limit.h" ++ ++#ifdef HAVE_UNISTD_H ++#include ++#endif ++#include ++#include ++#include ++#include ++#include ++#include + + #ifdef SHADOW_PASSWORDS ++#ifdef HAVE_SHADOW_H + #include + #endif ++#endif + +-#ifdef USE_PAM +-int +-tac_pam_auth(char *UserName,char *Password,struct authen_data *data,char *Service); +-#endif /* USE_PAM */ ++#include "pwlib.h" ++#include "expire.h" ++#include "time_limit.h" ++#include "report.h" ++#include "utils.h" ++#include "cfgfile.h" ++#include "pw.h" ++#include "choose_authen.h" /* for "struct authen_data" */ ++#include "packet.h" ++#include "main.h" ++#include "parse.h" + +-/* For database verification */ ++#ifdef USE_PAM ++#include "tac_pam.h" ++#endif + #ifdef DB +-int db_verify(); +-#endif /* DB */ +- +-/* For LDAP verification */ ++#include "db.h" /* For database verification */ ++#endif + #ifdef USE_LDAP +-#include "ldap.h" +-#endif /* LDAP */ ++#include "ldap_author.h" /* For LDAP verification */ ++#endif ++ ++ ++static int passwd_file_verify TAC_ARGS((const char *user, const char *supplied_passwd, struct authen_data *data, const char *filename)); ++ + + /* 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 TAC_ARGS((const char *exp_date, struct authen_data *data)); ++ + void + set_expiration_status(exp_date, data) +-char *exp_date; ++const char *exp_date; + struct authen_data *data; + { + int expired; +@@ -105,18 +127,18 @@ struct authen_data *data; + + Return 1 if password is valid */ + ++int verify TAC_ARGS((const char *name, const char *passwd, struct authen_data *data, int recurse)); ++ + int + verify(name, passwd, data, recurse) +-char *name, *passwd; ++const char *name; ++const char *passwd; + struct authen_data *data; + int recurse; + { +- char *exp_date; +- char *timestamp; +- char *cfg_passwd; +- char *p; ++ const char *exp_date, *cfg_passwd, *p, *timestamp; + +- timestamp = (char *)cfg_get_timestamp(name, recurse); ++ timestamp = cfg_get_timestamp(name, recurse); + if ( timestamp != NULL ) { + if( time_limit_process(timestamp) == 0 ) { + if ( debug & DEBUG_AUTHEN_FLAG ) +@@ -144,18 +166,18 @@ int recurse; + has been issued, attempt to use this password file */ + + if (!cfg_passwd) { +- char *file = cfg_get_authen_default(); ++ const char *file = cfg_get_authen_default(); + switch (cfg_get_authen_default_method()) { +- case (S_file): + +- if (file) { ++ 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( 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)) { +@@ -188,8 +210,8 @@ int recurse; + #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)) { ++ report(LOG_DEBUG, "PAM verify daemon [PAM] == NAS %s", 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; +@@ -210,9 +232,8 @@ int recurse; + /* otherwise, we fail */ + data->status = TAC_PLUS_AUTHEN_STATUS_FAIL; + return (0); +- + } +-} ++ } + + /* We have a configured password. Deal with it depending on its + type */ +@@ -323,11 +344,15 @@ int recurse; + return (0); + } + ++static int etc_passwd_file_verify TAC_ARGS((const char *user, const char *supplied_passwd, struct authen_data *data)); ++ + /* verify that this user/password is valid per /etc/passwd. +- Return 0 if invalid. */ ++ * Return 0 if invalid. ++ */ + static int + etc_passwd_file_verify(user, supplied_passwd, data) +-char *user, *supplied_passwd; ++const char *user; ++const char *supplied_passwd; + struct authen_data *data; + { + struct passwd *pw; +@@ -409,11 +434,14 @@ struct authen_data *data; + /* verify that this user/password is valid per a passwd(5) style + database. Return 0 if invalid. */ + ++static int passwd_file_verify TAC_ARGS((const char *user, const char *supplied_passwd, struct authen_data *data, const char *filename)); ++ + static int + passwd_file_verify(user, supplied_passwd, data, filename) +-char *user, *supplied_passwd; ++const char *user; ++const char *supplied_passwd; + struct authen_data *data; +-char *filename; ++const char *filename; + { + struct passwd *pw; + char *exp_date; +@@ -467,9 +495,12 @@ char *filename; + * return 1 if verified, 0 otherwise. + */ + ++int des_verify TAC_ARGS((const char *users_passwd, const char *encrypted_passwd)); ++ + int + des_verify(users_passwd, encrypted_passwd) +-char *users_passwd, *encrypted_passwd; ++const char *users_passwd; ++const char *encrypted_passwd; + { + char *ep; + +diff --git a/pwlib.h b/pwlib.h +new file mode 100644 +index 0000000..92b54af +--- /dev/null ++++ b/pwlib.h +@@ -0,0 +1,14 @@ ++#ifndef PWLIB_H ++#define PWLIB_H 1 ++ ++#include "tac_plus.h" ++ ++ ++struct authen_data; ++ ++extern void set_expiration_status TAC_ARGS((const char *exp_date, struct authen_data *data)); ++extern int verify TAC_ARGS((const char *name, const char *passwd, struct authen_data *data, int recurse)); ++extern int des_verify TAC_ARGS((const char *users_passwd, const char *encrypted_passwd)); ++ ++ ++#endif /* PWLIB_H */ +diff --git a/report.c b/report.c +index a458617..d46abfd 100644 +--- a/report.c ++++ b/report.c +@@ -17,20 +17,37 @@ + FITNESS FOR A PARTICULAR PURPOSE. + */ + ++ + #include "tac_plus.h" +-#include + +-#ifdef AIX ++#include + #include +-#else + #include ++#include ++#ifdef HAVE_FCNTL_H ++#include + #endif +- +-#ifdef __STDC__ +-#include /* ANSI C, variable length args */ +-#else +-#include /* has 'vararg' definitions */ ++#include ++#ifdef HAVE_UNISTD_H ++#include ++#endif ++#ifdef HAVE_SYSLOG_H ++#include + #endif ++#ifdef HAVE_SYS_SYSLOG_H ++#include ++#endif ++ ++#include "report.h" ++#include "utils.h" ++#include "main.h" ++ ++ ++/* Configurable: ++ */ ++ ++#define LOGFILE_DEFAULT "/var/log/tac_plus.log" ++ + + FILE *ostream = NULL; + +@@ -45,21 +62,30 @@ char *logfile = LOGFILE_DEFAULT; + * All other priorities are always logged to syslog. + */ + ++void report TAC_ARGS((int priority, const char *fmt, ...)) G_GNUC_PRINTF(2, 3); ++ + #ifdef __STDC__ ++ ++#include /* ANSI C, variable length args */ + void +-report(int priority, char *fmt,...) +-#else ++report(int priority, const char *fmt,...) ++ ++#else /* __STDC__ */ ++ ++#include /* has 'vararg' definitions */ + /* VARARGS2 */ + void + report(priority, fmt, va_alist) + int priority; +-char *fmt; ++const char *fmt; + va_dcl /* no terminating semi-colon */ +-#endif ++ ++#endif /* __STDC__ */ + { + char msg[255]; /* temporary string */ +- char *fp, *bufp, *charp; +- int len, m, i, n; ++ const char *fp; ++ char *bufp, *charp = NULL /* GCC paranoia */; ++ int len, m = 0 /* GCC paranoia */, i, n; + char digits[16]; + va_list ap; + +@@ -119,6 +145,9 @@ va_dcl /* no terminating semi-colon */ + m = strlen(digits); + charp = digits; + break; ++ default: ++ syslog(LOG_ERR, "Unknown format character '%c', ignoring it", *fp); ++ continue; + } + + if ((len + m + 1) >= n) { +@@ -167,7 +196,7 @@ va_dcl /* no terminating semi-colon */ + + ct[24] = '\0'; + tac_lockfd(logfile, logfd); +- sprintf(buf, "%s [%d]: ", ct, getpid()); ++ sprintf(buf, "%s [%d]: ", ct, (int) getpid()); + write(logfd, buf, strlen(buf)); + if (priority == LOG_ERR) + write(logfd, "Error ", 6); +@@ -190,6 +219,8 @@ va_dcl /* no terminating semi-colon */ + syslog(priority, "%s", msg); + } + ++void report_hex TAC_ARGS((int priority, u_char *p, int len)); ++ + /* format a hex dump for syslog */ + void + report_hex(priority, p, len) +@@ -225,6 +256,8 @@ int len; + } + + ++void report_string TAC_ARGS((int priority, u_char *p, int len)); ++ + /* format a non-null terminated string for syslog */ + void + report_string(priority, p, len) +@@ -251,10 +284,11 @@ int len; + report(priority, "%s", buf); + } + ++void tac_regerror TAC_ARGS((const char *s)); ++ + void +-regerror(s) +-char *s; ++tac_regerror(s) ++const char *s; + { + report(LOG_ERR, "in regular expression %s", s); + } +- +diff --git a/report.h b/report.h +new file mode 100644 +index 0000000..5e5d1bb +--- /dev/null ++++ b/report.h +@@ -0,0 +1,26 @@ ++#ifndef REPORT_H ++#define REPORT_H 1 ++ ++#include "tac_plus.h" ++ ++#include ++#include /* for u_* */ ++#ifdef HAVE_SYSLOG_H ++#include /* for LOG_* level values */ ++#endif ++#ifdef HAVE_SYS_SYSLOG_H ++#include /* for LOG_* level values */ ++#endif ++ ++ ++extern FILE *ostream; /* for logging to console */ ++extern char *logfile; ++ ++ ++extern void report TAC_ARGS((int priority, const char *fmt, ...)) G_GNUC_PRINTF(2, 3); ++extern void report_hex TAC_ARGS((int priority, u_char *p, int len)); ++extern void report_string TAC_ARGS((int priority, u_char *p, int len)); ++extern void tac_regerror TAC_ARGS((const char *s)); ++ ++ ++#endif /* REPORT_H */ +diff --git a/sendauth.c b/sendauth.c +index 68e5482..2076dc9 100644 +--- a/sendauth.c ++++ b/sendauth.c +@@ -17,21 +17,43 @@ + FITNESS FOR A PARTICULAR PURPOSE. + */ + ++ + #include "tac_plus.h" ++ ++#include ++ ++#include "sendauth.h" + #include "expire.h" + #include "md5.h" ++#include "report.h" ++#include "cfgfile.h" ++#include "utils.h" ++#include "pwlib.h" ++#include "choose_authen.h" /* for "struct authen_data" */ ++#include "do_author.h" /* for "struct identity" */ ++#include "packet.h" ++#include "main.h" + +-static int do_sendauth_fn(); +-static void outbound_chap(); + #ifdef MSCHAP +-static void outbound_mschap(); +-#endif /* MSCHAP */ +-void outbound_pap(); ++#include "default_fn.h" ++#endif ++ ++ ++static int do_sendauth_fn TAC_ARGS((struct authen_data *data)); ++static void outbound_chap TAC_ARGS((struct authen_data *data)); ++static void outbound_pap TAC_ARGS((struct authen_data *data)); ++ ++#ifdef MSCHAP ++static void outbound_mschap TAC_ARGS((struct authen_data *data)); ++#endif ++ ++ ++int sendauth_fn TAC_ARGS((struct authen_data *data)); + + int sendauth_fn(data) + struct authen_data *data; + { +- int status; ++ int retval; + char *name, *p; + + name = data->NAS_id->username; +@@ -39,8 +61,9 @@ struct authen_data *data; + if (STREQ(name, DEFAULT_USERNAME)) { + /* This username is only valid for authorization */ + data->status = TAC_PLUS_AUTHEN_STATUS_FAIL; ++ retval = 0; + } else { +- status = do_sendauth_fn(data); ++ retval = do_sendauth_fn(data); + } + + if (debug) { +@@ -71,7 +94,7 @@ struct authen_data *data; + (data->status == TAC_PLUS_AUTHEN_STATUS_PASS) ? + "accepted" : "rejected"); + } +- return(status); ++ return (retval); + } + + /* +@@ -84,11 +107,13 @@ struct authen_data *data; + * Return 0 if data->status is valid, otherwise 1 + */ + ++static int do_sendauth_fn TAC_ARGS((struct authen_data *data)); ++ + static int + do_sendauth_fn(data) + struct authen_data *data; + { +- char *name, *exp_date; ++ const char *name, *exp_date; + + data->status = TAC_PLUS_AUTHEN_STATUS_FAIL; + +@@ -129,11 +154,13 @@ struct authen_data *data; + return (0); + } + +-void ++static void outbound_pap TAC_ARGS((struct authen_data *data)); ++ ++static void + outbound_pap(data) + struct authen_data *data; + { +- char *secret, *p, *name; ++ const char *secret, *p, *name; + + name = data->NAS_id->username; + +@@ -163,17 +190,19 @@ struct authen_data *data; + return; + } + +- data->server_data = tac_strdup(p); +- data->server_dlen = strlen(data->server_data); ++ data->server_data = (unsigned char *) tac_strdup(p); ++ data->server_dlen = strlen((char *) data->server_data); + data->status = TAC_PLUS_AUTHEN_STATUS_PASS; + } + ++static void outbound_chap TAC_ARGS((struct authen_data *data)); ++ + static void + outbound_chap(data) + struct authen_data *data; + { +- char *name, *secret, *chal, digest[MD5_LEN]; +- char *p; ++ const char *name, *secret, *chal, *p; ++ char digest[MD5_LEN]; + u_char *mdp; + char id; + int chal_len, inlen; +@@ -262,12 +291,13 @@ struct authen_data *data; + + #ifdef MSCHAP + ++static void outbound_mschap TAC_ARGS((struct authen_data *data)); ++ + static void + outbound_mschap(data) + struct authen_data *data; + { +- char *name, *secret, *chal; +- char *p; ++ const char *name, *secret, *chal, *p; + char id; + int chal_len; + +diff --git a/sendauth.h b/sendauth.h +new file mode 100644 +index 0000000..d3e2fbc +--- /dev/null ++++ b/sendauth.h +@@ -0,0 +1,12 @@ ++#ifndef SENDAUTH_H ++#define SENDAUTH_H 1 ++ ++#include "tac_plus.h" ++ ++ ++struct authen_data; ++ ++extern int sendauth_fn TAC_ARGS((struct authen_data *data)); ++ ++ ++#endif /* SENDAUTH_H */ +diff --git a/sendpass.c b/sendpass.c +index f7f0b3c..9d8ab00 100644 +--- a/sendpass.c ++++ b/sendpass.c +@@ -17,11 +17,24 @@ + FITNESS FOR A PARTICULAR PURPOSE. + */ + ++ + #include "tac_plus.h" ++ ++#include "sendpass.h" + #include "expire.h" ++#include "report.h" ++#include "utils.h" ++#include "cfgfile.h" ++#include "choose_authen.h" /* for "struct authen_data" */ ++#include "do_author.h" /* for "struct identity" */ ++#include "main.h" ++#include "packet.h" + +-static int +-do_sendpass_fn(); ++ ++static int do_sendpass_fn TAC_ARGS((struct authen_data *data)); ++ ++ ++int sendpass_fn TAC_ARGS((struct authen_data *data)); + + int sendpass_fn(data) + struct authen_data *data; +@@ -63,17 +76,17 @@ struct authen_data *data; + * 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 */ ++ * Return 0 if data->status is valid, otherwise 1 ++ */ ++ ++static int do_sendpass_fn TAC_ARGS((struct authen_data *data)); + + static int + do_sendpass_fn(data) + struct authen_data *data; + { +- char *name; +- char *p; ++ const char *name, *exp_date, *secret, *p; + int expired; +- char *exp_date; +- char *secret; + + data->status = TAC_PLUS_AUTHEN_STATUS_FAIL; + +@@ -159,8 +172,8 @@ struct authen_data *data; + return(0); + } + +- data->server_data = tac_strdup(p); +- data->server_dlen = strlen(data->server_data); ++ data->server_data = (unsigned char *) tac_strdup(p); ++ data->server_dlen = strlen((char *) data->server_data); + data->status = TAC_PLUS_AUTHEN_STATUS_PASS; + if (expired == PW_EXPIRING) { + data->server_msg = tac_strdup("Secret will expire soon"); +diff --git a/sendpass.h b/sendpass.h +new file mode 100644 +index 0000000..7481cff +--- /dev/null ++++ b/sendpass.h +@@ -0,0 +1,12 @@ ++#ifndef SENDPASS_H ++#define SENDPASS_H 1 ++ ++#include "tac_plus.h" ++ ++ ++struct authen_data; ++ ++extern int sendpass_fn TAC_ARGS((struct authen_data *data)); ++ ++ ++#endif /* SENDPASS_H */ +diff --git a/skey_fn.c b/skey_fn.c +index d3c9860..0a74fa8 100644 +--- a/skey_fn.c ++++ b/skey_fn.c +@@ -17,17 +17,22 @@ + FITNESS FOR A PARTICULAR PURPOSE. + */ + +-#ifdef SKEY ++ + #include "tac_plus.h" ++ ++#ifdef SKEY ++ ++#include ++ ++#include "skey_fn.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 +- + struct private_data { + struct skey skey; + char password[MAX_PASSWD_LEN + 1]; +@@ -37,6 +42,8 @@ struct private_data { + /* Use s/key to verify a supplied password using state set up earlier + when the username was supplied */ + ++static int skey_verify TAC_ARGS((char *passwd, struct authen_data *data)); ++ + static int + skey_verify(passwd, data) + char *passwd; +@@ -73,6 +80,8 @@ struct authen_data *data; + * Return 0 if data->status is valid, otherwise 1 + */ + ++int skey_fn TAC_ARGS((struct authen_data *data)); ++ + int + skey_fn(data) + struct authen_data *data; +@@ -222,12 +231,9 @@ struct authen_data *data; + 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 */ ++#else /* SKEY */ + +-static int dummy = 0; ++TAC_SOURCEFILE_EMPTY + + #endif /* SKEY */ +diff --git a/skey_fn.h b/skey_fn.h +new file mode 100644 +index 0000000..1b7474a +--- /dev/null ++++ b/skey_fn.h +@@ -0,0 +1,14 @@ ++#ifndef SKEY_FN_H ++#define SKEY_FN_H 1 ++ ++#include "tac_plus.h" ++ ++#ifdef SKEY ++ ++ ++extern int skey_fn TAC_ARGS((struct authen_data *data)); ++ ++ ++#endif /* SKEY */ ++ ++#endif /* SKEY_FN_H */ +diff --git a/tac_pam.c b/tac_pam.c +index bf1b215..4f088a0 100644 +--- a/tac_pam.c ++++ b/tac_pam.c +@@ -1,12 +1,10 @@ +-#ifdef USE_PAM +- + /* tac_pam.auth.c + * A simple pam authentication routine written by + * Max Liccardo + * PAM_RUSER=username/rem_addr. + */ + +- /* ++/* + This program was contributed by Shane Watts + [modifications by AGM] + +@@ -14,23 +12,39 @@ + # 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 "tac_plus.h" ++ ++#ifdef USE_PAM + + #include + #include + #include + #include +-#include "tac_plus.h" + +-typedef struct +-{ +- char *UserName; +- char *Passwd; ++#include "tac_pam.h" ++#include "report.h" ++#include "utils.h" ++#include "choose_authen.h" /* for "struct authen_data" */ ++#include "do_author.h" /* for "struct identity" */ ++#include "main.h" ++ ++ ++typedef struct { ++ const char *UserName; ++ const char *Passwd; + } UserCred; + + +-static int fconv(int num_msg, const struct pam_message **msg, +- struct pam_response **resp,void *appdata_ptr) ++static int fconv TAC_ARGS((int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr)); ++ ++static int fconv(num_msg, msg, resp, appdata_ptr) ++int num_msg; ++const struct pam_message **msg; ++struct pam_response **resp; ++void *appdata_ptr; + { + int i; + UserCred *lUserCred; +@@ -38,19 +52,16 @@ static int fconv(int num_msg, const struct pam_message **msg, + + lUserCred = appdata_ptr; + +- if(lUserCred == NULL) +- { ++ if(lUserCred == NULL) { + report(LOG_ERR,"argh....maybe a SunOs 5.6 ???"); + return(PAM_CONV_ERR); + } + ++ *resp = (struct pam_response *) tac_malloc(num_msg * sizeof(struct pam_response)); + +- *resp = (struct pam_response *) calloc(num_msg,sizeof(struct pam_response)); ++ for (i=0; imsg_style) { + +- for(i=0;imsg_style) +- { + case PAM_PROMPT_ECHO_OFF: + resp[i]->resp = strdup(lUserCred->Passwd); + break; +@@ -60,6 +71,7 @@ static int fconv(int num_msg, const struct pam_message **msg, + break; + + default: ++ resp[i]->resp = NULL; + report(LOG_DEBUG,"conv default"); + break; + } +@@ -70,12 +82,16 @@ static int fconv(int num_msg, const struct pam_message **msg, + } + + +- ++int tac_pam_auth TAC_ARGS((const char *aszUserName, const char *aszPassword, struct authen_data *data, const char *aszService)); + + int +-tac_pam_auth(char *aszUserName,char *aszPassword,struct authen_data *data,char *aszService) ++tac_pam_auth(aszUserName, aszPassword, data, aszService) ++const char *aszUserName; ++const char *aszPassword; ++struct authen_data *data; ++const char *aszService; + { +- pam_handle_t *pamh=NULL; ++ pam_handle_t *pamh = NULL; + int retval; + char *lpszRemoteUser; /* Username/NAC address */ + struct pam_conv s_conv; +@@ -89,17 +105,13 @@ tac_pam_auth(char *aszUserName,char *aszPassword,struct authen_data *data,char * + 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); +- } ++ lpszRemoteUser = tac_malloc((strlen(aszUserName)+1+strlen(data->NAS_id->NAC_address)+1) * sizeof(char)); + + retval = pam_start(aszService,aszUserName , &s_conv, &pamh); + +- if (retval != PAM_SUCCESS) +- { ++ if (retval != PAM_SUCCESS) { + report(LOG_ERR, "cannot start pam-authentication"); ++ free(lpszRemoteUser); + pamh = NULL; + return(1); + } +@@ -130,10 +142,15 @@ tac_pam_auth(char *aszUserName,char *aszPassword,struct authen_data *data,char * + * Devrim SERAL + */ + ++int tac_pam_authorization TAC_ARGS((const char *aszUserName, struct author_data *data, const char *aszService)); ++ + int +-tac_pam_authorization (char *aszUserName,struct author_data *data,char *aszService) ++tac_pam_authorization(aszUserName, data, aszService) ++const char *aszUserName; ++struct author_data *data; ++const char *aszService; + { +- pam_handle_t *pamh=NULL; ++ pam_handle_t *pamh = NULL; + int retval; + char *lpszRemoteUser; /* Username/NAC address */ + struct pam_conv s_conv; +@@ -145,24 +162,18 @@ tac_pam_authorization (char *aszUserName,struct author_data *data,char *aszServi + s_conv.conv = fconv; + s_conv.appdata_ptr = (void *) &s_UserCred; + +- if (aszService== NULL) +- { ++ 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); +- } ++ lpszRemoteUser = tac_malloc((strlen(aszUserName)+strlen(data->id->NAC_address)+2) * sizeof(char)); + + retval = pam_start(aszService,aszUserName , &s_conv, &pamh); + +- if (retval != PAM_SUCCESS) +- { ++ if (retval != PAM_SUCCESS) { + report(LOG_ERR, "cannot start pam-authentication"); ++ free(lpszRemoteUser); + pamh = NULL; + return(1); + } +@@ -177,11 +188,12 @@ tac_pam_authorization (char *aszUserName,struct author_data *data,char *aszServi + + retval = pam_acct_mgmt(pamh, 0); /* Is user permit to gain access system */ + +- if(retval != PAM_SUCCESS) ++ if (retval != PAM_SUCCESS) + report(LOG_ERR, "Pam Account Managment:%s",pam_strerror(pamh,retval)); +- else ++ 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; +@@ -191,9 +203,8 @@ tac_pam_authorization (char *aszUserName,struct author_data *data,char *aszServi + return ( retval == PAM_SUCCESS ? 0:1 ); /* indicate success */ + } + ++#else /* USE_PAM */ + +-#endif /* USE_PAM */ +- +- +- ++TAC_SOURCEFILE_EMPTY + ++#endif /* USE_PAM */ +diff --git a/tac_pam.h b/tac_pam.h +new file mode 100644 +index 0000000..c8306ba +--- /dev/null ++++ b/tac_pam.h +@@ -0,0 +1,18 @@ ++#ifndef TAC_PAM_H ++#define TAC_PAM_H 1 ++ ++#include "tac_plus.h" ++ ++#ifdef USE_PAM ++ ++ ++struct authen_data; ++struct author_data; ++ ++extern int tac_pam_auth TAC_ARGS((const char *aszUserName, const char *aszPassword, struct authen_data *data, const char *aszService)); ++extern int tac_pam_authorization TAC_ARGS((const char *aszUserName, struct author_data *data, const char *aszService)); ++ ++ ++#endif /* USE_PAM */ ++ ++#endif /* TAC_PAM_H */ +diff --git a/tac_plus.1 b/tac_plus.1 +index 579c7ee..69f3502 100644 +--- a/tac_plus.1 ++++ b/tac_plus.1 +@@ -111,6 +111,8 @@ the following values are recognised: + .nf + + Value Meaning ++2 config file parsing debugging ++4 process forking debugging + 8 authorisation debugging + 16 authentication debugging + 32 password file processing debugging +@@ -120,6 +122,11 @@ Value Meaning + 512 encryption/decryption + 1024 MD5 hash algorithm debugging + 2048 very low level encryption/decryption ++4096 config file memory allocation freeing ++8192 pre/post authorization program arguments substitutions ++16384 config file expressions with entity tracing ++32768 maxsess (concurrent logins) debugging ++65536 file locking progress reporting + + .fi + .TP +diff --git a/tac_plus.cfg b/tac_plus.cfg +index 31e53f5..d217258 100644 +--- a/tac_plus.cfg ++++ b/tac_plus.cfg +@@ -51,4 +51,3 @@ user = DEFAULT { + #group = staff { + # time = "Wd1800-1817|!Wd1819-2000" + #} +- +diff --git a/tac_plus.h b/tac_plus.h +index 99d95e0..1bb658f 100644 +--- a/tac_plus.h ++++ b/tac_plus.h +@@ -1,3 +1,6 @@ ++#ifndef TAC_PLUS_H ++#define TAC_PLUS_H 1 ++ + /* + Copyright (c) 1995-1998 by Cisco systems, Inc. + +@@ -16,8 +19,12 @@ + WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE. + */ +-/* For autoconfig */ ++ ++ ++#ifdef HAVE_CONFIG_H + #include "config.h" ++#endif ++ + + /* + * If you are defining a system from scratch, the following may be useful. +@@ -33,25 +40,9 @@ + /* 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 */ + +@@ -64,23 +55,14 @@ + */ + /* #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)" ++#define VERSION_TAIL " (Extended Tac_plus)" + + /* + * System definitions. + */ + + #ifdef NETBSD +-#define STDLIB_MALLOC +-#define NO_PWAGE + #define CONST_SYSERRLIST +-#define VOIDSIG + #endif + + #ifdef AIX +@@ -97,15 +79,10 @@ + #define _BSD 1 + #define _BSD_INCLUDES + #define UNIONWAIT +-#define NO_PWAGE + #endif /* AIX */ + + #ifdef LINUX +-#define VOIDSIG +-#define NO_PWAGE + #define REAPCHILD +-#include +-#define REARMSIGNAL + #ifdef GLIBC + #define CONST_SYSERRLIST + #endif +@@ -122,30 +99,17 @@ + #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 +@@ -153,36 +117,26 @@ + #define MSCHAP_DIGEST_LEN 49 + #endif /* MSCHAP */ + +-#include +-#include +-#include +-#include +-#include +-#include +-#include +- +-#include +-#include +-#include +-#include +- +-#ifdef SYSLOG_IN_SYS +-#include +-#else +-#include +-#endif +- +-#include +- +-#include +- + #ifdef SYSV ++#ifdef HAVE_FCNTL_H + #include ++#endif + #define index strchr +-#else /* ! SYSV */ +-#include + #endif /* SYSV */ + ++/* Sometimes are bzero/bcopy/bcmp declared as prototypes with non-void argument ++ * pointers. In such case we would generate too much warnings. ++ */ ++#include ++#ifdef HAVE_STRINGS_H ++#include ++#endif ++ ++#define bzero(s, n) bzero((void *)(s), (size_t)(n)) ++#define bcopy(src, dest, n) bcopy((const void *)(src), (void *)(dest), (size_t)(n)) ++#define bcmp(s1, s2, n) bcmp((const void *)(s1), (const void *)(s2), (size_t)(n)) ++ ++ + #ifndef TACPLUS_PIDFILE + #define TACPLUS_PIDFILE "/var/run/tac_plus.pid" + #endif +@@ -193,559 +147,94 @@ + * 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 +- */ ++/* Stolen from pbmplus.h of netpbm: */ + +-#define AUTHEN_NAME_SIZE 128 ++#if __STDC__ ++#define TAC_ARGS(alist) alist ++#else /*__STDC__*/ ++#define TAC_ARGS(alist) () ++#endif /*__STDC__*/ + +-struct authen_type { +- char authen_name[AUTHEN_NAME_SIZE]; +- int (*authen_func)(); +- int authen_type; +-}; ++/* Stolen from glib of Gnome/GTK+ environment: */ + +-/* +- * 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. ++/* Provide macros to feature the GCC function attribute. + */ +- +-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). ++#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4) ++#define G_GNUC_PRINTF( format_idx, arg_idx ) \ ++ __attribute__((format (printf, format_idx, arg_idx))) ++#define G_GNUC_NORETURN \ ++ __attribute__((noreturn)) ++#define G_GNUC_CONST \ ++ __attribute__((const)) ++#define G_GNUC_UNUSED \ ++ __attribute__((unused)) ++#else /* !__GNUC__ */ ++#define G_GNUC_PRINTF( format_idx, arg_idx ) ++#define G_GNUC_NORETURN ++#define G_GNUC_CONST ++#define G_GNUC_UNUSED ++#endif /* !__GNUC__ */ ++ ++/* Provide convenience macros for handling structure ++ * fields through their offsets. ++ * Project tac_plus changed "gulong" to "glong" to be able to recover ++ * original structure base from pointer to its member. + */ ++#define G_STRUCT_OFFSET(struct_type, member) \ ++ ((long) ((char*) &((struct_type*) 0)->member)) ++#define G_STRUCT_MEMBER_P(struct_p, struct_offset) \ ++ ((void*) ((char*) (struct_p) + (long) (struct_offset))) ++#define G_STRUCT_MEMBER(member_type, struct_p, struct_offset) \ ++ (*(member_type*) G_STRUCT_MEMBER_P ((struct_p), (struct_offset))) + +-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 */ ++/* reversed, in fact: for inside "member_p" calculate "struct" address */ ++#define TAC_MEMBER_STRUCT(struct_type, member_p, member) \ ++ (G_STRUCT_MEMBER(struct_type,member_p,-G_STRUCT_OFFSET(struct_type,member))) + +- 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()). ++/* may define MAX & MIN without checking whether they ++ * were previously already defined: + */ +-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 ++#ifdef HAVE_SYS_PARAM_H ++#include + #endif + +-/* Define tac_plus name for hosts.* files */ +-#ifdef TCPWRAPPER +-#define TACNAME "tac_plus" ++/* Provide definitions for some commonly used macros. ++ * Some of them are only provided if they haven't already ++ * been defined. It is assumed that if they are already ++ * defined then the current definition is correct. ++ */ ++#ifndef NULL ++#define NULL ((void*) 0) + #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; ++#undef MAX ++#define MAX(a, b) (((a) > (b)) ? (a) : (b)) + +-#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 ++#undef MIN ++#define MIN(a, b) (((a) < (b)) ? (a) : (b)) + +- u_char priv_lvl; + +-#define TAC_PLUS_PRIV_LVL_MIN 0x0 +-#define TAC_PLUS_PRIV_LVL_MAX 0xf ++/* 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 */ + +- u_char authen_type; ++#define TAC_SOURCEFILE_EMPTY \ ++ static int dummy_unused G_GNUC_UNUSED = 0; + +-#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; +- /* */ +- /* */ +- /* */ +- /* */ +-}; +- +-#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 +- +- /* */ +- /* */ +-}; +- +-#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; +- +- /* */ +- /* */ +-}; +- +-#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 */ +- +- /* */ +- /* */ +- /* */ +- /* */ +- /* */ +-}; +- +-#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; +- +- /* */ +- /* */ +- /* */ +- /* */ +-}; +- +-#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 */ +- /* */ +- /* */ +- /* */ +- /* 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 ++#define DOLLARSIGN '$' + + /* 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; ++#endif /* TAC_PLUS_H */ +diff --git a/tac_plus.pam b/tac_plus.pam +new file mode 100644 +index 0000000..fc135fb +--- /dev/null ++++ b/tac_plus.pam +@@ -0,0 +1,5 @@ ++#%PAM-1.0 ++auth required /lib/security/pam_pwdb.so shadow ++account required /lib/security/pam_pwdb.so ++password required /lib/security/pam_pwdb.so nullok use_authtok shadow ++session required /lib/security/pam_pwdb.so +diff --git a/tac_plus.spec.in b/tac_plus.spec.in +new file mode 100644 +index 0000000..6df6899 +--- /dev/null ++++ b/tac_plus.spec.in +@@ -0,0 +1,145 @@ ++# This is tac_plus rpm spec file ++ ++%define ver @VERSION@ ++%define rel 1 ++%define prefix /usr ++ ++Summary: Cisco Tacacs+ Daemon ++Name: tac_plus ++Version: %ver ++Release: %rel ++Copyright: Cisco systems, Inc. ++Group: Networking/Daemons ++Source: http://www.gazi.edu.tr/tacacs/src/tac_plus-%{ver}.tar.gz ++Url: http://www.gazi.edu.tr/tacacs/ ++Packager: Jan Kratochvil ++BuildRoot: /var/tmp/@PACKAGE@-%{ver}-%{rel}-root ++#Docdir: %{prefix}/doc ++ ++%define __libtoolize true # we don't need it, is is otherwise run automatically ++ # don't %undefine it, there is expansion bug at least in rpm-4.0-4 ++ ++%description ++TACACS+ daemon using with Cisco's NASs (Or other vendors) for AAA (Authentication , Authorization and Accounting) propose. ++ ++ ++%prep ++%setup ++ ++%build ++# configure script have some options describe below ++# --with-pam : For PAM support ++# --with-db : If you like to use db feature you must enable it ++# --with-mysql: For MySQL database support ++# --with-mysql-prefix: If you install MySQL libs other than /usr/lib ++# --enable-maxsess: For check concurrent logins (It's uses finger!!) ++# --with-pgsql With PgSQL Support ++# --with-pgsql-prefix=PREFIX PgSQL prefix [default=/usr] ++# --with-tacuid: If you like to run tac_plus specify UID ++# --with-tacgid: If you like to run tac_plus specify GID ++# --with-tacplus_pid=PREFIX Tac_plus pid file location [default=/var/run] ++# --with-libwrap[=PATH] Compile in libwrap (tcp_wrappers) support ++ ++%configure --with-pam --with-db ++make ++ ++%install ++rm -rf "$RPM_BUILD_ROOT" ++%makeinstall ++install -d "$RPM_BUILD_ROOT"/%{_sysconfdir}/{tacacs,logrotate.d,pam.d,rc.d/{init.d,rc{0,1,2,3,4,5,6}.d}} ++install -c -m 0755 tac_plus.init "$RPM_BUILD_ROOT"/etc/rc.d/init.d/tac_plus ++install -b -c -m 0644 tac_plus.pam "$RPM_BUILD_ROOT"/etc/pam.d/pap ++install -b -c -m 0644 tac_plus.rotate "$RPM_BUILD_ROOT"/etc/logrotate.d/tac_plus ++ ++%clean ++rm -rf "$RPM_BUILD_ROOT" ++ ++%post ++/sbin/chkconfig --add tac_plus ++ ++%preun ++if [ $1 = 0 ]; then ++ if [ -f /var/lock/subsys/tac_plus ]; then ++ %{_sysconfdir}/rc.d/init.d/tac_plus stop ++ fi ++ /sbin/chkconfig --del tac_plus ++fi ++ ++%files ++%defattr(-, root, root) ++%config %{_sysconfdir}/tacacs/tac_plus.cfg ++%config %{_sysconfdir}/pam.d/pap ++%config %{_sysconfdir}/logrotate.d/tac_plus ++%doc users_guide CHANGES convert.pl ++%doc README.LDAP README.PAM tac_plus.sql ++%dir %{_sysconfdir}/tacacs ++%attr(750,root,root) %{_sysconfdir}/rc.d/init.d/tac_plus ++%attr(750,root,root) %{_bindir}/generate_passwd ++%attr(750,root,root) %{_sbindir}/tac_plus ++%attr(644,root,root) %{_mandir}/man1/* ++ ++%changelog ++* Mon Jul 9 2001 Jan Kratochvil ++- following changes supported by GTS (www.gts.com), cooperation by: ++ Pavel Ruzicka ++ Michael Macek ++- multiple "member" keyword memberships supported ++- "enlist" keyword supported to specify reverse memberships ++- "host" entity unified with "user"/"group" entities ++- "when" blocks implemented for NAS host based configuration ++- "authorization = recursive" implemented for full recursivity ++- line-trailing white spaces removed ++- function prototypes cleanup and K&R C compatibility ++- maintainer compilation is pedantic now, compiler warnings cleanup ++- uncomplete transition from system-name conditions to autoconf style ++- all Makefile options moved to configure.in ++- Makefile.in rewritten to automake Makefile.am ++- autogen script included for easy maintainer rebuilds ++- tac_plus.h split to headers for each particular source file ++- system regex is now preferred, own regex is just fallback ++- several files renamed to prevent auto*/system headers conflicts ++ ++* Sun Mar 25 2001 Devrim SERAL ++- Added PostgreSQL authentication and accounting function ++- Added tcpwrapper feature ++- Added LDAP Authentication from Harpes Patrick (patrick.harpes@tudor.lu) ++- Added more options to configure script ++- Added time_limit function for control user loging time ++- And more control for buffer overflow ++ ++* Fri Nov 17 2000 Devrim SERAL ++- packet.c is pached for overflow problem ++- Fix some log files name ++- Add new config parameters for database accounting ++- MySQL authentication code is functional ++- MySQL accounting code ready but not well tested ++ ++* Mon Mar 10 2000 Devrim SERAL ++- I am add PAM patch from Max Liccardo ++- Change PAM code to authorize user ++- Add db support from fil@artelecom.ru ++- I am write MySQL authentication code ++- MySQL code is still experimental ++ ++* Tue Nov 15 1999 Devrim SERAL ++- Take out documentation ++- Add more functional parameters tac_plus script ++- Change some code to authenticate with /etc/shadow ++- Fix some file permissions (Like accounting logs file) ++ ++* Sun Oct 24 1999 D'mon ++- I moved to RedHat 6.0 =) ++- changes of the package internals! ++ ++* Mon Oct 18 1999 D'mon ++- massive remake to suit RedHat 5.2 standard ++- patch for RedHat 5.2 ++ ++* Wed Aug 4 1999 Erhan Bilgili ++- fixes for the RPM_OPT_FLAGS ++- change the buildroot to /var/tmp/tacacsd ++ ++* Wed Aug 4 1999 Devrim SERAL ++- I just re-did the spec file ++- And added Tacac FAQ ++ +diff --git a/tac_regexp.c b/tac_regexp.c +index 9c0357a..390c5f8 100644 +--- a/tac_regexp.c ++++ b/tac_regexp.c +@@ -42,9 +42,23 @@ + * precedence is structured in regular expressions. Serious changes in + * regular-expression syntax might require a total rethink. + */ ++ ++ ++#include "tac_plus.h" ++ ++#ifdef WITH_INCLUDED_REGEX ++ + #include +-#include "regexp.h" +-#include "regmagic.h" ++#include ++#include /* malloc() can be found in OR */ ++#ifdef HAVE_MALLOC_H ++#include ++#endif ++ ++#include "tac_regexp.h" ++#include "tac_regmagic.h" ++#include "report.h" /* for regerror() */ ++ + + /* + * The "internal use only" fields in regexp.h are present to pass info from +@@ -149,7 +163,7 @@ + #define UCHARAT(p) ((int)*(p)&CHARBITS) + #endif + +-#define FAIL(m) { regerror(m); return(NULL); } ++#define FAIL(m) { tac_regerror(m); return(NULL); } + #define ISMULT(c) ((c) == '*' || (c) == '+' || (c) == '?') + #define META "^$.[()|?+*\\" + +@@ -164,7 +178,7 @@ + /* + * Global work variables for regcomp(). + */ +-static char *regparse; /* Input-scan pointer. */ ++static const char *regparse; /* Input-scan pointer. */ + static int regnpar; /* () count. */ + static char regdummy; + static char *regcode; /* Code-emit pointer; ®dummy = don't. */ +@@ -173,23 +187,24 @@ 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(); ++static char *reg TAC_ARGS((int paren, int *flagp)); ++static char *regbranch TAC_ARGS((int *flagp)); ++static char *regpiece TAC_ARGS((int *flagp)); ++static char *regatom TAC_ARGS((int *flagp)); ++static char *regnode TAC_ARGS((int op)); ++static void regc TAC_ARGS((int b)); ++static void reginsert TAC_ARGS((int op, char *opnd)); ++static void regtail TAC_ARGS((char *p, char *val)); ++static void regoptail TAC_ARGS((char *p, char *val)); ++static int regtry TAC_ARGS((tac_regexp *prog, const char *string)); ++static int regmatch TAC_ARGS((char *prog)); ++static int regrepeat TAC_ARGS((char *p)); ++static char *regnext TAC_ARGS((register char *p)); ++#ifndef HAVE_STRCSPN ++static int strcspn TAC_ARGS((char *s1, char *s2)); + #endif + ++ + /* + - regcomp - compile a regular expression into internal code + * +@@ -205,16 +220,18 @@ STATIC int strcspn(); + * Beware that the optimization-preparation code in here knows about some + * of the structure of the compiled regexp. + */ +-regexp * +-regcomp(exp) +-char *exp; ++ ++tac_regexp *tac_regcomp TAC_ARGS((const char *exp)); ++ ++tac_regexp * ++tac_regcomp(exp) ++const char *exp; + { +- register regexp *r; ++ register tac_regexp *r; + register char *scan; + register char *longest; + register int len; + int flags; +- extern char *malloc(); + + if (exp == NULL) + FAIL("NULL argument"); +@@ -233,7 +250,7 @@ char *exp; + FAIL("regexp too big"); + + /* Allocate space. */ +- r = (regexp *)malloc(sizeof(regexp) + (unsigned)regsize); ++ r = (tac_regexp *)malloc(sizeof(tac_regexp) + (unsigned)regsize); + if (r == NULL) + FAIL("out of space"); + +@@ -272,7 +289,7 @@ char *exp; + longest = NULL; + len = 0; + for (; scan != NULL; scan = regnext(scan)) +- if (OP(scan) == EXACTLY && strlen(OPERAND(scan)) >= len) { ++ if (OP(scan) == EXACTLY && strlen(OPERAND(scan)) >= (unsigned)len) { + longest = OPERAND(scan); + len = strlen(OPERAND(scan)); + } +@@ -293,6 +310,9 @@ char *exp; + * 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 TAC_ARGS((int paren, int *flagp)); ++ + static char * + reg(paren, flagp) + int paren; /* Parenthesized? */ +@@ -301,7 +321,7 @@ int *flagp; + register char *ret; + register char *br; + register char *ender; +- register int parno; ++ register int parno = 0 /* GCC paranoia */; + int flags; + + *flagp = HASWIDTH; /* Tentatively. */ +@@ -365,6 +385,9 @@ int *flagp; + * + * Implements the concatenation operator. + */ ++ ++static char *regbranch TAC_ARGS((int *flagp)); ++ + static char * + regbranch(flagp) + int *flagp; +@@ -404,6 +427,9 @@ int *flagp; + * It might seem that this node could be dispensed with entirely, but the + * endmarker role is not redundant. + */ ++ ++static char *regpiece TAC_ARGS((int *flagp)); ++ + static char * + regpiece(flagp) + int *flagp; +@@ -468,6 +494,9 @@ int *flagp; + * 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 TAC_ARGS((int *flagp)); ++ + static char * + regatom(flagp) + int *flagp; +@@ -577,9 +606,12 @@ int *flagp; + /* + - regnode - emit a node + */ ++ ++static char *regnode TAC_ARGS((int op)); ++ + static char * /* Location. */ + regnode(op) +-char op; ++int op; /* promoted "char" type */ + { + register char *ret; + register char *ptr; +@@ -602,9 +634,12 @@ char op; + /* + - regc - emit (if appropriate) a byte of code + */ ++ ++static void regc TAC_ARGS((int b)); ++ + static void + regc(b) +-char b; ++int b; /* promoted "char" type */ + { + if (regcode != ®dummy) + *regcode++ = b; +@@ -617,9 +652,12 @@ char b; + * + * Means relocating the operand. + */ ++ ++static void reginsert TAC_ARGS((int op, char *opnd)); ++ + static void + reginsert(op, opnd) +-char op; ++int op; /* promoted "char" type */ + char *opnd; + { + register char *src; +@@ -646,6 +684,9 @@ char *opnd; + /* + - regtail - set the next-pointer at the end of a node chain + */ ++ ++static void regtail TAC_ARGS((char *p, char *val)); ++ + static void + regtail(p, val) + char *p; +@@ -678,6 +719,9 @@ char *val; + /* + - regoptail - regtail on operand of first argument; nop if operandless + */ ++ ++static void regoptail TAC_ARGS((char *p, char *val)); ++ + static void + regoptail(p, val) + char *p; +@@ -696,44 +740,40 @@ char *val; + /* + * 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. */ ++static const char *reginput; /* String-input pointer. */ ++static const char *regbol; /* Beginning of input, for ^ check. */ ++static const char **regstartp; /* Pointer to startp array. */ ++static const 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(); ++static char *regprop TAC_ARGS((char *op)); ++static void regdump TAC_ARGS((tac_regexp *r)); + #endif + + /* + - regexec - match a regexp against a string + */ ++ ++int tac_regexec TAC_ARGS((register tac_regexp *prog, register const char *string)); ++ + int +-regexec(prog, string) +-register regexp *prog; +-register char *string; ++tac_regexec(prog, string) ++register tac_regexp *prog; ++register const char *string; + { +- register char *s; +- extern char *strchr(); ++ register const char *s; + + /* Be paranoid... */ + if (prog == NULL || string == NULL) { +- regerror("NULL parameter"); ++ tac_regerror("NULL parameter"); + return(0); + } + + /* Check validity of program. */ + if (UCHARAT(prog->program) != MAGIC) { +- regerror("corrupted program"); ++ tac_regerror("corrupted program"); + return(0); + } + +@@ -779,14 +819,17 @@ register char *string; + /* + - regtry - try match at specific point + */ ++ ++static int regtry TAC_ARGS((tac_regexp *prog, const char *string)); ++ + static int /* 0 failure, 1 success */ + regtry(prog, string) +-regexp *prog; +-char *string; ++tac_regexp *prog; ++const char *string; + { + register int i; +- register char **sp; +- register char **ep; ++ register const char **sp; ++ register const char **ep; + + reginput = string; + regstartp = prog->startp; +@@ -816,13 +859,15 @@ char *string; + * need to know whether the rest of the match failed) by a loop instead of + * by recursion. + */ ++ ++static int regmatch TAC_ARGS((char *prog)); ++ + 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 +@@ -888,7 +933,7 @@ char *prog; + case OPEN+8: + case OPEN+9: { + register int no; +- register char *save; ++ register const char *save; + + no = OP(scan) - OPEN; + save = reginput; +@@ -916,7 +961,7 @@ char *prog; + case CLOSE+8: + case CLOSE+9: { + register int no; +- register char *save; ++ register const char *save; + + no = OP(scan) - CLOSE; + save = reginput; +@@ -935,7 +980,7 @@ char *prog; + } + break; + case BRANCH: { +- register char *save; ++ register const char *save; + + if (OP(next) != BRANCH) /* No choice. */ + next = OPERAND(scan); /* Avoid recursion. */ +@@ -956,7 +1001,7 @@ char *prog; + case PLUS: { + register char nextch; + register int no; +- register char *save; ++ register const char *save; + register int min; + + /* +@@ -985,7 +1030,7 @@ char *prog; + return(1); /* Success! */ + break; + default: +- regerror("memory corruption"); ++ tac_regerror("memory corruption"); + return(0); + break; + } +@@ -997,21 +1042,23 @@ char *prog; + * We get here only if there's trouble -- normally "case END" is + * the terminating point. + */ +- regerror("corrupted pointers"); ++ tac_regerror("corrupted pointers"); + return(0); + } + + /* + - regrepeat - repeatedly match something simple, report how many + */ ++ ++static int regrepeat TAC_ARGS((char *p)); ++ + static int + regrepeat(p) + char *p; + { + register int count = 0; +- register char *scan; ++ register const char *scan; + register char *opnd; +- extern char *strchr(); + + scan = reginput; + opnd = OPERAND(p); +@@ -1039,7 +1086,7 @@ char *p; + } + break; + default: /* Oh dear. Called inappropriately. */ +- regerror("internal foulup"); ++ tac_regerror("internal foulup"); + count = 0; /* Best compromise. */ + break; + } +@@ -1051,6 +1098,9 @@ char *p; + /* + - regnext - dig the "next" pointer out of a node + */ ++ ++static char *regnext TAC_ARGS((register char *p)); ++ + static char * + regnext(p) + register char *p; +@@ -1072,19 +1122,19 @@ register char *p; + + #ifdef DEBUG + +-STATIC char *regprop(); +- + /* + - regdump - dump a regexp onto stdout in vaguely comprehensible form + */ +-void ++ ++static void regdump TAC_ARGS((tac_regexp *r)); ++ ++static void + regdump(r) +-regexp *r; ++tac_regexp *r; + { + register char *s; + register char op = EXACTLY; /* Arbitrary non-END op. */ + register char *next; +- extern char *strchr(); + + + s = r->program + 1; +@@ -1121,6 +1171,9 @@ regexp *r; + /* + - regprop - printable representation of opcode + */ ++ ++static char *regprop TAC_ARGS((char *op)); ++ + static char * + regprop(op) + char *op; +@@ -1192,7 +1245,7 @@ char *op; + p = "PLUS"; + break; + default: +- regerror("corrupted opcode"); ++ tac_regerror("corrupted opcode"); + break; + } + if (p != NULL) +@@ -1207,12 +1260,14 @@ char *op; + * about it; at least one public-domain implementation of those (highly + * useful) string routines has been published on Usenet. + */ +-#ifdef STRCSPN ++#ifndef HAVE_STRCSPN + /* + * strcspn - find length of initial segment of s1 consisting entirely + * of characters not from s2 + */ + ++static int strcspn TAC_ARGS((char *s1, char *s2)); ++ + static int + strcspn(s1, s2) + char *s1; +@@ -1231,4 +1286,10 @@ char *s2; + } + return(count); + } +-#endif ++#endif /* HAVE_STRCSPN */ ++ ++#else /* WITH_INCLUDED_REGEX */ ++ ++TAC_SOURCEFILE_EMPTY ++ ++#endif /* WITH_INCLUDED_REGEX */ +diff --git a/tac_regexp.h b/tac_regexp.h +index b23d97e..7a1a884 100644 +--- a/tac_regexp.h ++++ b/tac_regexp.h +@@ -1,3 +1,10 @@ ++#ifndef TAC_REGEXP_H ++#define TAC_REGEXP_H 1 ++ ++#include "tac_plus.h" ++ ++#ifdef WITH_INCLUDED_REGEX ++ + /* + Copyright (c) 1995-1998 by Cisco systems, Inc. + +@@ -17,6 +24,7 @@ + FITNESS FOR A PARTICULAR PURPOSE. + */ + ++ + /* + * Definitions etc. for regexp(3) routines. + * +@@ -24,17 +32,21 @@ + * not the System V one. + */ + #define NSUBEXP 10 +-typedef struct regexp { +- char *startp[NSUBEXP]; +- char *endp[NSUBEXP]; ++typedef struct tac_regexp { ++ const char *startp[NSUBEXP]; ++ const 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; ++} tac_regexp; ++ ++ ++extern tac_regexp *tac_regcomp TAC_ARGS((const char *exp)); ++extern int tac_regexec TAC_ARGS((register tac_regexp *prog, register const char *string)); ++ ++ ++#endif /* WITH_INCLUDED_REGEX */ + +-extern regexp *regcomp(); +-extern int regexec(); +-extern void regsub(); +-extern void regerror(); ++#endif /* TAC_REGEXP_H */ +diff --git a/tac_regmagic.h b/tac_regmagic.h +index 0b18b0a..ff321df 100644 +--- a/tac_regmagic.h ++++ b/tac_regmagic.h +@@ -1,3 +1,8 @@ ++#ifndef REGMAGIC_H ++#define REGMAGIC_H 1 ++ ++#include "tac_plus.h" ++ + /* + Copyright (c) 1995-1998 by Cisco systems, Inc. + +@@ -17,8 +22,12 @@ + 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 ++ ++ ++#endif /* REGMAGIC_H */ +diff --git a/tcpwrap.c b/tcpwrap.c +index ef3d3ad..744a1df 100644 +--- a/tcpwrap.c ++++ b/tcpwrap.c +@@ -4,21 +4,41 @@ + Writen by Devrim SERAL. This file protected by + GNU Copyright agreement. + */ ++ ++ ++#include "tac_plus.h" ++ + #ifdef TCPWRAPPER ++ + #include +-#include "tac_plus.h" + +-int allow_severity = LOG_INFO; ++#include "tcpwrap.h" ++#include "report.h" ++#include "packet.h" ++#include "do_author.h" /* for "struct identity" */ ++#include "main.h" ++ ++ ++int allow_severity = LOG_INFO; /* *_severity accessed from libwrap */ + int deny_severity = LOG_WARNING; + + ++/* Configurable: ++ */ ++ ++/* Define tac_plus name for hosts.* files */ ++#define TACNAME "tac_plus" ++ ++ ++int check_from_wrap TAC_ARGS((struct identity *datap)); ++ + 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); ++ request_init(&req, RQ_FILE,session.sock,RQ_DAEMON,TACNAME,RQ_CLIENT_ADDR,datap->NAS_name , NULL); + fromhost(&req); /* validate client host info */ + if (!hosts_access(&req)) + { +@@ -34,4 +54,9 @@ struct identity *datap; + return 1; + + } ++ ++#else /* TCPWRAPPER */ ++ ++TAC_SOURCEFILE_EMPTY ++ + #endif /* TCPWRAPPER */ +diff --git a/tcpwrap.h b/tcpwrap.h +new file mode 100644 +index 0000000..e8e9455 +--- /dev/null ++++ b/tcpwrap.h +@@ -0,0 +1,16 @@ ++#ifndef TCPWRAP_H ++#define TCPWRAP_H 1 ++ ++#include "tac_plus.h" ++ ++#ifdef TCPWRAPPER ++ ++ ++struct identity; ++ ++extern int check_from_wrap TAC_ARGS((struct identity *datap)); ++ ++ ++#endif /* TCPWRAPPER */ ++ ++#endif /* TCPWRAP_H */ +diff --git a/time_limit.c b/time_limit.c +index 08f6c55..75062f2 100644 +--- a/time_limit.c ++++ b/time_limit.c +@@ -25,180 +25,224 @@ Software Foundation; either version 2, or (at your option) any later version. + + */ + +-#include"time_limit.h" ++ + #include "tac_plus.h" + +-int problem=0; ++#include ++#include ++#include ++#include ++ ++#include "time_limit.h" ++#include "report.h" ++#include "main.h" ++#include "utils.h" ++ ++ ++static int str_token_proc TAC_ARGS((char *str)); ++static int process TAC_ARGS((char *str)); ++static int time_calc TAC_ARGS((char *str, int lct)); ++static int antoi TAC_ARGS((char *str, int n)); ++ ++ ++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}; ++ ++static int problem = 0; ++ ++ ++int time_limit_process TAC_ARGS((const char *str)); + + int + time_limit_process(str) +-char *str; ++const char *str; + { +-int ret=0; +-char *tmp_str; ++ int ret=0; ++ char *tmp_str, *str_copy; + +-tmp_str=(char *)strtok(str,",|"); +-while ( tmp_str != NULL) { +- ret|=str_token_proc(tmp_str); +- tmp_str=(char *)strtok(NULL,","); ++ str_copy = tac_strdup(str); ++ tmp_str = (char *) strtok(str_copy,",|"); ++ ++ while ( tmp_str != NULL) { ++ ret |= str_token_proc(tmp_str); ++ tmp_str = (char *) strtok(NULL,","); + } +-return (ret); ++ free(str_copy); ++ ++ return (ret); + } + +-int ++static int str_token_proc TAC_ARGS((char *str)); ++ ++static int + str_token_proc(str) + char *str; + { +-int inv=0,ret; ++ int inv = 0, ret; + +-/* Pass space characters */ +-while (isspace(*str)) str++; ++ /* Pass space characters */ ++ while (isspace((int) *str)) ++ str++; + +-if (*str=='!') { +- inv=1;str++; +-} ++ if (*str=='!') { ++ inv=1; ++ str++; ++ } + +-ret=process(str); ++ ret=process(str); + +-if (problem) { ++ if (problem) { + if ( debug & DEBUG_AUTHEN_FLAG ) + report(LOG_DEBUG,"Timestamp format incorrect"); + problem=0; + return(0); +-} ++ } + +-if (inv) ++ if (inv) + ret=!ret; +-return(ret); ++ ++ return(ret); + } + + +-int ++static void str_up TAC_ARGS((char *str)); ++ ++static void ++str_up(str) ++char *str; ++{ ++ while (*str) { ++ if (islower((int) *str)) ++ *str = toupper((int) *str); ++ str++; ++ } ++} ++ ++ ++static int process TAC_ARGS((char *str)); ++ ++static int + process(str) + char *str; + { +-int count=0,ret=0,i,j,localtm; +-char *head,*buf,*gec; +-long sec; +-struct tm *tms; ++ int count = 0, ret = 0, i, j, localtm; ++ char *head, *buf, *gec; ++ time_t sec; ++ struct tm *tms; + +-/* Pass space characters */ +-while (isspace(*str)) str++; ++ /* Pass space characters */ ++ while (isspace((int) *str)) ++ str++; + +-head=str; ++ head=str; + +-/* Count alphanumeric char */ +-while (isalpha(*str)) { ++ /* Count alphanumeric char */ ++ while (isalpha((int) *str)) { + count++; + str++; +-} ++ } + +-if ( count==0 || count%2 ) { ++ if ( count==0 || count%2 ) { + problem++; + return 0; +-} ++ } + +-buf=(char *)malloc(count+1); +-strncpy(buf,head,count); +-gec=buf; +-str_up(buf); ++ buf = (char *) tac_malloc(count+1); ++ strncpy(buf, head, count); ++ gec = buf; ++ str_up(buf); + +-for(i=1;i<=(count/2);i++) { +- for (j=0;jtm_hour)*60+tms->tm_min; +-ret=( week_day_val[tms->tm_wday] & ret ) && time_calc(str,localtm); ++ sec = time(NULL); ++ 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) ++ if (ret>0) + return (1); +-else +- return(0); ++ else ++ return (0); + } + +-str_up(str) +-char *str; +-{ +- while(*str) { +- if(islower(*str)) *str=toupper(*str); +- str++; +- } +-} + +-int +-time_calc(str,lct) ++static int time_calc TAC_ARGS((char *str, int lct)); ++ ++static int ++time_calc(str, lct) + char *str; + int lct; + { +-char *t1,*t2,*head; +-int say1,say2,count=0; ++ char *t1, *t2, *head; ++ int say1, say2, count=0; + +-head=str; ++ head = str; + +- while (isdigit(*head) || *head=='-') { ++ while (isdigit((int) *head) || *head=='-') { + count++; + head++; + } + +-if (*str=='\0' || count!= TPL ) { ++ if ( *str=='\0' || count!= TPL ) { + problem++; + return (0); +-} ++ } + +- t1=(char *) malloc(count); +- strncpy(t1,str,count); /*Put str value to t1*/ ++ t1 = (char *) tac_malloc(count); ++ strncpy(t1, str, count); /* Put str value to t1 */ + +- t2=(char *) strstr(t1,"-"); /* Find next time part */ ++ t2 = (char *) strstr(t1,"-"); /* Find next time part */ + +-if (t2==NULL) { ++ if (t2==NULL) { + free(t1); + problem++; + return(0); +-} ++ } + +-*t2='\0';t2++; ++ *t2++ = '\0'; + +-if ( strlen(t1)<4 || strlen(t2)<4 ) { ++ 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); ++ } ++ say1 = antoi(t1,2)*60 + antoi(t1+2,2); ++ say2 = antoi(t2,2)*60 + antoi(t2+2,2); + +-free(t1); ++ free(t1); + +-if (say1<=say2) { +- if( (lct>=say1) && (lct<=say2) ) return(1); +-} +-else { +- if( (lct>=say1) || (lct<=say2) ) return(1); ++ if (say1 <= say2) { ++ if( (lct>=say1) && (lct<=say2) ) ++ return(1); ++ } else { ++ if( (lct>=say1) || (lct<=say2) ) ++ return(1); ++ } ++ return(0); + } +-return(0); + +-} ++static int antoi TAC_ARGS((char *str, int n)); + +-int +-antoi(str,n) +-char *str;int n; ++static int ++antoi(str, n) ++char *str; ++int n; + { +-char *buf; +-int ret; ++ char *buf; ++ int ret; + +- buf=(char *) malloc(n); +- strncpy(buf,str,n); +- ret=atoi(buf); ++ buf = (char *) tac_malloc(n); ++ strncpy(buf, str, n); ++ ret = atoi(buf); + free(buf); + +-return(ret); ++ return(ret); + } +diff --git a/time_limit.h b/time_limit.h +index e8bb6fb..eac2096 100644 +--- a/time_limit.h ++++ b/time_limit.h +@@ -1,13 +1,14 @@ +-#include +-#include +-#include +-#include +-#include ++#ifndef TIME_LIMIT_H ++#define TIME_LIMIT_H 1 ++ ++#include "tac_plus.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(); ++extern int time_limit_process TAC_ARGS((const char *str)); ++ ++ ++#endif /* TIME_LIMIT_H */ +diff --git a/users_guide b/users_guide +index 2626115..c1a8cfd 100644 +--- a/users_guide ++++ b/users_guide +@@ -337,6 +337,83 @@ 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. + ++MEMBERSHIP IN MULTIPLE GROUPS ++----------------------------- ++ ++Besides the descibed single-membership of user to some group, you may also find ++useful if user (or host) belongs to multiple groups at once. You can naturally ++specify multiple "member = group_X" commands for such user: ++ ++user = fred { ++ # fred is a member of both groups: admins_company_A, admins_company_B ++ member = admins_company_A ++ member = admins_company_B ++} ++ ++group = admins_company_A { ++ # group admins_company_A is not a member of any group ++ member = admins_company_A_privilege_X ++ member = admins_company_A_privilege_Y ++} ++group = admins_company_A_privilege_X { ++} ++group = admins_company_A_privilege_Y { ++} ++ ++group = admins_company_B { ++ # group admins_company_B is not a member of any group ++} ++ ++Here it is important to respect the ordering of "member" commands: Any ++searching for attributes/values is done by Depth-First Search - so Daemon would ++first try to look all members of admins_company_A and THEN (after it would ++failed to find any) it would start to searching through admins_company_B. The ++searching through the proposed example would be done in the following order: ++ ++ fred ++ admins_company_A ++ admins_company_A_privilege_X ++ admins_company_A_privilege_Y ++ admins_company_B ++ ++Sometimes you would want to only list some members (users, hosts or groups) but ++you don't want to specify any attributes for them. You would be able to do it: ++ ++group = city_X_NASes { ++} ++host first.NAS.X.city { ++ member = city_X_NASes ++} ++host second.NAS.X.city { ++ member = city_X_NASes ++} ++ ++But you will probably find more comfortable to use "enlist" keyword. It has ++the same functionality but it goes from the other way: ++ ++ member: current entity is connected as CHILD to the specified PARENT entity ++ enlist: specified entity is connected as CHILD to the current one as PARENT ++ ++The example would be re-written using "enlist" keyword as: ++ ++group = city_X_NASes { ++ enlist = host first.NAS.X.city ++ enlist = host second.NAS.X.city ++} ++ ++As you can see, "enlist" doesn't require the existence of the given entity as ++it would loose its primary purpose. If the entity doesn't exist it will be ++automatically created (as empty one) - this doesn't apply to "member"! Any ++argument to "member" MUST already exist. "enlist" is provided to save you from ++writing a lot of empty definition lines like: ++ ++host = first.NAS.X.city { ++} ++ ++All forward references are not a problem, you can still make membership or ++enlistment on the top of the file with the entity which will be defined on the ++end of the configuration file. ++ + CONFIGURING USER AUTHENTICATION + ------------------------------- + +@@ -1013,6 +1090,229 @@ group = admin { + } + + ++CONFIGURATION RESPECTING NAS HOST OF THE USER ++--------------------------------------------- ++ ++Sometimes you would want to modify the configuration file according to the ++source NAS where the user is being authenticated/authorized. For example if you ++are big ISP you want to permit administrator of company X to be able to monitor ++status of the links on NAS in company X but, of course, she should be able to ++monitor any other links on any other NAS of the same ISP. As all the NASes are ++authorized from the same Daemon, it seems as a problem. (You can workaround it ++by using custom authorization program - see "USING PROGRAMS TO DO ++AUTHORIZATION" section below - but it is not nice solution.) ++ ++For this purposes there exists another entity 'host': ++ ++ user wilma ++ group admin ++ host 198.133.219.25 ++ host nas.cisco.com ++ ++As you can see you may use either IP address or DNS name. Anyway, we strongly ++recommend to always use only IP addresses - DNS subsystem may fail or it may be ++forged by the enemy. ++ ++You have two methods of utilizing the differences between NASes: ++ ++1) Current user is always automatically enlisted (=given membership to) to its ++ current NAS host. This looks weird as only groups can have members but this ++ is the only exception to this rule, current NAS host can really have a ++ member: ++ ++ ( user "current_user" | user DEFAULT ) ++ | ++ +-- host "current_NAS_IP_address" ++ | | ++ | +- group DEFAULT ++ | ++ +-- host "current_NAS_hostname" ++ | | ++ | +-- group DEFAULT ++ | ++ +-- group DEFAULT ++ ++ Each link only exists in the case it there exist both its peers, of course. ++ user/group DEFAULT is written here only for the completeness of the chart. ++ DEFAULT is discussed elsewhere in this documentation. ++ ++ According to the shown ordering, attributes/values in the host of current ++ NAS identified by its IP address has _higher_ precence over the attributes ++ in the current NAS identified by its (reverse-resolved) hostname. ++ ++ According to this auto-connections, you can for example permit some command ++ to ALL the users on such NAS: ++ ++user = fred { ++ login = cleartext LLLL ++} ++host = machine.A.company { ++ cmd = write { ++ permit terminal ++ } ++} ++ ++ In this configuration file ALL the valid users can do "write terminal" when ++ logged in on NAS "machine.A.company". ++ ++2) Sometimes you need to do the authorization specific only to some users on ++ some NASes. For example to permit "write terminal" ONLY to user fred ++ connected to NAS "machine.A.company". That means that you want to forbid it ++ to user fred on any other NAS and yuo also want to forbid it to all the ++ other users on NAS "machine.A.company". (Line "authorization = recursive" is ++ required but read the following section "FULL RECURSIVITY" to know all its ++ consequences.) ++ ++authorization = recursive ++user = fred { ++ login = cleartext LLLL ++} ++host = machine.A.company { ++ when = user fred { ++ cmd = write { ++ permit terminal ++ } ++ } ++} ++ ++ This file has the same effect as: ++ ++authorization = recursive ++user = fred { ++ login = cleartext LLLL ++ when = host machine.A.company { ++ cmd = write { ++ permit terminal ++ } ++ } ++} ++ ++ You can see the (nested) command "when" can limit the scope of existence of ++ its contents. Definition of "host machine.A.company" with empty block (no ++ attributes) isn't needed as "when" line will automatically create hosts not ++ defined elsewhere, in the same style as "enlist" keyword creates them. Any ++ "user"s or "group"s referenced by "when" MUST be defined, such entities are ++ never created automatically! ++ ++ Unfortunately you cannot use "when" to limit any items, just a few of them ++ are possible: ++ ++member ++enlist ++cmd ++cmd arguments (to limit specific "permit"/"deny" lines) ++service (or incl. "protocol" specification) ++service AV pairs (to limit specific "attr=value" lines) ++when (enabling pure nesting of "when" blocks) ++ ++ Full flexibility to limit any contents may be done in future (needs complete ++ cfgfile.c rewrite) but currently it is not supported. Fortunately you can ++ get the same behaviour by appropriate usage of "member" keyword - all the ++ attributes to be conditioned are put into separate group and you limit only ++ the "member" keyword to such group. ++ ++ "when" command has the form "when = CONDITION { BLOCK }", CONDITION can be: ++ ++user USR i.e. that current user is "USR") ++host HOSTNAME i.e. that current user is on NAS "HOSTNAME") ++group GRP i.e. current user belongs to the group "GRP", ++ All possible "when" conditions to reach such belonging ++ have to be successfuly met to make this condition successful. ++ Realize that "member" can be limited by "when" keyword. ++CONDITION and CONDITION and CONDITION ... ++CONDITION or CONDITION or CONDITION ... ++not CONDITION ++( CONDITION ) ++ ++ You can see that you CANNOT use for example "user A and user B or user C", ++ such condition would have ambiguous precedence of operators, you must ++ explicitly write: ( user A and user B ) or user C ++ or: user A and ( user B or user C ) ++ ++ Both parentheses have to be written as separate words, NOT "(user A)" ++ but "( user A )" instead. ++ (Proper solution would also need the complete cfgfile.c rewrite.) ++ ++ "not" operator has the highest precendence, so: not user A or user B ++ has the same meaning as: ( not user A ) or user B ++ ++ Sometimes the "when" condition is so-called unresolvable. Example: ++ ++group = GRP { } ++user = USR { ++ when = group GRP { ++ member = GRP ++ } ++} ++ ++ It is looping, when we would be the member of GRP, we would be really the ++ member of GRP but otherwise sould wouldn't be the member of GRP. Weird? ++ Yes, such case is considered as configuration bug, it is reported as: ++ ++ Unable to resolve expression from line 243, some looping occured ++ ++ Generally such unresolvable conditional expression is considered as UNKNOWN ++ and the "when" keyword is then evaluated as "false" (=skip it's block). ++ This MAY produce unwanted privilege access (if you were conditionally ++ forbidding some privileges) so always watch out all the error messages in ++ logs! (You should generally do default deny and specifically only "permit" ++ all the privileges so the unintentional unresolvable expressions shouldn't ++ hurt you in real.) ++ ++FULL RECURSIVITY ++---------------- ++ ++We have written in the previous examples line: ++ ++authorization = recursive ++ ++This changes some behaviour of Daemon: ++ ++1) Looping of memberships: By default any looped memberships are reported as ++ error with message: recursively defined groups: ... ++ ++ After "authorization = recursive" any looping is silently accepted as it ++ would be sometimes hard to prevent it in some complex configurations. ++ Searching is done in normal Depth-First Search, but traversion is ++ backtracked one step when the entity was already visited. Simply it will ++ work 'magically', just safely ignore the fact. ++ ++2) "default service = default" is supported only with: ++ authorization = recursive ++ This isn't any real change, just some formality. In fact each entity block ++ has "default service = default" in effect as default (in the case you don't ++ write any "default service =" assign). ++ ++ "authorization = recursive" is enforced here due to the change of Daemon ++ behaviour: In non-recursive (old) case the Daemon assumes "deny .*" as the ++ last line of "cmd" block. This makes proper NAS-host based permissions ++ impossible. In "authorization = recursive" mode it requires appropriate ++ "permit" or "deny" line to apply - otherwise the Depth-First Search through ++ the entities will continue to find another applicable "cmd" block (toplevel ++ "default authorization" may get into effect as the last resort). ++ ++ "default service" keyword takes then another meaning: In non-recursive mode ++ it will mean "if the command wasn't found". In fully-recursive mode it will ++ mean "if any cmd block didn't decide". ++ ++ Please keep in mind that "service =" (with its possible "protocol =" mate) ++ has always the same behaviour, nothing is changed with "authorization = ++ recursive" enabled. Its functionality is already a bit complex (with AV ++ pairs get erased, which added, which copied, which are optional from NAS, ++ which optional from Daemon, whether to do default permit or deny etc.). ++ Fortunately we didn't found that there would be needed some extended ++ 'recursiveness' functionality of "service" for application of NAS-host based ++ authorization. ++ ++3) "host" entities are not recursive at all (=its attributes aren't looked up ++ in its parent groups) without "authorization = recursive". This isn't any ++ much change as in the previous versions of Daemon "host" entity was either ++ completely unsupported or it was supported only with the only one attribute: ++ ++ "key": Attribute defines different protocol key (instead of the specified ++ toplevel one) for the specified host. Its use is recommended to ++ improve overall network security (by using unique key for each NAS). ++ + USING PROGRAMS TO DO AUTHORIZATION + ---------------------------------- + +diff --git a/utils.c b/utils.c +index b597e70..43b50f3 100644 +--- a/utils.c ++++ b/utils.c +@@ -17,19 +17,36 @@ + FITNESS FOR A PARTICULAR PURPOSE. + */ + +-#include "tac_plus.h" + +-#ifdef STDLIB_MALLOC ++#include "tac_plus.h" + +-#include ++#include /* malloc() can be found in OR */ ++#ifdef HAVE_MALLOC_H ++#include ++#endif ++#include ++#ifdef HAVE_FCNTL_H ++#include /* for "struct flock" */ ++#endif ++#include ++#ifdef HAVE_UNISTD_H ++#include ++#endif ++#ifdef HAVE_SYSLOG_H ++#include ++#endif ++#ifdef HAVE_SYS_SYSLOG_H ++#include ++#endif + +-#else /* !STDLIB_MALLOC */ ++#include "utils.h" ++#include "report.h" ++#include "main.h" + +-#include + +-#endif /* STDLIB_MALLOC */ ++void *tac_malloc TAC_ARGS((int size)); + +-char * ++void * + tac_malloc(size) + int size; + { +@@ -49,9 +66,11 @@ int size; + return (p); + } + +-char * ++void *tac_realloc TAC_ARGS((void *ptr, int size)); ++ ++void * + tac_realloc(ptr, size) +-char *ptr; ++void *ptr; + int size; + { + char *p; +@@ -70,6 +89,9 @@ int size; + return (p); + } + ++void tac_exit TAC_ARGS((int status)) G_GNUC_NORETURN; ++ ++void + tac_exit(status) + int status; + { +@@ -78,9 +100,11 @@ int status; + exit(status); + } + ++char *tac_strdup TAC_ARGS((const char *p)); ++ + char * + tac_strdup(p) +-char *p; ++const char *p; + { + char *n = strdup(p); + +@@ -91,6 +115,8 @@ char *p; + return (n); + } + ++char *tac_make_string TAC_ARGS((u_char *p, int len)); ++ + char * + tac_make_string(p, len) + u_char *p; +@@ -116,9 +142,13 @@ int len; + /* return a pointer to the end of substring in string, or NULL. Substring + must begin at start of string. + */ +-char * ++ ++const char *tac_find_substring TAC_ARGS((const char *substring, const char *string)); ++ ++const char * + tac_find_substring(substring, string) +-char *substring, *string; ++const char *substring; ++const char *string; + { + int len; + +@@ -190,6 +220,8 @@ bcmp(s1,s2,n) + are at the mercy of SUN's lockd, which is probably a bad idea + */ + ++int tac_lockfd TAC_ARGS((char *filename, int lockfd)); ++ + int + tac_lockfd (filename, lockfd) + char *filename; +@@ -273,7 +305,8 @@ int lockfd; + are at the mercy of SUN's lockd, which is probably a bad idea + */ + +-int ++#if 0 /* unused */ ++static int + tac_unlockfd (filename,lockfd) + char *filename; + int lockfd; +@@ -303,3 +336,184 @@ int lockfd; + } + return(0); + } ++#endif /* unused */ ++ ++/* Management of bidirectional lists. ++*/ ++ ++#ifdef TAC_LIST_PARANOIA ++ ++static void tac_list_check_magic TAC_ARGS((const struct tac_list *list)); ++ ++static void ++tac_list_check_magic(list) ++const struct tac_list *list; ++{ ++ if (list->_magic != TAC_LIST_MAGIC) ++ report(LOG_ERR, "MAGIC fail for tac_list"); ++} ++ ++static void tac_list_node_check_magic TAC_ARGS((const struct tac_list_node *node)); ++ ++static void ++tac_list_node_check_magic(node) ++const struct tac_list_node *node; ++{ ++ if (node->_magic != TAC_LIST_NODE_MAGIC) ++ report(LOG_ERR, "MAGIC fail for tac_list_node"); ++} ++#else /* TAC_LIST_PARANOIA */ ++ ++#define tac_list_check_magic(list) ++#define tac_list_node_check_magic(node) ++ ++#endif /* TAC_LIST_PARANOIA */ ++ ++ ++void tac_list_init TAC_ARGS((struct tac_list *list)); ++ ++void ++tac_list_init(list) ++struct tac_list *list; ++{ ++#ifdef TAC_LIST_PARANOIA ++ list->_magic = TAC_LIST_MAGIC; ++#endif ++ list->_head = NULL; ++ list->_tail = NULL; ++ ++ tac_list_check_magic(list); ++} ++ ++void tac_list_node_init TAC_ARGS((struct tac_list_node *node)); ++ ++void ++tac_list_node_init(node) ++struct tac_list_node *node; ++{ ++#ifdef TAC_LIST_PARANOIA ++ node->_magic = TAC_LIST_NODE_MAGIC; ++#endif ++ node->_list = NULL; ++ ++ tac_list_node_check_magic(node); ++} ++ ++struct tac_list *tac_list_node_get_list TAC_ARGS((struct tac_list_node *node)); ++ ++struct tac_list * ++tac_list_node_get_list(node) ++struct tac_list_node *node; ++{ ++ tac_list_node_check_magic(node); ++ ++ return (node->_list); ++} ++ ++struct tac_list_node *tac_list_first_node TAC_ARGS((struct tac_list *list)); ++ ++struct tac_list_node * ++tac_list_first_node(list) ++struct tac_list *list; ++{ ++ tac_list_check_magic(list); ++ ++ return (list->_head); ++} ++ ++#if 0 /* unused */ ++struct tac_list_node *tac_list_last_node TAC_ARGS((struct tac_list *list)); ++ ++struct tac_list_node * ++tac_list_last_node(list) ++struct tac_list *list; ++{ ++ tac_list_check_magic(list); ++ ++ return (list->_tail); ++} ++#endif /* unused */ ++ ++void tac_list_addhead TAC_ARGS((struct tac_list *list, struct tac_list_node *node)); ++ ++void ++tac_list_addhead(list, node) ++struct tac_list *list; ++struct tac_list_node *node; ++{ ++ tac_list_check_magic(list); ++ tac_list_node_check_magic(node); ++ ++ if ((node->_next = list->_head)) ++ node->_next->_prev = node; ++ list->_head = node; ++ node->_prev = NULL; ++ if (!list->_tail) ++ list->_tail = node; ++ node->_list = list; ++} ++ ++void tac_list_addtail TAC_ARGS((struct tac_list *list, struct tac_list_node *node)); ++ ++void ++tac_list_addtail(list, node) ++struct tac_list *list; ++struct tac_list_node *node; ++{ ++ tac_list_check_magic(list); ++ tac_list_node_check_magic(node); ++ ++ if ((node->_prev = list->_tail)) ++ node->_prev->_next = node; ++ list->_tail = node; ++ node->_next = NULL; ++ if (!list->_head) ++ list->_head = node; ++ node->_list = list; ++} ++ ++struct tac_list_node *tac_list_node_next TAC_ARGS((struct tac_list_node *node)); ++ ++struct tac_list_node * ++tac_list_node_next(node) ++struct tac_list_node *node; ++{ ++ tac_list_node_check_magic(node); ++ ++ return (node->_next); ++} ++ ++#if 0 /* unused */ ++struct tac_list_node *tac_list_node_prev TAC_ARGS((struct tac_list_node *node)); ++ ++struct tac_list_node * ++tac_list_node_prev(node) ++struct tac_list_node *node; ++{ ++ tac_list_node_check_magic(node); ++ ++ return (node->_prev); ++} ++#endif /* unused */ ++ ++void tac_list_node_remove TAC_ARGS((struct tac_list_node *node)); ++ ++void ++tac_list_node_remove(node) ++struct tac_list_node *node; ++{ ++ tac_list_node_check_magic(node); ++ tac_list_check_magic(node->_list); ++ ++ if (node->_next) ++ node->_next->_prev = node->_prev; ++ else ++ node->_list->_tail = node->_prev; ++ ++ if (node->_prev) ++ node->_prev->_next = node->_next; ++ else ++ node->_list->_head = node->_next; ++ ++ node->_list = NULL; ++} +diff --git a/utils.h b/utils.h +new file mode 100644 +index 0000000..f8c39e8 +--- /dev/null ++++ b/utils.h +@@ -0,0 +1,59 @@ ++#ifndef UTILS_H ++#define UTILS_H 1 ++ ++#include "tac_plus.h" ++ ++#include /* for u_* */ ++ ++ ++/* Configurable: ++ */ ++#define TAC_LIST_PARANOIA 1 ++ ++ ++/* Bidirectional lists */ ++ ++#ifdef TAC_LIST_PARANOIA ++#define TAC_LIST_MAGIC (0xBEF78147U) ++#define TAC_LIST_NODE_MAGIC (0x297DA735U) ++#endif ++ ++struct tac_list { ++#ifdef TAC_LIST_PARANOIA ++ unsigned _magic; ++#endif ++ struct tac_list_node *_head, *_tail; ++}; ++ ++struct tac_list_node { ++#ifdef TAC_LIST_PARANOIA ++ unsigned _magic; ++#endif ++ struct tac_list_node *_next, *_prev; ++ struct tac_list *_list; ++}; ++ ++ ++extern void *tac_malloc TAC_ARGS((int size)); ++extern void *tac_realloc TAC_ARGS((void *ptr, int size)); ++extern void tac_exit TAC_ARGS((int status)) G_GNUC_NORETURN; ++extern char *tac_strdup TAC_ARGS((const char *p)); ++extern char *tac_make_string TAC_ARGS((u_char *p, int len)); ++extern const char *tac_find_substring TAC_ARGS((const char *substring, const char *string)); ++extern int tac_lockfd TAC_ARGS((char *filename, int lockfd)); ++ ++/* Bidirectional lists: */ ++ ++extern void tac_list_init TAC_ARGS((struct tac_list *list)); ++extern void tac_list_node_init TAC_ARGS((struct tac_list_node *node)); ++extern struct tac_list *tac_list_node_get_list TAC_ARGS((struct tac_list_node *node)); ++struct tac_list_node *tac_list_first_node TAC_ARGS((struct tac_list *list)); ++struct tac_list_node *tac_list_last_node TAC_ARGS((struct tac_list *list)); ++extern void tac_list_addhead TAC_ARGS((struct tac_list *list, struct tac_list_node *node)); ++extern void tac_list_addtail TAC_ARGS((struct tac_list *list, struct tac_list_node *node)); ++extern struct tac_list_node *tac_list_node_next TAC_ARGS((struct tac_list_node *node)); ++extern struct tac_list_node *tac_list_node_prev TAC_ARGS((struct tac_list_node *node)); ++extern void tac_list_node_remove TAC_ARGS((struct tac_list_node *node)); ++ ++ ++#endif /* UTILS_H */ diff --git a/project/tac_plus/tac_plus-F4.0.3.alpha.8.gts4.diff.gz b/project/tac_plus/tac_plus-F4.0.3.alpha.8.gts4.diff.gz deleted file mode 100644 index a6b722cb904f9dfa04e5ef264703d0057e7966df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 194171 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%0D@AH0XIGtxPIFpJ^wQs4!Wa} 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

V}t*p4aK3tPVL7R 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­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 Qo6=GGf52ugRdsI+0B+0VKmY&$ -- 1.8.3.1