ext_time_quota_acl.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) 1996-2023 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
52const char *db_path = DEFAULT_QUOTA_DB;
53const char *program_name;
54
55TDB_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
74static int pauseLength = 300;
75
76static FILE *logfile = stderr;
77static int tq_debug_enabled = false;
78
79static 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
88static 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
98static 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
109static 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
118static 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
127static 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
136static 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
146static void shutdown_db(void)
147{
148 tdb_close(db);
149}
150
151static 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
169static 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
187static 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
212static 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
257static 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(nullptr, "/")) == NULL) {
291 fprintf(stderr, "ERROR: missing 'budget' field on line %u of '%s'.\n", lineCount, filename);
292 continue;
293 }
294 if ((period = strtok(nullptr, "/")) == 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);
309 writeTime(username, KEY_TIME_BUDGET_LEFT, t);
310 }
311 }
312 fclose(FH);
313 } else {
314 perror(filename);
315 }
316}
317
318static 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
398static 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
412int main(int argc, char **argv)
413{
414 char request[HELPER_INPUT_BUFFER];
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':
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, nullptr);
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
#define HELPER_INPUT_BUFFER
Definition: UserRequest.cc:24
static time_t now
Definition: cachemgr.cc:109
#define KEY_TIME_BUDGET_LEFT
static void log_error(const char *format,...)
static void shutdown_db(void)
TDB_CONTEXT * db
int main(int argc, char **argv)
#define TQ_BUFFERSIZE
static void log_debug(const char *format,...)
static void processActivity(const char *user_key)
static void writeTime(const char *user_key, const char *sub_key, time_t t)
static void init_db(void)
static time_t readTime(const char *user_key, const char *sub_key)
static void parseTime(const char *s, time_t *secs, time_t *start)
#define KEY_PERIOD_LENGTH_CONFIGURED
static void open_log(const char *logfilename)
static void vlog(const char *level, const char *format, va_list args)
#define KEY_LAST_ACTIVITY
static void log_info(const char *format,...)
const char * db_path
static int tq_debug_enabled
static void readConfig(const char *filename)
static FILE * logfile
static char * KeyString(int &len, const char *user_key, const char *sub_key)
static void log_fatal(const char *format,...)
static void usage(void)
const char * program_name
#define KEY_TIME_BUDGET_CONFIGURED
#define KEY_PERIOD_START
static int pauseLength
int getopt(int nargc, char *const *nargv, const char *ostr)
Definition: getopt.c:62
int optind
Definition: getopt.c:48
char * optarg
Definition: getopt.c:51
#define SEND_ERR(x)
#define SEND_OK(x)
#define HLP_MSG(text)
#define SEND_BH(x)
#define NULL
Definition: types.h:145

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors