| // Copyright 2022 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef SECAGENTD_PLUGINS_H_ |
| #define SECAGENTD_PLUGINS_H_ |
| |
| #include <cstdint> |
| #include <memory> |
| #include <string> |
| #include <unordered_map> |
| #include <utility> |
| #include <variant> |
| #include <vector> |
| |
| #include "absl/status/status.h" |
| #include "absl/strings/str_format.h" |
| #include "attestation/proto_bindings/interface.pb.h" |
| #include "attestation-client/attestation/dbus-proxies.h" |
| #include "base/check.h" |
| #include "base/files/file_path.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/timer/timer.h" |
| #include "cryptohome/proto_bindings/auth_factor.pb.h" |
| #include "cryptohome/proto_bindings/UserDataAuth.pb.h" |
| #include "missive/proto/record_constants.pb.h" |
| #include "secagentd/batch_sender.h" |
| #include "secagentd/bpf/bpf_types.h" |
| #include "secagentd/bpf_skeleton_wrappers.h" |
| #include "secagentd/common.h" |
| #include "secagentd/device_user.h" |
| #include "secagentd/message_sender.h" |
| #include "secagentd/metrics_sender.h" |
| #include "secagentd/policies_features_broker.h" |
| #include "secagentd/process_cache.h" |
| #include "secagentd/proto/security_xdr_events.pb.h" |
| #include "tpm_manager/proto_bindings/tpm_manager.pb.h" |
| #include "tpm_manager-client/tpm_manager/dbus-proxies.h" |
| #include "user_data_auth/dbus-proxies.h" |
| |
| namespace secagentd { |
| |
| using AuthFactorType = cros_xdr::reporting::Authentication_AuthenticationType; |
| |
| // If the auth factor is not yet filled wait to see |
| // if dbus signal is late. |
| static constexpr base::TimeDelta kWaitForAuthFactorS = base::Seconds(1); |
| static constexpr uint64_t kMaxDelayForLockscreenAttemptsS = 3; |
| |
| namespace testing { |
| class AgentPluginTestFixture; |
| class ProcessPluginTestFixture; |
| class NetworkPluginTestFixture; |
| class AuthenticationPluginTestFixture; |
| } // namespace testing |
| |
| class PluginInterface { |
| public: |
| // Activate the plugin, must be idempotent. |
| virtual absl::Status Activate() = 0; |
| // Deactivate the plugin, must be idempotent. |
| virtual absl::Status Deactivate() = 0; |
| // Is the plugin currently activated? |
| virtual bool IsActive() const = 0; |
| virtual std::string GetName() const = 0; |
| // Flushes the batch sender if it exists. |
| virtual void Flush() = 0; |
| virtual ~PluginInterface() = default; |
| }; |
| template <typename HashT, |
| typename XdrT, |
| typename XdrAtomicVariantT, |
| Types::BpfSkeleton SkelType, |
| reporting::Destination destination> |
| struct PluginConfig { |
| using HashType = HashT; |
| using XdrType = XdrT; |
| using XdrAtomicType = XdrAtomicVariantT; |
| static constexpr Types::BpfSkeleton skeleton_type{SkelType}; |
| static constexpr reporting::Destination reporting_destination{destination}; |
| }; |
| |
| class BpfSkeletonHelperInterface { |
| public: |
| virtual absl::Status LoadAndAttach(struct BpfCallbacks callbacks) = 0; |
| virtual absl::Status DetachAndUnload() = 0; |
| virtual bool IsAttached() const = 0; |
| virtual ~BpfSkeletonHelperInterface() = default; |
| }; |
| |
| template <Types::BpfSkeleton BpfSkeletonType> |
| class BpfSkeletonHelper : public BpfSkeletonHelperInterface { |
| public: |
| BpfSkeletonHelper( |
| scoped_refptr<BpfSkeletonFactoryInterface> bpf_skeleton_factory, |
| uint32_t batch_interval_s) |
| : batch_interval_s_(batch_interval_s), weak_ptr_factory_(this) { |
| CHECK(bpf_skeleton_factory); |
| factory_ = std::move(bpf_skeleton_factory); |
| } |
| |
| void BpfSkeletonConsumeEvent() const { skeleton_wrapper_->ConsumeEvent(); } |
| |
| absl::Status LoadAndAttach(struct BpfCallbacks callbacks) override { |
| if (skeleton_wrapper_) { |
| return absl::OkStatus(); |
| } |
| // If ring_buffer_read_ready_callback is set by the plugin, then don't |
| // override. If not set, use this default callback |
| if (!callbacks.ring_buffer_read_ready_callback) { |
| callbacks.ring_buffer_read_ready_callback = |
| base::BindRepeating(&BpfSkeletonHelper::BpfSkeletonConsumeEvent, |
| weak_ptr_factory_.GetWeakPtr()); |
| } |
| skeleton_wrapper_ = factory_->Create(BpfSkeletonType, std::move(callbacks), |
| batch_interval_s_); |
| if (skeleton_wrapper_ == nullptr) { |
| return absl::InternalError( |
| absl::StrFormat("%s BPF program loading error.", BpfSkeletonType)); |
| } |
| |
| return absl::OkStatus(); |
| } |
| |
| absl::Status DetachAndUnload() override { |
| // Unset the skeleton_wrapper_ unloads and cleans up the BPFs. |
| skeleton_wrapper_ = nullptr; |
| return absl::OkStatus(); |
| } |
| |
| bool IsAttached() const override { return skeleton_wrapper_ != nullptr; } |
| |
| private: |
| // friend testing::BpfSkeletonHelperTestFixture; |
| |
| uint32_t batch_interval_s_; |
| base::WeakPtrFactory<BpfSkeletonHelper> weak_ptr_factory_; |
| scoped_refptr<BpfSkeletonFactoryInterface> factory_; |
| std::unique_ptr<BpfSkeletonInterface> skeleton_wrapper_; |
| }; |
| |
| class NetworkPlugin : public PluginInterface { |
| public: |
| NetworkPlugin( |
| scoped_refptr<BpfSkeletonFactoryInterface> bpf_skeleton_factory, |
| scoped_refptr<MessageSenderInterface> message_sender, |
| scoped_refptr<ProcessCacheInterface> process_cache, |
| scoped_refptr<PoliciesFeaturesBrokerInterface> policies_features_broker, |
| scoped_refptr<DeviceUserInterface> device_user, |
| uint32_t batch_interval_s); |
| |
| // Load, verify and attach the process BPF applications. |
| absl::Status Activate() override; |
| absl::Status Deactivate() override; |
| bool IsActive() const override; |
| std::string GetName() const override; |
| void Flush() override { |
| if (batch_sender_) { |
| batch_sender_->Flush(); |
| } |
| } |
| |
| // Handles an individual incoming Process BPF event. |
| void HandleRingBufferEvent(const bpf::cros_event& bpf_event); |
| |
| /* Given a set of addresses (in network byte order) |
| * ,a set of ports and a protocol ID compute the |
| * community flow ID hash. |
| */ |
| static std::string ComputeCommunityHashv1( |
| const absl::Span<const uint8_t>& saddr_in, |
| const absl::Span<const uint8_t>& daddr_in, |
| uint16_t sport, |
| uint16_t dport, |
| uint8_t proto, |
| uint16_t seed = 0); |
| |
| private: |
| friend testing::NetworkPluginTestFixture; |
| |
| using BatchSenderType = |
| BatchSenderInterface<std::string, |
| cros_xdr::reporting::XdrNetworkEvent, |
| cros_xdr::reporting::NetworkEventAtomicVariant>; |
| // Inject the given (mock) BatchSender object for unit testing. |
| void SetBatchSenderForTesting(std::unique_ptr<BatchSenderType> given) { |
| batch_sender_ = std::move(given); |
| } |
| // Pushes the given process event into the next outgoing batch. |
| void EnqueueBatchedEvent( |
| std::unique_ptr<cros_xdr::reporting::NetworkEventAtomicVariant> |
| atomic_event); |
| |
| void OnDeviceUserRetrieved( |
| std::unique_ptr<cros_xdr::reporting::NetworkEventAtomicVariant> |
| atomic_event, |
| const std::string& device_user); |
| |
| template <typename ProtoT> |
| void FillProcessTree(ProtoT proto, |
| const bpf::cros_process_start& process_start, |
| bool has_full_process_start) const; |
| |
| std::unique_ptr<cros_xdr::reporting::NetworkSocketListenEvent> |
| MakeListenEvent( |
| const secagentd::bpf::cros_network_socket_listen& listen_event) const; |
| std::unique_ptr<cros_xdr::reporting::NetworkFlowEvent> MakeFlowEvent( |
| const secagentd::bpf::cros_synthetic_network_flow& flow_event) const; |
| std::unique_ptr< |
| base::LRUCache<bpf::cros_flow_map_key, bpf::cros_flow_map_value>> |
| prev_tx_rx_totals_; // declaring this as a value member strangely seems |
| // to make it const. |
| |
| base::WeakPtrFactory<NetworkPlugin> weak_ptr_factory_; |
| scoped_refptr<ProcessCacheInterface> process_cache_; |
| scoped_refptr<PoliciesFeaturesBrokerInterface> policies_features_broker_; |
| scoped_refptr<DeviceUserInterface> device_user_; |
| std::unique_ptr<BatchSenderType> batch_sender_; |
| std::unique_ptr<BpfSkeletonHelperInterface> bpf_skeleton_helper_; |
| }; |
| |
| class ProcessPlugin : public PluginInterface { |
| public: |
| ProcessPlugin( |
| scoped_refptr<BpfSkeletonFactoryInterface> bpf_skeleton_factory, |
| scoped_refptr<MessageSenderInterface> message_sender, |
| scoped_refptr<ProcessCacheInterface> process_cache, |
| scoped_refptr<PoliciesFeaturesBrokerInterface> policies_features_broker, |
| scoped_refptr<DeviceUserInterface> device_user, |
| uint32_t batch_interval_s); |
| // Load, verify and attach the process BPF applications. |
| absl::Status Activate() override; |
| absl::Status Deactivate() override; |
| bool IsActive() const override; |
| std::string GetName() const override; |
| void Flush() override { |
| if (batch_sender_) { |
| batch_sender_->Flush(); |
| } |
| } |
| |
| // Handles an individual incoming Process BPF event. |
| void HandleRingBufferEvent(const bpf::cros_event& bpf_event); |
| // Requests immediate event consumption from BPF. |
| void HandleBpfRingBufferReadReady() const; |
| |
| private: |
| friend class testing::ProcessPluginTestFixture; |
| |
| using BatchSenderType = |
| BatchSenderInterface<std::string, |
| cros_xdr::reporting::XdrProcessEvent, |
| cros_xdr::reporting::ProcessEventAtomicVariant>; |
| |
| // Pushes the given process event into the next outgoing batch. |
| void EnqueueBatchedEvent( |
| std::unique_ptr<cros_xdr::reporting::ProcessEventAtomicVariant> |
| atomic_event); |
| // Converts the BPF process start event into a XDR process exec |
| // protobuf. |
| std::unique_ptr<cros_xdr::reporting::ProcessExecEvent> MakeExecEvent( |
| const secagentd::bpf::cros_process_start& process_start); |
| std::unique_ptr<cros_xdr::reporting::ProcessTerminateEvent> |
| // Converts the BPF process exit event into a XDR process terminate |
| // protobuf. |
| MakeTerminateEvent(const secagentd::bpf::cros_process_exit& process_exit); |
| // Callback function that is ran when the device user is ready. |
| void OnDeviceUserRetrieved( |
| std::unique_ptr<cros_xdr::reporting::ProcessEventAtomicVariant> |
| atomic_event, |
| const std::string& device_user); |
| // Inject the given (mock) BatchSender object for unit testing. |
| void SetBatchSenderForTesting(std::unique_ptr<BatchSenderType> given) { |
| batch_sender_ = std::move(given); |
| } |
| |
| base::WeakPtrFactory<ProcessPlugin> weak_ptr_factory_; |
| scoped_refptr<ProcessCacheInterface> process_cache_; |
| scoped_refptr<PoliciesFeaturesBrokerInterface> policies_features_broker_; |
| scoped_refptr<DeviceUserInterface> device_user_; |
| std::unique_ptr<BatchSenderType> batch_sender_; |
| std::unique_ptr<BpfSkeletonHelperInterface> bpf_skeleton_helper_; |
| }; |
| |
| class AuthenticationPlugin : public PluginInterface { |
| public: |
| AuthenticationPlugin( |
| scoped_refptr<MessageSenderInterface> message_sender, |
| scoped_refptr<PoliciesFeaturesBrokerInterface> policies_features_broker, |
| scoped_refptr<DeviceUserInterface> device_user, |
| uint32_t batch_interval_s); |
| // Starts reporting user authentication events. |
| absl::Status Activate() override; |
| absl::Status Deactivate() override; |
| bool IsActive() const override; |
| std::string GetName() const override; |
| void Flush() override { |
| if (batch_sender_) { |
| batch_sender_->Flush(); |
| } |
| } |
| |
| private: |
| friend class testing::AuthenticationPluginTestFixture; |
| |
| using BatchSenderType = |
| BatchSenderInterface<std::monostate, |
| cros_xdr::reporting::XdrUserEvent, |
| cros_xdr::reporting::UserEventAtomicVariant>; |
| |
| // Creates and sends a screen Lock event. |
| void OnScreenLock(); |
| // Creates and sends a screen Unlock event. |
| void OnScreenUnlock(); |
| // Logs error if registration fails. |
| void OnRegistrationResult(const std::string& interface, |
| const std::string& signal, |
| bool success); |
| // Creates and sends a login/out event based on the state. |
| void OnSessionStateChange(const std::string& state); |
| // Used to fill the auth factor for login and unlock. |
| // Also fills in the device user. |
| void OnAuthenticateAuthFactorCompleted( |
| const user_data_auth::AuthenticateAuthFactorCompleted& result); |
| // Fills the proto's auth factor if auth_factor_ is known. |
| // Returns if auth factor was filled. |
| bool FillAuthFactor(cros_xdr::reporting::Authentication* proto); |
| // If there is an entry event but auth factor is not filled, wait and |
| // then check again for auth factor. If still not found send message anyway. |
| void DelayedCheckForAuthSignal( |
| std::unique_ptr<cros_xdr::reporting::UserEventAtomicVariant> xdr_proto, |
| cros_xdr::reporting::Authentication* authentication); |
| // Callback function that is ran when the device user is ready. |
| void OnDeviceUserRetrieved( |
| std::unique_ptr<cros_xdr::reporting::UserEventAtomicVariant> atomic_event, |
| const std::string& device_user); |
| // When the first session start happens in the case of a secagentd restart |
| // check if a user is already signed in and if so send a login event. |
| void OnFirstSessionStart(const std::string& device_user); |
| // Inject the given (mock) BatchSender object for unit testing. |
| void SetBatchSenderForTesting(std::unique_ptr<BatchSenderType> given) { |
| batch_sender_ = std::move(given); |
| } |
| |
| base::WeakPtrFactory<AuthenticationPlugin> weak_ptr_factory_; |
| scoped_refptr<PoliciesFeaturesBrokerInterface> policies_features_broker_; |
| scoped_refptr<DeviceUserInterface> device_user_; |
| std::unique_ptr<BatchSenderType> batch_sender_; |
| std::unique_ptr<org::chromium::UserDataAuthInterfaceProxyInterface> |
| cryptohome_proxy_; |
| AuthFactorType auth_factor_type_ = |
| AuthFactorType::Authentication_AuthenticationType_AUTH_TYPE_UNKNOWN; |
| const std::unordered_map<user_data_auth::AuthFactorType, AuthFactorType> |
| auth_factor_map_ = { |
| {user_data_auth::AUTH_FACTOR_TYPE_UNSPECIFIED, |
| AuthFactorType::Authentication_AuthenticationType_AUTH_TYPE_UNKNOWN}, |
| {user_data_auth::AUTH_FACTOR_TYPE_PASSWORD, |
| AuthFactorType::Authentication_AuthenticationType_AUTH_PASSWORD}, |
| {user_data_auth::AUTH_FACTOR_TYPE_PIN, |
| AuthFactorType::Authentication_AuthenticationType_AUTH_PIN}, |
| {user_data_auth::AUTH_FACTOR_TYPE_CRYPTOHOME_RECOVERY, |
| AuthFactorType:: |
| Authentication_AuthenticationType_AUTH_ONLINE_RECOVERY}, |
| {user_data_auth::AUTH_FACTOR_TYPE_KIOSK, |
| AuthFactorType::Authentication_AuthenticationType_AUTH_KIOSK}, |
| {user_data_auth::AUTH_FACTOR_TYPE_SMART_CARD, |
| AuthFactorType::Authentication_AuthenticationType_AUTH_SMART_CARD}, |
| {user_data_auth::AUTH_FACTOR_TYPE_LEGACY_FINGERPRINT, |
| AuthFactorType::Authentication_AuthenticationType_AUTH_FINGERPRINT}, |
| {user_data_auth::AUTH_FACTOR_TYPE_FINGERPRINT, |
| AuthFactorType::Authentication_AuthenticationType_AUTH_FINGERPRINT}, |
| }; |
| std::string signed_in_user_ = device_user::kEmpty; |
| int64_t latest_successful_login_timestamp_{-1}; |
| uint64_t latest_pin_failure_{0}; |
| bool is_active_{false}; |
| bool last_auth_was_password_{false}; |
| }; |
| |
| class AgentPlugin : public PluginInterface { |
| static constexpr char kBootDataFilepath[] = "sys/kernel/boot_params/data"; |
| |
| public: |
| AgentPlugin(scoped_refptr<MessageSenderInterface> message_sender, |
| scoped_refptr<DeviceUserInterface> device_user, |
| std::unique_ptr<org::chromium::AttestationProxyInterface> |
| attestation_proxy, |
| std::unique_ptr<org::chromium::TpmManagerProxyInterface> |
| tpm_manager_proxy, |
| base::OnceCallback<void()> cb, |
| uint32_t heartbeat_timer); |
| |
| // Initialize Agent proto and starts agent heartbeat events. |
| absl::Status Activate() override; |
| absl::Status Deactivate() override; |
| std::string GetName() const override; |
| bool IsActive() const override { return is_active_; } |
| void Flush() override {} |
| |
| private: |
| friend class testing::AgentPluginTestFixture; |
| |
| // Allow calling the private test-only constructor without befriending |
| // unique_ptr. |
| template <typename... Args> |
| static std::unique_ptr<AgentPlugin> CreateForTesting(Args&&... args) { |
| return base::WrapUnique(new AgentPlugin(std::forward<Args>(args)...)); |
| } |
| |
| // Accepts root_path for testing. |
| AgentPlugin(scoped_refptr<MessageSenderInterface> message_sender, |
| scoped_refptr<DeviceUserInterface> device_user, |
| std::unique_ptr<org::chromium::AttestationProxyInterface> |
| attestation_proxy, |
| std::unique_ptr<org::chromium::TpmManagerProxyInterface> |
| tpm_manager_proxy, |
| base::OnceCallback<void()> cb, |
| const base::FilePath& root_path, |
| uint32_t heartbeat_timer); |
| |
| // Starts filling in the tcb fields of the agent proto and initializes async |
| // timers that wait for tpm_manager and attestation to be ready. When services |
| // are ready GetCrosSecureBootInformation() and GetTpmInformation() |
| // will be called to fill remaining fields. |
| void StartInitializingAgentProto(); |
| // Callback that is used when the attestation service is ready that calls |
| // GetCrosSecureBootInformation and sends metrics. |
| void AttestationCb(bool available); |
| // Delayed function that will be called when attestation is ready. Fills the |
| // boot information in the agent proto if Cros Secure boot is used. |
| metrics::CrosBootmode GetCrosSecureBootInformation(bool available); |
| // Callback that is used when the tpm service is ready that calls |
| // GetTpmInformation and sends metrics. |
| void TpmCb(bool available); |
| // Delayed function that will be called when tpm_manager is ready. Fills the |
| // tpm information in the agent proto. |
| metrics::Tpm GetTpmInformation(bool available); |
| // Fills the boot information in the agent proto if Uefi Secure boot is used. |
| // Note: Only for flex machines. |
| metrics::UefiBootmode GetUefiSecureBootInformation( |
| const base::FilePath& boot_params_filepath); |
| // Sends an agent event dependant on whether it is start or heartbeat event. |
| // Uses the StartEventStatusCallback() to handle the status of the message. |
| void SendAgentEvent(bool is_agent_start); |
| // Checks the message status of the agent start event. If the message is |
| // successfully sent it calls the daemon callback to run the remaining |
| // plugins. If the message fails to send it will retry sending the message |
| // every 3 seconds. |
| void StartEventStatusCallback(reporting::Status status); |
| inline void SendStartEvent() { SendAgentEvent(true); } |
| inline void SendHeartbeatEvent() { SendAgentEvent(false); } |
| // Callback function that is ran when the device user is ready. |
| void OnDeviceUserRetrieved( |
| std::unique_ptr<cros_xdr::reporting::AgentEventAtomicVariant> |
| atomic_event, |
| const std::string& device_user); |
| |
| base::RepeatingTimer agent_heartbeat_timer_; |
| cros_xdr::reporting::TcbAttributes tcb_attributes_; |
| base::WeakPtrFactory<AgentPlugin> weak_ptr_factory_; |
| scoped_refptr<MessageSenderInterface> message_sender_; |
| scoped_refptr<DeviceUserInterface> device_user_; |
| std::unique_ptr<org::chromium::AttestationProxyInterface> attestation_proxy_; |
| std::unique_ptr<org::chromium::TpmManagerProxyInterface> tpm_manager_proxy_; |
| base::OnceCallback<void()> daemon_cb_; |
| const base::FilePath root_path_; |
| base::Lock tcb_attributes_lock_; |
| base::TimeDelta heartbeat_timer_ = base::Minutes(5); |
| bool is_active_{false}; |
| }; |
| |
| class PluginFactoryInterface { |
| public: |
| virtual std::unique_ptr<PluginInterface> Create( |
| Types::Plugin type, |
| scoped_refptr<MessageSenderInterface> message_sender, |
| scoped_refptr<ProcessCacheInterface> process_cache, |
| scoped_refptr<PoliciesFeaturesBrokerInterface> policies_features_broker, |
| scoped_refptr<DeviceUserInterface> device_user, |
| uint32_t batch_interval_s) = 0; |
| virtual std::unique_ptr<PluginInterface> CreateAgentPlugin( |
| scoped_refptr<MessageSenderInterface> message_sender, |
| scoped_refptr<DeviceUserInterface> device_user, |
| std::unique_ptr<org::chromium::AttestationProxyInterface> |
| attestation_proxy, |
| std::unique_ptr<org::chromium::TpmManagerProxyInterface> |
| tpm_manager_proxy, |
| base::OnceCallback<void()> cb, |
| uint32_t heartbeat_timer) = 0; |
| virtual ~PluginFactoryInterface() = default; |
| }; |
| |
| // Support absl format for PluginType. |
| absl::FormatConvertResult<absl::FormatConversionCharSet::kString> |
| AbslFormatConvert(const Types::Plugin& type, |
| const absl::FormatConversionSpec& conversion_spec, |
| absl::FormatSink* output_sink); |
| |
| // Support streaming for PluginType. |
| std::ostream& operator<<(std::ostream& out, const Types::Plugin& type); |
| |
| class PluginFactory : public PluginFactoryInterface { |
| public: |
| PluginFactory(); |
| explicit PluginFactory( |
| scoped_refptr<BpfSkeletonFactoryInterface> bpf_skeleton_factory) |
| : bpf_skeleton_factory_(bpf_skeleton_factory) {} |
| std::unique_ptr<PluginInterface> Create( |
| Types::Plugin type, |
| scoped_refptr<MessageSenderInterface> message_sender, |
| scoped_refptr<ProcessCacheInterface> process_cache, |
| scoped_refptr<PoliciesFeaturesBrokerInterface> policies_features_broker, |
| scoped_refptr<DeviceUserInterface> device_user, |
| uint32_t batch_interval_s) override; |
| std::unique_ptr<PluginInterface> CreateAgentPlugin( |
| scoped_refptr<MessageSenderInterface> message_sender, |
| scoped_refptr<DeviceUserInterface> device_user, |
| std::unique_ptr<org::chromium::AttestationProxyInterface> |
| attestation_proxy, |
| std::unique_ptr<org::chromium::TpmManagerProxyInterface> |
| tpm_manager_proxy, |
| base::OnceCallback<void()> cb, |
| uint32_t heartbeat_timer) override; |
| |
| private: |
| scoped_refptr<BpfSkeletonFactoryInterface> bpf_skeleton_factory_; |
| }; |
| |
| } // namespace secagentd |
| #endif // SECAGENTD_PLUGINS_H_ |