ext_time_quota_acl.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2018 The Squid Software Foundation and contributors
3  *
4  * Squid software is distributed under GPLv2+ license and includes
5  * contributions from numerous individuals and organizations.
6  * Please see the COPYING and CONTRIBUTORS files for details.
7  */
8 
9 /*
10  * ext_time_quota_acl: Squid external acl helper for quota on usage.
11  *
12  * Copyright (C) 2011 Dr. Tilmann Bubeck <t.bubeck@reinform.de>
13  *
14  * This program is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation; either version 2 of the License, or
17  * (at your option) any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22  * GNU General Public License for more details.
23  *
24  * You should have received a copy of the GNU General Public License
25  * along with this program; if not, write to the Free Software
26  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
27  */
28 
29 #include "squid.h"
31 
32 #include <cstdlib>
33 #include <cstring>
34 #include <ctime>
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <fcntl.h>
38 #if HAVE_UNISTD_H
39 #include <unistd.h>
40 #endif
41 #if HAVE_GETOPT_H
42 #include <getopt.h>
43 #endif
44 #if HAVE_TDB_H
45 #include <tdb.h>
46 #endif
47 
48 #ifndef DEFAULT_QUOTA_DB
49 #error "Please define DEFAULT_QUOTA_DB preprocessor constant."
50 #endif
51 
52 const char *db_path = DEFAULT_QUOTA_DB;
53 const char *program_name;
54 
55 TDB_CONTEXT *db = nullptr;
56 
57 #define KEY_LAST_ACTIVITY "last-activity"
58 #define KEY_PERIOD_START "period-start"
59 #define KEY_PERIOD_LENGTH_CONFIGURED "period-length-configured"
60 #define KEY_TIME_BUDGET_LEFT "time-budget-left"
61 #define KEY_TIME_BUDGET_CONFIGURED "time-budget-configured"
62 
64 #define TQ_BUFFERSIZE 1024
65 
74 static int pauseLength = 300;
75 
76 static FILE *logfile = stderr;
77 static int tq_debug_enabled = false;
78 
79 static void open_log(const char *logfilename)
80 {
81  logfile = fopen(logfilename, "a");
82  if ( logfile == NULL ) {
83  perror(logfilename);
84  logfile = stderr;
85  }
86 }
87 
88 static void vlog(const char *level, const char *format, va_list args)
89 {
90  time_t now = time(NULL);
91 
92  fprintf(logfile, "%ld %s| %s: ", static_cast<long int>(now),
93  program_name, level);
94  vfprintf (logfile, format, args);
95  fflush(logfile);
96 }
97 
98 static void log_debug(const char *format, ...)
99 {
100  va_list args;
101 
102  if ( tq_debug_enabled ) {
103  va_start (args, format);
104  vlog("DEBUG", format, args);
105  va_end (args);
106  }
107 }
108 
109 static void log_info(const char *format, ...)
110 {
111  va_list args;
112 
113  va_start (args, format);
114  vlog("INFO", format, args);
115  va_end (args);
116 }
117 
118 static void log_error(const char *format, ...)
119 {
120  va_list args;
121 
122  va_start (args, format);
123  vlog("ERROR", format, args);
124  va_end (args);
125 }
126 
127 static void log_fatal(const char *format, ...)
128 {
129  va_list args;
130 
131  va_start (args, format);
132  vlog("FATAL", format, args);
133  va_end (args);
134 }
135 
136 static void init_db(void)
137 {
138  log_info("opening time quota database \"%s\".\n", db_path);
139  db = tdb_open(db_path, 0, TDB_CLEAR_IF_FIRST, O_CREAT | O_RDWR, 0666);
140  if (!db) {
141  log_fatal("Failed to open time_quota db '%s'\n", db_path);
142  exit(EXIT_FAILURE);
143  }
144 }
145 
146 static void shutdown_db(void)
147 {
148  tdb_close(db);
149 }
150 
151 static char *KeyString(int &len, const char *user_key, const char *sub_key)
152 {
153  static char keybuffer[TQ_BUFFERSIZE];
154  *keybuffer = 0;
155 
156  len = snprintf(keybuffer, sizeof(keybuffer), "%s-%s", user_key, sub_key);
157  if (len < 0) {
158  log_error("Cannot add entry: %s-%s", user_key, sub_key);
159  len = 0;
160 
161  } else if (static_cast<size_t>(len) >= sizeof(keybuffer)) {
162  log_error("key too long (%s,%s)\n", user_key, sub_key);
163  len = 0;
164  }
165 
166  return keybuffer;
167 }
168 
169 static void writeTime(const char *user_key, const char *sub_key, time_t t)
170 {
171  int len = 0;
172  if (/* const */ char *keybuffer = KeyString(len, user_key, sub_key)) {
173 
174  TDB_DATA key, data;
175 
176  key.dptr = reinterpret_cast<unsigned char *>(keybuffer);
177  key.dsize = len;
178 
179  data.dptr = reinterpret_cast<unsigned char *>(&t);
180  data.dsize = sizeof(t);
181 
182  tdb_store(db, key, data, TDB_REPLACE);
183  log_debug("writeTime(\"%s\", %d)\n", keybuffer, t);
184  }
185 }
186 
187 static time_t readTime(const char *user_key, const char *sub_key)
188 {
189  int len = 0;
190  if (/* const */ char *keybuffer = KeyString(len, user_key, sub_key)) {
191 
192  TDB_DATA key;
193  key.dptr = reinterpret_cast<unsigned char *>(keybuffer);
194  key.dsize = len;
195 
196  auto data = tdb_fetch(db, key);
197 
198  time_t t = 0;
199  if (data.dsize != sizeof(t)) {
200  log_error("CORRUPTED DATABASE (%s)\n", keybuffer);
201  } else {
202  memcpy(&t, data.dptr, sizeof(t));
203  }
204 
205  log_debug("readTime(\"%s\")=%d\n", keybuffer, t);
206  return t;
207  }
208 
209  return 0;
210 }
211 
212 static void parseTime(const char *s, time_t *secs, time_t *start)
213 {
214  double value;
215  char unit;
216  struct tm *ltime;
217  int periodLength = 3600;
218 
219  *secs = 0;
220  *start = time(NULL);
221  ltime = localtime(start);
222 
223  sscanf(s, " %lf %c", &value, &unit);
224  switch (unit) {
225  case 's':
226  periodLength = 1;
227  break;
228  case 'm':
229  periodLength = 60;
230  *start -= ltime->tm_sec;
231  break;
232  case 'h':
233  periodLength = 3600;
234  *start -= ltime->tm_min * 60 + ltime->tm_sec;
235  break;
236  case 'd':
237  periodLength = 24 * 3600;
238  *start -= ltime->tm_hour * 3600 + ltime->tm_min * 60 + ltime->tm_sec;
239  break;
240  case 'w':
241  periodLength = 7 * 24 * 3600;
242  *start -= ltime->tm_hour * 3600 + ltime->tm_min * 60 + ltime->tm_sec;
243  *start -= ltime->tm_wday * 24 * 3600;
244  *start += 24 * 3600; // in europe, the week starts monday
245  break;
246  default:
247  log_error("Wrong time unit \"%c\". Only \"m\", \"h\", \"d\", or \"w\" allowed.\n", unit);
248  break;
249  }
250 
251  *secs = (long)(periodLength * value);
252 }
253 
257 static void readConfig(const char *filename)
258 {
259  char line[TQ_BUFFERSIZE]; /* the buffer for the lines read
260  from the dict file */
261  char *cp; /* a char pointer used to parse
262  each line */
263  char *username; /* for the username */
264  char *budget;
265  char *period;
266  FILE *FH;
267  time_t t;
268  time_t budgetSecs, periodSecs;
269  time_t start;
270 
271  log_info("reading config file \"%s\".\n", filename);
272 
273  FH = fopen(filename, "r");
274  if ( FH ) {
275  /* the pointer to the first entry in the linked list */
276  unsigned int lineCount = 0;
277  while (fgets(line, sizeof(line), FH)) {
278  ++lineCount;
279  if (line[0] == '#') {
280  continue;
281  }
282  if ((cp = strchr (line, '\n')) != NULL) {
283  /* chop \n characters */
284  *cp = '\0';
285  }
286  log_debug("read config line %u: \"%s\".\n", lineCount, line);
287  if ((username = strtok(line, "\t ")) != NULL) {
288 
289  /* get the time budget */
290  if ((budget = strtok(NULL, "/")) == NULL) {
291  fprintf(stderr, "ERROR: missing 'budget' field on line %u of '%s'.\n", lineCount, filename);
292  continue;
293  }
294  if ((period = strtok(NULL, "/")) == NULL) {
295  fprintf(stderr, "ERROR: missing 'period' field on line %u of '%s'.\n", lineCount, filename);
296  continue;
297  }
298 
299  parseTime(budget, &budgetSecs, &start);
300  parseTime(period, &periodSecs, &start);
301 
302  log_debug("read time quota for user \"%s\": %lds / %lds starting %lds\n",
303  username, budgetSecs, periodSecs, start);
304 
305  writeTime(username, KEY_PERIOD_START, start);
306  writeTime(username, KEY_PERIOD_LENGTH_CONFIGURED, periodSecs);
307  writeTime(username, KEY_TIME_BUDGET_CONFIGURED, budgetSecs);
308  t = readTime(username, KEY_TIME_BUDGET_CONFIGURED);
309  writeTime(username, KEY_TIME_BUDGET_LEFT, t);
310  }
311  }
312  fclose(FH);
313  } else {
314  perror(filename);
315  }
316 }
317 
318 static void processActivity(const char *user_key)
319 {
320  time_t now = time(NULL);
321  time_t lastActivity;
322  time_t activityLength;
323  time_t periodStart;
324  time_t periodLength;
325  time_t userPeriodLength;
326  time_t timeBudgetCurrent;
327  time_t timeBudgetConfigured;
328  char message[TQ_BUFFERSIZE];
329 
330  log_debug("processActivity(\"%s\")\n", user_key);
331 
332  // [1] Reset period if over
333  periodStart = readTime(user_key, KEY_PERIOD_START);
334  if ( periodStart == 0 ) {
335  // This is the first period ever.
336  periodStart = now;
337  writeTime(user_key, KEY_PERIOD_START, periodStart);
338  }
339 
340  periodLength = now - periodStart;
341  userPeriodLength = readTime(user_key, KEY_PERIOD_LENGTH_CONFIGURED);
342  if ( userPeriodLength == 0 ) {
343  // This user is not configured. Allow anything.
344  log_debug("No period length found for user \"%s\". Quota for this user disabled.\n", user_key);
346  } else {
347  if ( periodLength >= userPeriodLength ) {
348  // a new period has started.
349  log_debug("New time period started for user \"%s\".\n", user_key);
350  while ( periodStart < now ) {
351  periodStart += periodLength;
352  }
353  writeTime(user_key, KEY_PERIOD_START, periodStart);
354  timeBudgetConfigured = readTime(user_key, KEY_TIME_BUDGET_CONFIGURED);
355  if ( timeBudgetConfigured == 0 ) {
356  log_debug("No time budget configured for user \"%s\". Quota for this user disabled.\n", user_key);
358  } else {
359  writeTime(user_key, KEY_TIME_BUDGET_LEFT, timeBudgetConfigured);
360  }
361  }
362  }
363 
364  // [2] Decrease time budget iff activity
365  lastActivity = readTime(user_key, KEY_LAST_ACTIVITY);
366  if ( lastActivity == 0 ) {
367  // This is the first request ever
368  writeTime(user_key, KEY_LAST_ACTIVITY, now);
369  } else {
370  activityLength = now - lastActivity;
371  if ( activityLength >= pauseLength ) {
372  // This is an activity pause.
373  log_debug("Activity pause detected for user \"%s\".\n", user_key);
374  writeTime(user_key, KEY_LAST_ACTIVITY, now);
375  } else {
376  // This is real usage.
377  writeTime(user_key, KEY_LAST_ACTIVITY, now);
378 
379  log_debug("Time budget reduced by %ld for user \"%s\".\n",
380  activityLength, user_key);
381  timeBudgetCurrent = readTime(user_key, KEY_TIME_BUDGET_LEFT);
382  timeBudgetCurrent -= activityLength;
383  writeTime(user_key, KEY_TIME_BUDGET_LEFT, timeBudgetCurrent);
384  }
385  }
386 
387  timeBudgetCurrent = readTime(user_key, KEY_TIME_BUDGET_LEFT);
388  snprintf(message, TQ_BUFFERSIZE, "message=\"Remaining quota for '%s' is %d seconds.\"", user_key, (int)timeBudgetCurrent);
389  if ( timeBudgetCurrent > 0 ) {
390  log_debug("OK %s.\n", message);
391  SEND_OK(message);
392  } else {
393  log_debug("ERR %s\n", message);
394  SEND_ERR("Time budget exceeded.");
395  }
396 }
397 
398 static void usage(void)
399 {
400  log_error("Wrong usage. Please reconfigure in squid.conf.\n");
401 
402  fprintf(stderr, "Usage: %s [-d] [-l logfile] [-b dbpath] [-p pauselen] [-h] configfile\n", program_name);
403  fprintf(stderr, " -d enable debugging output to logfile\n");
404  fprintf(stderr, " -l logfile log messages to logfile\n");
405  fprintf(stderr, " -b dbpath Path where persistent session database will be kept\n");
406  fprintf(stderr, " If option is not used, then " DEFAULT_QUOTA_DB " will be used.\n");
407  fprintf(stderr, " -p pauselen length in seconds to describe a pause between 2 requests.\n");
408  fprintf(stderr, " -h show show command line help.\n");
409  fprintf(stderr, "configfile is a file containing time quota definitions.\n");
410 }
411 
412 int main(int argc, char **argv)
413 {
415  int opt;
416 
417  program_name = argv[0];
418 
419  while ((opt = getopt(argc, argv, "dp:l:b:h")) != -1) {
420  switch (opt) {
421  case 'd':
422  tq_debug_enabled = true;
423  break;
424  case 'l':
425  open_log(optarg);
426  break;
427  case 'b':
428  db_path = optarg;
429  break;
430  case 'p':
431  pauseLength = atoi(optarg);
432  break;
433  case 'h':
434  usage();
435  exit(EXIT_SUCCESS);
436  break;
437  }
438  }
439 
440  log_info("Starting %s\n", __FILE__);
441  setbuf(stdout, NULL);
442 
443  init_db();
444 
445  if ( optind + 1 != argc ) {
446  usage();
447  exit(EXIT_FAILURE);
448  } else {
449  readConfig(argv[optind]);
450  }
451 
452  log_info("Waiting for requests...\n");
453  while (fgets(request, HELPER_INPUT_BUFFER, stdin)) {
454  // we expect the following line syntax: %LOGIN
455  const char *user_key = strtok(request, " \n");
456  if (!user_key) {
457  SEND_BH(HLP_MSG("User name missing"));
458  continue;
459  }
460  processActivity(user_key);
461  }
462  log_info("Ending %s\n", __FILE__);
463  shutdown_db();
464  return EXIT_SUCCESS;
465 }
466 
static void parseTime(const char *s, time_t *secs, time_t *start)
int main(int, char *[])
Definition: unitTestMain.h:25
static void vlog(const char *level, const char *format, va_list args)
void usage()
Definition: find_password.c:40
static void log_fatal(const char *format,...)
static void writeTime(const char *user_key, const char *sub_key, time_t t)
static void shutdown_db(void)
struct _request * request(char *urlin)
Definition: tcp-banger2.c:291
static int tq_debug_enabled
#define TQ_BUFFERSIZE
static void init_db(void)
#define SEND_OK(x)
static time_t readTime(const char *user_key, const char *sub_key)
#define HELPER_INPUT_BUFFER
Definition: UserRequest.cc:26
#define KEY_LAST_ACTIVITY
#define HLP_MSG(text)
void const char HLPCB void * data
Definition: stub_helper.cc:16
static void open_log(const char *logfilename)
static void processActivity(const char *user_key)
int getopt(int nargc, char *const *nargv, const char *ostr)
Definition: getopt.c:62
static int pauseLength
#define KEY_PERIOD_START
#define KEY_PERIOD_LENGTH_CONFIGURED
int unsigned int const char *desc STUB void int len
Definition: stub_fd.cc:20
int optind
Definition: getopt.c:48
static FILE * logfile
#define KEY_TIME_BUDGET_CONFIGURED
#define SEND_BH(x)
static void log_info(const char *format,...)
char * db_path
static void log_debug(const char *format,...)
static void log_error(const char *format,...)
char * program_name
static hrtime_t now
Definition: ProfStats.cc:256
#define SEND_ERR(x)
static void readConfig(const char *filename)
#define KEY_TIME_BUDGET_LEFT
TDB_CONTEXT * db
char * optarg
Definition: getopt.c:51
static char * KeyString(int &len, const char *user_key, const char *sub_key)
#define NULL
Definition: types.h:166

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors