| /* Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #ifdef WIN32 |
| |
| #include <sys/stat.h> |
| #include <stdarg.h> |
| #include <time.h> |
| #include <stdlib.h> |
| |
| #include "httpd.h" |
| #include "http_log.h" |
| |
| /* Returns TRUE if the input string is a string |
| * of one or more '.' characters. |
| */ |
| static BOOL OnlyDots(char *pString) |
| { |
| char *c; |
| |
| if (*pString == '\0') |
| return FALSE; |
| |
| for (c = pString;*c;c++) |
| if (*c != '.') |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| |
| /* Accepts as input a pathname, and tries to match it to an |
| * existing path and return the pathname in the case that |
| * is present on the existing path. This routine also |
| * converts alias names to long names. |
| * |
| * WARNING: Folding to systemcase fails when /path/to/foo/../bar |
| * is given and foo does not exist, is not a directory. |
| */ |
| API_EXPORT(char *) ap_os_systemcase_filename(pool *pPool, |
| const char *szFile) |
| { |
| char *buf, *t, *r; |
| const char *q, *p; |
| BOOL bDone = FALSE; |
| BOOL bFileExists = TRUE; |
| HANDLE hFind; |
| WIN32_FIND_DATA wfd; |
| size_t buflen; |
| int slack = 0; |
| |
| if (!szFile || strlen(szFile) == 0) |
| return ap_pstrdup(pPool, ""); |
| |
| buflen = strlen(szFile); |
| t = buf = ap_palloc(pPool, buflen + 1); |
| q = szFile; |
| |
| /* If there is drive information, copy it over. */ |
| if (szFile[1] == ':') { |
| /* Lowercase, so that when systemcase is used for |
| * comparison, d: designations will match |
| */ |
| *(t++) = tolower(*(q++)); |
| *(t++) = *(q++); |
| } |
| else if ((*q == '/') || (*q == '\\')) { |
| /* Get past the root path (/ or //foo/bar/) so we can go |
| * on to normalize individual path elements. |
| */ |
| *(t++) = '\\', ++q; |
| if ((*q == '/') || (*q == '\\')) /* UNC name */ |
| { |
| /* Lower-case the machine name, so compares match. |
| * FindFirstFile won't parse \\machine alone |
| */ |
| *(t++) = '\\', ++q; |
| for (p = q; *p && (*p != '/') && (*p != '\\'); ++p) |
| /* continue */ ; |
| if (*p || p > q) |
| { |
| /* Lower-case the machine name, so compares match. |
| * FindFirstFile won't parse \\machine\share alone |
| */ |
| memcpy(t, q, p - q); |
| t[p - q] = '\0'; |
| strlwr(t); |
| t += p - q; |
| q = p; |
| if (*p) { |
| *(t++) = '\\', ++q; |
| for (p = q; *p && (*p != '/') && (*p != '\\'); ++p) |
| /* continue */ ; |
| if (*p || p > q) |
| { |
| /* Copy the lower-cased share name. FindFirstFile |
| * cannot not find a \\machine\share name only |
| */ |
| memcpy(t, q, p - q); |
| t[p - q] = '\0'; |
| strlwr(t); |
| t += p - q; |
| q = p; |
| if (*p) |
| *(t++) = '\\', ++q; |
| else |
| bFileExists = FALSE; |
| } |
| else |
| bFileExists = FALSE; |
| } |
| else |
| bFileExists = FALSE; |
| } |
| else |
| bFileExists = FALSE; |
| } |
| } |
| |
| while (bFileExists) { |
| |
| /* parse past any leading slashes */ |
| for (; (*q == '/') || (*q == '\\'); ++q) |
| *(t++) = '\\'; |
| |
| /* break on end of string */ |
| if (!*q) |
| break; |
| |
| /* get to the end of this path segment */ |
| for (p = q; *p && (*p != '/') && (*p != '\\'); ++p) |
| /* continue */ ; |
| |
| /* copy the segment */ |
| memcpy(t, q, p - q); |
| t[p - q] = '\0'; |
| |
| /* Test for nasties that can exhibit undesired effects */ |
| if (strpbrk(t, "?\"<>*|:")) { |
| t += p - q; |
| q = p; |
| break; |
| } |
| |
| /* If the path exists so far, call FindFirstFile |
| * again. However, if this portion of the path contains |
| * only '.' charaters, skip the call to FindFirstFile |
| * since it will convert '.' and '..' to actual names. |
| * On win32, '...' is an alias for '..', so we gain |
| * a bit of slack. |
| */ |
| if (*t == '.' && OnlyDots(t)) { |
| if (p - q == 3) { |
| t += 2; |
| q = p; |
| ++slack; |
| } |
| else { |
| t += p - q; |
| q = p; |
| } |
| /* Paths of 4 dots or more are invalid */ |
| if (p - q > 3) |
| break; |
| } |
| else { |
| if ((hFind = FindFirstFile(buf, &wfd)) == INVALID_HANDLE_VALUE) { |
| t += p - q; |
| q = p; |
| break; |
| } |
| else { |
| size_t fnlen = strlen(wfd.cFileName); |
| FindClose(hFind); |
| /* the string length just changed, could have shrunk |
| * (trailing spaces or dots) or could have grown |
| * (longer filename aliases). Realloc as necessary |
| */ |
| slack -= fnlen - (p - q); |
| if (slack < 0) { |
| char *n; |
| slack += buflen + fnlen - (p - q); |
| buflen += buflen + fnlen - (p - q); |
| n = ap_palloc(pPool, buflen + 1); |
| memcpy (n, buf, t - buf); |
| t = n + (t - buf); |
| buf = n; |
| } |
| memcpy(t, wfd.cFileName, fnlen); |
| t += fnlen; |
| q = p; |
| if (!(wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) |
| break; |
| } |
| } |
| } |
| |
| /* Convert all parsed '\'s to '/' for canonical form (doesn't touch |
| * the non-existant portion of the path whatsoever.) |
| */ |
| for (r = buf; r < t; ++r) { |
| if (*r == '\\') |
| *r = '/'; |
| } |
| |
| /* Copy the non-existant portion (minimally nul-terminates the string) */ |
| strcpy(t, q); |
| |
| return buf; |
| } |
| |
| |
| /* Perform canonicalization with the exception that the |
| * input case is preserved. |
| */ |
| API_EXPORT(char *) ap_os_case_canonical_filename(pool *pPool, |
| const char *szFile) |
| { |
| char *pNewStr; |
| char *s; |
| char *p; |
| char *q; |
| |
| if (szFile == NULL || strlen(szFile) == 0) |
| return ap_pstrdup(pPool, ""); |
| |
| pNewStr = ap_pstrdup(pPool, szFile); |
| |
| /* Change all '\' characters to '/' characters. |
| * While doing this, remove any trailing '.'. |
| * Also, blow away any directories with 3 or |
| * more '.' |
| */ |
| for (p = pNewStr,s = pNewStr; *s; s++,p++) { |
| if (*s == '\\' || *s == '/') { |
| |
| q = p; |
| while (p > pNewStr && *(p-1) == '.') |
| p--; |
| |
| if (p == pNewStr && q-p <= 2 && *p == '.') |
| p = q; |
| else if (p > pNewStr && p < q && *(p-1) == '/') { |
| if (q-p > 2) |
| p--; |
| else |
| p = q; |
| } |
| |
| *p = '/'; |
| } |
| else { |
| *p = *s; |
| } |
| } |
| *p = '\0'; |
| |
| /* Blow away any final trailing '.' since on Win32 |
| * foo.bat == foo.bat. == foo.bat... etc. |
| * Also blow away any trailing spaces since |
| * "filename" == "filename " |
| */ |
| q = p; |
| while (p > pNewStr && (*(p-1) == '.' || *(p-1) == ' ')) |
| p--; |
| if ((p > pNewStr) || |
| (p == pNewStr && q-p > 2)) |
| *p = '\0'; |
| |
| |
| /* One more security issue to deal with. Win32 allows |
| * you to create long filenames. However, alias filenames |
| * are always created so that the filename will |
| * conform to 8.3 rules. According to the Microsoft |
| * Developer's network CD (1/98) |
| * "Automatically generated aliases are composed of the |
| * first six characters of the filename plus ~n |
| * (where n is a number) and the first three characters |
| * after the last period." |
| * Here, we attempt to detect and decode these names. |
| * |
| * XXX: Netware network clients may have alternate short names, |
| * simply truncated, with no embedded '~'. Further, this behavior |
| * can be modified on WinNT volumes. This was not a safe test, |
| * therefore exclude the '~' pretest. |
| */ |
| #ifdef WIN32_SHORT_FILENAME_INSECURE_BEHAVIOR |
| p = strchr(pNewStr, '~'); |
| if (p != NULL) |
| #endif |
| /* ap_os_systemcase_filename now changes the case of only |
| * the pathname elements that are found. |
| */ |
| pNewStr = ap_os_systemcase_filename(pPool, pNewStr); |
| |
| return pNewStr; |
| } |
| |
| /* Perform complete canonicalization. |
| */ |
| API_EXPORT(char *) ap_os_canonical_filename(pool *pPool, const char *szFile) |
| { |
| char *pNewName; |
| pNewName = ap_os_case_canonical_filename(pPool, szFile); |
| strlwr(pNewName); |
| return pNewName; |
| } |
| |
| |
| /* |
| * ap_os_is_filename_valid is given a filename, and returns 0 if the filename |
| * is not valid for use on this system. On Windows, this means it fails any |
| * of the tests below. Otherwise returns 1. |
| * |
| * Test for filename validity on Win32. This is of tests come in part from |
| * the MSDN article at "Technical Articles, Windows Platform, Base Services, |
| * Guidelines, Making Room for Long Filenames" although the information |
| * in MSDN about filename testing is incomplete or conflicting. There is a |
| * similar set of tests in "Technical Articles, Windows Platform, Base Services, |
| * Guidelines, Moving Unix Applications to Windows NT". |
| * |
| * The tests are: |
| * |
| * 1) total path length greater than MAX_PATH |
| * |
| * 2) anything using the octets 0-31 or characters " < > | : |
| * (these are reserved for Windows use in filenames. In addition |
| * each file system has its own additional characters that are |
| * invalid. See KB article Q100108 for more details). |
| * |
| * 3) anything ending in "." (no matter how many) |
| * (filename doc, doc. and doc... all refer to the same file) |
| * |
| * 4) any segment in which the basename (before first period) matches |
| * one of the DOS device names |
| * (the list comes from KB article Q100108 although some people |
| * reports that additional names such as "COM5" are also special |
| * devices). |
| * |
| * If the path fails ANY of these tests, the result must be to deny access. |
| */ |
| |
| API_EXPORT(int) ap_os_is_filename_valid(const char *file) |
| { |
| const char *segstart; |
| unsigned int seglength; |
| const char *pos; |
| static const char * const invalid_characters = "?\"<>*|:"; |
| static const char * const invalid_filenames[] = { |
| "CON", "AUX", "COM1", "COM2", "COM3", |
| "COM4", "LPT1", "LPT2", "LPT3", "PRN", "NUL", NULL |
| }; |
| |
| /* Test 1 */ |
| if (strlen(file) >= MAX_PATH) { |
| /* Path too long for Windows. Note that this test is not valid |
| * if the path starts with //?/ or \\?\. */ |
| return 0; |
| } |
| |
| pos = file; |
| |
| /* Skip any leading non-path components. This can be either a |
| * drive letter such as C:, or a UNC path such as \\SERVER\SHARE\. |
| * We continue and check the rest of the path based on the rules above. |
| * This means we could eliminate valid filenames from servers which |
| * are not running NT (such as Samba). |
| */ |
| |
| if (pos[0] && pos[1] == ':') { |
| /* Skip leading drive letter */ |
| pos += 2; |
| } |
| else { |
| if ((pos[0] == '\\' || pos[0] == '/') && |
| (pos[1] == '\\' || pos[1] == '/')) { |
| /* Is a UNC, so skip the server name and share name */ |
| pos += 2; |
| while (*pos && *pos != '/' && *pos != '\\') |
| pos++; |
| if (!*pos) { |
| /* No share name */ |
| return 0; |
| } |
| pos++; /* Move to start of share name */ |
| while (*pos && *pos != '/' && *pos != '\\') |
| pos++; |
| if (!*pos) { |
| /* No path information */ |
| return 0; |
| } |
| } |
| } |
| |
| while (*pos) { |
| unsigned int idx; |
| unsigned int baselength; |
| |
| while (*pos == '/' || *pos == '\\') { |
| pos++; |
| } |
| if (*pos == '\0') { |
| break; |
| } |
| segstart = pos; /* start of segment */ |
| while (*pos && *pos != '/' && *pos != '\\') { |
| pos++; |
| } |
| seglength = pos - segstart; |
| /* |
| * Now we have a segment of the path, starting at position "segstart" |
| * and length "seglength" |
| */ |
| |
| /* Test 2 */ |
| for (idx = 0; idx < seglength; idx++) { |
| if ((segstart[idx] > 0 && segstart[idx] < 32) || |
| strchr(invalid_characters, segstart[idx])) { |
| return 0; |
| } |
| } |
| |
| /* Test 3 */ |
| if (segstart[seglength-1] == '.') { |
| return 0; |
| } |
| |
| /* Test 4 */ |
| for (baselength = 0; baselength < seglength; baselength++) { |
| if (segstart[baselength] == '.') { |
| break; |
| } |
| } |
| |
| /* baselength is the number of characters in the base path of |
| * the segment (which could be the same as the whole segment length, |
| * if it does not include any dot characters). */ |
| if (baselength == 3 || baselength == 4) { |
| for (idx = 0; invalid_filenames[idx]; idx++) { |
| if (strlen(invalid_filenames[idx]) == baselength && |
| !strnicmp(invalid_filenames[idx], segstart, baselength)) { |
| return 0; |
| } |
| } |
| } |
| } |
| |
| return 1; |
| } |
| |
| |
| API_EXPORT(ap_os_dso_handle_t) ap_os_dso_load(const char *module_name) |
| { |
| UINT em; |
| ap_os_dso_handle_t dsoh; |
| char path[MAX_PATH], *p; |
| /* Load the module... |
| * per PR2555, the LoadLibraryEx function is very picky about slashes. |
| * Debugging on NT 4 SP 6a reveals First Chance Exception within NTDLL. |
| * LoadLibrary in the MS PSDK also reveals that it -explicitly- states |
| * that backslashes must be used. |
| * |
| * Transpose '\' for '/' in the filename. |
| */ |
| ap_cpystrn(path, module_name, MAX_PATH); |
| p = path; |
| while (p = strchr(p, '/')) |
| *p = '\\'; |
| |
| /* First assume the dso/dll's required by -this- dso are sitting in the |
| * same path or can be found in the usual places. Failing that, let's |
| * let that dso look in the apache root. |
| */ |
| em = SetErrorMode(SEM_FAILCRITICALERRORS); |
| dsoh = LoadLibraryEx(path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); |
| if (!dsoh) { |
| dsoh = LoadLibraryEx(path, NULL, 0); |
| } |
| SetErrorMode(em); |
| return dsoh; |
| } |
| |
| API_EXPORT(const char *) ap_os_dso_error(void) |
| { |
| int len, nErrorCode; |
| static char errstr[120]; |
| /* This is -not- threadsafe code, but it's about the best we can do. |
| * mostly a potential problem for isapi modules, since LoadModule |
| * errors are handled within a single config thread. |
| */ |
| |
| nErrorCode = GetLastError(); |
| len = ap_snprintf(errstr, sizeof(errstr), "(%d) ", nErrorCode); |
| |
| len += FormatMessage( |
| FORMAT_MESSAGE_FROM_SYSTEM, |
| NULL, |
| nErrorCode, |
| MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* Default language */ |
| (LPTSTR) errstr + len, |
| sizeof(errstr) - len, |
| NULL |
| ); |
| /* FormatMessage may have appended a newline (\r\n). So remove it |
| * and use ": " instead like the Unix errors. The error may also |
| * end with a . before the return - if so, trash it. |
| */ |
| if (len > 1 && errstr[len-2] == '\r' && errstr[len-1] == '\n') { |
| if (len > 2 && errstr[len-3] == '.') |
| len--; |
| errstr[len-2] = ':'; |
| errstr[len-1] = ' '; |
| } |
| return errstr; |
| } |
| |
| #endif /* WIN32 */ |