------------------------------------------------------------ revno: 11424 revision-id: squid3@treenet.co.nz-20110509124259-fwaqg5dp9fypdwla parent: chtsanti@users.sourceforge.net-20110509074855-yuaawlrolql5c6qs author: Tilmann Bubeck committer: Amos Jeffries branch nick: trunk timestamp: Tue 2011-05-10 00:42:59 +1200 message: Add ext_time_quota_acl helper Allows an administrator to define time budgets for the users of squid to limit the time using squid. This is useful for corporate lunch time allocations, wifi portal pay-per-minute installations or for parental control of children. The administrator can define a time budget (e.g. 1 hour per day) which is enforced through this helper. ------------------------------------------------------------ # Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: squid3@treenet.co.nz-20110509124259-fwaqg5dp9fypdwla # target_branch: http://www.squid-cache.org/bzr/squid3/trunk/ # testament_sha1: 21c7c2deb1f9b0ee4c400c1f4138c1e4ff141479 # timestamp: 2011-05-09 12:47:06 +0000 # source_branch: http://bzr.squid-cache.org/bzr/squid3/trunk/ # base_revision_id: chtsanti@users.sourceforge.net-20110509074855-\ # yuaawlrolql5c6qs # # Begin patch === modified file 'configure.ac' --- configure.ac 2011-05-08 23:21:44 +0000 +++ configure.ac 2011-05-09 12:42:59 +0000 @@ -3451,6 +3451,7 @@ helpers/external_acl/session/Makefile \ helpers/external_acl/unix_group/Makefile \ helpers/external_acl/wbinfo_group/Makefile \ + helpers/external_acl/time_quota/Makefile \ helpers/log_daemon/Makefile \ helpers/log_daemon/DB/Makefile \ helpers/log_daemon/file/Makefile \ === modified file 'errors/Makefile.am' --- errors/Makefile.am 2011-02-01 11:37:01 +0000 +++ errors/Makefile.am 2011-05-09 12:42:59 +0000 @@ -13,6 +13,7 @@ ## List of automated translations possible: ERROR_TEMPLATES = \ templates/ERR_ACCESS_DENIED \ + templates/ERR_ACL_TIME_QUOTA_EXCEEDED \ templates/ERR_CACHE_ACCESS_DENIED \ templates/ERR_CACHE_MGR_ACCESS_DENIED \ templates/ERR_CANNOT_FORWARD \ === added file 'errors/templates/ERR_ACL_TIME_QUOTA_EXCEEDED' --- errors/templates/ERR_ACL_TIME_QUOTA_EXCEEDED 1970-01-01 00:00:00 +0000 +++ errors/templates/ERR_ACL_TIME_QUOTA_EXCEEDED 2011-05-09 12:42:59 +0000 @@ -0,0 +1,38 @@ + + + +ERROR: The requested URL could not be retrieved + + +
+

ERROR

+

The requested URL could not be retrieved

+
+
+ +
+

The following error was encountered while trying to retrieve the URL: %U

+ +
+

Time Quota Exceeded.

+
+ +

This proxy limits your time online with a quota. Your time budget is now empty but will be refilled when the configured time period starts again.

+

These limits have been established by the Internet Service Provider who operates this cache. Please contact them directly if you feel this is an error.

+ +

Your cache administrator is %w.

+
+
+ +
+ + === modified file 'helpers/external_acl/Makefile.am' --- helpers/external_acl/Makefile.am 2010-08-13 12:11:07 +0000 +++ helpers/external_acl/Makefile.am 2011-05-09 12:42:59 +0000 @@ -6,6 +6,7 @@ LDAP_group \ LM_group \ session \ + time_quota \ unix_group \ wbinfo_group === added directory 'helpers/external_acl/time_quota' === added file 'helpers/external_acl/time_quota/Makefile.am' --- helpers/external_acl/time_quota/Makefile.am 1970-01-01 00:00:00 +0000 +++ helpers/external_acl/time_quota/Makefile.am 2011-05-09 12:42:59 +0000 @@ -0,0 +1,12 @@ +include $(top_srcdir)/src/Common.am + +libexec_PROGRAMS = ext_time_quota_acl +man_MANS = ext_time_quota_acl.8 +EXTRA_DIST = ext_time_quota_acl.8 config.test +ext_time_quota_acl_SOURCES = ext_time_quota_acl.cc + +DEFS += -DDEFAULT_QUOTA_DB=\"$(localstatedir)/ext_time_quota.db\" + +LDADD = \ + $(COMPAT_LIB) \ + $(LIB_DB) === added file 'helpers/external_acl/time_quota/config.test' --- helpers/external_acl/time_quota/config.test 1970-01-01 00:00:00 +0000 +++ helpers/external_acl/time_quota/config.test 2011-05-09 12:42:59 +0000 @@ -0,0 +1,10 @@ +#!/bin/sh + +# Actual intended test +if [ -f /usr/include/db_185.h ]; then + exit 0 +fi +if [ -f /usr/include/db.h ] && grep dbopen /usr/include/db.h; then + exit 0 +fi +exit 1 === added file 'helpers/external_acl/time_quota/ext_time_quota_acl.8' --- helpers/external_acl/time_quota/ext_time_quota_acl.8 1970-01-01 00:00:00 +0000 +++ helpers/external_acl/time_quota/ext_time_quota_acl.8 2011-05-09 12:42:59 +0000 @@ -0,0 +1,251 @@ +.if !'po4a'hide' .TH ext_time_quota_acl 8 "22 March 2011" +. +.SH NAME +.if !'po4a'hide' .B ext_time_quota_acl +.if !'po4a'hide' \- +Squid time quota external acl helper. +.PP +Version 1.0 +. +.SH SYNOPSIS +.if !'po4a'hide' .B ext_time_quota_acl +.if !'po4a'hide' .B "[\-b database] [\-l logfile] [\-d] [\-p pauselen] [\-h] configfile +. +.SH DESCRIPTION +.B ext_time_quota_acl +allows an administrator to define time budgets for the users of squid +to limit the time using squid. +.PP +This is useful for corporate lunch time allocations, wifi portal +pay-per-minute installations or for parental control of children. The +administrator can define a time budget (e.g. 1 hour per day) which is enforced +through this helper. +. +.SH OPTIONS +. +.if !'po4a'hide' .TP +.if !'po4a'hide' .B "\-b database" +.B Filename +of persistent database. This defaults to ext_time_quota.db in Squids state +directory. +. +.if !'po4a'hide' .TP +.if !'po4a'hide' .B "\-p pauselen" +.B Pauselen +is given in seconds and defines the period between two requests to be treated as part of the same session. +Pauses shorter than this value will be counted against the quota, longer ones ignored. +Default is 300 seconds (5 minutes). +. +.if !'po4a'hide' .TP +.if !'po4a'hide' .B "\-l logfile" +.B Filename +where all logging and debugging information will be written. If none is given, +then stderr will be used and the logging will go to Squids main cache.log. +. +.if !'po4a'hide' .TP +.if !'po4a'hide' .B "\-d" +Enables debug logging in the logfile. +. +.if !'po4a'hide' .TP +.if !'po4a'hide' .B "\-h" +show a short command line help. +. +.if !'po4a'hide' .TP +.if !'po4a'hide' .B configfile +This file contains the definition of the time budgets for the users. +.PP +. +.SH CONFIGURATION +.PP +The time quotas of the users are defined in a text file typically +residing in /etc/squid/time_quota. Any line starting with "#" contains +a comment and is ignored. Every line must start with a user +followed by a time budget and a corresponding time period separated by +"/". Here is an example file: +.PP +.if !'po4a'hide' .RS +# user budget / period +.if !'po4a'hide' .br +.if !'po4a'hide' .B john 8h / 1d +.if !'po4a'hide' .br +.if !'po4a'hide' .B littlejoe 1h / 1d +.if !'po4a'hide' .br +.if !'po4a'hide' .B babymary 30m / 1w +.if !'po4a'hide' .br +.if !'po4a'hide' .RE +.PP +John has a time budget of 8 hours every day, littlejoe is only allowed +1 hour and the poor babymary only 30 minutes a week. +.PP +You can use "s" for seconds, "m" for minutes, "h" for hours, "d" for +days and "w" for weeks. Numerical values can be given as integer +values or with a fraction. E.g. "0.5h" means 30 minutes. +.PP +This helper is configured in +.B squid.conf +using the +.B external_acl_type +directive then access controls which use it to allow or deny. +. +.PP +Here is an example. +.PP +.if !'po4a'hide' .RS +# Ensure that users have a valid login. We need their username. +.if !'po4a'hide' .br +.if !'po4a'hide' .br +.if !'po4a'hide' .B acl users proxy_auth REQUIRED +.if !'po4a'hide' .br +.if !'po4a'hide' .B http_access deny !users +.if !'po4a'hide' .br +# Define program and quota file +.if !'po4a'hide' .br +.if !'po4a'hide' .B external_acl_type time_quota ttl=60 children-max=1 %LOGIN /usr/libexec/ext_time_quota_acl /etc/squid/time_quota +.if !'po4a'hide' .br +.if !'po4a'hide' .br +.if !'po4a'hide' .B acl noquota src all +.if !'po4a'hide' .br +.if !'po4a'hide' .B acl time_quota external time_quota +.if !'po4a'hide' .br +.if !'po4a'hide' .B deny_info ERR_ACL_TIME_QUOTA_EXCEEDED noquota +.if !'po4a'hide' .br +.if !'po4a'hide' .B http_access deny !time_quota noquota +.if !'po4a'hide' .RE +. +.PP +In this example, after restarting Squid it should allow access only for users as long as they have time budget left. +If the budget is exceeded the user will be presented with an error page informing them. +.PP +In this example we use separate +.B users +access control and +.B noquota +ACL in order to keep the username and password prompt and the quota-exceeded messages separated. +. +.PP +User is just a unique key value. The above example uses %LOGIN and the username but any of the +.B external_acl_type +format tags can be substituted in its place. +.B %EXT_TAG +, +.B %LOGIN +, +.B %IDENT +, +.B %EXT_USER +, +.B %SRC +, +.B %SRCEUI48 +, and +.B %SRCEUI64 +are all likely candidates for client identification. +The Squid wiki has more examples at http://wiki.squid-cache.org/ConfigExamples. +. +.SH LIMITATIONS +.PP +This helper only controls access to the Internet through HTTP. It does +not control other protocols, like VOIP, ICQ, IRC, FTP, IMAP, SMTP or +SSH. +. +.PP +Desktop browsers are typically able to deal with authentication to HTTP proxies like +.B squid . +But more and more different programs and devices (smartphones, +games on mobile devices, ...) are using the Internet over HTTP. These +devices are often not able to work through an authenticating proxy. +Means other than %LOGIN authentication are required to authorize these devices and software. +. +.PP +A more general control to Internet access could be a captive portal approach +(such as pfSense or ChilliSpot) using %SRC, %SRCEUI48 and %SRCEUI64 as keys +or maybe a 802.11X solution. But the latter is often not supported by mobile devices. +. +.SH IMPLEMENTATION +.PP +When the helper is called it will be asked if the current user is allowed to +access squid. The helper will reduce the remaining time budget of this user +and return +.B OK +if there is budget left. Otherwise it will return +.B ERR . +. +.PP +The +.B ttl=N +parameter in +.B squid.conf +determines how often the helper will be called, the example config uses a 1 minute TTL. +The interaction is that Squid will only call the helper on new requests +.B if +there has been more than TTL seconds passed since last check. +This handling creates an amount of slippage outside the quota by whatever amount is configured. +TTL can be set as short as desired, down to and including zero. +Though values of 1 or more are recommended due to a quota resolution of one second. +. +.PP +If the configured time period (e.g. "1w" for babymary) is over, the +time budget will be restored to the configured value thus allowing the +user to access squid with a fresh budget. +. +.PP +If the time between the current request and the previous request is greater than +.B pauselen +(default 5 minutes and adjustable with command line parameter +.B "-p" +), the current request will be considered as a new request and the time budget will +not be decreased. If the time is less than +.B pauselen +, then both requests will be considered as part of the same active time period and the time budget will +be decreased by the time difference. This allows the user to take arbitrary +breaks during Internet access without losing their time budget. +. +.SH FURTHER IDEAS +The following ideas could further improve this helper. Maybe someone +wants to help? Any support or feedback is welcome! +.if !'po4a'hide' .TP +There should be a way for a user to see their configured and remaining +time budget. This could be realized by implementing a web page +accessing the database of the helper showing the corresponding +data. One of the problems to be solved is user authentication. +.if !'po4a'hide' .TP +We could always return "OK" and use the module simply as an Internet +usage tracker showing who has stayed how long in the WWW. +.PP +. +.SH AUTHOR +This program and documentation was written by +.if !'po4a'hide' .I Dr. Tilmann Bubeck +. +.SH COPYRIGHT +This program and documentation is copyright to the authors named above. +.PP +Distributed under the GNU General Public License (GNU GPL) version 2 or later (GPLv2+). +. +.SH QUESTIONS +Questions on the usage of this program can be sent to the +.I Squid Users mailing list +.if !'po4a'hide' +. +.SH REPORTING BUGS +Bug reports need to be made in English. +See http://wiki.squid-cache.org/SquidFaq/BugReporting for details of what you need to include with your bug report. +.PP +Report bugs or bug fixes using http://bugs.squid-cache.org/ +.PP +Report serious security bugs to +.I Squid Bugs +.PP +Report ideas for new improvements to the +.I Squid Developers mailing list +.if !'po4a'hide' +. +.SH SEE ALSO +.if !'po4a'hide' .BR squid "(8), " +.if !'po4a'hide' .BR GPL "(7), " +.br +The Squid FAQ wiki +.if !'po4a'hide' http://wiki.squid-cache.org/SquidFaq +.br +The Squid Configuration Manual +.if !'po4a'hide' http://www.squid-cache.org/Doc/config/ === added file 'helpers/external_acl/time_quota/ext_time_quota_acl.cc' --- helpers/external_acl/time_quota/ext_time_quota_acl.cc 1970-01-01 00:00:00 +0000 +++ helpers/external_acl/time_quota/ext_time_quota_acl.cc 2011-05-09 12:42:59 +0000 @@ -0,0 +1,448 @@ +/* + * ext_time_quota_acl: Squid external acl helper for quota on usage. + * + * Copyright (C) 2011 Dr. Tilmann Bubeck + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif +#include "helpers/defines.h" + +#include +#include +#include +#include +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_GETOPT_H +#include +#endif + +/* At this point all Bit Types are already defined, so we must + protect from multiple type definition on platform where + __BIT_TYPES_DEFINED__ is not defined. + */ +#ifndef __BIT_TYPES_DEFINED__ +#define __BIT_TYPES_DEFINED__ +#endif + +#if HAVE_DB_185_H +#include +#elif HAVE_DB_H +#include +#endif + +#ifndef DEFAULT_QUOTA_DB +#error "Please define DEFAULT_QUOTA_DB preprocessor constant." +#endif + +const char *db_path = DEFAULT_QUOTA_DB; +const char *program_name; + +DB *db = NULL; + +#define KEY_LAST_ACTIVITY "last-activity" +#define KEY_PERIOD_START "period-start" +#define KEY_PERIOD_LENGTH_CONFIGURED "period-length-configured" +#define KEY_TIME_BUDGET_LEFT "time-budget-left" +#define KEY_TIME_BUDGET_CONFIGURED "time-budget-configured" + +/** Maximum size of buffers used to read or display lines. */ +#define TQ_BUFFERSIZE 1024 + +/** If there is more than this given number of seconds between two + * sucessive requests, than the second request will be treated as a + * new request and the time between first and seconds request will + * be treated as a activity pause. + * + * Otherwise the following request will be treated as belonging to the + * same activity and the quota will be reduced. + */ +static int pauseLength = 300; + +static FILE *logfile = stderr; +static int tq_debug_enabled = false; + +static void open_log(const char *logfilename) { + logfile = fopen(logfilename, "a"); + if ( logfile == NULL ) { + perror(logfilename); + logfile = stderr; + } +} + +static void vlog(const char *level, const char *format, va_list args) +{ + time_t now = time(NULL); + + fprintf(logfile, "%ld %s| %s: ", now, program_name, level); + vfprintf (logfile, format, args); + fflush(logfile); +} + +static void log_debug(const char *format, ...) +{ + va_list args; + + if ( tq_debug_enabled ) { + va_start (args, format); + vlog("DEBUG", format, args); + va_end (args); + } +} + +static void log_info(const char *format, ...) +{ + va_list args; + + va_start (args, format); + vlog("INFO", format, args); + va_end (args); +} + +static void log_error(const char *format, ...) +{ + va_list args; + + va_start (args, format); + vlog("ERROR", format, args); + va_end (args); +} + +static void log_fatal(const char *format, ...) +{ + va_list args; + + va_start (args, format); + vlog("FATAL", format, args); + va_end (args); +} + +static void init_db(void) +{ + log_info("opening time quota database \"%s\".\n", db_path); + db = dbopen(db_path, O_CREAT | O_RDWR, 0666, DB_BTREE, NULL); + if (!db) { + log_fatal("Failed to open time_quota db '%s'\n", db_path); + exit(1); + } +} + +static void shutdown_db(void) +{ + db->close(db); +} + +static void writeTime(const char *user_key, const char *sub_key, time_t t) +{ + char keybuffer[TQ_BUFFERSIZE]; + DBT key, data; + + if ( strlen(user_key) + strlen(sub_key) + 1 + 1 > sizeof(keybuffer) ) { + log_error("key too long (%s,%s)\n", user_key, sub_key); + } else { + snprintf(keybuffer, sizeof(keybuffer), "%s-%s", user_key, sub_key); + + key.data = (void *)keybuffer; + key.size = strlen(keybuffer); + data.data = &t; + data.size = sizeof(t); + db->put(db, &key, &data, 0); + log_debug("writeTime(\"%s\", %d)\n", keybuffer, t); + } +} + +static time_t readTime(const char *user_key, const char *sub_key) +{ + char keybuffer[TQ_BUFFERSIZE]; + DBT key, data; + time_t t = 0; + + if ( strlen(user_key) + 1 + strlen(sub_key) + 1 > sizeof(keybuffer) ) { + log_error("key too long (%s,%s)\n", user_key, sub_key); + } else { + snprintf(keybuffer, sizeof(keybuffer), "%s-%s", user_key, sub_key); + + key.data = (void *)keybuffer; + key.size = strlen(keybuffer); + if (db->get(db, &key, &data, 0) == 0) { + if (data.size != sizeof(t)) { + log_error("CORRUPTED DATABASE (%s)\n", keybuffer); + } else { + memcpy(&t, data.data, sizeof(t)); + } + } + log_debug("readTime(\"%s\")=%d\n", keybuffer, t); + } + + return t; +} + +static void parseTime(const char *s, time_t *secs, time_t *start) +{ + double value; + char unit; + struct tm *ltime; + int periodLength = 3600; + + *secs = 0; + *start = time(NULL); + ltime = localtime(start); + + sscanf(s, " %lf %c", &value, &unit); + switch (unit) { + case 's': + periodLength = 1; + break; + case 'm': + periodLength = 60; + *start -= ltime->tm_sec; + break; + case 'h': + periodLength = 3600; + *start -= ltime->tm_min * 60 + ltime->tm_sec; + break; + case 'd': + periodLength = 24 * 3600; + *start -= ltime->tm_hour * 3600 + ltime->tm_min * 60 + ltime->tm_sec; + break; + case 'w': + periodLength = 7 * 24 * 3600; + *start -= ltime->tm_hour * 3600 + ltime->tm_min * 60 + ltime->tm_sec; + *start -= ltime->tm_wday * 24 * 3600; + *start += 24 * 3600; // in europe, the week starts monday + break; + default: + log_error("Wrong time unit \"%c\". Only \"m\", \"h\", \"d\", or \"w\" allowed.\n", unit); + break; + } + + *secs = (long)(periodLength * value); +} + + +/** This function parses the time quota file and stores it + * in memory. + */ +static void readConfig(const char *filename) +{ + char line[TQ_BUFFERSIZE]; /* the buffer for the lines read + from the dict file */ + char *cp; /* a char pointer used to parse + each line */ + char *username; /* for the username */ + char *budget; + char *period; + FILE *FH; + time_t t; + time_t budgetSecs, periodSecs; + time_t start; + + log_info("reading config file \"%s\".\n", filename); + + FH = fopen(filename, "r"); + if ( FH ) { + /* the pointer to the first entry in the linked list */ + while ((cp = fgets (line, sizeof(line), FH)) != NULL) { + if (line[0] == '#') { + continue; + } + if ((cp = strchr (line, '\n')) != NULL) { + /* chop \n characters */ + *cp = '\0'; + } + log_debug("read config line \"%s\".\n", line); + if ((cp = strtok (line, "\t ")) != NULL) { + username = cp; + + /* get the time budget */ + budget = strtok (NULL, "/"); + period = strtok (NULL, "/"); + + parseTime(budget, &budgetSecs, &start); + parseTime(period, &periodSecs, &start); + + log_debug("read time quota for user \"%s\": %lds / %lds starting %lds\n", + username, budgetSecs, periodSecs, start); + + writeTime(username, KEY_PERIOD_START, start); + writeTime(username, KEY_PERIOD_LENGTH_CONFIGURED, periodSecs); + writeTime(username, KEY_TIME_BUDGET_CONFIGURED, budgetSecs); + t = readTime(username, KEY_TIME_BUDGET_CONFIGURED); + writeTime(username, KEY_TIME_BUDGET_LEFT, t); + } + } + fclose(FH); + } else { + perror(filename); + } +} + +static void processActivity(const char *user_key) +{ + time_t now = time(NULL); + time_t lastActivity; + time_t activityLength; + time_t periodStart; + time_t periodLength; + time_t userPeriodLength; + time_t timeBudgetCurrent; + time_t timeBudgetConfigured; + char message[TQ_BUFFERSIZE]; + + log_debug("processActivity(\"%s\")\n", user_key); + + // [1] Reset period if over + periodStart = readTime(user_key, KEY_PERIOD_START); + if ( periodStart == 0 ) { + // This is the first period ever. + periodStart = now; + writeTime(user_key, KEY_PERIOD_START, periodStart); + } + + periodLength = now - periodStart; + userPeriodLength = readTime(user_key, KEY_PERIOD_LENGTH_CONFIGURED); + if ( userPeriodLength == 0 ) { + // This user is not configured. Allow anything. + log_debug("No period length found for user \"%s\". Quota for this user disabled.\n", user_key); + writeTime(user_key, KEY_TIME_BUDGET_LEFT, pauseLength); + } else { + if ( periodLength >= userPeriodLength ) { + // a new period has started. + log_debug("New time period started for user \"%s\".\n", user_key); + while ( periodStart < now ) { + periodStart += periodLength; + } + writeTime(user_key, KEY_PERIOD_START, periodStart); + timeBudgetConfigured = readTime(user_key, KEY_TIME_BUDGET_CONFIGURED); + if ( timeBudgetConfigured == 0 ) { + log_debug("No time budget configured for user \"%s\". Quota for this user disabled.\n", user_key); + writeTime(user_key, KEY_TIME_BUDGET_LEFT, pauseLength); + } else { + writeTime(user_key, KEY_TIME_BUDGET_LEFT, timeBudgetConfigured); + } + } + } + + // [2] Decrease time budget iff activity + lastActivity = readTime(user_key, KEY_LAST_ACTIVITY); + if ( lastActivity == 0 ) { + // This is the first request ever + writeTime(user_key, KEY_LAST_ACTIVITY, now); + } else { + activityLength = now - lastActivity; + if ( activityLength >= pauseLength ) { + // This is an activity pause. + log_debug("Activity pause detected for user \"%s\".\n", user_key); + writeTime(user_key, KEY_LAST_ACTIVITY, now); + } else { + // This is real usage. + writeTime(user_key, KEY_LAST_ACTIVITY, now); + + log_debug("Time budget reduced by %ld for user \"%s\".\n", + activityLength, user_key); + timeBudgetCurrent = readTime(user_key, KEY_TIME_BUDGET_LEFT); + timeBudgetCurrent -= activityLength; + writeTime(user_key, KEY_TIME_BUDGET_LEFT, timeBudgetCurrent); + } + } + + timeBudgetCurrent = readTime(user_key, KEY_TIME_BUDGET_LEFT); + snprintf(message, TQ_BUFFERSIZE, "message=\"Remaining quota for '%s' is %d seconds.\"", user_key, (int)timeBudgetCurrent); + if ( timeBudgetCurrent > 0 ) { + log_debug("OK %s.\n", message); + SEND_OK(message); + } else { + log_debug("ERR %s\n", message); + SEND_ERR("Time budget exceeded."); + } + + db->sync(db, 0); +} + +static void usage(void) +{ + log_error("Wrong usage. Please reconfigure in squid.conf.\n"); + + fprintf(stderr, "Usage: %s [-d] [-l logfile] [-b dbpath] [-p pauselen] [-h] configfile\n", program_name); + fprintf(stderr, " -d enable debugging output to logfile\n"); + fprintf(stderr, " -l logfile log messages to logfile\n"); + fprintf(stderr, " -b dbpath Path where persistent session database will be kept\n"); + fprintf(stderr, " If option is not used, then " DEFAULT_QUOTA_DB " will be used.\n"); + fprintf(stderr, " -p pauselen length in seconds to describe a pause between 2 requests.\n"); + fprintf(stderr, " -h show show command line help.\n"); + fprintf(stderr, "configfile is a file containing time quota definitions.\n"); +} + +int main(int argc, char **argv) +{ + char request[HELPER_INPUT_BUFFER]; + int opt; + + program_name = argv[0]; + + while ((opt = getopt(argc, argv, "dp:l:b:h")) != -1) { + switch (opt) { + case 'd': + tq_debug_enabled = true; + break; + case 'l': + open_log(optarg); + break; + case 'b': + db_path = optarg; + break; + case 'p': + pauseLength = atoi(optarg); + break; + case 'h': + usage(); + exit(0); + break; + } + } + + log_info("Starting %s\n", __FILE__); + setbuf(stdout, NULL); + + init_db(); + + if ( optind + 1 != argc ) { + usage(); + exit(1); + } else { + readConfig(argv[optind]); + } + + log_info("Waiting for requests...\n"); + while (fgets(request, HELPER_INPUT_BUFFER, stdin)) { + // we expect the following line syntax: "%LOGIN + const char *user_key = NULL; + user_key = strtok(request, " \n"); + + processActivity(user_key); + } + log_info("Ending %s\n", __FILE__); + shutdown_db(); + return 0; +}