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 
45 /* At this point all Bit Types are already defined, so we must
46  protect from multiple type definition on platform where
47  __BIT_TYPES_DEFINED__ is not defined.
48  */
49 #ifndef __BIT_TYPES_DEFINED__
50 #define __BIT_TYPES_DEFINED__
51 #endif
52 
53 #if HAVE_DB_185_H
54 #include <db_185.h>
55 #elif HAVE_DB_H
56 #include <db.h>
57 #endif
58 
59 #ifndef DEFAULT_QUOTA_DB
60 #error "Please define DEFAULT_QUOTA_DB preprocessor constant."
61 #endif
62 
63 const char *db_path = DEFAULT_QUOTA_DB;
64 const char *program_name;
65 
66 DB *db = NULL;
67 
68 #define KEY_LAST_ACTIVITY "last-activity"
69 #define KEY_PERIOD_START "period-start"
70 #define KEY_PERIOD_LENGTH_CONFIGURED "period-length-configured"
71 #define KEY_TIME_BUDGET_LEFT "time-budget-left"
72 #define KEY_TIME_BUDGET_CONFIGURED "time-budget-configured"
73 
75 #define TQ_BUFFERSIZE 1024
76 
85 static int pauseLength = 300;
86 
87 static FILE *logfile = stderr;
88 static int tq_debug_enabled = false;
89 
90 static void open_log(const char *logfilename)
91 {
92  logfile = fopen(logfilename, "a");
93  if ( logfile == NULL ) {
94  perror(logfilename);
95  logfile = stderr;
96  }
97 }
98 
99 static void vlog(const char *level, const char *format, va_list args)
100 {
101  time_t now = time(NULL);
102 
103  fprintf(logfile, "%ld %s| %s: ", static_cast<long int>(now),
104  program_name, level);
105  vfprintf (logfile, format, args);
106  fflush(logfile);
107 }
108 
109 static void log_debug(const char *format, ...)
110 {
111  va_list args;
112 
113  if ( tq_debug_enabled ) {
114  va_start (args, format);
115  vlog("DEBUG", format, args);
116  va_end (args);
117  }
118 }
119 
120 static void log_info(const char *format, ...)
121 {
122  va_list args;
123 
124  va_start (args, format);
125  vlog("INFO", format, args);
126  va_end (args);
127 }
128 
129 static void log_error(const char *format, ...)
130 {
131  va_list args;
132 
133  va_start (args, format);
134  vlog("ERROR", format, args);
135  va_end (args);
136 }
137 
138 static void log_fatal(const char *format, ...)
139 {
140  va_list args;
141 
142  va_start (args, format);
143  vlog("FATAL", format, args);
144  va_end (args);
145 }
146 
147 static void init_db(void)
148 {
149  log_info("opening time quota database \"%s\".\n", db_path);
150  db = dbopen(db_path, O_CREAT | O_RDWR, 0666, DB_BTREE, NULL);
151  if (!db) {
152  log_fatal("Failed to open time_quota db '%s'\n", db_path);
153  exit(EXIT_FAILURE);
154  }
155 }
156 
157 static void shutdown_db(void)
158 {
159  db->close(db);
160 }
161 
162 static void writeTime(const char *user_key, const char *sub_key, time_t t)
163 {
164  char keybuffer[TQ_BUFFERSIZE];
165  DBT key, data;
166 
167  if ( strlen(user_key) + strlen(sub_key) + 1 + 1 > sizeof(keybuffer) ) {
168  log_error("key too long (%s,%s)\n", user_key, sub_key);
169  } else {
170  snprintf(keybuffer, sizeof(keybuffer), "%s-%s", user_key, sub_key);
171 
172  key.data = (void *)keybuffer;
173  key.size = strlen(keybuffer);
174  data.data = &t;
175  data.size = sizeof(t);
176  db->put(db, &key, &data, 0);
177  log_debug("writeTime(\"%s\", %d)\n", keybuffer, t);
178  }
179 }
180 
181 static time_t readTime(const char *user_key, const char *sub_key)
182 {
183  char keybuffer[TQ_BUFFERSIZE];
184  DBT key, data;
185  time_t t = 0;
186 
187  if ( strlen(user_key) + 1 + strlen(sub_key) + 1 > sizeof(keybuffer) ) {
188  log_error("key too long (%s,%s)\n", user_key, sub_key);
189  } else {
190  snprintf(keybuffer, sizeof(keybuffer), "%s-%s", user_key, sub_key);
191 
192  key.data = (void *)keybuffer;
193  key.size = strlen(keybuffer);
194  if (db->get(db, &key, &data, 0) == 0) {
195  if (data.size != sizeof(t)) {
196  log_error("CORRUPTED DATABASE (%s)\n", keybuffer);
197  } else {
198  memcpy(&t, data.data, sizeof(t));
199  }
200  }
201  log_debug("readTime(\"%s\")=%d\n", keybuffer, t);
202  }
203 
204  return t;
205 }
206 
207 static void parseTime(const char *s, time_t *secs, time_t *start)
208 {
209  double value;
210  char unit;
211  struct tm *ltime;
212  int periodLength = 3600;
213 
214  *secs = 0;
215  *start = time(NULL);
216  ltime = localtime(start);
217 
218  sscanf(s, " %lf %c", &value, &unit);
219  switch (unit) {
220  case 's':
221  periodLength = 1;
222  break;
223  case 'm':
224  periodLength = 60;
225  *start -= ltime->tm_sec;
226  break;
227  case 'h':
228  periodLength = 3600;
229  *start -= ltime->tm_min * 60 + ltime->tm_sec;
230  break;
231  case 'd':
232  periodLength = 24 * 3600;
233  *start -= ltime->tm_hour * 3600 + ltime->tm_min * 60 + ltime->tm_sec;
234  break;
235  case 'w':
236  periodLength = 7 * 24 * 3600;
237  *start -= ltime->tm_hour * 3600 + ltime->tm_min * 60 + ltime->tm_sec;
238  *start -= ltime->tm_wday * 24 * 3600;
239  *start += 24 * 3600; // in europe, the week starts monday
240  break;
241  default:
242  log_error("Wrong time unit \"%c\". Only \"m\", \"h\", \"d\", or \"w\" allowed.\n", unit);
243  break;
244  }
245 
246  *secs = (long)(periodLength * value);
247 }
248 
252 static void readConfig(const char *filename)
253 {
254  char line[TQ_BUFFERSIZE]; /* the buffer for the lines read
255  from the dict file */
256  char *cp; /* a char pointer used to parse
257  each line */
258  char *username; /* for the username */
259  char *budget;
260  char *period;
261  FILE *FH;
262  time_t t;
263  time_t budgetSecs, periodSecs;
264  time_t start;
265 
266  log_info("reading config file \"%s\".\n", filename);
267 
268  FH = fopen(filename, "r");
269  if ( FH ) {
270  /* the pointer to the first entry in the linked list */
271  unsigned int lineCount = 0;
272  while (fgets(line, sizeof(line), FH)) {
273  ++lineCount;
274  if (line[0] == '#') {
275  continue;
276  }
277  if ((cp = strchr (line, '\n')) != NULL) {
278  /* chop \n characters */
279  *cp = '\0';
280  }
281  log_debug("read config line %u: \"%s\".\n", lineCount, line);
282  if ((username = strtok(line, "\t ")) != NULL) {
283 
284  /* get the time budget */
285  if ((budget = strtok(NULL, "/")) == NULL) {
286  fprintf(stderr, "ERROR: missing 'budget' field on line %u of '%s'.\n", lineCount, filename);
287  continue;
288  }
289  if ((period = strtok(NULL, "/")) == NULL) {
290  fprintf(stderr, "ERROR: missing 'period' field on line %u of '%s'.\n", lineCount, filename);
291  continue;
292  }
293 
294  parseTime(budget, &budgetSecs, &start);
295  parseTime(period, &periodSecs, &start);
296 
297  log_debug("read time quota for user \"%s\": %lds / %lds starting %lds\n",
298  username, budgetSecs, periodSecs, start);
299 
300  writeTime(username, KEY_PERIOD_START, start);
301  writeTime(username, KEY_PERIOD_LENGTH_CONFIGURED, periodSecs);
302  writeTime(username, KEY_TIME_BUDGET_CONFIGURED, budgetSecs);
303  t = readTime(username, KEY_TIME_BUDGET_CONFIGURED);
304  writeTime(username, KEY_TIME_BUDGET_LEFT, t);
305  }
306  }
307  fclose(FH);
308  } else {
309  perror(filename);
310  }
311 }
312 
313 static void processActivity(const char *user_key)
314 {
315  time_t now = time(NULL);
316  time_t lastActivity;
317  time_t activityLength;
318  time_t periodStart;
319  time_t periodLength;
320  time_t userPeriodLength;
321  time_t timeBudgetCurrent;
322  time_t timeBudgetConfigured;
323  char message[TQ_BUFFERSIZE];
324 
325  log_debug("processActivity(\"%s\")\n", user_key);
326 
327  // [1] Reset period if over
328  periodStart = readTime(user_key, KEY_PERIOD_START);
329  if ( periodStart == 0 ) {
330  // This is the first period ever.
331  periodStart = now;
332  writeTime(user_key, KEY_PERIOD_START, periodStart);
333  }
334 
335  periodLength = now - periodStart;
336  userPeriodLength = readTime(user_key, KEY_PERIOD_LENGTH_CONFIGURED);
337  if ( userPeriodLength == 0 ) {
338  // This user is not configured. Allow anything.
339  log_debug("No period length found for user \"%s\". Quota for this user disabled.\n", user_key);
341  } else {
342  if ( periodLength >= userPeriodLength ) {
343  // a new period has started.
344  log_debug("New time period started for user \"%s\".\n", user_key);
345  while ( periodStart < now ) {
346  periodStart += periodLength;
347  }
348  writeTime(user_key, KEY_PERIOD_START, periodStart);
349  timeBudgetConfigured = readTime(user_key, KEY_TIME_BUDGET_CONFIGURED);
350  if ( timeBudgetConfigured == 0 ) {
351  log_debug("No time budget configured for user \"%s\". Quota for this user disabled.\n", user_key);
353  } else {
354  writeTime(user_key, KEY_TIME_BUDGET_LEFT, timeBudgetConfigured);
355  }
356  }
357  }
358 
359  // [2] Decrease time budget iff activity
360  lastActivity = readTime(user_key, KEY_LAST_ACTIVITY);
361  if ( lastActivity == 0 ) {
362  // This is the first request ever
363  writeTime(user_key, KEY_LAST_ACTIVITY, now);
364  } else {
365  activityLength = now - lastActivity;
366  if ( activityLength >= pauseLength ) {
367  // This is an activity pause.
368  log_debug("Activity pause detected for user \"%s\".\n", user_key);
369  writeTime(user_key, KEY_LAST_ACTIVITY, now);
370  } else {
371  // This is real usage.
372  writeTime(user_key, KEY_LAST_ACTIVITY, now);
373 
374  log_debug("Time budget reduced by %ld for user \"%s\".\n",
375  activityLength, user_key);
376  timeBudgetCurrent = readTime(user_key, KEY_TIME_BUDGET_LEFT);
377  timeBudgetCurrent -= activityLength;
378  writeTime(user_key, KEY_TIME_BUDGET_LEFT, timeBudgetCurrent);
379  }
380  }
381 
382  timeBudgetCurrent = readTime(user_key, KEY_TIME_BUDGET_LEFT);
383  snprintf(message, TQ_BUFFERSIZE, "message=\"Remaining quota for '%s' is %d seconds.\"", user_key, (int)timeBudgetCurrent);
384  if ( timeBudgetCurrent > 0 ) {
385  log_debug("OK %s.\n", message);
386  SEND_OK(message);
387  } else {
388  log_debug("ERR %s\n", message);
389  SEND_ERR("Time budget exceeded.");
390  }
391 
392  db->sync(db, 0);
393 }
394 
395 static void usage(void)
396 {
397  log_error("Wrong usage. Please reconfigure in squid.conf.\n");
398 
399  fprintf(stderr, "Usage: %s [-d] [-l logfile] [-b dbpath] [-p pauselen] [-h] configfile\n", program_name);
400  fprintf(stderr, " -d enable debugging output to logfile\n");
401  fprintf(stderr, " -l logfile log messages to logfile\n");
402  fprintf(stderr, " -b dbpath Path where persistent session database will be kept\n");
403  fprintf(stderr, " If option is not used, then " DEFAULT_QUOTA_DB " will be used.\n");
404  fprintf(stderr, " -p pauselen length in seconds to describe a pause between 2 requests.\n");
405  fprintf(stderr, " -h show show command line help.\n");
406  fprintf(stderr, "configfile is a file containing time quota definitions.\n");
407 }
408 
409 int main(int argc, char **argv)
410 {
412  int opt;
413 
414  program_name = argv[0];
415 
416  while ((opt = getopt(argc, argv, "dp:l:b:h")) != -1) {
417  switch (opt) {
418  case 'd':
419  tq_debug_enabled = true;
420  break;
421  case 'l':
422  open_log(optarg);
423  break;
424  case 'b':
425  db_path = optarg;
426  break;
427  case 'p':
428  pauseLength = atoi(optarg);
429  break;
430  case 'h':
431  usage();
432  exit(EXIT_SUCCESS);
433  break;
434  }
435  }
436 
437  log_info("Starting %s\n", __FILE__);
438  setbuf(stdout, NULL);
439 
440  init_db();
441 
442  if ( optind + 1 != argc ) {
443  usage();
444  exit(EXIT_FAILURE);
445  } else {
446  readConfig(argv[optind]);
447  }
448 
449  log_info("Waiting for requests...\n");
450  while (fgets(request, HELPER_INPUT_BUFFER, stdin)) {
451  // we expect the following line syntax: %LOGIN
452  const char *user_key = strtok(request, " \n");
453  if (!user_key) {
454  SEND_BH(HLP_MSG("User name missing"));
455  continue;
456  }
457  processActivity(user_key);
458  }
459  log_info("Ending %s\n", __FILE__);
460  shutdown_db();
461  return EXIT_SUCCESS;
462 }
463 
static void parseTime(const char *s, time_t *secs, time_t *start)
static void vlog(const char *level, const char *format, va_list args)
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
int main(int argc, char **argv)
static int tq_debug_enabled
#define TQ_BUFFERSIZE
const char * program_name
static void init_db(void)
static void usage(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
DB * db
#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
const char * db_path
#define KEY_PERIOD_LENGTH_CONFIGURED
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,...)
static void log_debug(const char *format,...)
static void log_error(const char *format,...)
static hrtime_t now
Definition: ProfStats.cc:256
#define SEND_ERR(x)
static void readConfig(const char *filename)
#define KEY_TIME_BUDGET_LEFT
char * optarg
Definition: getopt.c:51
#define NULL
Definition: types.h:166

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors