1 /*
2   spamdyke -- a filter for stopping spam at connection time.
3   Copyright (C) 2015 Sam Clippinger (samc (at) silence (dot) org)
4 
5   This program is free software; you can redistribute it and/or modify
6   it under the terms of the GNU General Public License version 2 as
7   published by the Free Software Foundation.
8 
9   This program is distributed in the hope that it will be useful,
10   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   GNU General Public License for more details.
13 
14   You should have received a copy of the GNU General Public License
15   along with this program; if not, write to the Free Software
16   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17 */
18 
19 #include "config-qrv.h"
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <ctype.h>
24 #include <unistd.h>
25 #include <errno.h>
26 #include "spamdyke-qrv.h"
27 #include "fs-qrv.h"
28 #include "log-qrv.h"
29 
30 /*
31  * When passed an entire command line string (e.g. from a .qmail file), this
32  * function finds just the command itself, allowing for escaping characters
33  * and quoting.
34  *
35  * RETURNS:
36  *   characters copied to return_text
37  */
find_command(char * input_text,char * return_text,int size_return_text)38 int find_command(char *input_text, char *return_text, int size_return_text)
39   {
40   int return_value;
41   int i;
42   int escape;
43   int inside_quote;
44 
45   return_value = 0;
46   escape = 0;
47   inside_quote = 0;
48 
49   if ((input_text != NULL) &&
50       (return_text != NULL))
51     {
52     for (i = 0; input_text[i] != '\0'; i++)
53       if (!isspace((int)input_text[i]))
54         break;
55 
56     for (; (input_text[i] != '\0') && (return_value < size_return_text); i++)
57       if (isspace((int)input_text[i]) &&
58           !inside_quote &&
59           !escape)
60         break;
61       else if (escape)
62         {
63         return_text[return_value] = input_text[i];
64         return_value++;
65         escape = 0;
66         }
67       else if (input_text[i] == PATH_ESCAPE_CHAR)
68         escape = 1;
69       else if (input_text[i] == PATH_QUOTE_CHAR)
70         inside_quote = !inside_quote;
71       else
72         {
73         return_text[return_value] = input_text[i];
74         return_value++;
75         }
76 
77     return_text[return_value] = '\0';
78     }
79 
80   return(return_value);
81   }
82 
83 /*
84  * Return value:
85  *   NULL: no match
86  *   pointer to match within haystack: match
87  */
find_case_insensitive_needle(char * haystack,char * needle)88 char *find_case_insensitive_needle(char *haystack, char *needle)
89   {
90   char *return_value;
91   int i;
92   char tmp_buf[MAX_BUF + 1];
93 
94   if ((haystack != NULL) &&
95       (needle != NULL))
96     {
97     for (i = 0; (i < MAX_BUF) && (needle[i] != '\0'); i++)
98       tmp_buf[i] = tolower((int)needle[i]);
99     tmp_buf[i] = '\0';
100 
101     return_value = strstr(haystack, tmp_buf);
102     }
103   else
104     return_value = NULL;
105 
106   return(return_value);
107   }
108 
109 /*
110  * EXPECTS:
111  *   target_string = string content to search for, must not be NULL or zero length
112  *   strlen_target_string = length of target_string
113  *   target_entry = string content to search within, must not be NULL
114  *   strlen_target_entry = length of target_entry
115  *   start_wildcard = wildcard character that, if present as the first character in target_string, allows target_string to match in the middle of target_entry.  \0 disables this feature.
116  *   start_wildcard_matches = if start_wildcard matches the first character in target_string, a match is not found unless the character in target_entry immediately preceding the location is found
117  *     within start_wildcard_matches.  E.g. start_wildcard is '.', target_string is ".foo" and target_entry is "bar-foo-baz".  A match is found if start_wildcard_matches is "-!#" but not if it is
118  *     "/.,".  NULL disables this feature.
119  *   end_wildcard = like start_wildcard but at the end of target_string instead of the start
120  *   end_wildcard_matches = like start_wildcard_matches but at the end of target_string instead of the start
121  *
122  * Return value:
123  *   0: no match
124  *   1: match found
125  */
examine_entry(char * target_string,int strlen_target_string,char * target_entry,int strlen_target_entry,char start_wildcard,char * start_wildcard_matches,char end_wildcard,char * end_wildcard_matches)126 int examine_entry(char *target_string, int strlen_target_string, char *target_entry, int strlen_target_entry, char start_wildcard, char *start_wildcard_matches, char end_wildcard, char *end_wildcard_matches)
127   {
128   int return_value;
129   int check_start;
130   int check_end;
131   char *tmp_string;
132   char old_end_char;
133   char *tmp_entry;
134 
135   return_value = 0;
136   check_start = 0;
137   check_end = 0;
138 
139   if ((target_entry != NULL) &&
140       (strlen_target_entry > 0))
141     {
142     old_end_char = target_entry[strlen_target_entry - 1];
143     if ((end_wildcard != '\0') &&
144         (target_entry[strlen_target_entry - 1] == end_wildcard))
145       {
146       strlen_target_entry--;
147       target_entry[strlen_target_entry] = '\0';
148       check_end = 1;
149       }
150 
151     if ((start_wildcard != '\0') &&
152         (target_entry[0] == start_wildcard))
153       {
154       strlen_target_entry--;
155       tmp_entry = target_entry + 1;
156       check_start = 1;
157       }
158     else
159       tmp_entry = target_entry;
160 
161     tmp_string = find_case_insensitive_needle(target_string, tmp_entry);
162 
163     while ((tmp_string != NULL) &&
164            (return_value == 0))
165       if (((check_start &&
166             ((start_wildcard_matches == NULL) ||
167              (strchr(start_wildcard_matches, (tmp_string - 1)[0]) != NULL))) ||
168            (!check_start &&
169             (tmp_string == target_string))) &&
170           ((check_end &&
171             ((end_wildcard_matches == NULL) ||
172              (strchr(end_wildcard_matches, tmp_string[strlen_target_entry]) != NULL))) ||
173            (!check_end &&
174             (((tmp_string - target_string) + strlen_target_entry) == strlen_target_string))))
175         return_value = 1;
176       else
177         tmp_string = find_case_insensitive_needle(tmp_string + 1, tmp_entry);
178 
179     strlen_target_entry += check_end + check_start;
180     target_entry[strlen_target_entry - 1] = old_end_char;
181     }
182 
183   return(return_value);
184   }
185 
186 /*
187  * EXPECTS:
188  *   search_filename = the file to search, line by line
189  *   target_string = string content to search for, must not be NULL or zero length
190  *   strlen_target_string = length of target_string
191  *   start_wildcard = wildcard character that, if present as the first character in target_string, allows target_string to match in the middle of a line.  \0 disables this feature.
192  *   start_wildcard_matches = if start_wildcard matches the first character in target_string, a match is not found unless the character in the line immediately preceding the location is found
193  *     within start_wildcard_matches.  E.g. start_wildcard is '.', target_string is ".foo" and the line is "bar-foo-baz".  A match is found if start_wildcard_matches is "-!#" but not if it is
194  *     "/.,".  NULL disables this feature.
195  *   end_wildcard = like start_wildcard but at the end of target_string instead of the start
196  *   end_wildcard_matches = like start_wildcard_matches but at the end of target_string instead of the start
197  *
198  * Return value:
199  *   ERROR: -1
200  *   NOT FOUND: 0
201  *   FOUND: matching line number
202  */
search_file(struct qrv_settings * current_settings,char * search_filename,char * target_string,int strlen_target_string,char start_wildcard,char * start_wildcard_matches,char end_wildcard,char * end_wildcard_matches)203 int search_file(struct qrv_settings *current_settings, char *search_filename, char *target_string, int strlen_target_string, char start_wildcard, char *start_wildcard_matches, char end_wildcard, char *end_wildcard_matches)
204   {
205   int return_value;
206   FILE *tmp_file;
207   char tmp_buf[MAX_FILE_BUF + 1];
208   int line_num;
209   int i;
210   int strlen_buf;
211   char lower_start_wildcard;
212   char lower_end_wildcard;
213   char lower_target_string[MAX_BUF + 1];
214   int strlen_lower_target_string;
215   struct stat tmp_stat;
216 
217   return_value = 0;
218 
219   if ((target_string != NULL) &&
220       (strlen_target_string > 0))
221     {
222     QRV_LOG_EXCESSIVE(current_settings, LOG_EXCESSIVE_SEARCH_FILE, search_filename, strlen_target_string, target_string);
223 
224     if ((tmp_file = fopen(search_filename, "r")) != NULL)
225       {
226       line_num = 0;
227 
228       lower_start_wildcard = (start_wildcard != '\0') ? tolower((int)start_wildcard) : start_wildcard;
229       lower_end_wildcard = (end_wildcard != '\0') ? tolower((int)end_wildcard) : end_wildcard;
230 
231       strlen_lower_target_string = MINVAL(MAX_BUF, strlen_target_string);
232       for (i = 0; i < strlen_lower_target_string; i++)
233         lower_target_string[i] = tolower((int)target_string[i]);
234 
235       while (!feof(tmp_file) &&
236              (line_num < MAX_FILE_LINES))
237         {
238         if ((fscanf(tmp_file, "%" STRINGIFY(MAX_FILE_BUF) "[^\r\n]", tmp_buf) == 1) &&
239             (tmp_buf[0] != COMMENT_DELIMITER) &&
240             ((strlen_buf = strlen(tmp_buf)) > 0) &&
241             examine_entry(lower_target_string, strlen_lower_target_string, tmp_buf, strlen_buf, lower_start_wildcard, start_wildcard_matches, lower_end_wildcard, end_wildcard_matches))
242           {
243           return_value = line_num + 1;
244           break;
245           }
246 
247         fscanf(tmp_file, "%*1[\r\n]");
248         line_num++;
249         }
250 
251       if (line_num == MAX_FILE_LINES)
252         QRV_LOG_VERBOSE(current_settings, LOG_VERBOSE_FILE_TOO_LONG "%s", MAX_FILE_LINES, search_filename);
253 
254       fclose(tmp_file);
255       }
256     else if ((stat(search_filename, &tmp_stat) == -1) &&
257              (errno == ENOENT))
258       return_value = 0;
259     else
260       {
261       QRV_LOG_ERROR(current_settings, LOG_ERROR_OPEN_SEARCH "%s: %s", search_filename, strerror(errno));
262       return_value = -1;
263       }
264     }
265 
266   return(return_value);
267   }
268 
269 /*
270  * RETURNS:
271  *   -1 = error
272  *   0 = no match
273  *   >0 = line number where match was found
274  */
search_virtualdomains_file(struct qrv_settings * current_settings,char * search_filename,char * target_domain,int strlen_target_domain,char * return_entry,int * size_return_entry)275 int search_virtualdomains_file(struct qrv_settings *current_settings, char *search_filename, char *target_domain, int strlen_target_domain, char *return_entry, int *size_return_entry)
276   {
277   int return_value;
278   FILE *tmp_file;
279   char tmp_buf[MAX_FILE_BUF + 1];
280   int line_num;
281   int i;
282   int strlen_buf;
283   char lower_target_domain[MAX_BUF + 1];
284   int strlen_lower_target_domain;
285 
286   return_value = 0;
287 
288   if ((search_filename != NULL) &&
289       (target_domain != NULL) &&
290       (strlen_target_domain > 0))
291     {
292     if ((tmp_file = fopen(search_filename, "r")) != NULL)
293       {
294       line_num = 0;
295 
296       strlen_lower_target_domain = MINVAL(MAX_BUF, strlen_target_domain);
297       for (i = 0; i < strlen_lower_target_domain; i++)
298         lower_target_domain[i] = tolower((int)target_domain[i]);
299 
300       while (!feof(tmp_file) &&
301              (line_num < MAX_FILE_LINES))
302         {
303         if ((fscanf(tmp_file, "%" STRINGIFY(MAX_FILE_BUF) "[^\r\n]", tmp_buf) == 1) &&
304             (tmp_buf[0] != COMMENT_DELIMITER) &&
305             ((strlen_buf = strlen(tmp_buf)) > 0) &&
306             (strlen_buf > strlen_lower_target_domain) &&
307             !strncmp(tmp_buf, lower_target_domain, strlen_lower_target_domain) &&
308             (tmp_buf[strlen_lower_target_domain] == VIRTUALDOMAINS_DELIMITER))
309           {
310           return_value = line_num + 1;
311 
312           if ((return_entry != NULL) &&
313               (size_return_entry != NULL) &&
314               ((*size_return_entry) > 0))
315             {
316             *size_return_entry = MINVAL(strlen_buf - (strlen_lower_target_domain + 1), *size_return_entry);
317             memcpy(return_entry, tmp_buf + strlen_lower_target_domain + 1, *size_return_entry);
318             return_entry[*size_return_entry] = '\0';
319             }
320 
321           break;
322           }
323 
324         fscanf(tmp_file, "%*1[\r\n]");
325         line_num++;
326         }
327 
328       if (line_num == MAX_FILE_LINES)
329         QRV_LOG_VERBOSE(current_settings, LOG_VERBOSE_FILE_TOO_LONG "%s", MAX_FILE_LINES, search_filename);
330 
331       fclose(tmp_file);
332       }
333     else
334       {
335       QRV_LOG_ERROR(current_settings, LOG_ERROR_OPEN_SEARCH "%s: %s", search_filename, strerror(errno));
336       return_value = -1;
337       }
338     }
339 
340   return(return_value);
341   }
342 
343 /*
344  * start_line and end_line are 1-based.
345  * if end_line is -1, return_content will be realloc()ed as lines are read.
346  * if end_line is not -1, return_content must have at least ((end_line - start_line) + start_index + 1) entries preallocated.
347  * individual entries will always be allocated.
348  *
349  * Return value:
350  *   ERROR: -1
351  *   SUCCESS: number of lines read, excluding skipped lines (1-based)
352  */
read_file(struct qrv_settings * current_settings,char * target_filename,char *** return_content,int start_index,int start_line,int end_line,int all_lines)353 int read_file(struct qrv_settings *current_settings, char *target_filename, char ***return_content, int start_index, int start_line, int end_line, int all_lines)
354   {
355   int return_value;
356   int i;
357   int line_num;
358   int usable_line_num;
359   int strlen_line;
360   int zero_start;
361   FILE *tmp_file;
362   char file_buf[MAX_FILE_BUF + 1];
363   char **tmp_array;
364   char *tmp_char;
365 
366   return_value = 0;
367   line_num = 0;
368   usable_line_num = 0;
369 
370   if ((target_filename != NULL) &&
371       (target_filename[0] != '\0') &&
372       (return_content != NULL))
373     {
374     if ((tmp_file = fopen(target_filename, "r")) != NULL)
375       {
376       QRV_LOG_EXCESSIVE(current_settings, LOG_EXCESSIVE_OPEN_FILE, target_filename);
377 
378       zero_start = (start_line - 1) - start_index;
379 
380       if ((end_line == -1) &&
381           (start_index == 0))
382         *return_content = NULL;
383 
384       while (!feof(tmp_file) &&
385              (line_num < MAX_FILE_LINES) &&
386              ((end_line == -1) ||
387               (line_num < end_line)))
388         {
389         if ((fscanf(tmp_file, "%" STRINGIFY(MAX_FILE_BUF) "[^\r\n]", file_buf) == 1) &&
390             ((strlen_line = strlen(file_buf)) || 1) &&
391             (all_lines ||
392              ((file_buf[0] != COMMENT_DELIMITER) &&
393               (strlen_line > 0))) &&
394             (line_num >= zero_start))
395           {
396           QRV_LOG_EXCESSIVE(current_settings, LOG_EXCESSIVE_READ_LINE, strlen_line, target_filename, line_num + 1, file_buf);
397 
398           if (end_line == -1)
399             {
400             if ((tmp_array = (char **)realloc(*return_content, sizeof(char *) * ((line_num - zero_start) + 2))) != NULL)
401               {
402               tmp_array[line_num - zero_start] = NULL;
403               tmp_array[(line_num - zero_start) + 1] = NULL;
404               *return_content = tmp_array;
405               }
406             else
407               {
408               QRV_LOG_ERROR(current_settings, LOG_ERROR_MALLOC, sizeof(char *) * ((line_num - zero_start) + 2));
409               return_value = -1;
410               break;
411               }
412             }
413 
414           if ((tmp_char = (char *)malloc(sizeof(char) * (strlen_line + 1))) != NULL)
415             {
416             (*return_content)[line_num - zero_start] = tmp_char;
417             memcpy((*return_content)[line_num - zero_start], file_buf, sizeof(char) * strlen_line);
418             (*return_content)[line_num - zero_start][strlen_line] = '\0';
419             }
420           else
421             {
422             QRV_LOG_ERROR(current_settings, LOG_ERROR_MALLOC, sizeof(char) * (strlen_line + 1));
423             return_value = -1;
424             break;
425             }
426 
427           usable_line_num++;
428           }
429 
430         fscanf(tmp_file, "%*1[\r\n]");
431         line_num++;
432         }
433 
434       if (line_num == MAX_FILE_LINES)
435         QRV_LOG_VERBOSE(current_settings, LOG_VERBOSE_FILE_TOO_LONG "%s", MAX_FILE_LINES, target_filename);
436 
437       fclose(tmp_file);
438 
439       if (return_value == 0)
440         return_value = usable_line_num + 1;
441       else
442         {
443         if ((*return_content) != NULL)
444           {
445           for (i = start_index; i < (line_num - zero_start); i++)
446             if ((*return_content)[i] != NULL)
447               free((*return_content)[i]);
448 
449           (*return_content)[start_index] = NULL;
450           }
451 
452         if ((end_line == -1) &&
453             (start_index == 0))
454           {
455           free(*return_content);
456           *return_content = NULL;
457           }
458         }
459       }
460     else
461       {
462       QRV_LOG_ERROR(current_settings, LOG_ERROR_OPEN "%s: %s", target_filename, strerror(errno));
463       return_value = -1;
464       }
465     }
466 
467   return(return_value);
468   }
469 
470 /*
471  * Return value:
472  *   ERROR: -1
473  *   SUCCESS: length of returned line
474  */
read_file_first_line(struct qrv_settings * current_settings,char * target_filename,char ** return_content)475 int read_file_first_line(struct qrv_settings *current_settings, char *target_filename, char **return_content)
476   {
477   int return_value;
478   char *tmp_array[2];
479   char **tmp_ptr;
480 
481   return_value = 0;
482 
483   tmp_array[0] = NULL;
484   tmp_array[1] = NULL;
485 
486   /*
487    * Without this, read_file() crashes on the statement (*return_content)[0] = tmp_char;
488    * Why?
489    */
490   tmp_ptr = tmp_array;
491 
492   if ((return_content != NULL) &&
493       (read_file(current_settings, target_filename, (char ***)&tmp_ptr, 0, 1, 1, 0) != -1))
494     {
495     *return_content = tmp_array[0];
496     return_value = (tmp_array[0] != NULL) ? strlen(tmp_array[0]) : 0;
497     }
498 
499   return(return_value);
500   }
501 
502 /*
503  * Expects:
504  *   strlen_username is the length of the username if not NULL-terminated, or -1 if NULL-terminated
505  *   return_address is a preallocated buffer
506  *   max_return_address is the size of return_address, >= 0
507  *
508  * Return value:
509  *   return_address, filled with the reassembled address OR missing_data if the address is empty
510  */
reassemble_address(char * target_username,int strlen_username,char * target_domain,char * missing_data,char * return_address,int max_return_address,int * strlen_return_address)511 char *reassemble_address(char *target_username, int strlen_username, char *target_domain, char *missing_data, char *return_address, int max_return_address, int *strlen_return_address)
512   {
513   int tmp_strlen;
514 
515   tmp_strlen = 0;
516 
517   if ((return_address != NULL) &&
518       (max_return_address >= 0))
519     {
520     if ((target_username != NULL) &&
521         (target_username[0] != '\0'))
522       if ((target_domain != NULL) &&
523           (target_domain[0] != '\0'))
524         {
525         if (strlen_username == -1)
526           tmp_strlen = SNPRINTF(return_address, max_return_address, "%s@%s", target_username, target_domain);
527         else
528           tmp_strlen = SNPRINTF(return_address, max_return_address, "%.*s@%s", strlen_username, target_username, target_domain);
529         }
530       else
531         if (strlen_username == -1)
532           tmp_strlen = SNPRINTF(return_address, max_return_address, "%s", target_username);
533         else
534           tmp_strlen = SNPRINTF(return_address, max_return_address, "%.*s", strlen_username, target_username);
535     else if ((target_domain != NULL) &&
536              (target_domain[0] != '\0'))
537       tmp_strlen = SNPRINTF(return_address, max_return_address, "@%s", target_domain);
538     else if (missing_data != NULL)
539       tmp_strlen = SNPRINTF(return_address, max_return_address, "%s", missing_data);
540     else
541       return_address[0] = '\0';
542     }
543 
544   if (strlen_return_address != NULL)
545     *strlen_return_address = tmp_strlen;
546 
547   return(return_address);
548   }
549 
550 /*
551  * EXPECTS:
552  *   type_flag should be a value within the S_IFMT mask in stat.h -- S_IFDIR, S_IFREG, etc
553  *   permission_flags should be an OR of FILE_PERMISSION_READ, FILE_PERMISSION_WRITE or FILE_PERMISSION_EXECUTE, not bitshifted for user/group/other.
554  *   if target_uid is -1, geteuid() will be used
555  *   if target_gid is -1, getegid() and secondary groups will be used
556  *
557  * RETURNS:
558  *  -1: error
559  *  0: file does not exist, is the wrong type or does not have permissions
560  *  1: file exists, is the correct type and has the permissions
561  */
check_path_perms(struct qrv_settings * current_settings,char * target_path,int type_flag,int permission_flags,struct stat * target_stat,int target_uid,int target_gid)562 int check_path_perms(struct qrv_settings *current_settings, char *target_path, int type_flag, int permission_flags, struct stat *target_stat, int target_uid, int target_gid)
563   {
564   int return_value;
565   int i;
566   struct stat *tmp_stat;
567   struct stat internal_stat;
568   int found_match;
569   int num_groups;
570   gid_t *tmp_gid;
571 
572   return_value = 0;
573   tmp_gid = NULL;
574 
575   if (target_path != NULL)
576     {
577     tmp_stat = (target_stat != NULL) ? target_stat : &internal_stat;
578     if (!stat(target_path, tmp_stat))
579       {
580       QRV_LOG_EXCESSIVE(current_settings, LOG_EXCESSIVE_FILE_STAT, tmp_stat->st_mode, type_flag, tmp_stat->st_uid, tmp_stat->st_gid, target_path);
581 
582       if (((type_flag == 0) ||
583            ((tmp_stat->st_mode & S_IFMT) == type_flag)) &&
584           (((permission_flags & FILE_PERMISSION_SETUID) == 0) ||
585            (tmp_stat->st_mode & S_ISUID)) &&
586           (((permission_flags & FILE_PERMISSION_SETGID) == 0) ||
587            (tmp_stat->st_mode & S_ISGID)) &&
588           (((permission_flags & FILE_PERMISSION_STICKY) == 0) ||
589            (tmp_stat->st_mode & S_ISVTX)))
590         {
591         if ((permission_flags & FILE_PERMISSION_READ) ||
592             (permission_flags & FILE_PERMISSION_WRITE) ||
593             (permission_flags & FILE_PERMISSION_EXECUTE))
594           {
595           if (((target_uid != -1) &&
596                (tmp_stat->st_uid == target_uid)) ||
597               ((target_uid == -1) &&
598                (tmp_stat->st_uid == geteuid())))
599             {
600             if ((((permission_flags & FILE_PERMISSION_READ) == 0) ||
601                  (tmp_stat->st_mode & S_IRUSR)) &&
602                 (((permission_flags & FILE_PERMISSION_WRITE) == 0) ||
603                  (tmp_stat->st_mode & S_IWUSR)) &&
604                 (((permission_flags & FILE_PERMISSION_EXECUTE) == 0) ||
605                  (tmp_stat->st_mode & S_IXUSR)))
606               return_value = 1;
607             }
608           else
609             {
610             found_match = 0;
611 
612             if (target_gid != -1)
613               {
614               if (tmp_stat->st_gid == target_gid)
615                 found_match = 1;
616               }
617             else if (tmp_stat->st_gid == getegid())
618               found_match = 1;
619             else if ((num_groups = getgroups(0, NULL)) > 0)
620               {
621               if ((tmp_gid = (gid_t *)malloc(sizeof(gid_t) * num_groups)) != NULL)
622                 {
623                 if (getgroups(num_groups, tmp_gid) > 0)
624                   for (i = 0; i < num_groups; i++)
625                     if (tmp_stat->st_gid == tmp_gid[i])
626                       {
627                       /* Secondary group owns the directory. */
628                       found_match = 1;
629                       break;
630                       }
631 
632                 free(tmp_gid);
633                 tmp_gid = NULL;
634                 }
635               else
636                 {
637                 QRV_LOG_ERROR(current_settings, LOG_ERROR_MALLOC, (long)(sizeof(gid_t) * num_groups));
638                 return_value = -1;
639                 }
640               }
641 
642             if (found_match)
643               {
644               if ((((permission_flags & FILE_PERMISSION_READ) == 0) ||
645                    (tmp_stat->st_mode & S_IRGRP)) &&
646                   (((permission_flags & FILE_PERMISSION_WRITE) == 0) ||
647                    (tmp_stat->st_mode & S_IWGRP)) &&
648                   (((permission_flags & FILE_PERMISSION_EXECUTE) == 0) ||
649                    (tmp_stat->st_mode & S_IXGRP)))
650                 return_value = 1;
651               }
652             else
653               if ((((permission_flags & FILE_PERMISSION_READ) == 0) ||
654                    (tmp_stat->st_mode & S_IROTH)) &&
655                   (((permission_flags & FILE_PERMISSION_WRITE) == 0) ||
656                    (tmp_stat->st_mode & S_IWOTH)) &&
657                   (((permission_flags & FILE_PERMISSION_EXECUTE) == 0) ||
658                    (tmp_stat->st_mode & S_IXOTH)))
659                 return_value = 1;
660             }
661           }
662         else
663           return_value = 1;
664         }
665       }
666     else if ((errno == ENOENT) ||
667              (errno == ENOTDIR))
668       {
669       QRV_LOG_EXCESSIVE(current_settings, LOG_EXCESSIVE_FILE_STAT_FAIL, target_path, strerror(errno));
670       return_value = 0;
671       }
672     else
673       {
674       QRV_LOG_ERROR(current_settings, LOG_ERROR_STAT_ERRNO, target_path, strerror(errno));
675       return_value = -1;
676       }
677     }
678 
679   return(return_value);
680   }
681 
682 /*
683  * Return value:
684  *   NOT FOUND: 0
685  *   FOUND: 1
686  */
find_path(struct qrv_settings * current_settings,char * filename,char * return_filename,int size_return_filename)687 int find_path(struct qrv_settings *current_settings, char *filename, char *return_filename, int size_return_filename)
688   {
689   int return_value;
690   int strlen_filename;
691   char *tmp_start;
692   char *tmp_end;
693   char new_filename[MAX_BUF + 1];
694   struct stat tmp_stat;
695 
696   return_value = 0;
697 
698   if ((filename != NULL) &&
699       (return_filename != NULL) &&
700       (size_return_filename > 0))
701     {
702     if (stat(filename, &tmp_stat) == 0)
703       {
704       strlen_filename = MINVAL(size_return_filename, strlen(filename));
705       memcpy(return_filename, filename, sizeof(char) * strlen_filename);
706       return_filename[strlen_filename] = '\0';
707 
708       return_value = 1;
709       }
710     else if (strchr(filename, DIR_DELIMITER) == NULL)
711       {
712       tmp_start = current_settings->path;
713       tmp_end = NULL;
714       while (tmp_start != NULL)
715         {
716         if ((tmp_end = strchr(tmp_start, ENVIRONMENT_SEPARATOR)) != NULL)
717           {
718           strlen_filename = SNPRINTF(new_filename, MAX_BUF, "%.*s" DIR_DELIMITER_STR "%s", (int)(tmp_end - tmp_start), tmp_start, filename);
719           tmp_start = tmp_end + 1;
720           }
721         else
722           {
723           strlen_filename = SNPRINTF(new_filename, MAX_BUF, "%s" DIR_DELIMITER_STR "%s", tmp_start, filename);
724           tmp_start = NULL;
725           }
726 
727         if (stat(new_filename, &tmp_stat) == 0)
728           {
729           if (strlen_filename > size_return_filename)
730             strlen_filename = size_return_filename;
731 
732           memcpy(return_filename, new_filename, sizeof(char) * strlen_filename);
733           return_filename[strlen_filename] = '\0';
734 
735           return_value = 1;
736           break;
737           }
738         }
739       }
740     }
741 
742   return(return_value);
743   }
744 
745 /*
746  * EXPECTS:
747  *   type_flag should be a value within the S_IFMT mask in stat.h -- S_IFDIR, S_IFREG, etc
748  *   permission_flags should be an OR of FILE_PERMISSION_READ, FILE_PERMISSION_WRITE or FILE_PERMISSION_EXECUTE, not bitshifted for user/group/other.
749  *
750  * RETURNS:
751  *  -1: error
752  *  0: file does not exist, is the wrong type or does not have permissions
753  *  1: file exists, is the correct type and has the permissions
754  */
find_path_perms(struct qrv_settings * current_settings,char * target_path,int type_flag,int permission_flags,int target_uid,int target_gid)755 int find_path_perms(struct qrv_settings *current_settings, char *target_path, int type_flag, int permission_flags, int target_uid, int target_gid)
756   {
757   int return_value;
758   char tmp_cwd[MAX_PATH + 1];
759   char tmp_path[MAX_PATH + 1];
760 
761   return_value = 0;
762 
763   if (target_path != NULL)
764     {
765     if (target_path[0] == DIR_DELIMITER)
766       return_value = check_path_perms(current_settings, target_path, type_flag, permission_flags, NULL, target_uid, target_gid);
767     else
768       {
769       if (strchr(target_path, DIR_DELIMITER) != NULL)
770         {
771         if (getcwd(tmp_cwd, MAX_PATH) != NULL)
772           {
773           snprintf(tmp_path, MAX_PATH, "%s%c%s", tmp_cwd, DIR_DELIMITER, target_path);
774           return_value = check_path_perms(current_settings, tmp_path, type_flag, permission_flags, NULL, target_uid, target_gid);
775           }
776         else
777           {
778           QRV_LOG_ERROR(current_settings, LOG_ERROR_GETCWD, strerror(errno));
779           return_value = -1;
780           }
781         }
782       else if (find_path(current_settings, target_path, tmp_path, MAX_PATH))
783         return_value = check_path_perms(current_settings, tmp_path, type_flag, permission_flags, NULL, target_uid, target_gid);
784       }
785     }
786 
787   return(return_value);
788   }
789