mirror of https://github.com/Icinga/icinga2.git
Implemented message-based authorisation checks.
This commit is contained in:
parent
36eb5e1cf3
commit
18bffce0ed
|
@ -2,6 +2,7 @@
|
|||
## Created by Anjuta
|
||||
|
||||
SUBDIRS = ltdl \
|
||||
mmatch \
|
||||
base \
|
||||
cJSON \
|
||||
jsonrpc \
|
||||
|
|
|
@ -93,7 +93,12 @@
|
|||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<IncludePath>$(SolutionDir)\mmatch;$(IncludePath)</IncludePath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<IncludePath>$(SolutionDir)\mmatch;$(IncludePath)</IncludePath>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
|
|
|
@ -38,14 +38,7 @@ void ConfigCollection::AddObject(const ConfigObject::Ptr& object)
|
|||
RemoveObject(object);
|
||||
|
||||
Objects[object->GetName()] = object;
|
||||
|
||||
EventArgs ea;
|
||||
ea.Source = object;
|
||||
OnObjectCreated(ea);
|
||||
|
||||
ConfigHive::Ptr hive = m_Hive.lock();
|
||||
if (hive)
|
||||
hive->OnObjectCreated(ea);
|
||||
object->Commit();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -28,9 +28,8 @@ public:
|
|||
|
||||
void ForEachObject(function<int (const EventArgs&)> callback);
|
||||
|
||||
Event<EventArgs> OnObjectCreated;
|
||||
Event<EventArgs> OnObjectCommitted;
|
||||
Event<EventArgs> OnObjectRemoved;
|
||||
Event<PropertyChangedEventArgs> OnPropertyChanged;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -23,9 +23,8 @@ public:
|
|||
void ForEachObject(const string& type,
|
||||
function<int (const EventArgs&)> callback);
|
||||
|
||||
Event<EventArgs> OnObjectCreated;
|
||||
Event<EventArgs> OnObjectCommitted;
|
||||
Event<EventArgs> OnObjectRemoved;
|
||||
Event<PropertyChangedEventArgs> OnPropertyChanged;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -30,7 +30,6 @@ void ConfigObject::SetHive(const ConfigHive::WeakPtr& hive)
|
|||
throw InvalidArgumentException("Config object already has a parent hive.");
|
||||
|
||||
m_Hive = hive;
|
||||
OnPropertyChanged += bind_weak(&ConfigObject::PropertyChangedHandler, shared_from_this());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -118,21 +117,19 @@ bool ConfigObject::GetReplicated(void) const
|
|||
}
|
||||
|
||||
/**
|
||||
* PropertyChangedHandler
|
||||
* Commit
|
||||
*
|
||||
* Handles changed properties by propagating them to the hive
|
||||
* and collection this object is contained in.
|
||||
*
|
||||
* @param dpcea The event arguments.
|
||||
* @returns 0.
|
||||
*/
|
||||
int ConfigObject::PropertyChangedHandler(const PropertyChangedEventArgs& dpcea)
|
||||
void ConfigObject::Commit(void)
|
||||
{
|
||||
ConfigHive::Ptr hive = m_Hive.lock();
|
||||
if (hive) {
|
||||
hive->GetCollection(m_Type)->OnPropertyChanged(dpcea);
|
||||
hive->OnPropertyChanged(dpcea);
|
||||
EventArgs ea;
|
||||
ea.Source = shared_from_this();
|
||||
hive->GetCollection(m_Type)->OnObjectCommitted(ea);
|
||||
hive->OnObjectCommitted(ea);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -17,8 +17,6 @@ private:
|
|||
string m_Type;
|
||||
bool m_Replicated;
|
||||
|
||||
int PropertyChangedHandler(const PropertyChangedEventArgs& dpcea);
|
||||
|
||||
public:
|
||||
typedef shared_ptr<ConfigObject> Ptr;
|
||||
typedef weak_ptr<ConfigObject> WeakPtr;
|
||||
|
@ -36,6 +34,8 @@ public:
|
|||
|
||||
void SetReplicated(bool replicated);
|
||||
bool GetReplicated(void) const;
|
||||
|
||||
void Commit(void);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "i2-base.h"
|
||||
#include <mmatch.h>
|
||||
|
||||
using namespace icinga;
|
||||
|
||||
|
@ -137,3 +138,8 @@ shared_ptr<X509> Utility::GetX509Certificate(string pemfile)
|
|||
|
||||
return shared_ptr<X509>(cert, X509_free);
|
||||
}
|
||||
|
||||
bool Utility::Match(string pattern, string text)
|
||||
{
|
||||
return (match(pattern.c_str(), text.c_str()) != 0);
|
||||
}
|
||||
|
|
|
@ -47,6 +47,8 @@ public:
|
|||
static shared_ptr<SSL_CTX> MakeSSLContext(string pubkey, string privkey, string cakey);
|
||||
static string GetCertificateCN(const shared_ptr<X509>& certificate);
|
||||
static shared_ptr<X509> GetX509Certificate(string pemfile);
|
||||
|
||||
static bool Match(string pattern, string text);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -56,12 +56,22 @@ void ConfigFileComponent::Start(void)
|
|||
for (cJSON *property = object->child; property != NULL; property = property->next) {
|
||||
string key = property->string;
|
||||
|
||||
if (property->type != cJSON_String)
|
||||
continue;
|
||||
|
||||
if (property->type == cJSON_String) {
|
||||
string value = property->valuestring;
|
||||
|
||||
cfgobj->SetPropertyString(key, value);
|
||||
} else if (property->type == cJSON_Array) {
|
||||
Dictionary::Ptr items = make_shared<Dictionary>();
|
||||
|
||||
for (cJSON *item = property->child; item != NULL; item = item->next) {
|
||||
if (item->type != cJSON_String)
|
||||
continue;
|
||||
|
||||
items->AddUnnamedPropertyString(item->valuestring);
|
||||
}
|
||||
|
||||
cfgobj->SetPropertyDictionary(key, items);
|
||||
}
|
||||
}
|
||||
|
||||
GetApplication()->GetConfigHive()->AddObject(cfgobj);
|
||||
|
|
|
@ -19,24 +19,20 @@ void ConfigRpcComponent::Start(void)
|
|||
m_ConfigRpcEndpoint->RegisterMethodHandler("config::FetchObjects",
|
||||
bind_weak(&ConfigRpcComponent::FetchObjectsHandler, shared_from_this()));
|
||||
|
||||
configHive->OnObjectCreated += bind_weak(&ConfigRpcComponent::LocalObjectCreatedHandler, shared_from_this());
|
||||
configHive->OnObjectCommitted += bind_weak(&ConfigRpcComponent::LocalObjectCommittedHandler, shared_from_this());
|
||||
configHive->OnObjectRemoved += bind_weak(&ConfigRpcComponent::LocalObjectRemovedHandler, shared_from_this());
|
||||
configHive->OnPropertyChanged += bind_weak(&ConfigRpcComponent::LocalPropertyChangedHandler, shared_from_this());
|
||||
|
||||
m_ConfigRpcEndpoint->RegisterMethodSource("config::ObjectCreated");
|
||||
m_ConfigRpcEndpoint->RegisterMethodSource("config::ObjectCommitted");
|
||||
m_ConfigRpcEndpoint->RegisterMethodSource("config::ObjectRemoved");
|
||||
m_ConfigRpcEndpoint->RegisterMethodSource("config::PropertyChanged");
|
||||
}
|
||||
|
||||
endpointManager->OnNewEndpoint += bind_weak(&ConfigRpcComponent::NewEndpointHandler, shared_from_this());
|
||||
|
||||
m_ConfigRpcEndpoint->RegisterMethodSource("config::FetchObjects");
|
||||
m_ConfigRpcEndpoint->RegisterMethodHandler("config::ObjectCreated",
|
||||
bind_weak(&ConfigRpcComponent::RemoteObjectUpdatedHandler, shared_from_this()));
|
||||
m_ConfigRpcEndpoint->RegisterMethodHandler("config::ObjectCommitted",
|
||||
bind_weak(&ConfigRpcComponent::RemoteObjectCommittedHandler, shared_from_this()));
|
||||
m_ConfigRpcEndpoint->RegisterMethodHandler("config::ObjectRemoved",
|
||||
bind_weak(&ConfigRpcComponent::RemoteObjectRemovedHandler, shared_from_this()));
|
||||
m_ConfigRpcEndpoint->RegisterMethodHandler("config::PropertyChanged",
|
||||
bind_weak(&ConfigRpcComponent::RemoteObjectUpdatedHandler, shared_from_this()));
|
||||
|
||||
endpointManager->RegisterEndpoint(m_ConfigRpcEndpoint);
|
||||
}
|
||||
|
@ -115,7 +111,7 @@ int ConfigRpcComponent::FetchObjectsHandler(const NewRequestEventArgs& ea)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int ConfigRpcComponent::LocalObjectCreatedHandler(const EventArgs& ea)
|
||||
int ConfigRpcComponent::LocalObjectCommittedHandler(const EventArgs& ea)
|
||||
{
|
||||
ConfigObject::Ptr object = static_pointer_cast<ConfigObject>(ea.Source);
|
||||
|
||||
|
@ -141,32 +137,7 @@ int ConfigRpcComponent::LocalObjectRemovedHandler(const EventArgs& ea)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int ConfigRpcComponent::LocalPropertyChangedHandler(const PropertyChangedEventArgs& ea)
|
||||
{
|
||||
ConfigObject::Ptr object = static_pointer_cast<ConfigObject>(ea.Source);
|
||||
|
||||
if (!ShouldReplicateObject(object))
|
||||
return 0;
|
||||
|
||||
JsonRpcRequest msg = MakeObjectMessage(object, "config::PropertyChanged", false);
|
||||
Message params;
|
||||
msg.SetParams(params);
|
||||
|
||||
Message properties;
|
||||
params.GetDictionary()->SetPropertyDictionary("properties", properties.GetDictionary());
|
||||
|
||||
string value;
|
||||
if (!object->GetPropertyString(ea.Property, &value))
|
||||
return 0;
|
||||
|
||||
properties.GetDictionary()->SetPropertyString(ea.Property, value);
|
||||
|
||||
GetEndpointManager()->SendMulticastRequest(m_ConfigRpcEndpoint, msg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ConfigRpcComponent::RemoteObjectUpdatedHandler(const NewRequestEventArgs& ea)
|
||||
int ConfigRpcComponent::RemoteObjectCommittedHandler(const NewRequestEventArgs& ea)
|
||||
{
|
||||
JsonRpcRequest message = ea.Request;
|
||||
bool was_null = false;
|
||||
|
|
|
@ -12,12 +12,11 @@ private:
|
|||
int NewEndpointHandler(const NewEndpointEventArgs& ea);
|
||||
int SessionEstablishedHandler(const EventArgs& ea);
|
||||
|
||||
int LocalObjectCreatedHandler(const EventArgs& ea);
|
||||
int LocalObjectCommittedHandler(const EventArgs& ea);
|
||||
int LocalObjectRemovedHandler(const EventArgs& ea);
|
||||
int LocalPropertyChangedHandler(const PropertyChangedEventArgs& ea);
|
||||
|
||||
int FetchObjectsHandler(const NewRequestEventArgs& ea);
|
||||
int RemoteObjectUpdatedHandler(const NewRequestEventArgs& ea);
|
||||
int RemoteObjectCommittedHandler(const NewRequestEventArgs& ea);
|
||||
int RemoteObjectRemovedHandler(const NewRequestEventArgs& ea);
|
||||
|
||||
static JsonRpcRequest MakeObjectMessage(const ConfigObject::Ptr& object, string method, bool includeProperties);
|
||||
|
|
|
@ -27,15 +27,16 @@ void DiscoveryComponent::Start(void)
|
|||
GetConfig()->GetPropertyInteger("broker", &isBroker);
|
||||
m_Broker = (isBroker != 0);
|
||||
|
||||
if (IsBroker()) {
|
||||
m_DiscoveryEndpoint->RegisterMethodSource("discovery::NewComponent");
|
||||
m_DiscoveryEndpoint->RegisterMethodSource("discovery::RegisterComponent");
|
||||
m_DiscoveryEndpoint->RegisterMethodHandler("discovery::RegisterComponent",
|
||||
bind_weak(&DiscoveryComponent::RegisterComponentMessageHandler, shared_from_this()));
|
||||
}
|
||||
|
||||
m_DiscoveryEndpoint->RegisterMethodSource("discovery::RegisterComponent");
|
||||
if (IsBroker())
|
||||
m_DiscoveryEndpoint->RegisterMethodSource("discovery::NewComponent");
|
||||
|
||||
m_DiscoveryEndpoint->RegisterMethodHandler("discovery::NewComponent",
|
||||
bind_weak(&DiscoveryComponent::NewComponentMessageHandler, shared_from_this()));
|
||||
|
||||
m_DiscoveryEndpoint->RegisterMethodHandler("discovery::Welcome",
|
||||
bind_weak(&DiscoveryComponent::WelcomeMessageHandler, shared_from_this()));
|
||||
|
||||
|
@ -49,6 +50,9 @@ void DiscoveryComponent::Start(void)
|
|||
m_DiscoveryTimer->SetInterval(30);
|
||||
m_DiscoveryTimer->OnTimerExpired += bind_weak(&DiscoveryComponent::DiscoveryTimerHandler, shared_from_this());
|
||||
m_DiscoveryTimer->Start();
|
||||
|
||||
/* call the timer as soon as possible */
|
||||
m_DiscoveryTimer->Reschedule(0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -104,10 +108,8 @@ int DiscoveryComponent::NewEndpointHandler(const NewEndpointEventArgs& neea)
|
|||
{
|
||||
neea.Endpoint->OnIdentityChanged += bind_weak(&DiscoveryComponent::NewIdentityHandler, shared_from_this());
|
||||
|
||||
if (IsBroker()) {
|
||||
/* accept discovery::RegisterComponent messages from any endpoint */
|
||||
neea.Endpoint->RegisterMethodSource("discovery::RegisterComponent");
|
||||
}
|
||||
|
||||
/* accept discovery::Welcome messages from any endpoint */
|
||||
neea.Endpoint->RegisterMethodSource("discovery::Welcome");
|
||||
|
@ -231,12 +233,6 @@ int DiscoveryComponent::NewIdentityHandler(const EventArgs& ea)
|
|||
|
||||
GetEndpointManager()->ForEachEndpoint(bind(&DiscoveryComponent::CheckExistingEndpoint, this, endpoint, _1));
|
||||
|
||||
ConfigCollection::Ptr brokerCollection = GetApplication()->GetConfigHive()->GetCollection("broker");
|
||||
if (brokerCollection->GetObject(identity)) {
|
||||
/* accept discovery::NewComponent messages from brokers */
|
||||
endpoint->RegisterMethodSource("discovery::NewComponent");
|
||||
}
|
||||
|
||||
// we assume the other component _always_ wants
|
||||
// discovery::RegisterComponent messages from us
|
||||
endpoint->RegisterMethodSink("discovery::RegisterComponent");
|
||||
|
@ -394,6 +390,30 @@ void DiscoveryComponent::SendDiscoveryMessage(string method, string identity, En
|
|||
GetEndpointManager()->SendMulticastRequest(m_DiscoveryEndpoint, request);
|
||||
}
|
||||
|
||||
bool DiscoveryComponent::HasMessagePermission(Dictionary::Ptr roles, string messageType, string message)
|
||||
{
|
||||
ConfigHive::Ptr configHive = GetApplication()->GetConfigHive();
|
||||
ConfigCollection::Ptr roleCollection = configHive->GetCollection("role");
|
||||
|
||||
for (DictionaryIterator ip = roles->Begin(); ip != roles->End(); ip++) {
|
||||
ConfigObject::Ptr role = roleCollection->GetObject(ip->second);
|
||||
|
||||
if (!role)
|
||||
continue;
|
||||
|
||||
Dictionary::Ptr permissions;
|
||||
if (!role->GetPropertyDictionary(messageType, &permissions))
|
||||
continue;
|
||||
|
||||
for (DictionaryIterator is = permissions->Begin(); is != permissions->End(); is++) {
|
||||
if (Utility::Match(is->second.GetString(), message))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* ProcessDiscoveryMessage
|
||||
*
|
||||
|
@ -402,8 +422,9 @@ void DiscoveryComponent::SendDiscoveryMessage(string method, string identity, En
|
|||
*
|
||||
* @param identity The authorative identity of the component.
|
||||
* @param message The discovery message.
|
||||
* @param trusted Whether the message comes from a trusted source (i.e. a broker).
|
||||
*/
|
||||
void DiscoveryComponent::ProcessDiscoveryMessage(string identity, DiscoveryMessage message)
|
||||
void DiscoveryComponent::ProcessDiscoveryMessage(string identity, DiscoveryMessage message, bool trusted)
|
||||
{
|
||||
/* ignore discovery messages that are about ourselves */
|
||||
if (identity == GetEndpointManager()->GetIdentity())
|
||||
|
@ -416,13 +437,19 @@ void DiscoveryComponent::ProcessDiscoveryMessage(string identity, DiscoveryMessa
|
|||
message.GetNode(&info->Node);
|
||||
message.GetService(&info->Service);
|
||||
|
||||
ConfigHive::Ptr configHive = GetApplication()->GetConfigHive();
|
||||
ConfigCollection::Ptr endpointCollection = configHive->GetCollection("endpoint");
|
||||
|
||||
ConfigObject::Ptr endpointConfig = endpointCollection->GetObject(identity);
|
||||
Dictionary::Ptr roles;
|
||||
if (endpointConfig)
|
||||
endpointConfig->GetPropertyDictionary("roles", &roles);
|
||||
|
||||
Message provides;
|
||||
if (message.GetProvides(&provides)) {
|
||||
DictionaryIterator i;
|
||||
for (i = provides.GetDictionary()->Begin(); i != provides.GetDictionary()->End(); i++) {
|
||||
if (IsBroker()) {
|
||||
/* TODO: Add authorisation checks here */
|
||||
}
|
||||
if (trusted || HasMessagePermission(roles, "publish", i->second))
|
||||
info->PublishedMethods.insert(i->second);
|
||||
}
|
||||
}
|
||||
|
@ -431,9 +458,7 @@ void DiscoveryComponent::ProcessDiscoveryMessage(string identity, DiscoveryMessa
|
|||
if (message.GetSubscribes(&subscribes)) {
|
||||
DictionaryIterator i;
|
||||
for (i = subscribes.GetDictionary()->Begin(); i != subscribes.GetDictionary()->End(); i++) {
|
||||
if (IsBroker()) {
|
||||
/* TODO: Add authorisation checks here */
|
||||
}
|
||||
if (trusted || HasMessagePermission(roles, "subscribe", i->second))
|
||||
info->SubscribedMethods.insert(i->second);
|
||||
}
|
||||
}
|
||||
|
@ -472,7 +497,7 @@ int DiscoveryComponent::NewComponentMessageHandler(const NewRequestEventArgs& nr
|
|||
if (!message.GetIdentity(&identity))
|
||||
return 0;
|
||||
|
||||
ProcessDiscoveryMessage(identity, message);
|
||||
ProcessDiscoveryMessage(identity, message, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -486,45 +511,36 @@ int DiscoveryComponent::NewComponentMessageHandler(const NewRequestEventArgs& nr
|
|||
*/
|
||||
int DiscoveryComponent::RegisterComponentMessageHandler(const NewRequestEventArgs& nrea)
|
||||
{
|
||||
/* ignore discovery::RegisterComponent messages when we're not a broker */
|
||||
if (!IsBroker())
|
||||
return 0;
|
||||
|
||||
DiscoveryMessage message;
|
||||
nrea.Request.GetParams(&message);
|
||||
ProcessDiscoveryMessage(nrea.Sender->GetIdentity(), message);
|
||||
ProcessDiscoveryMessage(nrea.Sender->GetIdentity(), message, false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* BrokerConfigHandler
|
||||
* EndpointConfigHandler
|
||||
*
|
||||
* Processes "broker" config objects.
|
||||
* Processes "endpoint" config objects.
|
||||
*
|
||||
* @param ea Event arguments for the new config object.
|
||||
* @returns 0
|
||||
*/
|
||||
int DiscoveryComponent::BrokerConfigHandler(const EventArgs& ea)
|
||||
int DiscoveryComponent::EndpointConfigHandler(const EventArgs& ea)
|
||||
{
|
||||
ConfigObject::Ptr object = static_pointer_cast<ConfigObject>(ea.Source);
|
||||
|
||||
EndpointManager::Ptr endpointManager = GetEndpointManager();
|
||||
|
||||
/* Check if we're already connected to this broker. */
|
||||
/* Check if we're already connected to this endpoint. */
|
||||
if (endpointManager->GetEndpointByIdentity(object->GetName()))
|
||||
return 0;
|
||||
|
||||
string node;
|
||||
if (!object->GetPropertyString("node", &node))
|
||||
throw InvalidArgumentException("'node' property required for 'broker' config object.");
|
||||
|
||||
string service;
|
||||
if (!object->GetPropertyString("service", &service))
|
||||
throw InvalidArgumentException("'service' property required for 'broker' config object.");
|
||||
|
||||
/* reconnect to this broker */
|
||||
string node, service;
|
||||
if (object->GetPropertyString("node", &node) && object->GetPropertyString("service", &service)) {
|
||||
/* reconnect to this endpoint */
|
||||
endpointManager->AddConnection(node, service);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -545,9 +561,9 @@ int DiscoveryComponent::DiscoveryTimerHandler(const TimerEventArgs& tea)
|
|||
time_t now;
|
||||
time(&now);
|
||||
|
||||
/* check whether we have to reconnect to one of our upstream brokers */
|
||||
ConfigCollection::Ptr brokerCollection = GetApplication()->GetConfigHive()->GetCollection("broker");
|
||||
brokerCollection->ForEachObject(bind(&DiscoveryComponent::BrokerConfigHandler, this, _1));
|
||||
/* check whether we have to reconnect to one of our upstream endpoints */
|
||||
ConfigCollection::Ptr endpointCollection = GetApplication()->GetConfigHive()->GetCollection("endpoint");
|
||||
endpointCollection->ForEachObject(bind(&DiscoveryComponent::EndpointConfigHandler, this, _1));
|
||||
|
||||
map<string, ComponentDiscoveryInfo::Ptr>::iterator i;
|
||||
for (i = m_Components.begin(); i != m_Components.end(); ) {
|
||||
|
|
|
@ -36,7 +36,7 @@ private:
|
|||
int WelcomeMessageHandler(const NewRequestEventArgs& nrea);
|
||||
|
||||
void SendDiscoveryMessage(string method, string identity, Endpoint::Ptr recipient);
|
||||
void ProcessDiscoveryMessage(string identity, DiscoveryMessage message);
|
||||
void ProcessDiscoveryMessage(string identity, DiscoveryMessage message, bool trusted);
|
||||
|
||||
bool GetComponentDiscoveryInfo(string component, ComponentDiscoveryInfo::Ptr *info) const;
|
||||
|
||||
|
@ -51,7 +51,9 @@ private:
|
|||
|
||||
void FinishDiscoverySetup(Endpoint::Ptr endpoint);
|
||||
|
||||
int BrokerConfigHandler(const EventArgs& ea);
|
||||
int EndpointConfigHandler(const EventArgs& ea);
|
||||
|
||||
bool HasMessagePermission(Dictionary::Ptr roles, string messageType, string message);
|
||||
|
||||
static const int RegistrationTTL = 300;
|
||||
|
||||
|
|
|
@ -56,5 +56,5 @@ components/discovery/Makefile
|
|||
icinga/Makefile
|
||||
icinga-app/Makefile
|
||||
jsonrpc/Makefile
|
||||
|
||||
mmatch/Makefile
|
||||
])
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"privkey": "icinga-c1.key",
|
||||
"pubkey": "icinga-c1.crt",
|
||||
"cakey": "ca.crt",
|
||||
"node": "10.0.10.3",
|
||||
"node": "10.0.10.14",
|
||||
"service": "7777"
|
||||
}
|
||||
},
|
||||
|
@ -16,5 +16,8 @@
|
|||
},
|
||||
"host": {
|
||||
"localhost": { "node": "127.0.0.1" }
|
||||
},
|
||||
"include": {
|
||||
"permissions.conf": { "test": [ "hello", "world" ] }
|
||||
}
|
||||
}
|
|
@ -14,7 +14,21 @@
|
|||
"demo": { "replicate": "0" },
|
||||
"discovery": { "replicate": "0", "broker": "0" }
|
||||
},
|
||||
"endpoint": {
|
||||
"icinga-c1": {
|
||||
"replicate": "0",
|
||||
"node": "10.0.10.14",
|
||||
"service": "7777",
|
||||
"roles": [ "broker", "demo" ]
|
||||
}
|
||||
},
|
||||
"role": {
|
||||
"broker": {
|
||||
"icinga-c1": { "replicate": "0", "node": "10.0.10.3", "service": "7777" }
|
||||
"publish": [ "discovery::NewComponent" ]
|
||||
},
|
||||
"demo": {
|
||||
"publish": [ "demo::*" ],
|
||||
"subscribe": [ "demo::*" ]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,7 +14,21 @@
|
|||
"demo": { "replicate": "0" },
|
||||
"discovery": { "replicate": "0", "broker": "0" }
|
||||
},
|
||||
"endpoint": {
|
||||
"icinga-c1": {
|
||||
"replicate": "0",
|
||||
"node": "10.0.10.14",
|
||||
"service": "7777",
|
||||
"roles": [ "broker", "demo" ]
|
||||
}
|
||||
},
|
||||
"role": {
|
||||
"broker": {
|
||||
"icinga-c1": { "replicate": "0", "node": "10.0.10.3", "service": "7777" }
|
||||
"publish": [ "discovery::NewComponent" ]
|
||||
},
|
||||
"demo": {
|
||||
"publish": [ "demo::*" ],
|
||||
"subscribe": [ "demo::*" ]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,9 @@
|
|||
Microsoft Visual Studio Solution File, Format Version 11.00
|
||||
# Visual C++ Express 2010
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "base", "base\base.vcxproj", "{9C92DA90-FD53-43A9-A244-90F2E8AF9677}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{19CBCE06-3F5C-479A-BD75-E2AB6215D345} = {19CBCE06-3F5C-479A-BD75-E2AB6215D345}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jsonrpc", "jsonrpc\jsonrpc.vcxproj", "{8DD52FAC-ECEE-48C2-B266-E7C47ED485F8}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
|
@ -46,6 +49,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "discovery", "components\dis
|
|||
{C1FC77E1-04A4-481B-A78B-2F7AF489C2F8} = {C1FC77E1-04A4-481B-A78B-2F7AF489C2F8}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mmatch", "mmatch\mmatch.vcxproj", "{19CBCE06-3F5C-479A-BD75-E2AB6215D345}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Win32 = Debug|Win32
|
||||
|
@ -88,6 +93,10 @@ Global
|
|||
{EAD41628-BB96-4F99-9070-8A9676801295}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{EAD41628-BB96-4F99-9070-8A9676801295}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{EAD41628-BB96-4F99-9070-8A9676801295}.Release|Win32.Build.0 = Release|Win32
|
||||
{19CBCE06-3F5C-479A-BD75-E2AB6215D345}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{19CBCE06-3F5C-479A-BD75-E2AB6215D345}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{19CBCE06-3F5C-479A-BD75-E2AB6215D345}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{19CBCE06-3F5C-479A-BD75-E2AB6215D345}.Release|Win32.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
@ -30,14 +30,14 @@ int IcingaApplication::Main(const vector<string>& args)
|
|||
/* register handler for 'icinga' config objects */
|
||||
ConfigCollection::Ptr icingaCollection = GetConfigHive()->GetCollection("icinga");
|
||||
function<int (const EventArgs&)> NewIcingaConfigHandler = bind_weak(&IcingaApplication::NewIcingaConfigHandler, shared_from_this());
|
||||
icingaCollection->OnObjectCreated += NewIcingaConfigHandler;
|
||||
icingaCollection->OnObjectCommitted += NewIcingaConfigHandler;
|
||||
icingaCollection->ForEachObject(NewIcingaConfigHandler);
|
||||
icingaCollection->OnObjectRemoved += bind_weak(&IcingaApplication::DeletedIcingaConfigHandler, shared_from_this());
|
||||
|
||||
/* register handler for 'component' config objects */
|
||||
ConfigCollection::Ptr componentCollection = GetConfigHive()->GetCollection("component");
|
||||
function<int (const EventArgs&)> NewComponentHandler = bind_weak(&IcingaApplication::NewComponentHandler, shared_from_this());
|
||||
componentCollection->OnObjectCreated += NewComponentHandler;
|
||||
componentCollection->OnObjectCommitted += NewComponentHandler;
|
||||
componentCollection->ForEachObject(NewComponentHandler);
|
||||
componentCollection->OnObjectRemoved += bind_weak(&IcingaApplication::DeletedComponentHandler, shared_from_this());
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
## Process this file with automake to produce Makefile.in
|
||||
|
||||
|
||||
noinst_LTLIBRARIES = \
|
||||
libmmatch.la
|
||||
|
||||
libmmatch_la_SOURCES = \
|
||||
mmatch.c \
|
||||
mmatch.h
|
|
@ -0,0 +1,305 @@
|
|||
/*
|
||||
* IRC - Internet Relay Chat, common/match.c
|
||||
* Copyright (C) 1990 Jarkko Oikarinen
|
||||
*
|
||||
* 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 1, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* $Id: Match.cpp,v 1.2 2005/08/15 10:08:50 shroud23 Exp $
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include "mmatch.h"
|
||||
|
||||
#define ToLower tolower
|
||||
|
||||
/*
|
||||
* mmatch()
|
||||
*
|
||||
* Written by Run (carlo@runaway.xs4all.nl), 25-10-96
|
||||
*
|
||||
*
|
||||
* From: Carlo Wood <carlo@runaway.xs4all.nl>
|
||||
* Message-Id: <199609021026.MAA02393@runaway.xs4all.nl>
|
||||
* Subject: [C-Com] Analysis for `mmatch' (was: gline4 problem)
|
||||
* To: coder-com@mail.undernet.org (coder committee)
|
||||
* Date: Mon, 2 Sep 1996 12:26:01 +0200 (MET DST)
|
||||
*
|
||||
* We need a new function `mmatch(const char *old_mask, const char *new_mask)'
|
||||
* which returns `true' likewise the current `match' (start with copying it),
|
||||
* but which treats '*' and '?' in `new_mask' differently (not "\*" and "\?" !)
|
||||
* as follows: a '*' in `new_mask' does not match a '?' in `old_mask' and
|
||||
* a '?' in `new_mask' does not match a '\?' in `old_mask'.
|
||||
* And ofcourse... a '*' in `new_mask' does not match a '\*' in `old_mask'...
|
||||
* And last but not least, '\?' and '\*' in `new_mask' now become one character.
|
||||
*/
|
||||
|
||||
int mmatch(const char *old_mask, const char *new_mask)
|
||||
{
|
||||
const char *m = old_mask;
|
||||
const char *n = new_mask;
|
||||
const char *ma = m;
|
||||
const char *na = n;
|
||||
int wild = 0;
|
||||
int mq = 0, nq = 0;
|
||||
|
||||
while (1)
|
||||
{
|
||||
if (*m == '*')
|
||||
{
|
||||
while (*m == '*')
|
||||
m++;
|
||||
wild = 1;
|
||||
ma = m;
|
||||
na = n;
|
||||
}
|
||||
|
||||
if (!*m)
|
||||
{
|
||||
if (!*n)
|
||||
return 0;
|
||||
for (m--; (m > old_mask) && (*m == '?'); m--)
|
||||
;
|
||||
if ((*m == '*') && (m > old_mask) && (m[-1] != '\\'))
|
||||
return 0;
|
||||
if (!wild)
|
||||
return 1;
|
||||
m = ma;
|
||||
|
||||
/* Added to `mmatch' : Because '\?' and '\*' now is one character: */
|
||||
if ((*na == '\\') && ((na[1] == '*') || (na[1] == '?')))
|
||||
++na;
|
||||
|
||||
n = ++na;
|
||||
}
|
||||
else if (!*n)
|
||||
{
|
||||
while (*m == '*')
|
||||
m++;
|
||||
return (*m != 0);
|
||||
}
|
||||
if ((*m == '\\') && ((m[1] == '*') || (m[1] == '?')))
|
||||
{
|
||||
m++;
|
||||
mq = 1;
|
||||
}
|
||||
else
|
||||
mq = 0;
|
||||
|
||||
/* Added to `mmatch' : Because '\?' and '\*' now is one character: */
|
||||
if ((*n == '\\') && ((n[1] == '*') || (n[1] == '?')))
|
||||
{
|
||||
n++;
|
||||
nq = 1;
|
||||
}
|
||||
else
|
||||
nq = 0;
|
||||
|
||||
/*
|
||||
* This `if' has been changed compared to match() to do the following:
|
||||
* Match when:
|
||||
* old (m) new (n) boolean expression
|
||||
* * any (*m == '*' && !mq) ||
|
||||
* ? any except '*' (*m == '?' && !mq && (*n != '*' || nq)) ||
|
||||
* any except * or ? same as m (!((*m == '*' || *m == '?') && !mq) &&
|
||||
* ToLower(*m) == ToLower(*n) &&
|
||||
* !((mq && !nq) || (!mq && nq)))
|
||||
*
|
||||
* Here `any' also includes \* and \? !
|
||||
*
|
||||
* After reworking the boolean expressions, we get:
|
||||
* (Optimized to use boolean shortcircuits, with most frequently occuring
|
||||
* cases upfront (which took 2 hours!)).
|
||||
*/
|
||||
if ((*m == '*' && !mq) ||
|
||||
((!mq || nq) && ToLower(*m) == ToLower(*n)) ||
|
||||
(*m == '?' && !mq && (*n != '*' || nq)))
|
||||
{
|
||||
if (*m)
|
||||
m++;
|
||||
if (*n)
|
||||
n++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!wild)
|
||||
return 1;
|
||||
m = ma;
|
||||
|
||||
/* Added to `mmatch' : Because '\?' and '\*' now is one character: */
|
||||
if ((*na == '\\') && ((na[1] == '*') || (na[1] == '?')))
|
||||
++na;
|
||||
|
||||
n = ++na;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Compare if a given string (name) matches the given
|
||||
* mask (which can contain wild cards: '*' - match any
|
||||
* number of chars, '?' - match any single character.
|
||||
*
|
||||
* return 0, if match
|
||||
* 1, if no match
|
||||
*/
|
||||
|
||||
/*
|
||||
* match
|
||||
*
|
||||
* Rewritten by Andrea Cocito (Nemesi), November 1998.
|
||||
*
|
||||
*/
|
||||
|
||||
/****************** Nemesi's match() ***************/
|
||||
|
||||
int match(const char *mask, const char *string)
|
||||
{
|
||||
const char *m = mask, *s = string;
|
||||
char ch;
|
||||
const char *bm, *bs; /* Will be reg anyway on a decent CPU/compiler */
|
||||
|
||||
/* Process the "head" of the mask, if any */
|
||||
while ((ch = *m++) && (ch != '*'))
|
||||
switch (ch)
|
||||
{
|
||||
case '\\':
|
||||
if (*m == '?' || *m == '*')
|
||||
ch = *m++;
|
||||
default:
|
||||
if (ToLower(*s) != ToLower(ch))
|
||||
return 1;
|
||||
case '?':
|
||||
if (!*s++)
|
||||
return 1;
|
||||
};
|
||||
if (!ch)
|
||||
return *s;
|
||||
|
||||
/* We got a star: quickly find if/where we match the next char */
|
||||
got_star:
|
||||
bm = m; /* Next try rollback here */
|
||||
while ((ch = *m++))
|
||||
switch (ch)
|
||||
{
|
||||
case '?':
|
||||
if (!*s++)
|
||||
return 1;
|
||||
case '*':
|
||||
bm = m;
|
||||
continue; /* while */
|
||||
case '\\':
|
||||
if (*m == '?' || *m == '*')
|
||||
ch = *m++;
|
||||
default:
|
||||
goto break_while; /* C is structured ? */
|
||||
};
|
||||
break_while:
|
||||
if (!ch)
|
||||
return 0; /* mask ends with '*', we got it */
|
||||
ch = ToLower(ch);
|
||||
if (!*s) /* String is already empty, don't continue */
|
||||
return 1; /* This fixes the #quakenet access denied bug */
|
||||
while (ToLower(*s++) != ch)
|
||||
if (!*s)
|
||||
return 1;
|
||||
bs = s; /* Next try start from here */
|
||||
|
||||
/* Check the rest of the "chunk" */
|
||||
while ((ch = *m++))
|
||||
{
|
||||
switch (ch)
|
||||
{
|
||||
case '*':
|
||||
goto got_star;
|
||||
case '\\':
|
||||
if (*m == '?' || *m == '*')
|
||||
ch = *m++;
|
||||
default:
|
||||
if (ToLower(*s) != ToLower(ch))
|
||||
{
|
||||
/* If we've run out of string, give up */
|
||||
if (!*bs)
|
||||
return 1;
|
||||
m = bm;
|
||||
s = bs;
|
||||
goto got_star;
|
||||
};
|
||||
case '?':
|
||||
if (!*s++)
|
||||
return 1;
|
||||
};
|
||||
};
|
||||
if (*s)
|
||||
{
|
||||
m = bm;
|
||||
s = bs;
|
||||
goto got_star;
|
||||
};
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* collapse()
|
||||
* Collapse a pattern string into minimal components.
|
||||
* This particular version is "in place", so that it changes the pattern
|
||||
* which is to be reduced to a "minimal" size.
|
||||
*
|
||||
* (C) Carlo Wood - 6 Oct 1998
|
||||
* Speedup rewrite by Andrea Cocito, December 1998.
|
||||
* Note that this new optimized alghoritm can *only* work in place.
|
||||
*/
|
||||
|
||||
char *collapse(char *mask)
|
||||
{
|
||||
int star = 0;
|
||||
char *m = mask;
|
||||
char *b;
|
||||
|
||||
if (m)
|
||||
{
|
||||
do
|
||||
{
|
||||
if ((*m == '*') && ((m[1] == '*') || (m[1] == '?')))
|
||||
{
|
||||
b = m;
|
||||
do
|
||||
{
|
||||
if (*m == '*')
|
||||
star = 1;
|
||||
else
|
||||
{
|
||||
if (star && (*m != '?'))
|
||||
{
|
||||
*b++ = '*';
|
||||
star = 0;
|
||||
};
|
||||
*b++ = *m;
|
||||
if ((*m == '\\') && ((m[1] == '*') || (m[1] == '?')))
|
||||
*b++ = *++m;
|
||||
};
|
||||
}
|
||||
while (*m++);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((*m == '\\') && ((m[1] == '*') || (m[1] == '?')))
|
||||
m++;
|
||||
};
|
||||
}
|
||||
while (*m++);
|
||||
};
|
||||
return mask;
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
#ifndef MMATCH_H
|
||||
#define MMATCH_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
int mmatch(const char *old_mask, const char *new_mask);
|
||||
int match(const char *ma, const char *na);
|
||||
char *collapse(char *pattern);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MMATCH_H */
|
|
@ -0,0 +1,80 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="mmatch.c" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="mmatch.h" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{19CBCE06-3F5C-479A-BD75-E2AB6215D345}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>mmatch</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup />
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
Loading…
Reference in New Issue