2014-12-18 18:50:47 +00:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2014, Facebook, Inc.
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* This source code is licensed under the BSD-style license found in the
|
2015-01-21 20:56:25 +00:00
|
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
2014-12-18 18:50:47 +00:00
|
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
|
|
*
|
|
|
|
*/
|
2014-07-31 00:35:19 +00:00
|
|
|
|
2015-01-02 05:55:10 +00:00
|
|
|
#include <mutex>
|
2015-03-22 21:58:00 +00:00
|
|
|
#include <random>
|
2014-07-31 00:35:19 +00:00
|
|
|
#include <sstream>
|
|
|
|
|
2014-12-03 23:14:02 +00:00
|
|
|
#include <osquery/config.h>
|
|
|
|
#include <osquery/flags.h>
|
2015-01-16 01:40:42 +00:00
|
|
|
#include <osquery/hash.h>
|
2015-02-12 02:50:15 +00:00
|
|
|
#include <osquery/filesystem.h>
|
2015-01-21 21:36:55 +00:00
|
|
|
#include <osquery/logger.h>
|
2015-03-30 06:51:52 +00:00
|
|
|
#include <osquery/registry.h>
|
2014-12-11 01:35:21 +00:00
|
|
|
|
2014-07-31 00:35:19 +00:00
|
|
|
namespace pt = boost::property_tree;
|
2015-03-13 23:05:20 +00:00
|
|
|
|
|
|
|
typedef pt::ptree::value_type tree_node;
|
2015-02-12 02:50:15 +00:00
|
|
|
typedef std::map<std::string, std::vector<std::string> > EventFileMap_t;
|
|
|
|
|
2014-08-15 07:25:30 +00:00
|
|
|
namespace osquery {
|
2014-07-31 00:35:19 +00:00
|
|
|
|
2015-03-03 23:03:14 +00:00
|
|
|
CLI_FLAG(string, config_plugin, "filesystem", "Config plugin name");
|
2014-07-31 00:35:19 +00:00
|
|
|
|
2015-03-22 21:58:00 +00:00
|
|
|
FLAG(int32, schedule_splay_percent, 10, "Percent to splay config times");
|
|
|
|
|
2015-01-02 05:55:10 +00:00
|
|
|
Status Config::load() {
|
2015-03-08 21:52:13 +00:00
|
|
|
auto& config_plugin = Registry::getActive("config");
|
|
|
|
if (!Registry::exists("config", config_plugin)) {
|
|
|
|
return Status(1, "Missing config plugin " + config_plugin);
|
2015-03-04 02:40:24 +00:00
|
|
|
}
|
|
|
|
|
2015-03-20 17:34:36 +00:00
|
|
|
return genConfig();
|
|
|
|
}
|
|
|
|
|
|
|
|
Status Config::update(const std::map<std::string, std::string>& config) {
|
2015-03-30 06:51:52 +00:00
|
|
|
// A config plugin may call update from an extension. This will update
|
|
|
|
// the config instance within the extension process and the update must be
|
|
|
|
// reflected in the core.
|
|
|
|
if (Registry::external()) {
|
|
|
|
for (const auto& source : config) {
|
|
|
|
PluginRequest request = {
|
|
|
|
{"action", "update"},
|
|
|
|
{"source", source.first},
|
|
|
|
{"data", source.second},
|
|
|
|
};
|
|
|
|
// A "update" registry item within core should call the core's update
|
|
|
|
// method. The config plugin call action handling must also know to
|
|
|
|
// update.
|
|
|
|
Registry::call("config", "update", request);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-22 03:51:42 +00:00
|
|
|
// Request a unique write lock when updating config.
|
|
|
|
boost::unique_lock<boost::shared_mutex> unique_lock(getInstance().mutex_);
|
2015-01-02 05:55:10 +00:00
|
|
|
|
2015-03-22 21:58:00 +00:00
|
|
|
ConfigData conf;
|
2015-03-20 17:34:36 +00:00
|
|
|
for (const auto& source : config) {
|
2015-03-30 06:51:52 +00:00
|
|
|
if (Registry::external()) {
|
|
|
|
VLOG(1) << "Updating extension config source: " << source.first;
|
|
|
|
} else {
|
|
|
|
VLOG(1) << "Updating config source: " << source.first;
|
|
|
|
}
|
2015-03-20 17:34:36 +00:00
|
|
|
getInstance().raw_[source.first] = source.second;
|
2014-12-01 09:05:46 +00:00
|
|
|
}
|
|
|
|
|
2015-03-22 21:58:00 +00:00
|
|
|
// Now merge all sources together.
|
|
|
|
for (const auto& source : getInstance().raw_) {
|
|
|
|
mergeConfig(source.second, conf);
|
2014-07-31 00:35:19 +00:00
|
|
|
}
|
2015-03-22 03:51:42 +00:00
|
|
|
|
2015-03-22 21:58:00 +00:00
|
|
|
getInstance().data_ = conf;
|
|
|
|
return Status(0, "OK");
|
2014-07-31 00:35:19 +00:00
|
|
|
}
|
|
|
|
|
2015-03-20 17:34:36 +00:00
|
|
|
Status Config::genConfig() {
|
2015-01-30 18:44:25 +00:00
|
|
|
PluginResponse response;
|
2015-03-08 21:52:13 +00:00
|
|
|
auto status = Registry::call("config", {{"action", "genConfig"}}, response);
|
2015-01-30 18:44:25 +00:00
|
|
|
if (!status.ok()) {
|
|
|
|
return status;
|
2014-07-31 00:35:19 +00:00
|
|
|
}
|
2015-01-04 07:12:28 +00:00
|
|
|
|
2015-03-13 23:05:20 +00:00
|
|
|
if (response.size() > 0) {
|
2015-03-20 17:34:36 +00:00
|
|
|
return update(response[0]);
|
2015-03-13 23:05:20 +00:00
|
|
|
}
|
2014-12-11 01:35:21 +00:00
|
|
|
return Status(0, "OK");
|
|
|
|
}
|
|
|
|
|
2015-03-22 03:51:42 +00:00
|
|
|
inline void mergeOption(const tree_node& option, ConfigData& conf) {
|
2015-03-13 23:05:20 +00:00
|
|
|
conf.options[option.first.data()] = option.second.data();
|
2015-03-20 17:34:36 +00:00
|
|
|
if (conf.all_data.count("options") > 0) {
|
|
|
|
conf.all_data.get_child("options").erase(option.first);
|
|
|
|
}
|
2015-03-13 23:05:20 +00:00
|
|
|
conf.all_data.add_child("options." + option.first, option.second);
|
|
|
|
}
|
|
|
|
|
2015-03-22 03:51:42 +00:00
|
|
|
inline void mergeAdditional(const tree_node& node, ConfigData& conf) {
|
2015-03-20 17:34:36 +00:00
|
|
|
if (conf.all_data.count("additional_monitoring") > 0) {
|
|
|
|
conf.all_data.get_child("additional_monitoring").erase(node.first);
|
|
|
|
}
|
2015-03-13 23:05:20 +00:00
|
|
|
conf.all_data.add_child("additional_monitoring." + node.first, node.second);
|
|
|
|
|
|
|
|
// Support special merging of file paths.
|
Implement YARA table.
Currently only for OS X, will port to others soon.
Also need to add tests.
Remove old comment and add loading message.
Implement YARA table for Linux.
Use mask properly.
Use the various masks to specify the kinds of events we are interested
in. This removes the need to do the dirty "DELETED" check when the event
fires.
Make getYARAFiles return a const map.
Switch to LOG(WARNING) and emit error number.
Add vim .swp files to .gitignore.
Add yara_utils.(c|h).
Start to condense common code between the Linux and Darwin YARA tables
into a yara_utils.h. Right now it includes a function to compile rules
and store the results back in the map, indexed by category. It also has
the callback used by YARA when a rule is processed. I can not move much
more than that for the row creation code because the structures used in
the event callback are slightly different.
Include a better error message.
The errors are still printed by the compiler callback, but this will
allow my future work to return a Status from the event initialization to
print a useful message in summary.
Make Subscriber init() return Status.
Each EventSubscriber::init() now returns a Status. If the init() fails
for any reason the EventSubscriber is still stored but the failure is
tracked.
EventSubscribers now have a state member, which represents the current
state of the subscriber. The current supported states are:
uninitialized, running, paused, failed. Currently the only meaningful
ones are running and failed, but I put paused in there as a
forward-looking feature.
Subscriptions now have a subscriber_name member. This is used in
EventPublisherPlugin::fire() as a lookup to get the EventSubscriber and
check the state. If the EventSubscriber is not running the event will
not fire.
Only the EventSubscribers on OS X are using this. I'll do the Linux
implementation next.
Chase the init() changes to Linux.
This brings the Linux YARA table in line with the OS X one.
Require a EventSubscriberID when creating a subscription.
Now that Subscriptions are "tied" to EventSubscribers you must create a
Subscription with the name of the Subscriber it is for. This is because
when the event fires the list of Subscriptions is walked and the name is
used to lookup the EventSubscriber and make sure it is in the running
state.
Fix various tests.
Some tests would fire an event with only a Subscription, which is no
longer a valid thing to do. For these tests an EventSubscription is
created and registered in the EventFactory.
When Subscriptions are created pass the name of the EventSubscriber to
them. In some cases where no event is ever fired it is fine to pass a
bogus name.
Fix inotify tests.
Move a test down so the class is defined and make sure to create an
EventSubscriber and use it properly.
Add support for yara to provision.sh.
Right now this grabs yara 3.3.0 and applies the patch to fix min() and max(),
which is commit fc4696c8b725be1ac099d340359c8d550d116041 in the yara repo.
This has been tested under Ubuntu 14.04 only.
Remove NOMINMAX.
This is no longer necessary after the patch was backported to 3.3.0.
Revert "Add support for yara to provision.sh."
This reverts commit a8bd371498c0979f070adeff23d05571882ac3f1.
Use vendored YARA code in third-party.
This switches to using the YARA code contained in third-party, including
the patch to fix min/max macros.
Fix mismerge.
Remove unused function after merge.
Well, soon to be unused as soon as I fix up the Linux YARA table. ;)
Chase config changes.
Make the Linux YARA table use ConfigDataInstance along with files() and
yaraFiles().
2015-03-10 13:22:16 +00:00
|
|
|
if (node.first == "file_paths") {
|
|
|
|
for (const auto& category : node.second) {
|
|
|
|
for (const auto& path : category.second) {
|
|
|
|
resolveFilePattern(path.second.data(),
|
|
|
|
conf.files[category.first],
|
|
|
|
REC_LIST_FOLDERS | REC_EVENT_OPT);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (node.first == "yara") {
|
|
|
|
for (const auto& category : node.second) {
|
|
|
|
// Todo: the yara category key must come after "file_paths".
|
|
|
|
if (conf.files.find(category.first) == conf.files.end()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
for (const auto& file : category.second) {
|
|
|
|
conf.yara[category.first].push_back(
|
|
|
|
file.second.get_value<std::string>());
|
|
|
|
}
|
2015-03-13 23:05:20 +00:00
|
|
|
}
|
Implement YARA table.
Currently only for OS X, will port to others soon.
Also need to add tests.
Remove old comment and add loading message.
Implement YARA table for Linux.
Use mask properly.
Use the various masks to specify the kinds of events we are interested
in. This removes the need to do the dirty "DELETED" check when the event
fires.
Make getYARAFiles return a const map.
Switch to LOG(WARNING) and emit error number.
Add vim .swp files to .gitignore.
Add yara_utils.(c|h).
Start to condense common code between the Linux and Darwin YARA tables
into a yara_utils.h. Right now it includes a function to compile rules
and store the results back in the map, indexed by category. It also has
the callback used by YARA when a rule is processed. I can not move much
more than that for the row creation code because the structures used in
the event callback are slightly different.
Include a better error message.
The errors are still printed by the compiler callback, but this will
allow my future work to return a Status from the event initialization to
print a useful message in summary.
Make Subscriber init() return Status.
Each EventSubscriber::init() now returns a Status. If the init() fails
for any reason the EventSubscriber is still stored but the failure is
tracked.
EventSubscribers now have a state member, which represents the current
state of the subscriber. The current supported states are:
uninitialized, running, paused, failed. Currently the only meaningful
ones are running and failed, but I put paused in there as a
forward-looking feature.
Subscriptions now have a subscriber_name member. This is used in
EventPublisherPlugin::fire() as a lookup to get the EventSubscriber and
check the state. If the EventSubscriber is not running the event will
not fire.
Only the EventSubscribers on OS X are using this. I'll do the Linux
implementation next.
Chase the init() changes to Linux.
This brings the Linux YARA table in line with the OS X one.
Require a EventSubscriberID when creating a subscription.
Now that Subscriptions are "tied" to EventSubscribers you must create a
Subscription with the name of the Subscriber it is for. This is because
when the event fires the list of Subscriptions is walked and the name is
used to lookup the EventSubscriber and make sure it is in the running
state.
Fix various tests.
Some tests would fire an event with only a Subscription, which is no
longer a valid thing to do. For these tests an EventSubscription is
created and registered in the EventFactory.
When Subscriptions are created pass the name of the EventSubscriber to
them. In some cases where no event is ever fired it is fine to pass a
bogus name.
Fix inotify tests.
Move a test down so the class is defined and make sure to create an
EventSubscriber and use it properly.
Add support for yara to provision.sh.
Right now this grabs yara 3.3.0 and applies the patch to fix min() and max(),
which is commit fc4696c8b725be1ac099d340359c8d550d116041 in the yara repo.
This has been tested under Ubuntu 14.04 only.
Remove NOMINMAX.
This is no longer necessary after the patch was backported to 3.3.0.
Revert "Add support for yara to provision.sh."
This reverts commit a8bd371498c0979f070adeff23d05571882ac3f1.
Use vendored YARA code in third-party.
This switches to using the YARA code contained in third-party, including
the patch to fix min/max macros.
Fix mismerge.
Remove unused function after merge.
Well, soon to be unused as soon as I fix up the Linux YARA table. ;)
Chase config changes.
Make the Linux YARA table use ConfigDataInstance along with files() and
yaraFiles().
2015-03-10 13:22:16 +00:00
|
|
|
} else {
|
|
|
|
// Unknown additional monitoring key.
|
2015-03-13 23:05:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-22 21:58:00 +00:00
|
|
|
// inline void mergeScheduledQuery(const tree_node& node, ConfigData& conf) {
|
|
|
|
inline void mergeScheduledQuery(const std::string& name,
|
|
|
|
const tree_node& node,
|
|
|
|
ConfigData& conf) {
|
2015-03-13 23:05:20 +00:00
|
|
|
// Read tree/JSON into a query structure.
|
2015-03-22 21:58:00 +00:00
|
|
|
ScheduledQuery query;
|
2015-03-13 23:05:20 +00:00
|
|
|
query.query = node.second.get<std::string>("query", "");
|
|
|
|
query.interval = node.second.get<int>("interval", 0);
|
2015-03-22 21:58:00 +00:00
|
|
|
|
|
|
|
// Check if this query exists, if so, check if it was changed.
|
|
|
|
if (conf.schedule.count(name) > 0) {
|
|
|
|
if (query == conf.schedule.at(name)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// This is a new or updated scheduled query, update the splay.
|
|
|
|
query.splayed_interval =
|
|
|
|
splayValue(query.interval, FLAGS_schedule_splay_percent);
|
|
|
|
// Update the schedule map and replace the all_data node record.
|
|
|
|
conf.schedule[name] = query;
|
|
|
|
if (conf.all_data.count("schedule") > 0) {
|
|
|
|
conf.all_data.get_child("schedule").erase(name);
|
|
|
|
}
|
|
|
|
conf.all_data.add_child("schedule." + name, node.second);
|
2015-03-13 23:05:20 +00:00
|
|
|
}
|
|
|
|
|
2015-03-22 21:58:00 +00:00
|
|
|
void Config::mergeConfig(const std::string& source, ConfigData& conf) {
|
|
|
|
std::stringstream json_data;
|
|
|
|
json_data << source;
|
|
|
|
|
|
|
|
pt::ptree tree;
|
|
|
|
pt::read_json(json_data, tree);
|
2015-03-13 23:05:20 +00:00
|
|
|
|
2015-03-22 21:58:00 +00:00
|
|
|
// Legacy query schedule vector support.
|
|
|
|
if (tree.count("scheduledQueries") > 0) {
|
|
|
|
LOG(INFO) << RLOG(903) << "config 'scheduledQueries' is deprecated";
|
|
|
|
for (const auto& node : tree.get_child("scheduledQueries")) {
|
|
|
|
auto query_name = node.second.get<std::string>("name", "");
|
|
|
|
mergeScheduledQuery(query_name, node, conf);
|
|
|
|
}
|
|
|
|
}
|
2015-03-13 23:05:20 +00:00
|
|
|
|
2015-03-22 21:58:00 +00:00
|
|
|
// Key/value query schedule map support.
|
|
|
|
if (tree.count("schedule") > 0) {
|
|
|
|
for (const auto& node : tree.get_child("schedule")) {
|
|
|
|
mergeScheduledQuery(node.first.data(), node, conf);
|
2014-12-01 09:05:46 +00:00
|
|
|
}
|
2015-03-22 21:58:00 +00:00
|
|
|
}
|
2015-02-10 22:32:54 +00:00
|
|
|
|
2015-03-22 21:58:00 +00:00
|
|
|
if (tree.count("additional_monitoring") > 0) {
|
|
|
|
for (const auto& node : tree.get_child("additional_monitoring")) {
|
|
|
|
mergeAdditional(node, conf);
|
2015-02-13 20:32:54 +00:00
|
|
|
}
|
2015-03-22 21:58:00 +00:00
|
|
|
}
|
2014-07-31 00:35:19 +00:00
|
|
|
|
Implement YARA table.
Currently only for OS X, will port to others soon.
Also need to add tests.
Remove old comment and add loading message.
Implement YARA table for Linux.
Use mask properly.
Use the various masks to specify the kinds of events we are interested
in. This removes the need to do the dirty "DELETED" check when the event
fires.
Make getYARAFiles return a const map.
Switch to LOG(WARNING) and emit error number.
Add vim .swp files to .gitignore.
Add yara_utils.(c|h).
Start to condense common code between the Linux and Darwin YARA tables
into a yara_utils.h. Right now it includes a function to compile rules
and store the results back in the map, indexed by category. It also has
the callback used by YARA when a rule is processed. I can not move much
more than that for the row creation code because the structures used in
the event callback are slightly different.
Include a better error message.
The errors are still printed by the compiler callback, but this will
allow my future work to return a Status from the event initialization to
print a useful message in summary.
Make Subscriber init() return Status.
Each EventSubscriber::init() now returns a Status. If the init() fails
for any reason the EventSubscriber is still stored but the failure is
tracked.
EventSubscribers now have a state member, which represents the current
state of the subscriber. The current supported states are:
uninitialized, running, paused, failed. Currently the only meaningful
ones are running and failed, but I put paused in there as a
forward-looking feature.
Subscriptions now have a subscriber_name member. This is used in
EventPublisherPlugin::fire() as a lookup to get the EventSubscriber and
check the state. If the EventSubscriber is not running the event will
not fire.
Only the EventSubscribers on OS X are using this. I'll do the Linux
implementation next.
Chase the init() changes to Linux.
This brings the Linux YARA table in line with the OS X one.
Require a EventSubscriberID when creating a subscription.
Now that Subscriptions are "tied" to EventSubscribers you must create a
Subscription with the name of the Subscriber it is for. This is because
when the event fires the list of Subscriptions is walked and the name is
used to lookup the EventSubscriber and make sure it is in the running
state.
Fix various tests.
Some tests would fire an event with only a Subscription, which is no
longer a valid thing to do. For these tests an EventSubscription is
created and registered in the EventFactory.
When Subscriptions are created pass the name of the EventSubscriber to
them. In some cases where no event is ever fired it is fine to pass a
bogus name.
Fix inotify tests.
Move a test down so the class is defined and make sure to create an
EventSubscriber and use it properly.
Add support for yara to provision.sh.
Right now this grabs yara 3.3.0 and applies the patch to fix min() and max(),
which is commit fc4696c8b725be1ac099d340359c8d550d116041 in the yara repo.
This has been tested under Ubuntu 14.04 only.
Remove NOMINMAX.
This is no longer necessary after the patch was backported to 3.3.0.
Revert "Add support for yara to provision.sh."
This reverts commit a8bd371498c0979f070adeff23d05571882ac3f1.
Use vendored YARA code in third-party.
This switches to using the YARA code contained in third-party, including
the patch to fix min/max macros.
Fix mismerge.
Remove unused function after merge.
Well, soon to be unused as soon as I fix up the Linux YARA table. ;)
Chase config changes.
Make the Linux YARA table use ConfigDataInstance along with files() and
yaraFiles().
2015-03-10 13:22:16 +00:00
|
|
|
Status Config::getMD5(std::string& hash_string) {
|
|
|
|
std::string config_string;
|
|
|
|
auto s = genConfig(config_string);
|
|
|
|
if (!s.ok()) {
|
|
|
|
return s;
|
2015-03-13 23:05:20 +00:00
|
|
|
}
|
2014-07-31 00:35:19 +00:00
|
|
|
|
2015-01-16 20:03:23 +00:00
|
|
|
Status Config::getMD5(std::string& hash_string) {
|
2015-03-22 03:51:42 +00:00
|
|
|
// Request an accessor to our own config, outside of an update.
|
|
|
|
ConfigDataInstance config;
|
|
|
|
|
2015-03-13 23:05:20 +00:00
|
|
|
std::stringstream out;
|
2015-03-22 03:51:42 +00:00
|
|
|
write_json(out, config.data());
|
2014-12-11 01:35:21 +00:00
|
|
|
|
2015-01-20 22:05:01 +00:00
|
|
|
hash_string = osquery::hashFromBuffer(
|
2015-03-13 23:05:20 +00:00
|
|
|
HASH_TYPE_MD5, (void*)out.str().c_str(), out.str().length());
|
2014-12-11 01:35:21 +00:00
|
|
|
|
|
|
|
return Status(0, "OK");
|
|
|
|
}
|
2015-01-21 20:56:25 +00:00
|
|
|
|
2015-03-20 17:34:36 +00:00
|
|
|
Status Config::checkConfig() { return load(); }
|
2015-01-30 18:44:25 +00:00
|
|
|
|
|
|
|
Status ConfigPlugin::call(const PluginRequest& request,
|
|
|
|
PluginResponse& response) {
|
|
|
|
if (request.count("action") == 0) {
|
|
|
|
return Status(1, "Config plugins require an action in PluginRequest");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (request.at("action") == "genConfig") {
|
2015-03-13 23:05:20 +00:00
|
|
|
std::map<std::string, std::string> config;
|
|
|
|
auto stat = genConfig(config);
|
|
|
|
response.push_back(config);
|
|
|
|
return stat;
|
2015-03-25 03:59:28 +00:00
|
|
|
} else if (request.at("action") == "update") {
|
|
|
|
if (request.count("source") == 0 || request.count("data") == 0) {
|
|
|
|
return Status(1, "Missing source or data");
|
|
|
|
}
|
|
|
|
return Config::update({{request.at("source"), request.at("data")}});
|
2015-01-30 18:44:25 +00:00
|
|
|
}
|
|
|
|
return Status(1, "Config plugin action unknown: " + request.at("action"));
|
|
|
|
}
|
2015-03-22 21:58:00 +00:00
|
|
|
|
|
|
|
int splayValue(int original, int splayPercent) {
|
|
|
|
if (splayPercent <= 0 || splayPercent > 100) {
|
|
|
|
return original;
|
|
|
|
}
|
|
|
|
|
|
|
|
float percent_to_modify_by = (float)splayPercent / 100;
|
|
|
|
int possible_difference = original * percent_to_modify_by;
|
|
|
|
int max_value = original + possible_difference;
|
|
|
|
int min_value = original - possible_difference;
|
|
|
|
|
|
|
|
if (max_value == min_value) {
|
|
|
|
return max_value;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::default_random_engine generator;
|
|
|
|
std::uniform_int_distribution<int> distribution(min_value, max_value);
|
|
|
|
return distribution(generator);
|
|
|
|
}
|
2014-08-15 07:25:30 +00:00
|
|
|
}
|