Integrated Kostas Kalevras' rlm_counter code, plus sundry fixes.
authorcmiller <cmiller>
Sun, 1 Jul 2001 23:51:07 +0000 (23:51 +0000)
committercmiller <cmiller>
Sun, 1 Jul 2001 23:51:07 +0000 (23:51 +0000)
debian/changelog
raddb/radiusd.conf.in
src/modules/rlm_counter/config.h.in [new file with mode: 0644]
src/modules/rlm_counter/configure
src/modules/rlm_counter/configure.in
src/modules/rlm_counter/rlm_counter.c

index 93a399c..7334050 100644 (file)
@@ -8,6 +8,7 @@ radiusd-freeradius (?) unstable; urgency=low
   * Added user 'freerad' into the 'shadow' group.
   * Fixed UUCP-style of restricting time of log-in.
   * Changed debugging messages to give more info about execution flow.
+  * Better counter module.  
 
  -- Chad Miller <cmiller@debian.org>  [TBA]
 
index 2f1ba20..579dd17 100644 (file)
@@ -595,7 +595,7 @@ modules {
        #  file used to access that counter.  e.g.
        #
        #  DEFAULT  Daily-Session-Time > 3600, Auth-Type = Reject
-       #           Reply-Message = "You've used up more than one hour today"
+       #      Reply-Message = "You've used up more than one hour today"
        #
        counter {
                filename = ${raddbdir}/db.counter
@@ -603,9 +603,11 @@ modules {
                count-attribute = Acct-Session-Time
                reset = daily
                counter-name = Daily-Session-Time
+               check-name = Max-Daily-Session
+               allowed-servicetype = Framed-User
+               cache-size = 5000
        }
 
-       #
        # The "always" module is here for debugging purposes. Each instance simply
        # returns the same result, always, without doing anything.
        #
diff --git a/src/modules/rlm_counter/config.h.in b/src/modules/rlm_counter/config.h.in
new file mode 100644 (file)
index 0000000..d6cd425
--- /dev/null
@@ -0,0 +1,2 @@
+
+#undef NEEDS_GDBM_SYNC
index 6b14c6d..88457f4 100755 (executable)
@@ -526,25 +526,105 @@ fi
 
 if test x$with_rlm_counter != xno; then
 
+       echo $ac_n "checking how to run the C preprocessor""... $ac_c" 1>&6
+echo "configure:531: checking how to run the C preprocessor" >&5
+# On Suns, sometimes $CPP names a directory.
+if test -n "$CPP" && test -d "$CPP"; then
+  CPP=
+fi
+if test -z "$CPP"; then
+if eval "test \"`echo '$''{'ac_cv_prog_CPP'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+    # This must be in double quotes, not single quotes, because CPP may get
+  # substituted into the Makefile and "${CC-cc}" will confuse make.
+  CPP="${CC-cc} -E"
+  # On the NeXT, cc -E runs the code through the compiler's parser,
+  # not just through cpp.
+  cat > conftest.$ac_ext <<EOF
+#line 546 "configure"
+#include "confdefs.h"
+#include <assert.h>
+Syntax Error
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:552: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+  :
+else
+  echo "$ac_err" >&5
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  CPP="${CC-cc} -E -traditional-cpp"
+  cat > conftest.$ac_ext <<EOF
+#line 563 "configure"
+#include "confdefs.h"
+#include <assert.h>
+Syntax Error
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:569: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+  :
+else
+  echo "$ac_err" >&5
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  CPP="${CC-cc} -nologo -E"
+  cat > conftest.$ac_ext <<EOF
+#line 580 "configure"
+#include "confdefs.h"
+#include <assert.h>
+Syntax Error
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:586: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+  :
+else
+  echo "$ac_err" >&5
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  CPP=/lib/cpp
+fi
+rm -f conftest*
+fi
+rm -f conftest*
+fi
+rm -f conftest*
+  ac_cv_prog_CPP="$CPP"
+fi
+  CPP="$ac_cv_prog_CPP"
+else
+  ac_cv_prog_CPP="$CPP"
+fi
+echo "$ac_t""$CPP" 1>&6
+
        
 
 ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'`
 echo $ac_n "checking for gdbm.h""... $ac_c" 1>&6
-echo "configure:534: checking for gdbm.h" >&5
+echo "configure:614: checking for gdbm.h" >&5
 
 smart_include=
 smart_include_dir=
 
   old_CFLAGS="$CFLAGS"
   cat > conftest.$ac_ext <<EOF
-#line 541 "configure"
+#line 621 "configure"
 #include "confdefs.h"
 #include <gdbm.h>
 int main() {
  int a = 1;
 ; return 0; }
 EOF
-if { (eval echo configure:548: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+if { (eval echo configure:628: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
   rm -rf conftest*
   smart_include=" "
 else
@@ -588,14 +668,14 @@ eval "smart_include_dir=\"\$smart_include_dir $DIRS\""
       CFLAGS="$old_CFLAGS -I$try"
 
       cat > conftest.$ac_ext <<EOF
-#line 592 "configure"
+#line 672 "configure"
 #include "confdefs.h"
 #include <gdbm.h>
 int main() {
  int a = 1;
 ; return 0; }
 EOF
-if { (eval echo configure:599: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+if { (eval echo configure:679: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
   rm -rf conftest*
   smart_include="-I$try"
 else
@@ -625,7 +705,7 @@ fi
        
 
 echo $ac_n "checking for gdbm_open in -lgdbm""... $ac_c" 1>&6
-echo "configure:629: checking for gdbm_open in -lgdbm" >&5
+echo "configure:709: checking for gdbm_open in -lgdbm" >&5
 
 smart_lib=
 smart_lib_dir=
@@ -633,14 +713,14 @@ smart_lib_dir=
   old_LIBS="$LIBS"
   LIBS="$LIBS -lgdbm"
   cat > conftest.$ac_ext <<EOF
-#line 637 "configure"
+#line 717 "configure"
 #include "confdefs.h"
 extern char gdbm_open();
 int main() {
  gdbm_open()
 ; return 0; }
 EOF
-if { (eval echo configure:644: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+if { (eval echo configure:724: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
   rm -rf conftest*
   smart_lib="-lgdbm"
 else
@@ -709,14 +789,14 @@ eval "smart_lib_dir=\"\$smart_lib_dir $DIRS\""
       LIBS="$old_LIBS -L$try -lgdbm"
 
       cat > conftest.$ac_ext <<EOF
-#line 713 "configure"
+#line 793 "configure"
 #include "confdefs.h"
 extern char gdbm_open();
 int main() {
  gdbm_open()
 ; return 0; }
 EOF
-if { (eval echo configure:720: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+if { (eval echo configure:800: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
   rm -rf conftest*
   smart_lib="-L$try -lgdbm"
 else
@@ -741,8 +821,41 @@ rm -f conftest*
 fi
 
        if test "x$ac_cv_lib_gdbm_gdbm_open" != "xyes"; then
-          fail="$fail libgdbm"
-        fi
+               fail="$fail libgdbm"
+       fi
+
+       echo $ac_n "checking to see GDBM_SYNC status""... $ac_c" 1>&6
+echo "configure:829: checking to see GDBM_SYNC status" >&5
+       cat > conftest.$ac_ext <<EOF
+#line 831 "configure"
+#include "confdefs.h"
+
+#include <gdbm.h>
+#ifdef GDBM_SYNC
+       found-gdbm-sync!
+#else
+       not found.  this version must use sync by default.
+#endif
+               
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+  egrep "found-gdbm-sync" >/dev/null 2>&1; then
+  rm -rf conftest*
+  
+                       cat >> confdefs.h <<\EOF
+#define NEED_GDBM_SYNC yes
+EOF
+                       echo "$ac_t""needs it." 1>&6
+               
+else
+  rm -rf conftest*
+  
+                       echo "$ac_t""SYNCs by default." 1>&6
+               
+fi
+rm -f conftest*
+
 
        targetname=rlm_counter
 else
@@ -765,6 +878,7 @@ counter_cflags=$SMART_CFLAGS
 
 
 
+
 trap '' 1 2 15
 cat > confcache <<\EOF
 # This file is a shell script that caches the results of configure
@@ -828,19 +942,7 @@ fi
 
 trap 'rm -f $CONFIG_STATUS conftest*; exit 1' 1 2 15
 
-# Transform confdefs.h into DEFS.
-# Protect against shell expansion while executing Makefile rules.
-# Protect against Makefile macro expansion.
-cat > conftest.defs <<\EOF
-s%#define \([A-Za-z_][A-Za-z0-9_]*\) *\(.*\)%-D\1=\2%g
-s%[    `~#$^&*(){}\\|;'"<>?]%\\&%g
-s%\[%\\&%g
-s%\]%\\&%g
-s%\$%$$%g
-EOF
-DEFS=`sed -f conftest.defs confdefs.h | tr '\012' ' '`
-rm -f conftest.defs
-
+DEFS=-DHAVE_CONFIG_H
 
 # Without the "./", some shells look in PATH for config.status.
 : ${CONFIG_STATUS=./config.status}
@@ -877,7 +979,7 @@ done
 
 ac_given_srcdir=$srcdir
 
-trap 'rm -fr `echo "Makefile" | sed "s/:[^ ]*//g"` conftest*; exit 1' 1 2 15
+trap 'rm -fr `echo "Makefile config.h" | sed "s/:[^ ]*//g"` conftest*; exit 1' 1 2 15
 EOF
 cat >> $CONFIG_STATUS <<EOF
 
@@ -909,6 +1011,7 @@ s%@includedir@%$includedir%g
 s%@oldincludedir@%$oldincludedir%g
 s%@infodir@%$infodir%g
 s%@mandir@%$mandir%g
+s%@CPP@%$CPP%g
 s%@counter_ldflags@%$counter_ldflags%g
 s%@counter_cflags@%$counter_cflags%g
 s%@targetname@%$targetname%g
@@ -1007,6 +1110,113 @@ s%@top_srcdir@%$top_srcdir%g
 fi; done
 rm -f conftest.s*
 
+# These sed commands are passed to sed as "A NAME B NAME C VALUE D", where
+# NAME is the cpp macro being defined and VALUE is the value it is being given.
+#
+# ac_d sets the value in "#define NAME VALUE" lines.
+ac_dA='s%^\([  ]*\)#\([        ]*define[       ][      ]*\)'
+ac_dB='\([     ][      ]*\)[^  ]*%\1#\2'
+ac_dC='\3'
+ac_dD='%g'
+# ac_u turns "#undef NAME" with trailing blanks into "#define NAME VALUE".
+ac_uA='s%^\([  ]*\)#\([        ]*\)undef\([    ][      ]*\)'
+ac_uB='\([     ]\)%\1#\2define\3'
+ac_uC=' '
+ac_uD='\4%g'
+# ac_e turns "#undef NAME" without trailing blanks into "#define NAME VALUE".
+ac_eA='s%^\([  ]*\)#\([        ]*\)undef\([    ][      ]*\)'
+ac_eB='$%\1#\2define\3'
+ac_eC=' '
+ac_eD='%g'
+
+if test "${CONFIG_HEADERS+set}" != set; then
+EOF
+cat >> $CONFIG_STATUS <<EOF
+  CONFIG_HEADERS="config.h"
+EOF
+cat >> $CONFIG_STATUS <<\EOF
+fi
+for ac_file in .. $CONFIG_HEADERS; do if test "x$ac_file" != x..; then
+  # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in".
+  case "$ac_file" in
+  *:*) ac_file_in=`echo "$ac_file"|sed 's%[^:]*:%%'`
+       ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;;
+  *) ac_file_in="${ac_file}.in" ;;
+  esac
+
+  echo creating $ac_file
+
+  rm -f conftest.frag conftest.in conftest.out
+  ac_file_inputs=`echo $ac_file_in|sed -e "s%^%$ac_given_srcdir/%" -e "s%:% $ac_given_srcdir/%g"`
+  cat $ac_file_inputs > conftest.in
+
+EOF
+
+# Transform confdefs.h into a sed script conftest.vals that substitutes
+# the proper values into config.h.in to produce config.h.  And first:
+# Protect against being on the right side of a sed subst in config.status.
+# Protect against being in an unquoted here document in config.status.
+rm -f conftest.vals
+cat > conftest.hdr <<\EOF
+s/[\\&%]/\\&/g
+s%[\\$`]%\\&%g
+s%#define \([A-Za-z_][A-Za-z0-9_]*\) *\(.*\)%${ac_dA}\1${ac_dB}\1${ac_dC}\2${ac_dD}%gp
+s%ac_d%ac_u%gp
+s%ac_u%ac_e%gp
+EOF
+sed -n -f conftest.hdr confdefs.h > conftest.vals
+rm -f conftest.hdr
+
+# This sed command replaces #undef with comments.  This is necessary, for
+# example, in the case of _POSIX_SOURCE, which is predefined and required
+# on some systems where configure will not decide to define it.
+cat >> conftest.vals <<\EOF
+s%^[   ]*#[    ]*undef[        ][      ]*[a-zA-Z_][a-zA-Z_0-9]*%/* & */%
+EOF
+
+# Break up conftest.vals because some shells have a limit on
+# the size of here documents, and old seds have small limits too.
+
+rm -f conftest.tail
+while :
+do
+  ac_lines=`grep -c . conftest.vals`
+  # grep -c gives empty output for an empty file on some AIX systems.
+  if test -z "$ac_lines" || test "$ac_lines" -eq 0; then break; fi
+  # Write a limited-size here document to conftest.frag.
+  echo '  cat > conftest.frag <<CEOF' >> $CONFIG_STATUS
+  sed ${ac_max_here_lines}q conftest.vals >> $CONFIG_STATUS
+  echo 'CEOF
+  sed -f conftest.frag conftest.in > conftest.out
+  rm -f conftest.in
+  mv conftest.out conftest.in
+' >> $CONFIG_STATUS
+  sed 1,${ac_max_here_lines}d conftest.vals > conftest.tail
+  rm -f conftest.vals
+  mv conftest.tail conftest.vals
+done
+rm -f conftest.vals
+
+cat >> $CONFIG_STATUS <<\EOF
+  rm -f conftest.frag conftest.h
+  echo "/* $ac_file.  Generated automatically by configure.  */" > conftest.h
+  cat conftest.in >> conftest.h
+  rm -f conftest.in
+  if cmp -s $ac_file conftest.h 2>/dev/null; then
+    echo "$ac_file is unchanged"
+    rm -f conftest.h
+  else
+    # Remove last slash and all that follows it.  Not all systems have dirname.
+      ac_dir=`echo $ac_file|sed 's%/[^/][^/]*$%%'`
+      if test "$ac_dir" != "$ac_file" && test "$ac_dir" != .; then
+      # The file is in a subdirectory.
+      test ! -d "$ac_dir" && mkdir "$ac_dir"
+    fi
+    rm -f $ac_file
+    mv conftest.h $ac_file
+  fi
+fi; done
+
 EOF
 cat >> $CONFIG_STATUS <<EOF
 
index a127586..dbf678e 100644 (file)
@@ -4,11 +4,27 @@ AC_DEFUN(modname,[rlm_counter])
 
 if test x$with_[]modname != xno; then
 
+       AC_PROG_CPP
        AC_SMART_CHECK_INCLUDE(gdbm.h)
        AC_SMART_CHECK_LIB(gdbm, gdbm_open)
        if test "x$ac_cv_lib_gdbm_gdbm_open" != "xyes"; then
-          fail="$fail libgdbm"
-        fi
+               fail="$fail libgdbm"
+       fi
+
+       AC_MSG_CHECKING(to see GDBM_SYNC status)
+       AC_EGREP_CPP(found-gdbm-sync, [
+#include <gdbm.h>
+#ifdef GDBM_SYNC
+       found-gdbm-sync!
+#else
+       not found.  this version must use sync by default.
+#endif
+               ], [
+                       AC_DEFINE(NEED_GDBM_SYNC, yes) 
+                       AC_MSG_RESULT(needs it.)
+               ], [
+                       AC_MSG_RESULT(SYNCs by default.)
+               ])
 
        targetname=modname
 else
@@ -31,4 +47,5 @@ counter_cflags=$SMART_CFLAGS
 AC_SUBST(counter_ldflags)
 AC_SUBST(counter_cflags)
 AC_SUBST(targetname)
+AC_CONFIG_HEADER(config.h)
 AC_OUTPUT(Makefile)
index d9d3ed5..57b0c85 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * rlm_counter.c
  *
- * Version:    $Id$
+ * Version:  $Id$
  *
  *   This program is free software; you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License as published by
  *
  * Copyright 2001  The FreeRADIUS server project
  * Copyright 2001  Alan DeKok <aland@ox.org>
+ * Copyright 2001  Kostas Kalevras <kkalev@noc.ntua.gr>
  */
 
+#include "config.h"
 #include "autoconf.h"
+#include "libradius.h"
 
 #include <stdio.h>
 #include <stdlib.h>
 #include "conffile.h"
 
 #include <gdbm.h>
+#include <time.h>
+
+#ifdef NEEDS_GDBM_SYNC
+#      define GDBM_SYNCOPT GDBM_SYNC
+#else
+#      define GDBM_SYNCOPT 0
+#endif
+
 
 static const char rcsid[] = "$Id$";
 
@@ -43,16 +54,20 @@ static const char rcsid[] = "$Id$";
  *     be used as the instance handle.
  */
 typedef struct rlm_counter_t {
-       char    *filename;      /* name of the database file */
-       char    *reset;         /* daily, weekly, monthly */
-       char    *key_name;      /* User-Name */
-       char    *count_attribute;    /* Acct-Session-Time */
-       char    *counter_name;    /* Daily-Session-Time */
-       int     key_attr;
-       int     count_attr;
-       time_t  reset_time;
-       time_t  reset_count;
-       int     dict_attr;      /* attribute number for the counter. */
+       char *filename;  /* name of the database file */
+       char *reset;  /* daily, weekly, monthly */
+       char *key_name;  /* User-Name */
+       char *count_attribute;  /* Acct-Session-Time */
+       char *counter_name;  /* Daily-Session-Time */
+       char *check_name;  /* Daily-Max-Session */
+       char *service_type;  /* Service-Type to search for */
+       int cache_size;
+       int service_val;
+       int key_attr;
+       int count_attr;
+       time_t reset_time;
+       time_t last_reset;
+       int dict_attr;  /* attribute number for the counter. */
        GDBM_FILE gdbm;
 } rlm_counter_t;
 
@@ -66,21 +81,23 @@ typedef struct rlm_counter_t {
  *     buffer over-flows.
  */
 static CONF_PARSER module_config[] = {
-  { "filename",  PW_TYPE_STRING_PTR, offsetof(rlm_counter_t,filename), NULL,  NULL},
-  { "key",       PW_TYPE_STRING_PTR, offsetof(rlm_counter_t,key_name), NULL,  NULL},
-  { "reset",     PW_TYPE_STRING_PTR, offsetof(rlm_counter_t,reset), NULL,  NULL},
-  { "count-attribute",  PW_TYPE_STRING_PTR, offsetof(rlm_counter_t,count_attribute), NULL,  NULL},
-  { "counter-name",  PW_TYPE_STRING_PTR, offsetof(rlm_counter_t,counter_name), NULL,  NULL},
-  { NULL, -1, 0, NULL, NULL }          /* end the list */
+  { "filename", PW_TYPE_STRING_PTR, offsetof(rlm_counter_t,filename), NULL, NULL },
+  { "key", PW_TYPE_STRING_PTR, offsetof(rlm_counter_t,key_name), NULL, NULL },
+  { "reset", PW_TYPE_STRING_PTR, offsetof(rlm_counter_t,reset), NULL,  NULL },
+  { "count-attribute", PW_TYPE_STRING_PTR, offsetof(rlm_counter_t,count_attribute), NULL, NULL },
+  { "counter-name", PW_TYPE_STRING_PTR, offsetof(rlm_counter_t,counter_name), NULL,  NULL },
+  { "check-name", PW_TYPE_STRING_PTR, offsetof(rlm_counter_t,check_name), NULL, NULL },
+  { "allowed-servicetype", PW_TYPE_STRING_PTR, offsetof(rlm_counter_t,service_type),NULL, NULL },
+  { "cache-size", PW_TYPE_INTEGER, offsetof(rlm_counter_t,cache_size), NULL, "1000" },
+  { NULL, -1, 0, NULL, NULL }
 };
 
-#define SECONDS_PER_WEEK (SECONDS_PER_DAY * 7)
 
 /*
  *     See if the counter matches.
  */
 static int counter_cmp(void *instance, VALUE_PAIR *request, VALUE_PAIR *check,
-                      VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs)
+               VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs)
 {
        rlm_counter_t *data = (rlm_counter_t *) instance;
        datum key_datum;
@@ -95,7 +112,7 @@ static int counter_cmp(void *instance, VALUE_PAIR *request, VALUE_PAIR *check,
         *      Find the key attribute.
         */
        key_vp = pairfind(request, data->key_attr);
-       if (!key_vp) {
+       if (key_vp == NULL) {
                return RLM_MODULE_NOOP;
        }
 
@@ -111,6 +128,50 @@ static int counter_cmp(void *instance, VALUE_PAIR *request, VALUE_PAIR *check,
        return counter - check->lvalue;
 }
 
+
+static int find_next_reset(rlm_counter_t *data, time_t timeval)
+{
+       int ret=0;
+       struct tm *tm=NULL;
+
+       tm = localtime(&timeval);
+       tm->tm_sec = tm->tm_min = 0;
+
+       if (strcmp(data->reset, "hourly") == 0) {
+               /*
+                *  Round up to the next nearest hour.
+                */
+               tm->tm_hour++;
+               data->reset_time = mktime(tm);
+       } else if (strcmp(data->reset, "daily") == 0) {
+               /*
+                *  Round up to the next nearest day.
+                */
+               tm->tm_hour = 0;
+               tm->tm_mday++;
+               data->reset_time = mktime(tm);
+       } else if (strcmp(data->reset, "weekly") == 0) {
+               /*
+                *  Round up to the next nearest week.
+                */
+               tm->tm_hour = 0;
+               tm->tm_mday += (7 - tm->tm_wday);
+               data->reset_time = mktime(tm);
+       } else if (strcmp(data->reset, "monthly") == 0) {
+               tm->tm_hour = 0;
+               tm->tm_mday = 1;
+               tm->tm_mon++;
+               data->reset_time = mktime(tm);
+       } else {
+               radlog(L_ERR, "rlm_counter: Unknown reset timer \"%s\"",
+                               data->reset);
+               ret=-1;
+       }
+
+       return ret;
+}
+
+
 /*
  *     Do any per-module initialization that is separate to each
  *     configured instance of the module.  e.g. set up connections
@@ -125,7 +186,9 @@ static int counter_instantiate(CONF_SECTION *conf, void **instance)
 {
        rlm_counter_t *data;
        DICT_ATTR *dattr;
+       DICT_VALUE *dval;
        time_t now;
+       int cache_size;
        
        /*
         *      Set up a storage area for instance data
@@ -140,14 +203,19 @@ static int counter_instantiate(CONF_SECTION *conf, void **instance)
                free(data);
                return -1;
        }
+       cache_size = data->cache_size;
 
        /*
         *      Discover the attribute number of the key. 
         */
+       if (data->key_name == NULL) {
+               radlog(L_ERR, "rlm_counter: 'key' must be set.");
+               exit(0);
+       }
        dattr = dict_attrbyname(data->key_name);
-       if (!dattr) {
+       if (dattr == NULL) {
                radlog(L_ERR, "rlm_counter: No such attribute %s",
-                      data->key_name);
+                               data->key_name);
                return -1;
        }
        data->key_attr = dattr->attr;
@@ -155,10 +223,14 @@ static int counter_instantiate(CONF_SECTION *conf, void **instance)
        /*
         *      Discover the attribute number of the counter. 
         */
+       if (data->count_attribute == NULL) {
+               radlog(L_ERR, "rlm_counter: 'count-attribute' must be set.");
+               exit(0);
+       }
        dattr = dict_attrbyname(data->count_attribute);
-       if (!dattr) {
+       if (dattr == NULL) {
                radlog(L_ERR, "rlm_counter: No such attribute %s",
-                      data->count_attribute);
+                               data->count_attribute);
                return -1;
        }
        data->count_attr = dattr->attr;
@@ -166,63 +238,80 @@ static int counter_instantiate(CONF_SECTION *conf, void **instance)
        /*
         *  Create a new attribute for the counter.
         */
+       if (data->counter_name == NULL) {
+               radlog(L_ERR, "rlm_counter: 'counter-name' must be set.");
+               exit(0);
+       }
        dict_addattr(data->counter_name, 0, PW_TYPE_INTEGER, -1);
        dattr = dict_attrbyname(data->counter_name);
-       if (!dattr) {
+       if (dattr == NULL) {
                radlog(L_ERR, "rlm_counter: Failed to create counter attribute %s",
-                      data->counter_name);
+                               data->counter_name);
                return -1;
        }
        data->dict_attr = dattr->attr;
-       DEBUG2("Counter attribute %s is number %d",
-              data->counter_name, data->dict_attr);
+       DEBUG2("rlm_counter: Counter attribute %s is number %d",
+                       data->counter_name, data->dict_attr);
+
+       /*
+        * Create a new attribute for the check item.
+        */
+       if (data->check_name == NULL) {
+               radlog(L_ERR, "rlm_counter: 'check-name' must be set.");
+               exit(0);
+       }
+       dict_addattr(data->check_name, 0, PW_TYPE_INTEGER, -1);
+       dattr = dict_attrbyname(data->check_name);
+       if (dattr == NULL) {
+               radlog(L_ERR, "rlm_counter: Failed to create check attribute %s",
+                               data->counter_name);
+               return -1;
+       }
+
+       /*
+        * Find the attribute for the allowed protocol
+        */
+       if (data->service_type == NULL) {
+               radlog(L_ERR, "rlm_counter: 'allowed-servicetype' must be set.");
+               exit(0);
+       }
+       if (data->service_type != NULL) {
+               if ((dval = dict_valbyname(PW_SERVICE_TYPE, data->service_type)) == NULL) {
+                       radlog(L_ERR, "rlm_counter: Failed to find attribute number for %s",
+                                       data->service_type);
+                       return -1;
+               }
+               data->service_val = dval->value;
+       }       
 
        /*
         *  Discover when next to reset the database.
         */
+       if (data->reset == NULL) {
+               radlog(L_ERR, "rlm_counter: 'reset' must be set.");
+               exit(0);
+       }
        now = time(NULL);
        data->reset_time = 0;
-       data->reset_count = 0;
-       if (strcmp(data->reset, "hourly") == 0) {
-               /*
-                *  Round up to the next nearest hour.
-                */
-               data->reset_count = 3600;
-               data->reset_time = (now + 3600 - 1);
-               data->reset_time -= (data->reset_time % 3600);
-               
-       } else if (strcmp(data->reset, "daily") == 0) {
-               /*
-                *  Round up to the next nearest day.
-                */
-               data->reset_count = SECONDS_PER_DAY;
-               data->reset_time = (now + SECONDS_PER_DAY - 1);
-               data->reset_time -= (data->reset_time % SECONDS_PER_DAY);
-       } else if (strcmp(data->reset, "weekly") == 0) {
-               /*
-                *  Round up to the next nearest week.
-                */
-               data->reset_count = SECONDS_PER_WEEK;
-               data->reset_time = (now + SECONDS_PER_WEEK - 1);
-               data->reset_time -= (data->reset_time % SECONDS_PER_WEEK);
-       } else if (strcmp(data->reset, "monthly") == 0) {
-               /*
-                *  Yuck.  This involves more work.
-                */
-       } else {
-               radlog(L_ERR, "rlm_counter: Unknown reset timer \"%s\"",
-                      data->reset);
-               return -1;
-       }
 
+       if (find_next_reset(data,now) == -1)
+               return -1;
+       DEBUG2("rlm_counter: Next reset %d", (int)data->reset_time);
 
+       if (data->filename == NULL) {
+               radlog(L_ERR, "rlm_counter: 'filename' must be set.");
+               exit(0);
+       }
        data->gdbm = gdbm_open(data->filename, sizeof(int),
-                                  GDBM_WRCREAT, 0600, NULL);
+                       GDBM_WRCREAT | GDBM_SYNCOPT, 0600, NULL);
        if (data->gdbm == NULL) {
                radlog(L_ERR, "rlm_counter: Failed to open file %s: %s",
-                      data->filename, strerror(errno));
+                               data->filename, strerror(errno));
                return -1;
        }
+       if (gdbm_setopt(data->gdbm, GDBM_CACHESIZE, &cache_size, sizeof(int)) == -1)
+               radlog(L_ERR, "rlm_counter: Failed to set cache size");
+
 
        /*
         *      Register the counter comparison operation.
@@ -239,84 +328,97 @@ static int counter_instantiate(CONF_SECTION *conf, void **instance)
  */
 static int counter_accounting(void *instance, REQUEST *request)
 {
-       rlm_counter_t *data = (rlm_counter_t *) instance;
+       rlm_counter_t *data = (rlm_counter_t *)instance;
        datum key_datum;
        datum count_datum;
-       VALUE_PAIR *key_vp, *count_vp;
+       VALUE_PAIR *key_vp, *count_vp, *proto_vp;
        int counter;
        int rcode;
+       time_t diff;
 
        /*
         *      Before doing anything else, see if we have to reset
         *      the counters.
         */
-       if (data->reset_time &&
-           (data->reset_time < request->timestamp)) {
+       if (data->reset_time && (data->reset_time <= request->timestamp)) {
+               int cache_size = data->cache_size;
+
                gdbm_close(data->gdbm);
 
                /*
                 *      Re-set the next time to clean the database.
                 */
-               data->reset_time += data->reset_count;
+               data->last_reset = data->reset_time;
+               find_next_reset(data,request->timestamp);
 
                /*
                 *      Open a completely new database.
                 */
                data->gdbm = gdbm_open(data->filename, sizeof(int),
-                                      GDBM_NEWDB, 0600, NULL);
+                               GDBM_NEWDB | GDBM_SYNCOPT, 0600, NULL);
                if (data->gdbm == NULL) {
                        radlog(L_ERR, "rlm_counter: Failed to open file %s: %s",
-                              data->filename, strerror(errno));
+                                       data->filename, strerror(errno));
                        return RLM_MODULE_FAIL;
                }
+               if (gdbm_setopt(data->gdbm, GDBM_CACHESIZE, &cache_size, sizeof(int)) == -1)
+                       radlog(L_ERR, "rlm_counter: Failed to set cache size");
        }
+       /*
+        * Check if we need to watch out for a specific service-type. If yes then check it
+        */
+       if (data->service_type != NULL) {
+               if ((proto_vp = pairfind(request->packet->vps, PW_SERVICE_TYPE)) == NULL)
+                       return RLM_MODULE_NOOP;
+               if (proto_vp->lvalue != data->service_val)
+                       return RLM_MODULE_NOOP;
+
+       }       
+       
 
        /*
         *      Look for the key.  User-Name is special.  It means
         *      The REAL username, after stripping.
         */
-       if (data->key_attr == PW_USER_NAME) {
-               key_vp = request->username;
-       } else {
-               key_vp = pairfind(request->packet->vps, data->key_attr);
-       }
-       if (!key_vp) {
+       key_vp = (data->key_attr == PW_USER_NAME) ? request->username : pairfind(request->packet->vps, data->key_attr);
+       if (key_vp == NULL)
                return RLM_MODULE_NOOP;
-       }
 
        /*
         *      Look for the attribute to use as a counter.
         */
        count_vp = pairfind(request->packet->vps, data->count_attr);
-       if (!count_vp) {
+       if (count_vp == NULL)
                return RLM_MODULE_NOOP;
-       }
 
        key_datum.dptr = key_vp->strvalue;
        key_datum.dsize = key_vp->length;
 
        count_datum = gdbm_fetch(data->gdbm, key_datum);
-       if (count_datum.dptr == NULL) {
+       if (count_datum.dptr == NULL)
                counter = 0;
-       } else {
+       else
                memcpy(&counter, count_datum.dptr, sizeof(int));
-       }
 
-       counter += count_vp->lvalue;
+       /*
+        * if session time < diff then the user got in after the last reset. So add his session time
+        * else add the diff.
+        * That way if he logged in at 23:00 and we reset the daily counter at 24:00 and he logged out
+        * at 01:00 then we will only count one hour (the one in the new day). That is the right thing
+        */
+
+       diff = request->timestamp - data->last_reset;
+       counter += (count_vp->lvalue < diff) ? count_vp->lvalue : diff;
        count_datum.dptr = (char *) &counter;
        count_datum.dsize = sizeof(int);
 
        rcode = gdbm_store(data->gdbm, key_datum, count_datum, GDBM_REPLACE);
        if (rcode < 0) {
                radlog(L_ERR, "rlm_counter: Failed storing data to %s: %s",
-                      data->filename, gdbm_strerror(gdbm_errno));
+                               data->filename, gdbm_strerror(gdbm_errno));
                return RLM_MODULE_FAIL;
        }
 
-       DEBUG2("rlm_counter: Added %d, New value for %s = %d",
-              count_vp->lvalue, key_vp->strvalue, counter);
-              
-       
        return RLM_MODULE_OK;
 }
 
@@ -328,11 +430,139 @@ static int counter_accounting(void *instance, REQUEST *request)
  */
 static int counter_authorize(void *instance, REQUEST *request)
 {
+       rlm_counter_t *data = (rlm_counter_t *) instance;
+       int ret=RLM_MODULE_NOOP;
+       datum key_datum;
+       datum count_datum;
+       int counter=0;
+       int res=0;
+       DICT_ATTR *dattr;
+       VALUE_PAIR *key_vp, *check_vp;
+       VALUE_PAIR *reply_item;
+       char msg[128];
+
        /* quiet the compiler */
        instance = instance;
        request = request;
 
-       return RLM_MODULE_NOOP;
+       /*
+        *      Before doing anything else, see if we have to reset
+        *      the counters.
+        */
+       if (data->reset_time && (data->reset_time <= request->timestamp)) {
+               int cache_size = data->cache_size;
+
+               gdbm_close(data->gdbm);
+
+               /*
+                *      Re-set the next time to clean the database.
+                */
+               data->last_reset = data->reset_time;
+               find_next_reset(data,request->timestamp);
+
+               /*
+                *      Open a completely new database.
+                */
+               data->gdbm = gdbm_open(data->filename, sizeof(int),
+                               GDBM_NEWDB | GDBM_SYNCOPT, 0600, NULL);
+               if (data->gdbm == NULL) {
+                       radlog(L_ERR, "rlm_counter: Failed to open file %s: %s",
+                                       data->filename, strerror(errno));
+                       return RLM_MODULE_FAIL;
+               }
+               if (gdbm_setopt(data->gdbm, GDBM_CACHESIZE, &cache_size, sizeof(int)) == -1)
+                       radlog(L_ERR, "rlm_counter: Failed to set cache size");
+       }
+
+
+       /*
+       *      Look for the key.  User-Name is special.  It means
+       *      The REAL username, after stripping.
+       */
+       DEBUG2("rlm_counter: Entering module authorize code");
+       key_vp = (data->key_attr == PW_USER_NAME) ? request->username : pairfind(request->packet->vps, data->key_attr);
+       if (key_vp == NULL) {
+               DEBUG2("rlm_counter: Could not find Key value pair");
+               return ret;
+       }
+
+       /*
+       *      Look for the check item
+       */
+       
+       if ((dattr = dict_attrbyname(data->check_name)) == NULL)
+               return ret;
+       if ((check_vp= pairfind(request->config_items, dattr->attr)) == NULL) {
+               DEBUG2("rlm_counter: Could not find Check item value pair");
+               return ret;
+       }
+
+       key_datum.dptr = key_vp->strvalue;
+       key_datum.dsize = key_vp->length;
+       
+       count_datum = gdbm_fetch(data->gdbm, key_datum);
+       if (count_datum.dptr != NULL)
+               memcpy(&counter, count_datum.dptr, sizeof(int));
+
+       /*
+        * Check if check item > counter
+        */
+       res=check_vp->lvalue - counter;
+       if (res > 0) {
+               /*
+                * We are assuming that simultaneous-use=1. But even if that does
+                * not happen then our user could login at max for 2*max-usage-time
+                * Is that acceptable?
+                */
+
+               /*
+                *  User is allowed, but set Session-Timeout.
+                *  Stolen from main/auth.c
+                */
+
+               /*
+                * If we are near a reset then add the next limit, so that the user will
+                * not need to login again
+                */
+
+               if (res >= (data->reset_time - request->timestamp))
+                       res += check_vp->lvalue;
+
+               DEBUG2("rlm_counter: (Check item - counter) is greater than zero");
+               if ((reply_item = pairfind(request->reply->vps, PW_SESSION_TIMEOUT)) != NULL) {
+                       if (reply_item->lvalue > res)
+                               reply_item->lvalue = res;
+               } else {
+                       if ((reply_item = paircreate(PW_SESSION_TIMEOUT, PW_TYPE_INTEGER)) == NULL) {
+                               radlog(L_ERR|L_CONS, "no memory");
+                               return RLM_MODULE_NOOP;
+                       }
+                       reply_item->lvalue = res;
+                       pairadd(&request->reply->vps, reply_item);
+               }
+
+               ret=RLM_MODULE_OK;
+
+               DEBUG2("rlm_counter: Authorized user %s, check_item=%d, counter=%d",
+                               key_vp->strvalue,check_vp->lvalue,counter);
+               DEBUG2("rlm_counter: Sent Reply-Item for user %s, Type=Session-Timeout, value=%d",
+                               key_vp->strvalue,res);
+       }
+       else{
+               /*
+                * User is denied access, send back a reply message
+               */
+               sprintf(msg, "Your maximum %s usage time has been reached", data->reset);
+               reply_item=pairmake("Reply-Message", msg, T_OP_EQ);
+               pairadd(&request->reply->vps, reply_item);
+
+               ret=RLM_MODULE_REJECT;
+
+               DEBUG2("rlm_counter: Rejected user %s, check_item=%d, counter=%d",
+                               key_vp->strvalue,check_vp->lvalue,counter);
+       }
+
+       return ret;
 }
 
 static int counter_detach(void *instance)