]> git.ipfire.org Git - thirdparty/squid.git/blame - src/acl/external/time_quota/ext_time_quota_acl.cc
Bug 5428: Warn if pkg-config is not found (#1902)
[thirdparty/squid.git] / src / acl / external / time_quota / ext_time_quota_acl.cc
CommitLineData
ca02e0ec 1/*
b8ae064d 2 * Copyright (C) 1996-2023 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
4878cfe5
FC
29/* DEBUG: section 82 External ACL Helpers */
30
f7f3304a 31#include "squid.h"
4878cfe5 32#include "debug/Stream.h"
079b1d0f 33#include "helper/protocol_defines.h"
4878cfe5 34#include "sbuf/Stream.h"
9938b57f 35
074d6a40 36#include <ctime>
9938b57f
TB
37#if HAVE_GETOPT_H
38#include <getopt.h>
39#endif
acd207af
AJ
40#if HAVE_TDB_H
41#include <tdb.h>
9938b57f
TB
42#endif
43
44#ifndef DEFAULT_QUOTA_DB
45#error "Please define DEFAULT_QUOTA_DB preprocessor constant."
46#endif
47
4878cfe5 48static const auto MY_DEBUG_SECTION = 82;
9938b57f
TB
49const char *db_path = DEFAULT_QUOTA_DB;
50const char *program_name;
51
acd207af 52TDB_CONTEXT *db = nullptr;
9938b57f 53
4878cfe5
FC
54static const auto KeyLastActivity = "last-activity";
55static const auto KeyPeriodStart = "period-start";
56static const auto KeyPeriodLengthConfigured = "period-length-configured";
57static const auto KeyTimeBudgetLeft = "time-budget-left";
58static const auto KeyTimeBudgetConfigured = "time-budget-configured";
9938b57f
TB
59
60/** Maximum size of buffers used to read or display lines. */
4878cfe5 61static const size_t TQ_BUFFERSIZE = 1024;
9938b57f
TB
62
63/** If there is more than this given number of seconds between two
2f8abb64 64 * successive requests, than the second request will be treated as a
9938b57f
TB
65 * new request and the time between first and seconds request will
66 * be treated as a activity pause.
67 *
68 * Otherwise the following request will be treated as belonging to the
69 * same activity and the quota will be reduced.
70 */
71static int pauseLength = 300;
72
9938b57f
TB
73static void init_db(void)
74{
4878cfe5
FC
75 debugs(MY_DEBUG_SECTION, 2, "opening time quota database \"" << db_path << "\".");
76
acd207af 77 db = tdb_open(db_path, 0, TDB_CLEAR_IF_FIRST, O_CREAT | O_RDWR, 0666);
9938b57f 78 if (!db) {
4878cfe5 79 debugs(MY_DEBUG_SECTION, DBG_CRITICAL, "FATAL: Failed to open time_quota db '" << db_path << '\'');
24885773 80 exit(EXIT_FAILURE);
9938b57f 81 }
4878cfe5
FC
82 // count the number of entries in the database, only used for debugging
83 debugs(MY_DEBUG_SECTION, 2, "Database contains " << tdb_traverse(db, nullptr, nullptr) << " entries");
9938b57f
TB
84}
85
86static void shutdown_db(void)
87{
acd207af 88 tdb_close(db);
9938b57f
TB
89}
90
4878cfe5 91static SBuf KeyString(const char *user_key, const char *sub_key)
9938b57f 92{
4878cfe5 93 return ToSBuf(user_key, "-", sub_key);
acd207af
AJ
94}
95
96static void writeTime(const char *user_key, const char *sub_key, time_t t)
97{
4878cfe5
FC
98 auto ks = KeyString(user_key, sub_key);
99 const TDB_DATA key {
100 reinterpret_cast<unsigned char *>(const_cast<char *>(ks.rawContent())),
101 ks.length()
102 };
103 const TDB_DATA data {
104 reinterpret_cast<unsigned char *>(&t),
105 sizeof(t)
106 };
107
108 tdb_store(db, key, data, TDB_REPLACE);
109 debugs(MY_DEBUG_SECTION, 3, "writeTime(\"" << ks << "\", " << t << ')');
9938b57f
TB
110}
111
112static time_t readTime(const char *user_key, const char *sub_key)
113{
4878cfe5
FC
114 auto ks = KeyString(user_key, sub_key);
115 const TDB_DATA key {
116 reinterpret_cast<unsigned char *>(const_cast<char *>(ks.rawContent())),
117 ks.length()
118 };
119 auto data = tdb_fetch(db, key);
120
121 if (!data.dptr) {
122 debugs(MY_DEBUG_SECTION, 3, "no data found for key \"" << ks << "\".");
123 return 0;
124 }
acd207af 125
4878cfe5
FC
126 time_t t = 0;
127 if (data.dsize == sizeof(t)) {
128 memcpy(&t, data.dptr, sizeof(t));
129 } else {
130 debugs(MY_DEBUG_SECTION, DBG_IMPORTANT, "ERROR: Incompatible or corrupted database. " <<
131 "key: '" << ks <<
132 "', expected time value size: " << sizeof(t) <<
133 ", actual time value size: " << data.dsize);
9938b57f
TB
134 }
135
4878cfe5
FC
136 debugs(MY_DEBUG_SECTION, 3, "readTime(\"" << ks << "\")=" << t);
137 return t;
9938b57f
TB
138}
139
140static void parseTime(const char *s, time_t *secs, time_t *start)
141{
142 double value;
143 char unit;
144 struct tm *ltime;
145 int periodLength = 3600;
146
147 *secs = 0;
148 *start = time(NULL);
149 ltime = localtime(start);
150
151 sscanf(s, " %lf %c", &value, &unit);
152 switch (unit) {
153 case 's':
f8901ea9
A
154 periodLength = 1;
155 break;
9938b57f 156 case 'm':
f8901ea9
A
157 periodLength = 60;
158 *start -= ltime->tm_sec;
159 break;
9938b57f 160 case 'h':
f8901ea9
A
161 periodLength = 3600;
162 *start -= ltime->tm_min * 60 + ltime->tm_sec;
163 break;
9938b57f 164 case 'd':
f8901ea9
A
165 periodLength = 24 * 3600;
166 *start -= ltime->tm_hour * 3600 + ltime->tm_min * 60 + ltime->tm_sec;
167 break;
9938b57f 168 case 'w':
f8901ea9
A
169 periodLength = 7 * 24 * 3600;
170 *start -= ltime->tm_hour * 3600 + ltime->tm_min * 60 + ltime->tm_sec;
171 *start -= ltime->tm_wday * 24 * 3600;
172 *start += 24 * 3600; // in europe, the week starts monday
173 break;
9938b57f 174 default:
4878cfe5 175 debugs(MY_DEBUG_SECTION, DBG_IMPORTANT, "ERROR: Wrong time unit \"" << unit << "\". Only \"m\", \"h\", \"d\", or \"w\" allowed");
f8901ea9 176 break;
9938b57f
TB
177 }
178
179 *secs = (long)(periodLength * value);
180}
181
9938b57f
TB
182/** This function parses the time quota file and stores it
183 * in memory.
184 */
f8901ea9 185static void readConfig(const char *filename)
9938b57f
TB
186{
187 char line[TQ_BUFFERSIZE]; /* the buffer for the lines read
f53969cc
SM
188 from the dict file */
189 char *cp; /* a char pointer used to parse
190 each line */
191 char *username; /* for the username */
9938b57f
TB
192 char *budget;
193 char *period;
194 FILE *FH;
195 time_t t;
196 time_t budgetSecs, periodSecs;
197 time_t start;
198
4878cfe5 199 debugs(MY_DEBUG_SECTION, 2, "reading config file \"" << filename << "\".");
9938b57f
TB
200
201 FH = fopen(filename, "r");
202 if ( FH ) {
f8901ea9 203 /* the pointer to the first entry in the linked list */
03f581b0
AJ
204 unsigned int lineCount = 0;
205 while (fgets(line, sizeof(line), FH)) {
206 ++lineCount;
f8901ea9
A
207 if (line[0] == '#') {
208 continue;
209 }
210 if ((cp = strchr (line, '\n')) != NULL) {
211 /* chop \n characters */
212 *cp = '\0';
213 }
4878cfe5 214 debugs(MY_DEBUG_SECTION, 3, "read config line " << lineCount << ": \"" << line << '\"');
03f581b0 215 if ((username = strtok(line, "\t ")) != NULL) {
f8901ea9
A
216
217 /* get the time budget */
a1b1756c 218 if ((budget = strtok(nullptr, "/")) == NULL) {
4878cfe5 219 debugs(MY_DEBUG_SECTION, DBG_IMPORTANT, "ERROR: missing 'budget' field on line " << lineCount << " of '" << filename << '\'');
03f581b0
AJ
220 continue;
221 }
a1b1756c 222 if ((period = strtok(nullptr, "/")) == NULL) {
4878cfe5 223 debugs(MY_DEBUG_SECTION, DBG_IMPORTANT, "ERROR: missing 'period' field on line " << lineCount << " of '" << filename << '\'');
03f581b0
AJ
224 continue;
225 }
f8901ea9
A
226
227 parseTime(budget, &budgetSecs, &start);
228 parseTime(period, &periodSecs, &start);
229
4878cfe5
FC
230 debugs(MY_DEBUG_SECTION, 3, "read time quota for user \"" << username << "\": " <<
231 budgetSecs << "s / " << periodSecs << "s starting " << start);
f8901ea9 232
4878cfe5
FC
233 writeTime(username, KeyPeriodStart, start);
234 writeTime(username, KeyPeriodLengthConfigured, periodSecs);
235 writeTime(username, KeyTimeBudgetConfigured, budgetSecs);
236 t = readTime(username, KeyTimeBudgetConfigured);
237 writeTime(username, KeyTimeBudgetLeft, t);
f8901ea9
A
238 }
239 }
240 fclose(FH);
9938b57f 241 } else {
f8901ea9 242 perror(filename);
9938b57f
TB
243 }
244}
245
246static void processActivity(const char *user_key)
247{
f8901ea9
A
248 time_t now = time(NULL);
249 time_t lastActivity;
250 time_t activityLength;
251 time_t periodStart;
252 time_t periodLength;
253 time_t userPeriodLength;
254 time_t timeBudgetCurrent;
255 time_t timeBudgetConfigured;
f8901ea9 256
4878cfe5 257 debugs(MY_DEBUG_SECTION, 3, "processActivity(\"" << user_key << "\")");
f8901ea9
A
258
259 // [1] Reset period if over
4878cfe5 260 periodStart = readTime(user_key, KeyPeriodStart);
f8901ea9
A
261 if ( periodStart == 0 ) {
262 // This is the first period ever.
263 periodStart = now;
4878cfe5 264 writeTime(user_key, KeyPeriodStart, periodStart);
f8901ea9
A
265 }
266
267 periodLength = now - periodStart;
4878cfe5 268 userPeriodLength = readTime(user_key, KeyPeriodLengthConfigured);
f8901ea9
A
269 if ( userPeriodLength == 0 ) {
270 // This user is not configured. Allow anything.
4878cfe5
FC
271 debugs(MY_DEBUG_SECTION, 3, "disabling user quota for user '" <<
272 user_key << "': no period length found");
273 writeTime(user_key, KeyTimeBudgetLeft, pauseLength);
f8901ea9
A
274 } else {
275 if ( periodLength >= userPeriodLength ) {
276 // a new period has started.
4878cfe5 277 debugs(MY_DEBUG_SECTION, 3, "New time period started for user \"" << user_key << '\"');
f8901ea9
A
278 while ( periodStart < now ) {
279 periodStart += periodLength;
280 }
4878cfe5
FC
281 writeTime(user_key, KeyPeriodStart, periodStart);
282 timeBudgetConfigured = readTime(user_key, KeyTimeBudgetConfigured);
f8901ea9 283 if ( timeBudgetConfigured == 0 ) {
4878cfe5
FC
284 debugs(MY_DEBUG_SECTION, 3, "No time budget configured for user \"" << user_key <<
285 "\". Quota for this user disabled.");
286 writeTime(user_key, KeyTimeBudgetLeft, pauseLength);
f8901ea9 287 } else {
4878cfe5 288 writeTime(user_key, KeyTimeBudgetLeft, timeBudgetConfigured);
f8901ea9
A
289 }
290 }
291 }
292
293 // [2] Decrease time budget iff activity
4878cfe5 294 lastActivity = readTime(user_key, KeyLastActivity);
f8901ea9
A
295 if ( lastActivity == 0 ) {
296 // This is the first request ever
4878cfe5 297 writeTime(user_key, KeyLastActivity, now);
f8901ea9
A
298 } else {
299 activityLength = now - lastActivity;
300 if ( activityLength >= pauseLength ) {
301 // This is an activity pause.
4878cfe5
FC
302 debugs(MY_DEBUG_SECTION, 3, "Activity pause detected for user \"" << user_key << "\".");
303 writeTime(user_key, KeyLastActivity, now);
f8901ea9
A
304 } else {
305 // This is real usage.
4878cfe5 306 writeTime(user_key, KeyLastActivity, now);
f8901ea9 307
4878cfe5
FC
308 debugs(MY_DEBUG_SECTION, 3, "Time budget reduced by " << activityLength <<
309 " for user \"" << user_key << "\".");
310 timeBudgetCurrent = readTime(user_key, KeyTimeBudgetLeft);
f8901ea9 311 timeBudgetCurrent -= activityLength;
4878cfe5 312 writeTime(user_key, KeyTimeBudgetLeft, timeBudgetCurrent);
f8901ea9
A
313 }
314 }
315
4878cfe5
FC
316 timeBudgetCurrent = readTime(user_key, KeyTimeBudgetLeft);
317
318 const auto message = ToSBuf(HLP_MSG("Remaining quota for '"), user_key, "' is ", timeBudgetCurrent, " seconds.");
f8901ea9 319 if ( timeBudgetCurrent > 0 ) {
f8901ea9
A
320 SEND_OK(message);
321 } else {
f8901ea9
A
322 SEND_ERR("Time budget exceeded.");
323 }
9938b57f
TB
324}
325
326static void usage(void)
327{
4878cfe5
FC
328 debugs(MY_DEBUG_SECTION, DBG_CRITICAL, "Wrong usage. Please reconfigure in squid.conf.");
329
330 std::cerr <<
331 "Usage: " << program_name << " [-d] [-b dbpath] [-p pauselen] [-h] configfile\n"
332 " -d enable debugging output\n"
333 " -l logfile log messages to logfile\n"
334 " -b dbpath Path where persistent session database will be kept\n"
335 " If option is not used, then " DEFAULT_QUOTA_DB " will be used.\n"
336 " -p pauselen length in seconds to describe a pause between 2 requests.\n"
337 " -h show show command line help.\n"
338 "configfile is a file containing time quota definitions.\n";
9938b57f
TB
339}
340
341int main(int argc, char **argv)
342{
343 char request[HELPER_INPUT_BUFFER];
344 int opt;
345
346 program_name = argv[0];
4878cfe5 347 Debug::NameThisHelper("ext_time_quota_acl");
9938b57f 348
2d93cfe7 349 while ((opt = getopt(argc, argv, "dp:b:h")) != -1) {
9938b57f
TB
350 switch (opt) {
351 case 'd':
4878cfe5 352 Debug::Levels[MY_DEBUG_SECTION] = DBG_DATA;
9938b57f 353 break;
9938b57f
TB
354 case 'b':
355 db_path = optarg;
356 break;
357 case 'p':
358 pauseLength = atoi(optarg);
359 break;
360 case 'h':
361 usage();
24885773 362 exit(EXIT_SUCCESS);
9938b57f 363 break;
2d93cfe7
FC
364 default:
365 // getopt() emits error message to stderr
366 usage();
367 exit(EXIT_FAILURE);
368 break;
9938b57f
TB
369 }
370 }
371
4878cfe5 372 debugs(MY_DEBUG_SECTION, DBG_IMPORTANT, "Starting " << program_name);
a1b1756c 373 setbuf(stdout, nullptr);
9938b57f
TB
374
375 init_db();
376
377 if ( optind + 1 != argc ) {
f8901ea9 378 usage();
24885773 379 exit(EXIT_FAILURE);
9938b57f 380 } else {
f8901ea9 381 readConfig(argv[optind]);
9938b57f
TB
382 }
383
4878cfe5 384 debugs(MY_DEBUG_SECTION, 2, "Waiting for requests...");
9938b57f 385 while (fgets(request, HELPER_INPUT_BUFFER, stdin)) {
03f581b0
AJ
386 // we expect the following line syntax: %LOGIN
387 const char *user_key = strtok(request, " \n");
388 if (!user_key) {
194ccc9c 389 SEND_BH(HLP_MSG("User name missing"));
03f581b0
AJ
390 continue;
391 }
f8901ea9 392 processActivity(user_key);
9938b57f 393 }
4878cfe5 394 debugs(MY_DEBUG_SECTION, DBG_IMPORTANT, "Ending " << program_name);
9938b57f 395 shutdown_db();
24885773 396 return EXIT_SUCCESS;
9938b57f 397}
f53969cc 398