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