mirror of
https://github.com/wesnoth/wesnoth
synced 2025-05-07 21:48:35 +00:00
campaignd: Quick-and-dirty implementation of upload-time blacklisting
A path to a blacklist WML file may be provided in the campaignd configuration file (server.cfg). The BL file, in turn, may contain attributes for comma-delimited lists of wildcard patterns, accepting shell-style '*' and '?', by leveraging utils::wildcard_string_match()'s functionality. The following lists of patterns are recognized at this time: ip=<numeric IPv4 address masks> email=<add-on author email masks> name=<add-on id/name masks> title=<add-on title masks> author=<add-on author masks> description=<add-on description masks> Currently, the IP address mask list also takes '*' and '?' wildcards. My intention is to use CIDR subnet masks instead of or in addition to this. The blacklist WML file is only read by campaignd once at startup and it's never written to, thus allowing to preserve any WML comment lines that may be included in the file by admins for convenience. The IP, email, name, title, author, and description of a (new or existing) uploaded add-on are matched against the blacklist in that order. If the add-on matches, the upload is aborted and the user is shown a generic message in English, just like with every other possible campaignd-side error (on the plus side, this means it can be backported to 1.12). All matches (except for IP matches, which only contain digits and punctuation) are done case-insensitively. Because of campaignd protocol limitations, the upload is only checked and aborted after the client has already uploaded a weighty WML document including the add-on archive. Such is life, I guess.
This commit is contained in:
parent
f3b87a9366
commit
344fa792c2
@ -1,4 +1,6 @@
|
||||
Version 1.13.0-dev:
|
||||
* Add-ons server:
|
||||
* Add-on metadata pattern blacklisting implemented.
|
||||
* AI:
|
||||
* New Micro AI: Fast AI
|
||||
* Messenger Escort Micro AI: new optional parameters [filter],
|
||||
|
@ -1098,6 +1098,7 @@ if(ENABLE_CAMPAIGN_SERVER)
|
||||
set(campaignd_SRC
|
||||
network_worker.cpp # NEEDED when compiling with ANA support
|
||||
addon/validation.cpp
|
||||
campaign_server/blacklist.cpp
|
||||
campaign_server/campaign_server.cpp
|
||||
server/input_stream.cpp
|
||||
${network_implementation_files}
|
||||
|
@ -581,6 +581,7 @@ if env["host"] in ["x86_64-nacl", "i686-nacl"]:
|
||||
client_env.WesnothProgram("wesnoth", wesnoth_objects, have_client_prereqs)
|
||||
|
||||
campaignd_sources = Split("""
|
||||
campaign_server/blacklist.cpp
|
||||
server/input_stream.cpp
|
||||
""")
|
||||
|
||||
|
134
src/campaign_server/blacklist.cpp
Normal file
134
src/campaign_server/blacklist.cpp
Normal file
@ -0,0 +1,134 @@
|
||||
/*
|
||||
Copyright (C) 2014 by Ignacio Riquelme Morelle <shadowm2006@gmail.com>
|
||||
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#include "campaign_server/blacklist.hpp"
|
||||
|
||||
#include "log.hpp"
|
||||
#include "serialization/string_utils.hpp"
|
||||
#include "serialization/unicode.hpp"
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
static lg::log_domain log_campaignd_bl("campaignd/blacklist");
|
||||
#define LOG_BL LOG_STREAM(err, log_campaignd_bl)
|
||||
|
||||
namespace campaignd
|
||||
{
|
||||
|
||||
blacklist::blacklist()
|
||||
: names_()
|
||||
, titles_()
|
||||
, descriptions_()
|
||||
, authors_()
|
||||
, ips_()
|
||||
, emails_()
|
||||
{
|
||||
}
|
||||
|
||||
blacklist::blacklist(const config& cfg)
|
||||
: names_()
|
||||
, titles_()
|
||||
, descriptions_()
|
||||
, authors_()
|
||||
, ips_()
|
||||
, emails_()
|
||||
{
|
||||
this->read(cfg);
|
||||
}
|
||||
|
||||
void blacklist::clear()
|
||||
{
|
||||
names_.clear();
|
||||
titles_.clear();
|
||||
descriptions_.clear();
|
||||
|
||||
authors_.clear();
|
||||
ips_.clear();
|
||||
emails_.clear();
|
||||
}
|
||||
|
||||
void blacklist::read(const config& cfg)
|
||||
{
|
||||
parse_str_to_globlist(cfg["name"], names_);
|
||||
parse_str_to_globlist(cfg["title"], titles_);
|
||||
parse_str_to_globlist(cfg["description"], descriptions_);
|
||||
|
||||
parse_str_to_globlist(cfg["author"], authors_);
|
||||
parse_str_to_globlist(cfg["ip"], ips_);
|
||||
parse_str_to_globlist(cfg["email"], emails_);
|
||||
}
|
||||
|
||||
void blacklist::parse_str_to_globlist(const std::string& str, blacklist::globlist& glist)
|
||||
{
|
||||
glist = utils::split(str);
|
||||
}
|
||||
|
||||
bool blacklist::is_blacklisted(const std::string& name,
|
||||
const std::string& title,
|
||||
const std::string& description,
|
||||
const std::string& author,
|
||||
const std::string& ip,
|
||||
const std::string& email) const
|
||||
{
|
||||
// Checks done in increasing order of performance impact and decreasing
|
||||
// order of relevance.
|
||||
return is_in_ip_masklist(ip, ips_) ||
|
||||
is_in_globlist(email, emails_) ||
|
||||
is_in_globlist(name, names_) ||
|
||||
is_in_globlist(title, titles_) ||
|
||||
is_in_globlist(author, authors_) ||
|
||||
is_in_globlist(description, descriptions_);
|
||||
}
|
||||
|
||||
bool blacklist::is_in_globlist(const std::string& str, const blacklist::globlist& glist) const
|
||||
{
|
||||
if (!str.empty())
|
||||
{
|
||||
const std::string& lc_str = utf8::lowercase(str);
|
||||
BOOST_FOREACH(const std::string& glob, glist)
|
||||
{
|
||||
const std::string& lc_glob = utf8::lowercase(glob);
|
||||
if (utils::wildcard_string_match(lc_str, lc_glob)) {
|
||||
LOG_BL << "Blacklisted field found: " << str << " (" << glob << ")\n";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool blacklist::is_in_ip_masklist(const std::string& ip, const blacklist::globlist& mlist) const
|
||||
{
|
||||
if (!ip.empty())
|
||||
{
|
||||
BOOST_FOREACH(const std::string& ip_mask, mlist)
|
||||
{
|
||||
if (ip_matches(ip, ip_mask)) {
|
||||
LOG_BL << "Blacklisted IP found: " << ip << " (" << ip_mask << ")\n";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool blacklist::ip_matches(const std::string& ip, const blacklist::glob& ip_mask) const
|
||||
{
|
||||
// TODO: we want CIDR subnet mask matching here, not glob matching!
|
||||
return utils::wildcard_string_match(ip, ip_mask);
|
||||
}
|
||||
|
||||
}
|
82
src/campaign_server/blacklist.hpp
Normal file
82
src/campaign_server/blacklist.hpp
Normal file
@ -0,0 +1,82 @@
|
||||
/*
|
||||
Copyright (C) 2014 by Ignacio Riquelme Morelle <shadowm2006@gmail.com>
|
||||
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#ifndef CAMPAIGN_SERVER_BLACKLIST_HPP_INCLUDED
|
||||
#define CAMPAIGN_SERVER_BLACKLIST_HPP_INCLUDED
|
||||
|
||||
#include "config.hpp"
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
namespace campaignd
|
||||
{
|
||||
|
||||
class blacklist : private boost::noncopyable
|
||||
{
|
||||
public:
|
||||
typedef std::string glob;
|
||||
typedef std::vector<glob> globlist;
|
||||
|
||||
blacklist();
|
||||
explicit blacklist(const config& cfg);
|
||||
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* Initializes the blacklist from WML.
|
||||
*
|
||||
* @param cfg WML node object with the contents of the [blacklist] tag.
|
||||
*/
|
||||
void read(const config& cfg);
|
||||
|
||||
/**
|
||||
* Writes the blacklist to a WML node.
|
||||
*
|
||||
* @param cfg WML node object to write to. Any existing contents are
|
||||
* erased by this method.
|
||||
*/
|
||||
void write(config& cfg) const;
|
||||
|
||||
/**
|
||||
* Whether an add-on described by these fields is blacklisted.
|
||||
*
|
||||
* Empty parameters are ignored.
|
||||
*/
|
||||
bool is_blacklisted(const std::string& name,
|
||||
const std::string& title,
|
||||
const std::string& description,
|
||||
const std::string& author,
|
||||
const std::string& ip,
|
||||
const std::string& email) const;
|
||||
|
||||
private:
|
||||
globlist names_;
|
||||
globlist titles_;
|
||||
globlist descriptions_;
|
||||
|
||||
globlist authors_;
|
||||
globlist ips_;
|
||||
globlist emails_;
|
||||
|
||||
void parse_str_to_globlist(const std::string& str, globlist& glist);
|
||||
|
||||
bool is_in_globlist(const std::string& str, const globlist& glist) const;
|
||||
|
||||
bool is_in_ip_masklist(const std::string& ip, const globlist& mlist) const;
|
||||
bool ip_matches(const std::string& ip, const glob& ip_mask) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -28,6 +28,7 @@
|
||||
#include "serialization/unicode.hpp"
|
||||
#include "game_config.hpp"
|
||||
#include "addon/validation.hpp"
|
||||
#include "campaign_server/blacklist.hpp"
|
||||
#include "version.hpp"
|
||||
#include "server/input_stream.hpp"
|
||||
#include "util.hpp"
|
||||
@ -199,6 +200,8 @@ namespace {
|
||||
const config& server_info() const { return cfg_.child("server_info"); }
|
||||
config& server_info() { return cfg_.child("server_info"); }
|
||||
|
||||
void load_blacklist();
|
||||
|
||||
config cfg_;
|
||||
const std::string file_;
|
||||
const network::manager net_manager_;
|
||||
@ -210,6 +213,9 @@ namespace {
|
||||
/** Feedback URL format string used for add-ons. */
|
||||
std::string feedback_url_format_;
|
||||
|
||||
campaignd::blacklist blacklist_;
|
||||
std::string blacklist_file_;
|
||||
|
||||
const network::server_manager server_manager_;
|
||||
|
||||
};
|
||||
@ -268,9 +274,35 @@ namespace {
|
||||
feedback_url_format_ = svinfo_cfg["feedback_url_format"].str();
|
||||
}
|
||||
|
||||
blacklist_file_ = cfg_["blacklist_file"].str();
|
||||
load_blacklist();
|
||||
|
||||
return cfg_["port"].to_int(default_campaignd_port);
|
||||
}
|
||||
|
||||
void campaign_server::load_blacklist()
|
||||
{
|
||||
// We *always* want to clear the blacklist first, especially if we are
|
||||
// reloading the configuration and the blacklist is no longer enabled.
|
||||
blacklist_.clear();
|
||||
|
||||
if(blacklist_file_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
scoped_istream stream = istream_file(blacklist_file_);
|
||||
config cfg;
|
||||
|
||||
read(cfg, *stream);
|
||||
|
||||
blacklist_.read(cfg);
|
||||
LOG_CS << "using blacklist from " << blacklist_file_ << '\n';
|
||||
} catch(const config::error&) {
|
||||
LOG_CS << "ERROR: failed to read blacklist from " << blacklist_file_ << ", blacklist disabled\n";
|
||||
}
|
||||
}
|
||||
|
||||
campaign_server::campaign_server(const std::string& cfgfile,
|
||||
size_t min_thread, size_t max_thread) :
|
||||
cfg_(),
|
||||
@ -281,6 +313,8 @@ namespace {
|
||||
compress_level_(0), // Will be properly set by load_config()
|
||||
read_only_(false),
|
||||
feedback_url_format_(), // Will be properly set by load_config()
|
||||
blacklist_(),
|
||||
blacklist_file_(),
|
||||
server_manager_(load_config())
|
||||
{
|
||||
#ifndef _MSC_VER
|
||||
@ -542,6 +576,19 @@ namespace {
|
||||
const time_t upload_ts = time(NULL);
|
||||
|
||||
LOG_CS << "Upload is owner upload.\n";
|
||||
|
||||
if(blacklist_.is_blacklisted(name,
|
||||
upload["title"].str(),
|
||||
upload["description"].str(),
|
||||
upload["author"].str(),
|
||||
addr,
|
||||
upload["email"].str()))
|
||||
{
|
||||
LOG_CS << "Upload denied - blacklisted add-on information.\n";
|
||||
network::send_data(construct_error("Add-on upload denied. Please contact the server administration for assistance."), sock);
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string message = "Add-on accepted.";
|
||||
|
||||
if (!version_info(upload["version"]).good()) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user