/************************************************* * Exim - an Internet mail transport agent * *************************************************/ /* Copyright (c) University of Cambridge 1995 - 2004 */ /* See the file NOTICE for conditions of use and distribution. */ #include "../exim.h" #include "lf_functions.h" #include "lsearch.h" enum { LSEARCH_PLAIN, LSEARCH_WILD, LSEARCH_NWILD, LSEARCH_IP }; /************************************************* * Open entry point * *************************************************/ /* See local README for interface description */ void * lsearch_open(uschar *filename, uschar **errmsg) { FILE *f = Ufopen(filename, "rb"); if (f == NULL) { int save_errno = errno; *errmsg = string_open_failed(errno, "%s for linear search", filename); errno = save_errno; return NULL; } return f; } /************************************************* * Check entry point * *************************************************/ BOOL lsearch_check(void *handle, uschar *filename, int modemask, uid_t *owners, gid_t *owngroups, uschar **errmsg) { return lf_check_file(fileno((FILE *)handle), filename, S_IFREG, modemask, owners, owngroups, "lsearch", errmsg) == 0; } /************************************************* * Internal function for lsearch/(n)wildlsearch * *************************************************/ /* See local README for interface description, plus two one additional arguments: wild is TRUE for wildlsearch, and expand is TRUE for pattern expansion. There is some messy logic in here to cope with very long data lines that do not fit into the fixed sized buffer. Most of the time this will never be exercised, but people do occasionally do weird things. */ static int internal_lsearch_find(void *handle, uschar *filename, uschar *keystring, int length, uschar **result, uschar **errmsg, int type) { FILE *f = (FILE *)handle; BOOL last_was_eol = TRUE; BOOL this_is_eol = TRUE; uschar buffer[4096]; filename = filename; /* Keep picky compilers happy */ errmsg = errmsg; rewind(f); for (last_was_eol = TRUE; Ufgets(buffer, sizeof(buffer), f) != NULL; last_was_eol = this_is_eol) { int ptr, size; int p = Ustrlen(buffer); int linekeylength; uschar *yield; uschar *s = buffer; /* Check whether this the final segment of a line. If it follows an incomplete part-line, skip it. */ this_is_eol = p > 0 && buffer[p-1] == '\n'; if (!last_was_eol) continue; /* We now have the start of a physical line. If this is a final line segment, remove trailing white space. */ if (this_is_eol) { while (p > 0 && isspace((uschar)buffer[p-1])) p--; buffer[p] = 0; } /* If the buffer is empty it might be (a) a complete empty line, or (b) the start of a line that begins with so much white space that it doesn't all fit in the buffer. In both cases we want to skip the entire physical line. If the buffer begins with # it is a comment line; if it begins with white space it is a logical continuation; again, we want to skip the entire physical line. */ if (buffer[0] == 0 || buffer[0] == '#' || isspace(buffer[0])) continue; /* We assume that they key will fit in the buffer. If the key starts with ", read it as a quoted string. We don't use string_dequote() because that uses new store for the result, and we may be doing this many times in a long file. We know that the dequoted string must be shorter than the original, because we are removing the quotes, and also any escape sequences always turn two or more characters into one character. Therefore, we can store the new string in the same buffer. */ if (*s == '\"') { uschar *t = s++; while (*s != 0 && *s != '\"') { if (*s == '\\') *t++ = string_interpret_escape(&s); else *t++ = *s; s++; } if (*s != 0) s++; /* Past terminating " */ linekeylength = t - buffer; } /* Otherwise it is terminated by a colon or white space */ else { while (*s != 0 && *s != ':' && !isspace(*s)) s++; linekeylength = s - buffer; } switch(type) { /* A non-wild lsearch treats each key as a literal */ case LSEARCH_PLAIN: if (linekeylength != length || strncmpic(buffer, keystring, length) != 0) continue; break; /* A wild lsearch treats each key as a possible wildcarded string. */ case LSEARCH_WILD: /* Like wildlsearch, but without expanding the key */ case LSEARCH_NWILD: { int rc; int save = buffer[linekeylength]; uschar *list = buffer; buffer[linekeylength] = 0; rc = match_isinlist(keystring, &list, /* Single-item list, possibly expanded */ UCHAR_MAX+(type == LSEARCH_WILD? 1:2), NULL, /* No anchor */ NULL, /* No caching */ MCL_STRING, TRUE, /* Caseless */ NULL); buffer[linekeylength] = save; if (rc == FAIL) continue; if (rc == DEFER) return DEFER; }; break; /* Compare an ip address against a list of network/ip addresses */ case LSEARCH_IP: /* Allow * as search-key */ if (!(length == 1 && linekeylength == 1 && buffer[0] == '*' && keystring[0] == '*')) { /* This was no wildcard, so try to match key and buffer */ int save = buffer[linekeylength]; buffer[linekeylength] = 0; if (!host_is_in_net(keystring, buffer)) continue; buffer[linekeylength] = save; } break; } /* The key has matched. Skip spaces after the key, and allow an optional colon after the spaces. This is an odd specification, but it's for compatibility. */ while (isspace((uschar)*s)) s++; if (*s == ':') { s++; while (isspace((uschar)*s)) s++; } /* Now we want to build the result string to contain the data. There can be two kinds of continuation: (a) the physical line may not all have fitted into the buffer, and (b) there may be logical continuation lines, for which we must convert all leading white space into a single blank. Initialize, and copy the first segment of data. */ size = 100; ptr = 0; yield = store_get(size); if (*s != 0) yield = string_cat(yield, &size, &ptr, s, Ustrlen(s)); /* Now handle continuations */ for (last_was_eol = this_is_eol; Ufgets(buffer, sizeof(buffer), f) != NULL; last_was_eol = this_is_eol) { s = buffer; p = Ustrlen(buffer); this_is_eol = p > 0 && buffer[p-1] == '\n'; /* Remove trailing white space from a physical line end */ if (this_is_eol) { while (p > 0 && isspace((uschar)buffer[p-1])) p--; buffer[p] = 0; } /* If this is not a physical line continuation, skip it entirely if it's empty or starts with #. Otherwise, break the loop if it doesn't start with white space. Otherwise, replace leading white space with a single blank. */ if (last_was_eol) { if (buffer[0] == 0 || buffer[0] == '#') continue; if (!isspace((uschar)buffer[0])) break; while (isspace((uschar)*s)) s++; *(--s) = ' '; } /* Join a physical or logical line continuation onto the result string. */ yield = string_cat(yield, &size, &ptr, s, Ustrlen(s)); } yield[ptr] = 0; *result = yield; return OK; } return FAIL; } /************************************************* * Find entry point for lsearch * *************************************************/ /* See local README for interface description */ int lsearch_find(void *handle, uschar *filename, uschar *keystring, int length, uschar **result, uschar **errmsg) { return internal_lsearch_find(handle, filename, keystring, length, result, errmsg, LSEARCH_PLAIN); } /************************************************* * Find entry point for wildlsearch * *************************************************/ /* See local README for interface description */ int wildlsearch_find(void *handle, uschar *filename, uschar *keystring, int length, uschar **result, uschar **errmsg) { return internal_lsearch_find(handle, filename, keystring, length, result, errmsg, LSEARCH_WILD); } /************************************************* * Find entry point for nwildlsearch * *************************************************/ /* See local README for interface description */ int nwildlsearch_find(void *handle, uschar *filename, uschar *keystring, int length, uschar **result, uschar **errmsg) { return internal_lsearch_find(handle, filename, keystring, length, result, errmsg, LSEARCH_NWILD); } /************************************************* * Find entry point for iplsearch * *************************************************/ /* See local README for interface description */ int iplsearch_find(void *handle, uschar *filename, uschar *keystring, int length, uschar **result, uschar **errmsg) { if (string_is_ip_address(keystring, NULL) || (length == 1 && keystring[0] == '*')) { return internal_lsearch_find(handle, filename, keystring, length, result, errmsg, LSEARCH_IP); } else { *errmsg = string_sprintf("\"%s\" is not a valid iplsearch key", keystring); return DEFER; }; } /************************************************* * Close entry point * *************************************************/ /* See local README for interface description */ void lsearch_close(void *handle) { fclose((FILE *)handle); } /* End of lookups/lsearch.c */