1 /*
2 * ufdbUserlist.c - URLfilterDB
3 *
4 * ufdbGuard is copyrighted (C) 2005-2020 by URLfilterDB with all rights reserved.
5 *
6 * Parts of ufdbGuard are based on squidGuard.
7 * This module is NOT based on squidGuard.
8 *
9 * RCS $Id: ufdbUserlist.c,v 1.12 2020/08/07 09:34:22 root Exp root $
10 */
11
12
13 #include "ufdb.h"
14 #include "sg.h"
15 #include "ufdblib.h"
16 #include "ufdblocks.h"
17
18 #include <stdio.h>
19 #include <errno.h>
20 #include <string.h>
21 #include <pthread.h>
22 #include <sys/stat.h>
23 #include <sys/types.h>
24
25 #ifdef __cplusplus
26 extern "C" {
27 #endif
28
29 extern pthread_rwlock_t TheDynamicSourcesLock; // TODO: put this in a header file
30
31
32
33 /* UFDBretrieveExecUserlist() uses a cache to support slow LDAP queries.
34 * During a reload the execuserlist results are not deleted any more and an
35 * unchanged command to retrieve a list of users is returned from the cache.
36 * For the 15-minute refresh of a userlist, there is a new function: UFDBrefreshExecUserlist();
37 */
38
39 typedef struct ulCacheElem {
40 char * command;
41 struct sgDb udb;
42 } ulCacheElem;
43
44 UFDB_GCC_ALIGN_CL
45 static ufdb_mutex ul_mutex = ufdb_mutex_initializer;
46
47 static unsigned NulCache = 0;
48 static ulCacheElem ulCache[UFDB_MAX_USERLISTS];
49
50
encodeCommand(char * command,char * encodedCommand,int maxlen)51 static void encodeCommand( char * command, char * encodedCommand, int maxlen )
52 {
53 int flen;
54
55 flen = 0;
56 while (*command != '\0')
57 {
58 if (*command == ' ' || *command == '\t')
59 {
60 *encodedCommand++ = '_';
61 }
62 else if (*command == '/' || *command == '\\')
63 {
64 *encodedCommand++ = '+';
65 }
66 else if (*command == '#' || *command == '|')
67 {
68 *encodedCommand++ = '+';
69 *encodedCommand++ = '-';
70 flen++;
71 }
72 else if (*command == '\'' || *command == '"' || *command == '`')
73 {
74 *encodedCommand++ = '+';
75 *encodedCommand++ = '+';
76 flen++;
77 }
78 else if (*command == '[' || *command == ']' || *command == '{' || *command == '}')
79 {
80 *encodedCommand++ = '-';
81 *encodedCommand++ = '+';
82 flen++;
83 }
84 else if (*command == '$' || *command == '%' || *command == '!' || *command == '~' ||
85 *command == '(' || *command == ')')
86 {
87 *encodedCommand++ = '_';
88 *encodedCommand++ = '_';
89 flen++;
90 }
91 else if (*command == '<' || *command == '>' || *command == '?')
92 {
93 *encodedCommand++ = '_';
94 *encodedCommand++ = '+';
95 flen++;
96 }
97 else
98 *encodedCommand++ = *command;
99 command++;
100
101 flen++;
102 if (flen == maxlen-8 && strlen(command) > 3)
103 {
104 /* The maximum filename length is maxlen characters and we are about to go over it.
105 * So calculate a hash for the rest of the command and put the hash value in the encoded command.
106 */
107 unsigned long h;
108 h = (69313 * *command) ^ *(command+1);
109 while (*command != '\0')
110 {
111 h = (h << 5) + ((*command * 7) ^ (h >> 3));
112 command++;
113 }
114 sprintf( encodedCommand, ".%6lu", h % 1000000 );
115 return;
116 }
117 }
118 *encodedCommand = '\0';
119 }
120
121
openCacheForWrite(struct ufdbGV * gv,char * command)122 static FILE * openCacheForWrite( struct ufdbGV * gv, char * command )
123 {
124 int ret;
125 FILE * fc;
126 char * dbhome;
127 char fileName[2048];
128
129 dbhome = gv->databaseDirectory;
130 strcpy( fileName, dbhome );
131 strcat( fileName, "/cache.execlists" );
132 errno = 0;
133 ret = mkdir( fileName, 0770 );
134 if (ret != 0 && errno != EEXIST)
135 {
136 ufdbLogError( "cannot create cache directory \"%s\": %s", fileName, strerror(errno) );
137 encodeCommand( command, fileName+strlen(fileName), 255 - sizeof("/cache.execlists") );
138 }
139 else
140 {
141 (void) chmod( fileName, 0770 );
142 strcat( fileName, "/" );
143 encodeCommand( command, fileName+strlen(fileName), 255 );
144 }
145
146 fc = fopen( fileName, "w" );
147 if (fc == NULL)
148 ufdbLogError( "cannot write to userlist cache file \"%s\": %s", fileName, strerror(errno) );
149 else if (gv->debug)
150 ufdbLogMessage( "writing to userlist cache file \"%s\"", fileName );
151
152 return fc;
153 }
154
155
openCacheForRead(struct ufdbGV * gv,char * command)156 static FILE * openCacheForRead( struct ufdbGV * gv, char * command )
157 {
158 FILE * fc;
159 char * dbhome;
160 struct stat stbuf;
161 char fileName[2048];
162
163 dbhome = gv->databaseDirectory;
164 strcpy( fileName, dbhome );
165 strcat( fileName, "/cache.execlists" );
166 if (stat( fileName, &stbuf ) == 0)
167 {
168 strcat( fileName, "/" );
169 encodeCommand( command, fileName+strlen(fileName), 255 );
170 }
171 else
172 encodeCommand( command, fileName+strlen(fileName), 255 - sizeof("/cache.execlists") );
173
174 fc = fopen( fileName, "r" );
175 if (fc != NULL)
176 {
177 if (gv->debug)
178 ufdbLogMessage( "reading from userlist cache file \"%s\"", fileName );
179 }
180
181 return fc;
182 }
183
184
readUserlistFromCache(FILE * fin,struct sgDb * db)185 static void readUserlistFromCache( FILE * fin, struct sgDb * db )
186 {
187 char line[10000];
188
189 db->dbhome = NULL;
190 db->dbcp = (void *) UFDBmemDBinit();
191 db->type = SGDBTYPE_EXECUSERLIST;
192
193 while (UFDBfgetsNoNL(line,sizeof(line),fin) != NULL)
194 {
195 UFDBmemDBinsert( (struct UFDBmemDB *) db->dbcp, line, NULL );
196 }
197 }
198
199
execUserlistCommand(struct ufdbGV * gv,char * command,struct sgDb * db)200 static int execUserlistCommand( struct ufdbGV * gv, char * command, struct sgDb * db )
201 {
202 int ullineno;
203 FILE * fin;
204 FILE * fcache;
205 char * lc;
206 time_t tb, te;
207 char line[10000];
208
209 if (gv->terminating)
210 return 0;
211
212 /* make a new cache file for this command */
213 fcache = openCacheForWrite( gv, command );
214
215 db->dbhome = NULL;
216 db->dbcp = (void *) UFDBmemDBinit();
217 db->type = SGDBTYPE_EXECUSERLIST;
218
219 tb = time( NULL );
220 errno = 0;
221 if ((fin = popen(command,"r")) == NULL)
222 {
223 ufdbLogError( "can't execute command of execuserlist \"%s\": %s *****", command, strerror(errno) );
224 return 0;
225 }
226 ullineno = 0;
227
228 while (UFDBfgetsNoNL(line,sizeof(line),fin) != NULL)
229 {
230 ullineno++;
231
232 if (gv->debug > 1 || gv->debugExternalScripts)
233 ufdbLogMessage( "execuserlist: received \"%s\"", line );
234
235 if (line[0] == '#') /* skip comments */
236 continue;
237
238 if (line[0] == '\0')
239 {
240 ufdbLogError( "execuserlist \"%s\": line %d: line is empty", command, ullineno );
241 continue;
242 }
243
244 for (lc = line; *lc != '\0'; lc++) /* convert username to lowercase chars */
245 {
246 if (*lc <= 'Z' && *lc >= 'A')
247 *lc += 'a' - 'A';
248 }
249 UFDBmemDBinsert( (struct UFDBmemDB *) db->dbcp, line, NULL );
250 if (fcache != NULL)
251 fprintf( fcache, "%s\n", line );
252 }
253 (void) pclose( fin );
254 if (fcache != NULL)
255 fclose( fcache );
256 te = time( NULL );
257
258 db->entries = ullineno;
259 ufdbLogMessage( "execuserlist: finished retrieving userlist (%d lines in %ld seconds) generated by \"%s\"",
260 ullineno, te - tb, command );
261
262 return 1;
263 }
264
265
UFDBdeleteUserlistCache(void)266 void UFDBdeleteUserlistCache( void )
267 {
268 unsigned i;
269
270 ufdb_mutex_lock( &ul_mutex ); // >>==========================
271
272 for (i = 0; i < NulCache; i++)
273 {
274 ufdbFree( ulCache[i].command );
275 UFDBmemDBdeleteDB( ulCache[i].udb.dbcp );
276 ulCache[i].udb.dbcp = NULL;
277 }
278 NulCache = 0;
279
280 ufdb_mutex_unlock( &ul_mutex ); // <<==========================
281 }
282
283
UFDBretrieveExecUserlist(struct ufdbGV * gv,char * command)284 struct sgDb * UFDBretrieveExecUserlist( struct ufdbGV * gv, char * command )
285 {
286 unsigned i;
287 FILE * fcache;
288
289 for (i = 0; i < NulCache; i++)
290 {
291 if (strcmp( ulCache[i].command, command ) == 0)
292 return &(ulCache[i].udb);
293 }
294
295 /* TO-DO: remove old userlist commands from the cache */
296 /* TO-DO: but do this only if in the last 2 reloads the cache is not used */
297
298 ufdb_mutex_lock( &ul_mutex );
299 i = NulCache;
300 ulCache[i].command = ufdbStrdup( command );
301 ulCache[i].udb.dbhome = NULL;
302 NulCache++;
303 ufdb_mutex_unlock( &ul_mutex );
304
305 if (NulCache == UFDB_MAX_USERLISTS)
306 ufdbLogFatalError( "UFDBrefreshExecUserlist: maximum number of %d dynamic userlists is reached", UFDB_MAX_USERLISTS );
307
308 /* See if we have the command cached in a file */
309 fcache = openCacheForRead( gv, command );
310 if (fcache == NULL)
311 execUserlistCommand( gv, command, &(ulCache[i].udb) );
312 else
313 {
314 if (gv->debug)
315 ufdbLogMessage( "reading cached userlist from file; command is \"%s\"", command );
316 readUserlistFromCache( fcache, &(ulCache[i].udb) );
317 fclose( fcache );
318 }
319
320 return &(ulCache[i].udb);
321 }
322
323
UFDBrefreshExecUserlist(struct ufdbGV * gv,char * command)324 void UFDBrefreshExecUserlist( struct ufdbGV * gv, char * command )
325 {
326 unsigned i;
327 int found;
328 int ret;
329 struct UFDBmemDB * oldUserlist;
330 struct sgDb tmpDb;
331
332 found = 0;
333 ufdb_mutex_lock( &ul_mutex ); // >>++++++++
334 for (i = 0; i < NulCache; i++)
335 {
336 if (strcmp( ulCache[i].command, command ) == 0)
337 {
338 found = 1;
339 break;
340 }
341 }
342 ufdb_mutex_unlock( &ul_mutex ); // <<++++++++
343
344 if (found)
345 {
346 execUserlistCommand( gv, command, &tmpDb );
347
348 if (gv->debugExternalScripts || gv->debug > 1)
349 ufdbLogMessage( "UFDBrefreshExecUserlist: replacing userlist generated with command \"%s\"", command );
350
351 ret = pthread_rwlock_wrlock( &TheDynamicSourcesLock ); // >-------------------------------------
352 if (ret != 0)
353 ufdbLogError( "UFDBrefreshExecUserlist: pthread_rwlock_wrlock TheDynamicSourcesLock failed: code %d",
354 ret );
355
356 (void) ufdb_mutex_lock( &ul_mutex ); // >>+++++++
357 oldUserlist = ulCache[i].udb.dbcp;
358 ulCache[i].udb.dbcp = tmpDb.dbcp;
359 (void) ufdb_mutex_unlock( &ul_mutex ); // <<+++++++
360
361 ret = pthread_rwlock_unlock( &TheDynamicSourcesLock ); // >-------------------------------------
362 if (ret != 0)
363 ufdbLogError( "UFDBrefreshExecUserlist: pthread_rwlock_unlock TheDynamicSourcesLock failed: code %d",
364 ret );
365
366 UFDBmemDBdeleteDB( oldUserlist );
367 }
368 else
369 {
370 ufdbLogError( "UFDBrefreshExecUserlist: could not find command in the cache: \"%s\" *****", command );
371
372 ufdb_mutex_lock( &ul_mutex ); // >>++++++++
373 i = NulCache;
374 ulCache[i].command = ufdbStrdup( command );
375 ulCache[i].udb.dbhome = NULL;
376 NulCache++;
377 ufdb_mutex_unlock( &ul_mutex ); // >>++++++++
378
379 if (NulCache == UFDB_MAX_USERLISTS)
380 ufdbLogFatalError( "UFDBrefreshExecUserlist: maximum number of %d dynamic userlists is reached",
381 UFDB_MAX_USERLISTS );
382
383 execUserlistCommand( gv, command, &tmpDb );
384
385 (void) ufdb_mutex_lock( &ul_mutex ); // >>++++++++
386 ulCache[i].udb.dbcp = tmpDb.dbcp;
387 (void) ufdb_mutex_unlock( &ul_mutex ); // <<++++++++
388 }
389 }
390
391
392 #ifdef __cplusplus
393 }
394 #endif
395