/** * @brief Validate a path supplied by a filelist. * @param[in,out] path Pointer to file (path) to download (high bits will be stripped). * @sa CL_QueueHTTPDownload * @sa CL_ParseFileList */ static void CL_CheckAndQueueDownload (char *path) { size_t length; const char *ext; bool pak; bool gameLocal; StripHighBits(path); length = strlen(path); if (length >= MAX_QPATH) return; ext = Com_GetExtension(path); if (ext == NULL) return; if (Q_streq(ext, "pk3")) { Com_Printf("NOTICE: Filelist is requesting a .pk3 file (%s)\n", path); pak = true; } else pak = false; if (!pak && !Q_streq(ext, "bsp") && !Q_streq(ext, "wav") && !Q_streq(ext, "md2") && !Q_streq(ext, "ogg") && !Q_streq(ext, "md3") && !Q_streq(ext, "png") && !Q_streq(ext, "jpg") && !Q_streq(ext, "obj") && !Q_streq(ext, "mat") && !Q_streq(ext, "ump")) { Com_Printf("WARNING: Illegal file type '%s' in filelist.\n", path); return; } if (path[0] == '@') { if (pak) { Com_Printf("WARNING: @ prefix used on a pk3 file (%s) in filelist.\n", path); return; } gameLocal = true; path++; length--; } else gameLocal = false; if (strstr(path, "..") || !isvalidchar(path[0]) || !isvalidchar(path[length - 1]) || strstr(path, "//") || strchr(path, '\\') || (!pak && !strchr(path, '/')) || (pak && strchr(path, '/'))) { Com_Printf("WARNING: Illegal path '%s' in filelist.\n", path); return; } /* by definition pk3s are game-local */ if (gameLocal || pak) { bool exists; /* search the user homedir to find the pk3 file */ if (pak) { char gamePath[MAX_OSPATH]; FILE *f; Com_sprintf(gamePath, sizeof(gamePath), "%s/%s", FS_Gamedir(), path); f = fopen(gamePath, "rb"); if (!f) exists = false; else { exists = true; fclose(f); } } else exists = FS_CheckFile("%s", path); if (!exists) { if (CL_QueueHTTPDownload(path)) { /* pk3s get bumped to the top and HTTP switches to single downloading. * this prevents someone on 28k dialup trying to do both the main .pk3 * and referenced configstrings data at once. */ if (pak) { dlqueue_t** anchor = &cls.downloadQueue; while ((*anchor)->next) anchor = &(*anchor)->next; /* Remove the last element from the end of the list ... */ dlqueue_t* const d = *anchor; *anchor = 0; /* ... and prepend it to the list. */ d->next = cls.downloadQueue; cls.downloadQueue = d; } } } } else CL_CheckOrDownloadFile(path); }
/* =============== CL_ParseFileList Validate a path supplied by a filelist. =============== */ static void CL_CheckAndQueueDownload (char *path) { size_t length; char *ext; qboolean pak; qboolean gameLocal; StripHighBits (path, 1); length = strlen(path); if (length >= MAX_QPATH) return; ext = strrchr (path, '.'); if (!ext) return; ext++; if (!ext[0]) return; Q_strlwr (ext); if ( !strcmp (ext, "pak") || !strcmp (ext, "pk3") ) { Com_Printf ("NOTICE: Filelist is requesting a .pak file (%s)\n", path); pak = true; } else pak = false; if (!pak && strcmp (ext, "pcx") && strcmp (ext, "wal") && strcmp (ext, "wav") && strcmp (ext, "md2") && strcmp (ext, "sp2") && strcmp (ext, "tga") && strcmp (ext, "png") && strcmp (ext, "jpg") && strcmp (ext, "bsp") && strcmp (ext, "ent") && strcmp (ext, "txt") && strcmp (ext, "dm2") && strcmp (ext, "loc")) { Com_Printf ("WARNING: Illegal file type '%s' in filelist.\n", MakePrintable(path, length)); return; } if (path[0] == '@') { if (pak) { Com_Printf ("WARNING: @ prefix used on a pak file (%s) in filelist.\n", MakePrintable(path, length)); return; } gameLocal = true; path++; length--; } else gameLocal = false; if (strstr (path, "..") || !IsValidChar (path[0]) || !IsValidChar (path[length-1]) || strstr(path, "//") || strchr (path, '\\') || (!pak && !strchr (path, '/')) || (pak && strchr(path, '/'))) { Com_Printf ("WARNING: Illegal path '%s' in filelist.\n", MakePrintable(path, length)); return; } // by definition paks are game-local if (gameLocal || pak) { qboolean exists; FILE *f; char gamePath[MAX_OSPATH]; if (pak) { Com_sprintf (gamePath, sizeof(gamePath),"%s/%s",FS_Gamedir(), path); f = fopen (gamePath, "rb"); if (!f) { exists = false;; } else { exists = true; fclose (f); } } else { // exists = FS_ExistsInGameDir (path); exists = FS_LocalFileExists (path); } if (!exists) { if (CL_QueueHTTPDownload (path)) { //paks get bumped to the top and HTTP switches to single downloading. //this prevents someone on 28k dialup trying to do both the main .pak //and referenced configstrings data at once. if (pak) { dlqueue_t *q, *last; last = q = &cls.downloadQueue; while (q->next) { last = q; q = q->next; } last->next = NULL; q->next = cls.downloadQueue.next; cls.downloadQueue.next = q; } } } } else { CL_CheckOrDownloadFile (path); } }
/* ================== SV_BeginDownload_f ================== */ void SV_BeginDownload_f(void) { char *name, *p; size_t length; qboolean valid; // extern int file_from_pak; // ZOID did file come from pak? int offset = 0; name = Cmd_Argv(1); if (Cmd_Argc() > 2) offset = atoi(Cmd_Argv(2)); // downloaded offset // r1ch fix: name is always filtered for security reasons StripHighBits (name, 1); // hacked by zoid to allow more conrol over download // first off, no .. or global allow check // r1ch fix: for some ./ references in maps, eg ./textures/map/file length = strlen(name); p = name; while ((p = strstr (p, "./"))) { memmove (p, p+2, length - (p - name) - 1); length -= 2; } // r1ch fix: block the really nasty ones - \server.cfg will download from mod root on win32, .. is obvious if (name[0] == '\\' || strstr (name, "..")) { Com_Printf ("Refusing illegal download path %s to %s\n", name, sv_client->name); MSG_WriteByte (&sv_client->netchan.message, svc_download); MSG_WriteShort (&sv_client->netchan.message, -1); MSG_WriteByte (&sv_client->netchan.message, 0); Com_Printf ("Client %s[%s] tried to download illegal path: %s\n", sv_client->name, NET_AdrToString (sv_client->netchan.remote_address), name); SV_DropClient (sv_client); return; } else if (offset < 0) // r1ch fix: negative offset will crash on read { Com_Printf ("Refusing illegal download offset %d to %s\n", offset, sv_client->name); MSG_WriteByte (&sv_client->netchan.message, svc_download); MSG_WriteShort (&sv_client->netchan.message, -1); MSG_WriteByte (&sv_client->netchan.message, 0); Com_Printf ("Client %s[%s] supplied illegal download offset for %s: %d\n", sv_client->name, NET_AdrToString (sv_client->netchan.remote_address), name, offset); SV_DropClient (sv_client); return; } else if ( !length || name[0] == 0 // empty name, maybe as result of ./ normalize || !IsValidChar(name[0]) // r1ch: \ is bad in general, client won't even write properly if we do sent it || strchr (name, '\\') // MUST be in a subdirectory, unless a pk3 || (!strstr (name, "/") && strcmp(name+strlen(name)-4, ".pk3")) // r1ch: another bug, maps/. will fopen(".") -> crash || !IsValidChar(name[length-1]) ) /* if (strstr (name, "..") || !allow_download->value // leading dot is no good || *name == '.' // leading slash bad as well, must be in subdir || *name == '/' // next up, skin check || (strncmp(name, "players/", 8) == 0 && !allow_download_players->value) // now models || (strncmp(name, "models/", 7) == 0 && !allow_download_models->value) // now sounds || (strncmp(name, "sound/", 6) == 0 && !allow_download_sounds->value) // now maps (note special case for maps, must not be in pak) || (strncmp(name, "maps/", 5) == 0 && !allow_download_maps->value) // MUST be in a subdirectory, unless a pk3 || (!strstr (name, "/") && strcmp(name+strlen(name)-4, ".pk3")) ) */ { // don't allow anything with .. path MSG_WriteByte (&sv_client->netchan.message, svc_download); MSG_WriteShort (&sv_client->netchan.message, -1); MSG_WriteByte (&sv_client->netchan.message, 0); return; } valid = true; if ( !allow_download->value || (strncmp(name, "players/", 8) == 0 && !allow_download_players->value) || (strncmp(name, "models/", 7) == 0 && !allow_download_models->value) || (strncmp(name, "sound/", 6) == 0 && !allow_download_sounds->value) || (strncmp(name, "maps/", 5) == 0 && !allow_download_maps->value) || (strncmp(name, "pics/", 5) == 0 && !allow_download_pics->value) || ( ((strncmp(name, "env/", 4) == 0 || strncmp(name, "textures/", 9) == 0)) && !allow_download_textures->value ) ) valid = false; if (!valid) { MSG_WriteByte (&sv_client->netchan.message, svc_download); MSG_WriteShort (&sv_client->netchan.message, -1); MSG_WriteByte (&sv_client->netchan.message, 0); return; } if (sv_client->download) FS_FreeFile (sv_client->download); sv_client->downloadsize = FS_LoadFile (name, (void **)&sv_client->download); sv_client->downloadcount = offset; if (offset > sv_client->downloadsize) sv_client->downloadcount = sv_client->downloadsize; // ZOID- special check for maps, if it came from a pak file, don't allow download if (!sv_client->download || (strncmp(name, "maps/", 5) == 0 && file_from_pak)) { Com_DPrintf ("Couldn't download %s to %s\n", name, sv_client->name); if (sv_client->download) { FS_FreeFile (sv_client->download); sv_client->download = NULL; } MSG_WriteByte (&sv_client->netchan.message, svc_download); MSG_WriteShort (&sv_client->netchan.message, -1); MSG_WriteByte (&sv_client->netchan.message, 0); return; } SV_NextDownload_f (); Com_DPrintf ("Downloading %s to %s\n", name, sv_client->name); }