]> git.ipfire.org Git - thirdparty/squid.git/blame - src/acl/external/time_quota/ext_time_quota_acl.cc
Docs: Copyright updates for 2018 (#114)
[thirdparty/squid.git] / src / acl / external / time_quota / ext_time_quota_acl.cc
CommitLineData
ca02e0ec 1/*
5b74111a 2 * Copyright (C) 1996-2018 The Squid Software Foundation and contributors
ca02e0ec
AJ
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
9938b57f
TB
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
f7f3304a 29#include "squid.h"
079b1d0f 30#include "helper/protocol_defines.h"
9938b57f 31
074d6a40
AJ
32#include <cstdarg>
33#include <cstdlib>
34#include <cstring>
35#include <ctime>
9938b57f
TB
36#include <sys/types.h>
37#include <sys/stat.h>
38#include <fcntl.h>
9938b57f
TB
39#if HAVE_UNISTD_H
40#include <unistd.h>
41#endif
9938b57f
TB
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
64const char *db_path = DEFAULT_QUOTA_DB;
65const char *program_name;
66
67DB *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
75/** Maximum size of buffers used to read or display lines. */
76#define TQ_BUFFERSIZE 1024
77
78/** If there is more than this given number of seconds between two
f8901ea9 79 * sucessive requests, than the second request will be treated as a
9938b57f
TB
80 * new request and the time between first and seconds request will
81 * be treated as a activity pause.
82 *
83 * Otherwise the following request will be treated as belonging to the
84 * same activity and the quota will be reduced.
85 */
86static int pauseLength = 300;
87
88static FILE *logfile = stderr;
89static int tq_debug_enabled = false;
90
f8901ea9
A
91static void open_log(const char *logfilename)
92{
9938b57f
TB
93 logfile = fopen(logfilename, "a");
94 if ( logfile == NULL ) {
f8901ea9
A
95 perror(logfilename);
96 logfile = stderr;
9938b57f
TB
97 }
98}
99
100static void vlog(const char *level, const char *format, va_list args)
101{
102 time_t now = time(NULL);
103
3876fdac
A
104 fprintf(logfile, "%ld %s| %s: ", static_cast<long int>(now),
105 program_name, level);
9938b57f
TB
106 vfprintf (logfile, format, args);
107 fflush(logfile);
108}
109
110static void log_debug(const char *format, ...)
111{
112 va_list args;
113
114 if ( tq_debug_enabled ) {
f8901ea9
A
115 va_start (args, format);
116 vlog("DEBUG", format, args);
117 va_end (args);
9938b57f
TB
118 }
119}
120
121static 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
130static 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
139static 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
148static 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);
24885773 154 exit(EXIT_FAILURE);
9938b57f
TB
155 }
156}
157
158static void shutdown_db(void)
159{
160 db->close(db);
161}
162
163static 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) ) {
f8901ea9 169 log_error("key too long (%s,%s)\n", user_key, sub_key);
9938b57f 170 } else {
f8901ea9
A
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);
9938b57f
TB
179 }
180}
181
182static 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) ) {
f8901ea9 189 log_error("key too long (%s,%s)\n", user_key, sub_key);
9938b57f 190 } else {
f8901ea9
A
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);
9938b57f
TB
203 }
204
205 return t;
206}
207
208static 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':
f8901ea9
A
222 periodLength = 1;
223 break;
9938b57f 224 case 'm':
f8901ea9
A
225 periodLength = 60;
226 *start -= ltime->tm_sec;
227 break;
9938b57f 228 case 'h':
f8901ea9
A
229 periodLength = 3600;
230 *start -= ltime->tm_min * 60 + ltime->tm_sec;
231 break;
9938b57f 232 case 'd':
f8901ea9
A
233 periodLength = 24 * 3600;
234 *start -= ltime->tm_hour * 3600 + ltime->tm_min * 60 + ltime->tm_sec;
235 break;
9938b57f 236 case 'w':
f8901ea9
A
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;
9938b57f 242 default:
f8901ea9
A
243 log_error("Wrong time unit \"%c\". Only \"m\", \"h\", \"d\", or \"w\" allowed.\n", unit);
244 break;
9938b57f
TB
245 }
246
247 *secs = (long)(periodLength * value);
248}
249
9938b57f
TB
250/** This function parses the time quota file and stores it
251 * in memory.
252 */
f8901ea9 253static void readConfig(const char *filename)
9938b57f
TB
254{
255 char line[TQ_BUFFERSIZE]; /* the buffer for the lines read
f53969cc
SM
256 from the dict file */
257 char *cp; /* a char pointer used to parse
258 each line */
259 char *username; /* for the username */
9938b57f
TB
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 ) {
f8901ea9 271 /* the pointer to the first entry in the linked list */
03f581b0
AJ
272 unsigned int lineCount = 0;
273 while (fgets(line, sizeof(line), FH)) {
274 ++lineCount;
f8901ea9
A
275 if (line[0] == '#') {
276 continue;
277 }
278 if ((cp = strchr (line, '\n')) != NULL) {
279 /* chop \n characters */
280 *cp = '\0';
281 }
03f581b0
AJ
282 log_debug("read config line %u: \"%s\".\n", lineCount, line);
283 if ((username = strtok(line, "\t ")) != NULL) {
f8901ea9
A
284
285 /* get the time budget */
03f581b0
AJ
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 }
f8901ea9
A
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);
9938b57f 309 } else {
f8901ea9 310 perror(filename);
9938b57f
TB
311 }
312}
313
314static void processActivity(const char *user_key)
315{
f8901ea9
A
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);
341 writeTime(user_key, KEY_TIME_BUDGET_LEFT, pauseLength);
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);
353 writeTime(user_key, KEY_TIME_BUDGET_LEFT, pauseLength);
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);
9938b57f
TB
394}
395
396static 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
410int main(int argc, char **argv)
411{
412 char request[HELPER_INPUT_BUFFER];
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':
f8901ea9 420 tq_debug_enabled = true;
9938b57f
TB
421 break;
422 case 'l':
f8901ea9 423 open_log(optarg);
9938b57f
TB
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();
24885773 433 exit(EXIT_SUCCESS);
9938b57f
TB
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 ) {
f8901ea9 444 usage();
24885773 445 exit(EXIT_FAILURE);
9938b57f 446 } else {
f8901ea9 447 readConfig(argv[optind]);
9938b57f
TB
448 }
449
450 log_info("Waiting for requests...\n");
451 while (fgets(request, HELPER_INPUT_BUFFER, stdin)) {
03f581b0
AJ
452 // we expect the following line syntax: %LOGIN
453 const char *user_key = strtok(request, " \n");
454 if (!user_key) {
194ccc9c 455 SEND_BH(HLP_MSG("User name missing"));
03f581b0
AJ
456 continue;
457 }
f8901ea9 458 processActivity(user_key);
9938b57f
TB
459 }
460 log_info("Ending %s\n", __FILE__);
461 shutdown_db();
24885773 462 return EXIT_SUCCESS;
9938b57f 463}
f53969cc 464