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